From 31672a2587f3e91c8d19433138f2fbb1eb3093e0 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 9 Jan 2018 01:56:37 +0900 Subject: [PATCH] Add litecoin to docker-compose fix bugs when two networks generate same address --- BTCPayServer.Tests/UnitTest1.cs | 8 +- BTCPayServer.Tests/docker-compose.yml | 100 ++++++++---------- BTCPayServer.Tests/docker-litecoin-cli.ps1 | 1 + .../Configuration/BTCPayServerOptions.cs | 9 +- BTCPayServer/Data/AddressInvoiceData.cs | 34 ++++++ BTCPayServer/Data/ApplicationDbContext.cs | 4 + .../Data/HistoricalAddressInvoiceData.cs | 20 ++++ BTCPayServer/HostedServices/InvoiceWatcher.cs | 9 +- BTCPayServer/Properties/launchSettings.json | 5 +- .../Services/Invoices/InvoiceRepository.cs | 29 +++-- 10 files changed, 136 insertions(+), 83 deletions(-) create mode 100644 BTCPayServer.Tests/docker-litecoin-cli.ps1 diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index e3e3ae217..31acb49ef 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -383,9 +383,9 @@ namespace BTCPayServer.Tests Assert.True(IsMapped(localInvoice, ctx)); invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult(); - var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == invoice.BitcoinAddress.ToString()); + var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress.ToString()); Assert.NotNull(historical1.UnAssigned); - var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == localInvoice.BitcoinAddress.ToString()); + var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress.ToString()); Assert.Null(historical2.UnAssigned); invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network); secondPayment = localInvoice.BtcDue; @@ -473,8 +473,8 @@ namespace BTCPayServer.Tests private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx) { - var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString(); - return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.Address == h) != null; + var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash; + return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.GetHash() == h) != null; } private void Eventually(Action act) diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index b6f7ec652..c3319b0da 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -11,19 +11,17 @@ services: dockerfile: BTCPayServer.Tests/Dockerfile environment: TESTS_RPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3 - TESTS_NBXPLORERURL: http://nbxplorer:32838/ + TESTS_BTCNBXPLORERURL: http://bitcoin-nbxplorer:32838/ + TESTS_LTCNBXPLORERURL: http://litecoin-nbxplorer:32839/ TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver TESTS_PORT: 80 TESTS_HOSTNAME: tests - # TEST_ECLAIR1: http://eclair1:8080/ - # TEST_ECLAIR2: http://eclair2:8080/ expose: - "80" links: - - bitcoind - - nbxplorer - # - eclair1 - # - eclair2 + - bitcoin-nbxplorer + - litecoin-nbxplorer + - postgres extra_hosts: - "tests:127.0.0.1" @@ -35,12 +33,11 @@ services: regtest=1 connect=bitcoind:39388 links: - - bitcoind - - nbxplorer - # - eclair1 - # - eclair2 + - bitcoin-nbxplorer + - litecoin-nbxplorer + - postgres - nbxplorer: + bitcoin-nbxplorer: image: nicolasdorier/nbxplorer:1.0.0.39 ports: - "32838:32838" @@ -57,49 +54,24 @@ services: NBXPLORER_NOAUTH: 1 links: - bitcoind - - postgres - eclair1: - image: acinq/eclair:latest - environment: - JAVA_OPTS: > - -Xmx512m - -Declair.printToConsole - -Declair.bitcoind.host=bitcoind - -Declair.bitcoind.rpcport=43782 - -Declair.bitcoind.rpcuser=ceiwHEbqWI83 - -Declair.bitcoind.rpcpassword=DwubwWsoo3 - -Declair.bitcoind.zmq=tcp://bitcoind:29000 - -Declair.chain=regtest - -Declair.api.binding-ip=0.0.0.0 - links: - - bitcoind + litecoin-nbxplorer: + image: nicolasdorier/nbxplorer:1.0.0.39 ports: - - "30992:8080" # api port - expose: - - "9735" # server port - - "8080" # api port - - eclair2: - image: acinq/eclair:latest + - "32839:32839" + expose: + - "32839" environment: - JAVA_OPTS: > - -Xmx512m - -Declair.printToConsole - -Declair.bitcoind.host=bitcoind - -Declair.bitcoind.rpcport=43782 - -Declair.bitcoind.rpcuser=ceiwHEbqWI83 - -Declair.bitcoind.rpcpassword=DwubwWsoo3 - -Declair.bitcoind.zmq=tcp://bitcoind:29000 - -Declair.chain=regtest - -Declair.api.binding-ip=0.0.0.0 + NBXPLORER_NETWORK: ltc-regtest + NBXPLORER_RPCURL: http://litecoind:43782/ + NBXPLORER_RPCUSER: ceiwHEbqWI83 + NBXPLORER_RPCPASSWORD: DwubwWsoo3 + NBXPLORER_NODEENDPOINT: litecoind:39388 + NBXPLORER_BIND: 0.0.0.0:32839 + NBXPLORER_VERBOSE: 1 + NBXPLORER_NOAUTH: 1 links: - - bitcoind - ports: - - "30993:8080" # api port - expose: - - "9735" # server port - - "8080" # api port + - litecoind bitcoind: container_name: btcpayserver_dev_bitcoind @@ -116,8 +88,30 @@ services: zmqpubrawblock=tcp://0.0.0.0:29000 zmqpubrawtx=tcp://0.0.0.0:29000 txindex=1 - ports: - - "43782:43782" # RPC + ports: + - "43782:43782" + expose: + - "43782" # RPC + - "39388" # P2P + - "29000" # zmq + + litecoind: + container_name: btcpayserver_dev_litecoind + image: nicolasdorier/docker-litecoin:0.14.2 + environment: + BITCOIN_EXTRA_ARGS: | + rpcuser=ceiwHEbqWI83 + rpcpassword=DwubwWsoo3 + regtest=1 + server=1 + rpcport=43782 + port=39388 + whitelist=0.0.0.0/0 + zmqpubrawblock=tcp://0.0.0.0:29000 + zmqpubrawtx=tcp://0.0.0.0:29000 + txindex=1 + ports: + - "43783:43782" expose: - "43782" # RPC - "39388" # P2P diff --git a/BTCPayServer.Tests/docker-litecoin-cli.ps1 b/BTCPayServer.Tests/docker-litecoin-cli.ps1 new file mode 100644 index 000000000..8e3038a95 --- /dev/null +++ b/BTCPayServer.Tests/docker-litecoin-cli.ps1 @@ -0,0 +1 @@ +docker exec -ti btcpayserver_dev_litecoind litecoin-cli -regtest -conf="/data/litecoin.conf" -datadir="/data" $args diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index 2e96979b7..801b102e6 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -45,11 +45,14 @@ namespace BTCPayServer.Configuration DataDir = conf.GetOrDefault("datadir", networkInfo.DefaultDataDirectory); Logs.Configuration.LogInformation("Network: " + Network); + + foreach (var net in new BTCPayNetworkProvider(Network).GetAll()) { + var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(net.NBitcoinNetwork.Name); var explorer = conf.GetOrDefault($"{net.CryptoCode}.explorer.url", null); - var cookieFile = conf.GetOrDefault($"{net.CryptoCode}.explorer.cookiefile", null); - if (explorer != null && cookieFile != null) + var cookieFile = conf.GetOrDefault($"{net.CryptoCode}.explorer.cookiefile", nbxplorer.GetDefaultCookieFile()); + if (explorer != null) { ExplorerFactories.Add(net.CryptoCode, (n) => CreateExplorerClient(n, explorer, cookieFile)); } @@ -58,7 +61,7 @@ namespace BTCPayServer.Configuration // Handle legacy explorer.url and explorer.cookiefile if (ExplorerFactories.Count == 0) { - var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(Network.Name); + var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(Network.Name); // Will get BTC info var explorer = conf.GetOrDefault($"explorer.url", new Uri(nbxplorer.GetDefaultExplorerUrl(), UriKind.Absolute)); var cookieFile = conf.GetOrDefault($"explorer.cookiefile", nbxplorer.GetDefaultCookieFile()); ExplorerFactories.Add("BTC", (n) => CreateExplorerClient(n, explorer, cookieFile)); diff --git a/BTCPayServer/Data/AddressInvoiceData.cs b/BTCPayServer/Data/AddressInvoiceData.cs index bf8bd0c5c..3945df22f 100644 --- a/BTCPayServer/Data/AddressInvoiceData.cs +++ b/BTCPayServer/Data/AddressInvoiceData.cs @@ -2,16 +2,49 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NBitcoin; namespace BTCPayServer.Data { public class AddressInvoiceData { + /// + /// Some crypto currencies share same address prefix + /// For not having exceptions thrown by two address on different network, we suffix by "#CRYPTOCODE" + /// + [Obsolete("Use GetHash instead")] public string Address { get; set; } + +#pragma warning disable CS0618 + public ScriptId GetHash() + { + if (Address == null) + return null; + var index = Address.IndexOf("#"); + if (index == -1) + return new ScriptId(Address); + return new ScriptId(Address.Substring(0, index)); + } + public AddressInvoiceData SetHash(ScriptId scriptId, string cryptoCode) + { + Address = scriptId + "#" + cryptoCode; + return this; + } + public string GetCryptoCode() + { + if (Address == null) + return null; + var index = Address.IndexOf("#"); + if (index == -1) + return "BTC"; + return Address.Substring(index + 1); + } +#pragma warning restore CS0618 + public InvoiceData InvoiceData { get; set; @@ -26,5 +59,6 @@ namespace BTCPayServer.Data { get; set; } + } } diff --git a/BTCPayServer/Data/ApplicationDbContext.cs b/BTCPayServer/Data/ApplicationDbContext.cs index 6089a14e1..75db05ae9 100644 --- a/BTCPayServer/Data/ApplicationDbContext.cs +++ b/BTCPayServer/Data/ApplicationDbContext.cs @@ -113,7 +113,9 @@ namespace BTCPayServer.Data .HasForeignKey(pt => pt.StoreDataId); builder.Entity() +#pragma warning disable CS0618 .HasKey(o => o.Address); +#pragma warning restore CS0618 builder.Entity() .HasKey(o => o.Id); @@ -128,7 +130,9 @@ namespace BTCPayServer.Data .HasKey(o => new { o.InvoiceDataId, +#pragma warning disable CS0618 o.Address +#pragma warning restore CS0618 }); } } diff --git a/BTCPayServer/Data/HistoricalAddressInvoiceData.cs b/BTCPayServer/Data/HistoricalAddressInvoiceData.cs index 51ad08c03..4db455f0f 100644 --- a/BTCPayServer/Data/HistoricalAddressInvoiceData.cs +++ b/BTCPayServer/Data/HistoricalAddressInvoiceData.cs @@ -12,6 +12,11 @@ namespace BTCPayServer.Data get; set; } + /// + /// Some crypto currencies share same address prefix + /// For not having exceptions thrown by two address on different network, we suffix by "#CRYPTOCODE" + /// + [Obsolete("Use GetCryptoCode instead")] public string Address { get; set; @@ -26,6 +31,21 @@ namespace BTCPayServer.Data { return string.IsNullOrEmpty(CryptoCode) ? "BTC" : CryptoCode; } + public string GetAddress() + { + if (Address == null) + return null; + var index = Address.IndexOf("#"); + if (index == -1) + return Address; + return Address.Substring(0, index); + } + public HistoricalAddressInvoiceData SetAddress(string depositAddress, string cryptoCode) + { + Address = depositAddress + "#" + cryptoCode; + CryptoCode = cryptoCode; + return this; + } #pragma warning restore CS0618 public DateTimeOffset Assigned diff --git a/BTCPayServer/HostedServices/InvoiceWatcher.cs b/BTCPayServer/HostedServices/InvoiceWatcher.cs index d2bb68660..50efa1d2e 100644 --- a/BTCPayServer/HostedServices/InvoiceWatcher.cs +++ b/BTCPayServer/HostedServices/InvoiceWatcher.cs @@ -63,9 +63,9 @@ namespace BTCPayServer.HostedServices } CompositeDisposable leases = new CompositeDisposable(); - async Task NotifyReceived(Script scriptPubKey) + async Task NotifyReceived(Script scriptPubKey, BTCPayNetwork network) { - var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey); + var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey, network.CryptoCode); if (invoice != null) _WatchRequests.Add(invoice); } @@ -149,7 +149,7 @@ namespace BTCPayServer.HostedServices var getCoinsResponses = getCoinsResponsesAsync.Select(g => g.Result).ToArray(); foreach (var response in getCoinsResponses) { - response.Coins = response.Coins.Where(c => invoice.AvailableAddressHashes.Contains(c.ScriptPubKey.Hash.ToString())).ToArray(); + response.Coins = response.Coins.Where(c => invoice.AvailableAddressHashes.Contains(c.ScriptPubKey.Hash.ToString() + response.Strategy.Network.CryptoCode)).ToArray(); } var coins = getCoinsResponses.Where(s => s.Coins.Length != 0).FirstOrDefault(); bool dirtyAddress = false; @@ -208,7 +208,6 @@ namespace BTCPayServer.HostedServices if (totalPaid < accounting.TotalDue && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial") { - Logs.PayServer.LogInformation("Paid to " + cryptoData.DepositAddress); invoice.ExceptionStatus = "paidPartial"; context.MarkDirty(); if (dirtyAddress) @@ -379,7 +378,7 @@ namespace BTCPayServer.HostedServices }, null, 0, (int)PollInterval.TotalMilliseconds); leases.Add(_EventAggregator.Subscribe(async b => { await NotifyBlock(); })); - leases.Add(_EventAggregator.Subscribe(async b => { await NotifyReceived(b.ScriptPubKey); })); + leases.Add(_EventAggregator.Subscribe(async b => { await NotifyReceived(b.ScriptPubKey, b.Network); })); leases.Add(_EventAggregator.Subscribe(b => { Watch(b.InvoiceId); })); return Task.CompletedTask; diff --git a/BTCPayServer/Properties/launchSettings.json b/BTCPayServer/Properties/launchSettings.json index 5404d0413..6074246f8 100644 --- a/BTCPayServer/Properties/launchSettings.json +++ b/BTCPayServer/Properties/launchSettings.json @@ -15,7 +15,8 @@ "commandName": "Project", "launchBrowser": true, "environmentVariables": { - "BTCPAY_EXPLORERURL": "http://127.0.0.1:32838/", + "BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/", + "BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32839/", "BTCPAY_NETWORK": "regtest", "ASPNETCORE_ENVIRONMENT": "Development", "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver" @@ -23,4 +24,4 @@ "applicationUrl": "http://localhost:14142/" } } -} \ No newline at end of file +} diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index fda2a47ae..0720e9340 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -70,11 +70,11 @@ namespace BTCPayServer.Services.Invoices } } - public async Task GetInvoiceIdFromScriptPubKey(Script scriptPubKey) + public async Task GetInvoiceIdFromScriptPubKey(Script scriptPubKey, string cryptoCode) { using (var db = _ContextFactory.CreateContext()) { - var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString()); + var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString() + "#" + cryptoCode); return result?.InvoiceDataId; } } @@ -130,19 +130,14 @@ namespace BTCPayServer.Services.Invoices throw new InvalidOperationException("CryptoCode unsupported"); context.AddressInvoices.Add(new AddressInvoiceData() { - Address = BitcoinAddress.Create(cryptoData.DepositAddress, network.NBitcoinNetwork).ScriptPubKey.Hash.ToString(), InvoiceDataId = invoice.Id, CreatedTime = DateTimeOffset.UtcNow, - }); + }.SetHash(BitcoinAddress.Create(cryptoData.DepositAddress, network.NBitcoinNetwork).ScriptPubKey.Hash, network.CryptoCode)); context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData() { InvoiceDataId = invoice.Id, - Address = cryptoData.DepositAddress, -#pragma warning disable CS0618 - CryptoCode = cryptoData.CryptoCode, -#pragma warning restore CS0618 Assigned = DateTimeOffset.UtcNow - }); + }.SetAddress(cryptoData.DepositAddress, cryptoData.CryptoCode)); textSearch.Add(cryptoData.DepositAddress); textSearch.Add(cryptoData.Calculate().TotalDue.ToString()); } @@ -189,17 +184,18 @@ namespace BTCPayServer.Services.Invoices { invoiceEntity.DepositAddress = currencyData.DepositAddress; } -#pragma warning disable CS0618 +#pragma warning restore CS0618 invoiceEntity.SetCryptoData(cryptoData); invoice.Blob = ToBytes(invoiceEntity); - context.AddressInvoices.Add(new AddressInvoiceData() { Address = bitcoinAddress.ScriptPubKey.Hash.ToString(), InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow }); + context.AddressInvoices.Add(new AddressInvoiceData() { + InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow } + .SetHash(bitcoinAddress.ScriptPubKey.Hash, network.CryptoCode)); context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData() { InvoiceDataId = invoiceId, - Address = bitcoinAddress.ToString(), Assigned = DateTimeOffset.UtcNow - }); + }.SetAddress(bitcoinAddress.ToString(), network.CryptoCode)); await context.SaveChangesAsync(); AddToTextSearch(invoice.Id, bitcoinAddress.ToString()); @@ -215,7 +211,7 @@ namespace BTCPayServer.Services.Invoices continue; var historical = new HistoricalAddressInvoiceData(); historical.InvoiceDataId = invoiceId; - historical.Address = address.Value.DepositAddress; + historical.SetAddress(address.Value.DepositAddress, cryptoCode); historical.UnAssigned = DateTimeOffset.UtcNow; context.Attach(historical); context.Entry(historical).Property(o => o.UnAssigned).IsModified = true; @@ -329,13 +325,12 @@ namespace BTCPayServer.Services.Invoices } if (invoice.AddressInvoices != null) { - entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.Address).ToHashSet(); + entity.AvailableAddressHashes = invoice.AddressInvoices.Select(a => a.GetHash() + a.GetCryptoCode()).ToHashSet(); } return entity; } - public async Task GetInvoices(InvoiceQuery queryObject) { using (var context = _ContextFactory.CreateContext()) @@ -431,7 +426,9 @@ namespace BTCPayServer.Services.Invoices PaymentEntity entity = new PaymentEntity { Outpoint = receivedCoin.Outpoint, +#pragma warning disable CS0618 Output = receivedCoin.TxOut, +#pragma warning restore CS0618 ReceivedTime = DateTime.UtcNow };