From 0f6ad75536bcb8f9b4dbcb2c6f1d035b0a3da027 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 13 Mar 2018 15:28:39 +0900 Subject: [PATCH 01/14] Remove internal exception thrown by NBitcoin --- BTCPayServer.Tests/UnitTest1.cs | 2 +- .../Bitcoin/BitcoinLikeOnChainPaymentMethod.cs | 13 +++++++------ .../Payments/Bitcoin/BitcoinLikePaymentHandler.cs | 2 +- BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs | 4 ++-- BTCPayServer/Services/Invoices/InvoiceEntity.cs | 4 ++-- BTCPayServer/Services/Invoices/InvoiceRepository.cs | 8 ++++---- BTCPayServer/Views/Invoice/ListInvoices.cshtml | 2 +- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 9ba40becd..92c209762 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -45,7 +45,7 @@ namespace BTCPayServer.Tests [Fact] public void CanCalculateCryptoDue2() { - var dummy = new Key().PubKey.GetAddress(Network.RegTest); + var dummy = new Key().PubKey.GetAddress(Network.RegTest).ToString(); #pragma warning disable CS0618 InvoiceEntity invoiceEntity = new InvoiceEntity(); invoiceEntity.Payments = new System.Collections.Generic.List(); diff --git a/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs b/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs index 7f83e7ac2..d283a5458 100644 --- a/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs +++ b/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs @@ -17,7 +17,7 @@ namespace BTCPayServer.Payments.Bitcoin public string GetPaymentDestination() { - return DepositAddress?.ToString(); + return DepositAddress; } public decimal GetTxFee() @@ -33,10 +33,7 @@ namespace BTCPayServer.Payments.Bitcoin public void SetPaymentDestination(string newPaymentDestination) { - if (newPaymentDestination == null) - DepositAddress = null; - else - DepositAddress = BitcoinAddress.Create(newPaymentDestination, DepositAddress.Network); + DepositAddress = newPaymentDestination; } // Those properties are JsonIgnore because their data is inside CryptoData class for legacy reason @@ -45,7 +42,11 @@ namespace BTCPayServer.Payments.Bitcoin [JsonIgnore] public Money TxFee { get; set; } [JsonIgnore] - public BitcoinAddress DepositAddress { get; set; } + public String DepositAddress { get; set; } + public BitcoinAddress GetDepositAddress(Network network) + { + return string.IsNullOrEmpty(DepositAddress) ? null : BitcoinAddress.Create(DepositAddress, network); + } /////////////////////////////////////////////////////////////////////////////////////// } } diff --git a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs index 47e9a2410..64c68532a 100644 --- a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs +++ b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs @@ -34,7 +34,7 @@ namespace BTCPayServer.Payments.Bitcoin Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod(); onchainMethod.FeeRate = await getFeeRate; onchainMethod.TxFee = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes - onchainMethod.DepositAddress = await getAddress; + onchainMethod.DepositAddress = (await getAddress).ToString(); return onchainMethod; } diff --git a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs index 3f65f730a..e33b9a9e7 100644 --- a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs +++ b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs @@ -351,11 +351,11 @@ namespace BTCPayServer.Payments.Bitcoin var paymentMethod = invoice.GetPaymentMethod(wallet.Network, PaymentTypes.BTCLike, _ExplorerClients.NetworkProviders); if (paymentMethod != null && paymentMethod.GetPaymentMethodDetails() is BitcoinLikeOnChainPaymentMethod btc && - btc.DepositAddress.ScriptPubKey == paymentData.Output.ScriptPubKey && + btc.GetDepositAddress(wallet.Network.NBitcoinNetwork).ScriptPubKey == paymentData.Output.ScriptPubKey && paymentMethod.Calculate().Due > Money.Zero) { var address = await wallet.ReserveAddressAsync(strategy); - btc.DepositAddress = address; + btc.DepositAddress = address.ToString(); await _InvoiceRepository.NewAddress(invoiceId, btc, wallet.Network); _Aggregator.Publish(new InvoiceNewAddressEvent(invoiceId, address.ToString(), wallet.Network)); paymentMethod.SetPaymentMethodDetails(btc); diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index d164c44cd..92d964675 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -582,7 +582,7 @@ namespace BTCPayServer.Services.Invoices return new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod() { FeeRate = FeeRate, - DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : BitcoinAddress.Create(DepositAddress, Network?.NBitcoinNetwork), + DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : DepositAddress, TxFee = TxFee }; } @@ -592,7 +592,7 @@ namespace BTCPayServer.Services.Invoices if (details is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod btcLike) { btcLike.TxFee = TxFee; - btcLike.DepositAddress = BitcoinAddress.Create(DepositAddress, Network?.NBitcoinNetwork); + btcLike.DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : DepositAddress; btcLike.FeeRate = FeeRate; } return details; diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 5fdc5d17a..fafec2908 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -130,7 +130,7 @@ namespace BTCPayServer.Services.Invoices throw new InvalidOperationException("CryptoCode unsupported"); var paymentDestination = paymentMethod.GetPaymentMethodDetails().GetPaymentDestination(); - string address = GetDestination(paymentMethod); + string address = GetDestination(paymentMethod, paymentMethod.Network.NBitcoinNetwork); context.AddressInvoices.Add(new AddressInvoiceData() { InvoiceDataId = invoice.Id, @@ -162,12 +162,12 @@ namespace BTCPayServer.Services.Invoices return invoice; } - private static string GetDestination(PaymentMethod paymentMethod) + private static string GetDestination(PaymentMethod paymentMethod, Network network) { // For legacy reason, BitcoinLikeOnChain is putting the hashes of addresses in database if (paymentMethod.GetId().PaymentType == Payments.PaymentTypes.BTCLike) { - return ((Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod)paymentMethod.GetPaymentMethodDetails()).DepositAddress.ScriptPubKey.Hash.ToString(); + return ((Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod)paymentMethod.GetPaymentMethodDetails()).GetDepositAddress(network).ScriptPubKey.Hash.ToString(); } /////////////// return paymentMethod.GetPaymentMethodDetails().GetPaymentDestination(); @@ -209,7 +209,7 @@ namespace BTCPayServer.Services.Invoices InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow } - .Set(GetDestination(currencyData), currencyData.GetId())); + .Set(GetDestination(currencyData, network.NBitcoinNetwork), currencyData.GetId())); context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData() { InvoiceDataId = invoiceId, diff --git a/BTCPayServer/Views/Invoice/ListInvoices.cshtml b/BTCPayServer/Views/Invoice/ListInvoices.cshtml index 35781fd26..96707d2a5 100644 --- a/BTCPayServer/Views/Invoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/Invoice/ListInvoices.cshtml @@ -48,7 +48,7 @@ InvoiceId Status Amount - Actions + Actions From 49cf804914479e5d13346590ebdec32b618d35fb Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 13 Mar 2018 15:39:52 +0900 Subject: [PATCH 02/14] bump --- BTCPayServer/BTCPayServer.csproj | 2 +- BTCPayServer/Services/Wallets/BTCPayWallet.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 00d5d7664..d55fca956 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.46 + 1.0.1.47 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Services/Wallets/BTCPayWallet.cs b/BTCPayServer/Services/Wallets/BTCPayWallet.cs index fe807afd7..af8cfd749 100644 --- a/BTCPayServer/Services/Wallets/BTCPayWallet.cs +++ b/BTCPayServer/Services/Wallets/BTCPayWallet.cs @@ -137,6 +137,7 @@ namespace BTCPayServer.Services.Wallets entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan; return result; }); + _FetchingUTXOs.TryRemove(strategy.ToString(), out var unused); completionSource.TrySetResult(utxos); } catch (Exception ex) From cbd40d49c1d95122104c4d221002ce1ca8d38aab Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Tue, 13 Mar 2018 15:56:17 +0900 Subject: [PATCH 03/14] bump --- BTCPayServer/BTCPayServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index d55fca956..74ef8db49 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.47 + 1.0.1.48 NU1701,CA1816,CA1308,CA1810,CA2208 From 9e2e102ec4a3c0f7716415b73994aeba1232f610 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 14 Mar 2018 19:32:24 +0900 Subject: [PATCH 04/14] Fix bug making it impossible to remove LTC xpub --- BTCPayServer/Controllers/StoresController.BTCLike.cs | 3 +-- BTCPayServer/Data/AddressInvoiceData.cs | 2 +- BTCPayServer/Data/StoreData.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/BTCPayServer/Controllers/StoresController.BTCLike.cs b/BTCPayServer/Controllers/StoresController.BTCLike.cs index bb02856ea..8ad036521 100644 --- a/BTCPayServer/Controllers/StoresController.BTCLike.cs +++ b/BTCPayServer/Controllers/StoresController.BTCLike.cs @@ -65,7 +65,6 @@ namespace BTCPayServer.Controllers strategy = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network); vm.DerivationScheme = strategy.ToString(); } - store.SetSupportedPaymentMethod(paymentMethodId, strategy); } catch { @@ -75,7 +74,7 @@ namespace BTCPayServer.Controllers } - if (vm.Confirmation) + if (vm.Confirmation || strategy == null) { try { diff --git a/BTCPayServer/Data/AddressInvoiceData.cs b/BTCPayServer/Data/AddressInvoiceData.cs index b37ef24e0..65e482367 100644 --- a/BTCPayServer/Data/AddressInvoiceData.cs +++ b/BTCPayServer/Data/AddressInvoiceData.cs @@ -33,7 +33,7 @@ namespace BTCPayServer.Data } public AddressInvoiceData Set(string address, PaymentMethodId paymentMethodId) { - Address = address + "#" + paymentMethodId?.ToString(); + Address = address + "#" + paymentMethodId.ToString(); return this; } public PaymentMethodId GetpaymentMethodId() diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index 5822e895c..020957db3 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -118,7 +118,7 @@ namespace BTCPayServer.Data { DerivationStrategy = null; } - else if (!existing) + else if (!existing && supportedPaymentMethod != null) strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod))); DerivationStrategies = strategies.ToString(); #pragma warning restore CS0618 From b28b3ef4ff9041b88292cc2c9ee301285813e3aa Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Wed, 14 Mar 2018 20:10:04 +0900 Subject: [PATCH 05/14] Fix: Invoice can't be paid in lightning anymore if lightning server sent error --- BTCPayServer/BTCPayServer.csproj | 2 +- BTCPayServer/Payments/Lightning/ChargeListener.cs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 74ef8db49..95181a9e3 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.48 + 1.0.1.49 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Payments/Lightning/ChargeListener.cs b/BTCPayServer/Payments/Lightning/ChargeListener.cs index a3e1fe964..c2b7f6e6c 100644 --- a/BTCPayServer/Payments/Lightning/ChargeListener.cs +++ b/BTCPayServer/Payments/Lightning/ChargeListener.cs @@ -60,9 +60,6 @@ namespace BTCPayServer.Payments.Lightning private async Task EnsureListening(string invoiceId, bool poll) { - if (Listening(invoiceId)) - return; - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); foreach (var paymentMethod in invoice.GetPaymentMethods(_NetworkProvider) .Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike)) @@ -98,7 +95,10 @@ namespace BTCPayServer.Payments.Lightning continue; } - StartListening(listenedInvoice); + if (!Listening(invoiceId)) + { + StartListening(listenedInvoice); + } } } @@ -232,10 +232,7 @@ namespace BTCPayServer.Payments.Lightning _ListeningLightning.Add(listen); listen.ContinueWith(_ => { - lock (_ListenedInvoiceByLightningUrl) - { - _ListeningLightning.Remove(listen); - } + DoneListening(listenedInvoice); }, TaskScheduler.Default); } _ListenedInvoiceByLightningUrl.Add(listenedInvoice.Uri, listenedInvoice); From 0d8affc68df25dc241259e99715a7df7d39ea1c2 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 17 Mar 2018 15:45:44 +0900 Subject: [PATCH 06/14] Remove dependency on Eclair for tests --- BTCPayServer.Tests/EclairTester.cs | 39 --- BTCPayServer.Tests/LightningDTester.cs | 24 ++ BTCPayServer.Tests/ServerTester.cs | 96 +++---- BTCPayServer.Tests/UnitTest1.cs | 1 - BTCPayServer.Tests/docker-compose.yml | 48 ++-- .../JsonConverters/LightMoneyJsonConverter.cs | 5 +- .../CLightning/RPC/CLightningRPCClient.cs | 118 ++++++++ .../{Eclair => CLightning/RPC}/NodeInfo.cs | 2 +- .../Lightning/CLightning/RPC/PeerInfo.cs | 60 +++++ .../Lightning/Eclair/AllChannelResponse.cs | 14 - .../Lightning/Eclair/ChannelResponse.cs | 21 -- .../Lightning/Eclair/EclairRPCClient.cs | 252 ------------------ .../Lightning/Eclair/GetInfoResponse.cs | 18 -- 13 files changed, 277 insertions(+), 421 deletions(-) delete mode 100644 BTCPayServer.Tests/EclairTester.cs create mode 100644 BTCPayServer.Tests/LightningDTester.cs create mode 100644 BTCPayServer/Payments/Lightning/CLightning/RPC/CLightningRPCClient.cs rename BTCPayServer/Payments/Lightning/{Eclair => CLightning/RPC}/NodeInfo.cs (91%) create mode 100644 BTCPayServer/Payments/Lightning/CLightning/RPC/PeerInfo.cs delete mode 100644 BTCPayServer/Payments/Lightning/Eclair/AllChannelResponse.cs delete mode 100644 BTCPayServer/Payments/Lightning/Eclair/ChannelResponse.cs delete mode 100644 BTCPayServer/Payments/Lightning/Eclair/EclairRPCClient.cs delete mode 100644 BTCPayServer/Payments/Lightning/Eclair/GetInfoResponse.cs diff --git a/BTCPayServer.Tests/EclairTester.cs b/BTCPayServer.Tests/EclairTester.cs deleted file mode 100644 index 3c886f764..000000000 --- a/BTCPayServer.Tests/EclairTester.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using BTCPayServer.Payments.Lightning.Eclair; -using NBitcoin; - -namespace BTCPayServer.Tests -{ - public class EclairTester - { - ServerTester parent; - public EclairTester(ServerTester parent, string environmentName, string defaultRPC, string defaultHost, Network network) - { - this.parent = parent; - RPC = new EclairRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), network); - P2PHost = parent.GetEnvironment(environmentName + "_HOST", defaultHost); - } - - public EclairRPCClient RPC { get; } - public string P2PHost { get; } - - NodeInfo _NodeInfo; - public async Task GetNodeInfoAsync() - { - if (_NodeInfo != null) - return _NodeInfo; - var info = await RPC.GetInfoAsync(); - _NodeInfo = new NodeInfo(info.NodeId, P2PHost, info.Port); - return _NodeInfo; - } - - public NodeInfo GetNodeInfo() - { - return GetNodeInfoAsync().GetAwaiter().GetResult(); - } - - } -} diff --git a/BTCPayServer.Tests/LightningDTester.cs b/BTCPayServer.Tests/LightningDTester.cs new file mode 100644 index 000000000..d014310c1 --- /dev/null +++ b/BTCPayServer.Tests/LightningDTester.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using BTCPayServer.Payments.Lightning.CLightning; +using BTCPayServer.Payments.Lightning.CLightning.RPC; +using NBitcoin; + +namespace BTCPayServer.Tests +{ + public class LightningDTester + { + ServerTester parent; + public LightningDTester(ServerTester parent, string environmentName, string defaultRPC, string defaultHost, Network network) + { + this.parent = parent; + RPC = new CLightningRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), network); + } + + public CLightningRPCClient RPC { get; } + public string P2PHost { get; } + + } +} diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 7b7255ff5..dbd01b834 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -17,8 +17,8 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Threading; -using BTCPayServer.Payments.Lightning.Eclair; using System.Globalization; +using BTCPayServer.Payments.Lightning.CLightning.RPC; namespace BTCPayServer.Tests { @@ -56,8 +56,8 @@ namespace BTCPayServer.Tests LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/"))); var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork; - CustomerEclair = new EclairTester(this, "TEST_ECLAIR", "http://eclair-cli:gpwefwmmewci@127.0.0.1:30992/", "eclair", btc); - MerchantCharge = new ChargeTester(this, "TEST_CHARGE", "http://api-token:foiewnccewuify@127.0.0.1:54938/", "lightning-charged", btc); + CustomerLightningD = new CLightningRPCClient(new Uri(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "http://127.0.0.1:30992/")), btc); + MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "http://api-token:foiewnccewuify@127.0.0.1:54938/", "lightning-charged", btc); PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay")) { @@ -73,55 +73,62 @@ namespace BTCPayServer.Tests /// - /// This will setup a channel going from customer to merchant + /// Connect a customer LN node to the merchant LN node /// public void PrepareLightning() { PrepareLightningAsync().GetAwaiter().GetResult(); } + + /// + /// Connect a customer LN node to the merchant LN node + /// + /// public async Task PrepareLightningAsync() { - // Activate segwit - var blockCount = ExplorerNode.GetBlockCountAsync(); - // Fetch node info, but that in cache - var merchantInfo = MerchantCharge.Client.GetInfoAsync(); - var customer = CustomerEclair.GetNodeInfoAsync(); - var channels = CustomerEclair.RPC.ChannelsAsync(); - - var info = await merchantInfo; - var clightning = new NodeInfo(info.Id, MerchantCharge.P2PHost, info.Port); - var connect = CustomerEclair.RPC.ConnectAsync(clightning); - await Task.WhenAll(blockCount, customer, channels, connect); - - // If the channel is not created, let's do it - if (channels.Result.Length == 0) + while (true) { - var c = (await CustomerEclair.RPC.ChannelsAsync()); - bool generated = false; - bool createdChannel = false; - CancellationTokenSource timeout = new CancellationTokenSource(); - timeout.CancelAfter(10000); - while (c.Length == 0 || c[0].State != "NORMAL") + var channel = (await CustomerLightningD.ListPeersAsync()) + .SelectMany(p => p.Channels) + .FirstOrDefault(); + switch (channel?.State) { - if (timeout.IsCancellationRequested) - { - timeout = new CancellationTokenSource(); - timeout.CancelAfter(10000); - createdChannel = c.Length == 0; - generated = false; - } - if (!createdChannel) - { - await CustomerEclair.RPC.OpenAsync(clightning, Money.Satoshis(16777215)); - createdChannel = true; - } - if (!generated && c.Length != 0 && c[0].State == "WAIT_FOR_FUNDING_CONFIRMED") - { - ExplorerNode.Generate(6); - generated = true; - } - c = (await CustomerEclair.RPC.ChannelsAsync()); + case null: + var merchantInfo = await WaitLNSynched(); + var clightning = new NodeInfo(merchantInfo.Id, MerchantCharge.P2PHost, merchantInfo.Port); + await CustomerLightningD.ConnectAsync(clightning); + var address = await CustomerLightningD.NewAddressAsync(); + await ExplorerNode.SendToAddressAsync(address, Money.Coins(0.2m)); + ExplorerNode.Generate(1); + await WaitLNSynched(); + await CustomerLightningD.FundChannelAsync(clightning, Money.Satoshis(16777215)); + break; + case "CHANNELD_AWAITING_LOCKIN": + ExplorerNode.Generate(1); + await WaitLNSynched(); + break; + case "CHANNELD_NORMAL": + return; + default: + throw new NotSupportedException(channel?.State ?? ""); + } + } + } + + private async Task WaitLNSynched() + { + while (true) + { + var merchantInfo = await MerchantCharge.Client.GetInfoAsync(); + var blockCount = await ExplorerNode.GetBlockCountAsync(); + if (merchantInfo.BlockHeight != blockCount) + { + await Task.Delay(1000); + } + else + { + return merchantInfo; } } } @@ -135,11 +142,10 @@ namespace BTCPayServer.Tests { var bolt11 = invoice.CryptoInfo.Where(o => o.PaymentUrls.BOLT11 != null).First().PaymentUrls.BOLT11; bolt11 = bolt11.Replace("lightning:", "", StringComparison.OrdinalIgnoreCase); - await CustomerEclair.RPC.SendAsync(bolt11); + await CustomerLightningD.SendAsync(bolt11); } - public EclairTester MerchantEclair { get; set; } - public EclairTester CustomerEclair { get; set; } + public CLightningRPCClient CustomerLightningD { get; set; } public ChargeTester MerchantCharge { get; private set; } internal string GetEnvironment(string variable, string defaultValue) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 92c209762..39d0edbdf 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -22,7 +22,6 @@ using BTCPayServer.Data; using Microsoft.EntityFrameworkCore; using BTCPayServer.Services.Rates; using Microsoft.Extensions.Caching.Memory; -using BTCPayServer.Payments.Lightning.Eclair; using System.Collections.Generic; using BTCPayServer.Models.StoreViewModels; using System.Threading.Tasks; diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index f96b52438..558aa6f1f 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -1,7 +1,7 @@ version: "3" # Run `docker-compose up dev` for bootstrapping your development environment -# Doing so will expose eclair API, NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run, +# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run, # The Visual Studio launch setting `Docker-Regtest` is configured to use this environment. services: @@ -17,8 +17,8 @@ services: TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver TESTS_PORT: 80 TESTS_HOSTNAME: tests - TEST_ECLAIR: http://eclair-cli:gpwefwmmewci@eclair:8080/ - TEST_CHARGE: http://api-token:foiewnccewuify@lightning-charged:9112/ + TEST_CUSTOMERLIGHTNINGD: http://lightningd:9835/ + TEST_MERCHANTCHARGE: http://api-token:foiewnccewuify@lightning-charged:9112/ expose: - "80" links: @@ -36,7 +36,7 @@ services: links: - nbxplorer - postgres - - eclair + - lightningd - lightning-charged nbxplorer: @@ -75,11 +75,6 @@ services: 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 - # Eclair is still using addwitnessaddress - deprecatedrpc=addwitnessaddress ports: - "43782:43782" expose: @@ -89,7 +84,7 @@ services: - "bitcoin_datadir:/data" lightning-charged: - image: shesek/lightning-charge:0.3.1 + image: shesek/lightning-charge:0.3.4 environment: NETWORK: regtest API_TOKEN: foiewnccewuify @@ -105,28 +100,23 @@ services: links: - bitcoind - eclair: - image: acinq/eclair@sha256:758eaf02683046a096ee03390d3a54df8fcfca50883f7560ab946a36ee4e81d8 - 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.api.enabled=true - -Declair.api.password=gpwefwmmewci - -Declair.chain=regtest - -Declair.api.binding-ip=0.0.0.0 - links: - - bitcoind + lightningd: + image: nicolasdorier/clightning + environment: + LIGHTNINGD_OPT: | + bitcoin-datadir=/etc/bitcoin + bitcoin-rpcconnect=bitcoind + network=regtest + log-level=debug ports: - - "30992:8080" # api port + - "30992:9835" # api port expose: - "9735" # server port - - "8080" # api port + - "9835" # api port + volumes: + - "bitcoin_datadir:/etc/bitcoin" + links: + - bitcoind litecoind: container_name: btcpayserver_dev_litecoind diff --git a/BTCPayServer/JsonConverters/LightMoneyJsonConverter.cs b/BTCPayServer/JsonConverters/LightMoneyJsonConverter.cs index 805a18c50..a0f023fb9 100644 --- a/BTCPayServer/JsonConverters/LightMoneyJsonConverter.cs +++ b/BTCPayServer/JsonConverters/LightMoneyJsonConverter.cs @@ -17,12 +17,15 @@ namespace BTCPayServer.JsonConverters return typeof(LightMoneyJsonConverter).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); } + Type longType = typeof(long).GetTypeInfo(); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { try { return reader.TokenType == JsonToken.Null ? null : - reader.TokenType == JsonToken.Integer ? new LightMoney((long)reader.Value) : + reader.TokenType == JsonToken.Integer ? + longType.IsAssignableFrom(reader.ValueType) ? new LightMoney((long)reader.Value) + : new LightMoney(long.MaxValue) : reader.TokenType == JsonToken.String ? new LightMoney(long.Parse((string)reader.Value, CultureInfo.InvariantCulture)) : null; } diff --git a/BTCPayServer/Payments/Lightning/CLightning/RPC/CLightningRPCClient.cs b/BTCPayServer/Payments/Lightning/CLightning/RPC/CLightningRPCClient.cs new file mode 100644 index 000000000..361539820 --- /dev/null +++ b/BTCPayServer/Payments/Lightning/CLightning/RPC/CLightningRPCClient.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using NBitcoin; +using NBitcoin.RPC; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Payments.Lightning.CLightning.RPC +{ + public class CLightningRPCClient + { + public Network Network { get; private set; } + public Uri Address { get; private set; } + + public CLightningRPCClient(Uri address, Network network) + { + if (address == null) + throw new ArgumentNullException(nameof(address)); + if (network == null) + throw new ArgumentNullException(nameof(network)); + Address = address; + Network = network; + } + + public Task GetInfoAsync() + { + return SendCommandAsync("getinfo"); + } + + public Task SendAsync(string bolt11) + { + return SendCommandAsync("pay", new[] { bolt11 }, true); + } + + public async Task ListPeersAsync() + { + var peers = await SendCommandAsync("listpeers", isArray: true); + foreach(var peer in peers) + { + peer.Channels = peer.Channels ?? Array.Empty(); + } + return peers; + } + + public Task FundChannelAsync(NodeInfo nodeInfo, Money money) + { + return SendCommandAsync("fundchannel", new object[] { nodeInfo.NodeId, money.Satoshi }, true); + } + + public Task ConnectAsync(NodeInfo nodeInfo) + { + return SendCommandAsync("connect", new[] { $"{nodeInfo.NodeId}@{nodeInfo.Host}:{nodeInfo.Port}" }, true); + } + + static Encoding UTF8 = new UTF8Encoding(false); + private async Task SendCommandAsync(string command, object[] parameters = null, bool noReturn = false, bool isArray = false) + { + parameters = parameters ?? Array.Empty(); + var domain = Address.DnsSafeHost; + if (!IPAddress.TryParse(domain, out IPAddress address)) + { + address = (await Dns.GetHostAddressesAsync(domain)).FirstOrDefault(); + if (address == null) + throw new Exception("Host not found"); + } + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + await socket.ConnectAsync(new IPEndPoint(address, Address.Port)); + using (var networkStream = new NetworkStream(socket)) + { + using (var textWriter = new StreamWriter(networkStream, UTF8, 1024 * 10, true)) + { + using (var jsonWriter = new JsonTextWriter(textWriter)) + { + var req = new JObject(); + req.Add("id", 0); + req.Add("method", command); + req.Add("params", new JArray(parameters)); + await req.WriteToAsync(jsonWriter); + await jsonWriter.FlushAsync(); + } + await textWriter.FlushAsync(); + } + await networkStream.FlushAsync(); + using (var textReader = new StreamReader(networkStream, UTF8, false, 1024 * 10, true)) + { + using (var jsonReader = new JsonTextReader(textReader)) + { + var result = await JObject.LoadAsync(jsonReader); + var error = result.Property("error"); + if(error != null) + { + throw new Exception(error.Value.ToString()); + } + if (noReturn) + return default(T); + if (isArray) + { + return result["result"].Children().First().Children().First().ToObject(); + } + return result["result"].ToObject(); + } + } + } + } + + public async Task NewAddressAsync() + { + var obj = await SendCommandAsync("newaddr"); + return BitcoinAddress.Create(obj.Property("address").Value.Value(), Network); + } + } +} diff --git a/BTCPayServer/Payments/Lightning/Eclair/NodeInfo.cs b/BTCPayServer/Payments/Lightning/CLightning/RPC/NodeInfo.cs similarity index 91% rename from BTCPayServer/Payments/Lightning/Eclair/NodeInfo.cs rename to BTCPayServer/Payments/Lightning/CLightning/RPC/NodeInfo.cs index e403d02b6..4f72eae99 100644 --- a/BTCPayServer/Payments/Lightning/Eclair/NodeInfo.cs +++ b/BTCPayServer/Payments/Lightning/CLightning/RPC/NodeInfo.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace BTCPayServer.Payments.Lightning.Eclair +namespace BTCPayServer.Payments.Lightning.CLightning.RPC { public class NodeInfo { diff --git a/BTCPayServer/Payments/Lightning/CLightning/RPC/PeerInfo.cs b/BTCPayServer/Payments/Lightning/CLightning/RPC/PeerInfo.cs new file mode 100644 index 000000000..2c9789469 --- /dev/null +++ b/BTCPayServer/Payments/Lightning/CLightning/RPC/PeerInfo.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NBitcoin; +using Newtonsoft.Json; + +namespace BTCPayServer.Payments.Lightning.CLightning.RPC +{ + public class ChannelInfo + { + public string State { get; set; } + public string Owner { get; set; } + + [JsonProperty("funding_txid")] + [JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))] + public uint256 FundingTxId { get; set; } + + [JsonProperty("msatoshi_to_us")] + [JsonConverter(typeof(JsonConverters.LightMoneyJsonConverter))] + public LightMoney ToUs { get; set; } + + [JsonProperty("msatoshi_total")] + [JsonConverter(typeof(JsonConverters.LightMoneyJsonConverter))] + public LightMoney Total { get; set; } + + [JsonProperty("dust_limit_satoshis")] + [JsonConverter(typeof(NBitcoin.JsonConverters.MoneyJsonConverter))] + public Money DustLimit { get; set; } + + [JsonProperty("max_htlc_value_in_flight_msat")] + [JsonConverter(typeof(JsonConverters.LightMoneyJsonConverter))] + public LightMoney MaxHTLCValueInFlight { get; set; } + + [JsonProperty("channel_reserve_satoshis")] + [JsonConverter(typeof(NBitcoin.JsonConverters.MoneyJsonConverter))] + public Money ChannelReserve { get; set; } + + [JsonProperty("htlc_minimum_msat")] + [JsonConverter(typeof(JsonConverters.LightMoneyJsonConverter))] + public LightMoney HTLCMinimum { get; set; } + + [JsonProperty("to_self_delay")] + public int ToSelfDelay { get; set; } + [JsonProperty("max_accepted_htlcs")] + public int MaxAcceptedHTLCS { get; set; } + public string[] Status { get; set; } + } + public class PeerInfo + { + public string State { get; set; } + public string Id { get; set; } + [JsonProperty("netaddr")] + public string[] NetworkAddresses { get; set; } + public bool Connected { get; set; } + public string Owner { get; set; } + public ChannelInfo[] Channels { get; set; } + + } +} diff --git a/BTCPayServer/Payments/Lightning/Eclair/AllChannelResponse.cs b/BTCPayServer/Payments/Lightning/Eclair/AllChannelResponse.cs deleted file mode 100644 index 67c33d4a9..000000000 --- a/BTCPayServer/Payments/Lightning/Eclair/AllChannelResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace BTCPayServer.Payments.Lightning.Eclair -{ - public class AllChannelResponse - { - public string ShortChannelId { get; set; } - public string NodeId1 { get; set; } - public string NodeId2 { get; set; } - } -} diff --git a/BTCPayServer/Payments/Lightning/Eclair/ChannelResponse.cs b/BTCPayServer/Payments/Lightning/Eclair/ChannelResponse.cs deleted file mode 100644 index 6e2424177..000000000 --- a/BTCPayServer/Payments/Lightning/Eclair/ChannelResponse.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace BTCPayServer.Payments.Lightning.Eclair -{ - public class ChannelResponse - { - - public string NodeId { get; set; } - public string ChannelId { get; set; } - public string State { get; set; } - } - public static class ChannelStates - { - public const string WAIT_FOR_FUNDING_CONFIRMED = "WAIT_FOR_FUNDING_CONFIRMED"; - - public const string NORMAL = "NORMAL"; - } -} diff --git a/BTCPayServer/Payments/Lightning/Eclair/EclairRPCClient.cs b/BTCPayServer/Payments/Lightning/Eclair/EclairRPCClient.cs deleted file mode 100644 index 771788b12..000000000 --- a/BTCPayServer/Payments/Lightning/Eclair/EclairRPCClient.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using NBitcoin; -using NBitcoin.JsonConverters; -using NBitcoin.RPC; - -namespace BTCPayServer.Payments.Lightning.Eclair -{ - public class SendResponse - { - public string PaymentHash { get; set; } - } - public class ChannelInfo - { - public string NodeId { get; set; } - public string ChannelId { get; set; } - public string State { get; set; } - } - public class EclairRPCClient - { - public EclairRPCClient(Uri address, Network network) - { - if (address == null) - throw new ArgumentNullException(nameof(address)); - if (network == null) - throw new ArgumentNullException(nameof(network)); - Address = address; - Network = network; - if (string.IsNullOrEmpty(address.UserInfo)) - throw new ArgumentException(paramName: nameof(address), message: "User info in the url should be provided"); - Password = address.UserInfo; - } - - public string Password { get; set; } - - public Network Network { get; private set; } - - - public GetInfoResponse GetInfo() - { - return GetInfoAsync().GetAwaiter().GetResult(); - } - - public Task GetInfoAsync() - { - return SendCommandAsync(new RPCRequest("getinfo", Array.Empty())); - } - - public async Task SendCommandAsync(RPCRequest request, bool throwIfRPCError = true) - { - var response = await SendCommandAsync(request, throwIfRPCError); - return Serializer.ToObject(response.ResultString, Network); - } - - public async Task SendCommandAsync(RPCRequest request, bool throwIfRPCError = true) - { - RPCResponse response = null; - HttpWebRequest webRequest = response == null ? CreateWebRequest() : null; - if (response == null) - { - var writer = new StringWriter(); - request.WriteJSON(writer); - writer.Flush(); - var json = writer.ToString(); - var bytes = Encoding.UTF8.GetBytes(json); -#if !(PORTABLE || NETCORE) - webRequest.ContentLength = bytes.Length; -#endif - var dataStream = await webRequest.GetRequestStreamAsync().ConfigureAwait(false); - await dataStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - await dataStream.FlushAsync().ConfigureAwait(false); - dataStream.Dispose(); - } - WebResponse webResponse = null; - WebResponse errorResponse = null; - try - { - webResponse = response == null ? await webRequest.GetResponseAsync().ConfigureAwait(false) : null; - response = response ?? RPCResponse.Load(await ToMemoryStreamAsync(webResponse.GetResponseStream()).ConfigureAwait(false)); - - if (throwIfRPCError) - response.ThrowIfError(); - } - catch (WebException ex) - { - if (ex.Response == null || ex.Response.ContentLength == 0 || - !ex.Response.ContentType.Equals("application/json", StringComparison.Ordinal)) - throw; - errorResponse = ex.Response; - response = RPCResponse.Load(await ToMemoryStreamAsync(errorResponse.GetResponseStream()).ConfigureAwait(false)); - if (throwIfRPCError) - response.ThrowIfError(); - } - finally - { - if (errorResponse != null) - { - errorResponse.Dispose(); - errorResponse = null; - } - if (webResponse != null) - { - webResponse.Dispose(); - webResponse = null; - } - } - return response; - } - - public AllChannelResponse[] AllChannels() - { - return AllChannelsAsync().GetAwaiter().GetResult(); - } - - public async Task AllChannelsAsync() - { - return await SendCommandAsync(new RPCRequest("allchannels", Array.Empty())).ConfigureAwait(false); - } - - public ChannelInfo[] Channels() - { - return ChannelsAsync().GetAwaiter().GetResult(); - } - - public async Task ChannelsAsync() - { - return await SendCommandAsync(new RPCRequest("channels", Array.Empty())).ConfigureAwait(false); - } - - public void Close(string channelId) - { - CloseAsync(channelId).GetAwaiter().GetResult(); - } - - public async Task SendAsync(string paymentRequest) - { - await SendCommandAsync(new RPCRequest("send", new[] { paymentRequest })).ConfigureAwait(false); - } - - public async Task CloseAsync(string channelId) - { - if (channelId == null) - throw new ArgumentNullException(nameof(channelId)); - try - { - await SendCommandAsync(new RPCRequest("close", new object[] { channelId })).ConfigureAwait(false); - } - catch (RPCException ex) when (ex.Message == "closing already in progress") - { - - } - } - - public ChannelResponse Channel(string channelId) - { - return ChannelAsync(channelId).GetAwaiter().GetResult(); - } - - public async Task ChannelAsync(string channelId) - { - if (channelId == null) - throw new ArgumentNullException(nameof(channelId)); - return await SendCommandAsync(new RPCRequest("channel", new object[] { channelId })).ConfigureAwait(false); - } - - public string[] AllNodes() - { - return AllNodesAsync().GetAwaiter().GetResult(); - } - - public async Task AllNodesAsync() - { - return await SendCommandAsync(new RPCRequest("allnodes", Array.Empty())).ConfigureAwait(false); - } - - public Uri Address { get; private set; } - - private HttpWebRequest CreateWebRequest() - { - var webRequest = (HttpWebRequest)WebRequest.Create(Address.AbsoluteUri); - webRequest.ContentType = "application/json"; - webRequest.Method = "POST"; - var auth = Convert.ToBase64String(Encoding.ASCII.GetBytes(Password)); - webRequest.Headers[HttpRequestHeader.Authorization] = $"Basic {auth}"; - return webRequest; - } - - - private async Task ToMemoryStreamAsync(Stream stream) - { - MemoryStream ms = new MemoryStream(); - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return ms; - } - - public string Open(NodeInfo node, Money fundingSatoshi, LightMoney pushAmount = null) - { - return OpenAsync(node, fundingSatoshi, pushAmount).GetAwaiter().GetResult(); - } - - public string Connect(NodeInfo node) - { - return ConnectAsync(node).GetAwaiter().GetResult(); - } - - public async Task ConnectAsync(NodeInfo node) - { - if (node == null) - throw new ArgumentNullException(nameof(node)); - return (await SendCommandAsync(new RPCRequest("connect", new object[] { node.NodeId, node.Host, node.Port })).ConfigureAwait(false)).ResultString; - } - - public string Receive(LightMoney amount, string description = null) - { - return ReceiveAsync(amount, description).GetAwaiter().GetResult(); - } - - public async Task ReceiveAsync(LightMoney amount, string description = null) - { - if (amount == null) - throw new ArgumentNullException(nameof(amount)); - List args = new List(); - args.Add(amount.MilliSatoshi); - if(description != null) - { - args.Add(description); - } - return (await SendCommandAsync(new RPCRequest("receive", args.ToArray())).ConfigureAwait(false)).ResultString; - } - - public async Task OpenAsync(NodeInfo node, Money fundingSatoshi, LightMoney pushAmount = null) - { - if (fundingSatoshi == null) - throw new ArgumentNullException(nameof(fundingSatoshi)); - if (node == null) - throw new ArgumentNullException(nameof(node)); - pushAmount = pushAmount ?? LightMoney.Zero; - - var result = await SendCommandAsync(new RPCRequest("open", new object[] { node.NodeId, fundingSatoshi.Satoshi, pushAmount.MilliSatoshi })); - - return result.ResultString; - } - - - } -} diff --git a/BTCPayServer/Payments/Lightning/Eclair/GetInfoResponse.cs b/BTCPayServer/Payments/Lightning/Eclair/GetInfoResponse.cs deleted file mode 100644 index d43b07bbe..000000000 --- a/BTCPayServer/Payments/Lightning/Eclair/GetInfoResponse.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using NBitcoin; -using Newtonsoft.Json; - -namespace BTCPayServer.Payments.Lightning.Eclair -{ - public class GetInfoResponse - { - public string NodeId { get; set; } - public string Alias { get; set; } - public int Port { get; set; } - public uint256 ChainHash { get; set; } - public int BlockHeight { get; set; } - } -} From 81328b26678daf843baaee6a8a6494200797b7f3 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 17 Mar 2018 17:49:42 +0900 Subject: [PATCH 07/14] Update charge in tests and fix two build time warnings --- BTCPayServer.Tests/docker-compose.yml | 2 +- BTCPayServer/Controllers/InvoiceController.UI.cs | 2 +- BTCPayServer/Services/Invoices/InvoiceEntity.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 558aa6f1f..ec45c6cff 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -84,7 +84,7 @@ services: - "bitcoin_datadir:/data" lightning-charged: - image: shesek/lightning-charge:0.3.4 + image: shesek/lightning-charge:0.3.5 environment: NETWORK: regtest API_TOKEN: foiewnccewuify diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index f6e04d78c..9d7c620a2 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -77,7 +77,7 @@ namespace BTCPayServer.Controllers var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod; if(onchainMethod != null) { - cryptoPayment.Address = onchainMethod.DepositAddress.ToString(); + cryptoPayment.Address = onchainMethod.DepositAddress; } cryptoPayment.Rate = FormatCurrency(data); cryptoPayment.PaymentUrl = cryptoInfo.PaymentUrls.BIP21; diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 92d964675..d1eedaf5a 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -615,7 +615,7 @@ namespace BTCPayServer.Services.Invoices { TxFee = bitcoinPaymentMethod.TxFee; FeeRate = bitcoinPaymentMethod.FeeRate; - DepositAddress = bitcoinPaymentMethod.DepositAddress.ToString(); + DepositAddress = bitcoinPaymentMethod.DepositAddress; } var jobj = JObject.Parse(JsonConvert.SerializeObject(paymentMethod)); PaymentMethodDetails = jobj; From 09f97915d6d839d83f3b809df413d37f90d49ecd Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 17 Mar 2018 19:26:30 +0900 Subject: [PATCH 08/14] Fix charge listener bug, and decouple charge from clightning in test docker compose --- BTCPayServer.Tests/README.md | 29 +++++++- BTCPayServer.Tests/ServerTester.cs | 1 + BTCPayServer.Tests/docker-compose.yml | 67 +++++++++++++------ .../docker-customer-lightning-cli.ps1 | 1 + .../Payments/Lightning/ChargeListener.cs | 33 ++++++--- BTCPayServer/Services/Wallets/BTCPayWallet.cs | 4 +- 6 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 BTCPayServer.Tests/docker-customer-lightning-cli.ps1 diff --git a/BTCPayServer.Tests/README.md b/BTCPayServer.Tests/README.md index 5b1d7689b..4b65f03a4 100644 --- a/BTCPayServer.Tests/README.md +++ b/BTCPayServer.Tests/README.md @@ -26,7 +26,7 @@ docker-compose down If you want to stop, and remove all existing data ``` -docker-compose down -v +docker-compose down --v ``` You can run the tests inside a container by running @@ -35,7 +35,9 @@ You can run the tests inside a container by running docker-compose run --rm tests ``` -## Send commands to bitcoind +## How to manually test payments + +### Using the test bitcoin-cli You can call bitcoin-cli inside the container with `docker exec`, for example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`: ``` @@ -47,7 +49,28 @@ If you are using Powershell: .\docker-bitcoin-cli.ps1 sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090 ``` -For sending to litecoin, use .\docker-litecoin-cli.ps1 instead. +### Using the test litecoin-cli + +Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` instead. + +### Using the test lightning-cli + +``` +docker exec -ti btcpayservertests_customer_lightningd_1 lightning-cli pay lnbcrt100u1pd2e6uspp5ajnadvhazjrz55twd5k6yeg9u87wpw0q2fdr7g960yl5asv5fmnqdq9d3hkccqpxmedyrk0ehw5ueqx5e0r4qrrv74cewddfcvsxaawqz7634cmjj39sqwy5tvhz0hasktkk6t9pqfdh3edmf3z09zst5y7khv3rvxh8ctqqw6mwhh +``` + +If you are using Powershell: +``` +.\docker-customer-lightning-cli.ps1 pay lnbcrt100u1pd2e6uspp5ajnadvhazjrz55twd5k6yeg9u87wpw0q2fdr7g960yl5asv5fmnqdq9d3hkccqpxmedyrk0ehw5ueqx5e0r4qrrv74cewddfcvsxaawqz7634cmjj39sqwy5tvhz0hasktkk6t9pqfdh3edmf3z09zst5y7khv3rvxh8ctqqw6mwhh +``` + +If you get this message: + +``` +{ "code" : 205, "message" : "Could not find a route", "data" : { "getroute_tries" : 1, "sendpay_tries" : 0 } } +``` + +Please, run the test `CanSetLightningServer`, this will establish a channel between the customer and the merchant, then, retry. ## FAQ diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index dbd01b834..04d526cd0 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -102,6 +102,7 @@ namespace BTCPayServer.Tests await ExplorerNode.SendToAddressAsync(address, Money.Coins(0.2m)); ExplorerNode.Generate(1); await WaitLNSynched(); + await Task.Delay(1000); await CustomerLightningD.FundChannelAsync(clightning, Money.Satoshis(16777215)); break; case "CHANNELD_AWAITING_LOCKIN": diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index ec45c6cff..7672bbec9 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -17,7 +17,7 @@ services: TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver TESTS_PORT: 80 TESTS_HOSTNAME: tests - TEST_CUSTOMERLIGHTNINGD: http://lightningd:9835/ + TEST_CUSTOMERLIGHTNINGD: http://customer_lightningd:9835/ TEST_MERCHANTCHARGE: http://api-token:foiewnccewuify@lightning-charged:9112/ expose: - "80" @@ -36,7 +36,8 @@ services: links: - nbxplorer - postgres - - lightningd + - customer_lightningd + - merchant_lightningd - lightning-charged nbxplorer: @@ -83,24 +84,7 @@ services: volumes: - "bitcoin_datadir:/data" - lightning-charged: - image: shesek/lightning-charge:0.3.5 - environment: - NETWORK: regtest - API_TOKEN: foiewnccewuify - SKIP_BITCOIND: 1 - BITCOIND_RPCCONNECT: bitcoind - volumes: - - "bitcoin_datadir:/etc/bitcoin" - expose: - - "9112" # Charge - - "9735" # Lightning - ports: - - "54938:9112" # Charge - links: - - bitcoind - - lightningd: + customer_lightningd: image: nicolasdorier/clightning environment: LIGHTNINGD_OPT: | @@ -115,6 +99,46 @@ services: - "9835" # api port volumes: - "bitcoin_datadir:/etc/bitcoin" + - "customer_lightningd_datadir:/root/.lightning" + links: + - bitcoind + + lightning-charged: + image: shesek/lightning-charge:0.3.5 + environment: + NETWORK: regtest + API_TOKEN: foiewnccewuify + SKIP_BITCOIND: 1 + BITCOIND_RPCCONNECT: bitcoind + volumes: + - "bitcoin_datadir:/etc/bitcoin" + - "lightning_charge_datadir:/data" + - "merchant_lightningd_datadir:/etc/lightning" + expose: + - "9112" # Charge + - "9735" # Lightning + ports: + - "54938:9112" # Charge + links: + - bitcoind + - merchant_lightningd + + merchant_lightningd: + image: nicolasdorier/clightning + environment: + LIGHTNINGD_OPT: | + bitcoin-datadir=/etc/bitcoin + bitcoin-rpcconnect=bitcoind + network=regtest + log-level=debug + ports: + - "30993:9835" # api port + expose: + - "9735" # server port + - "9835" # api port + volumes: + - "bitcoin_datadir:/etc/bitcoin" + - "merchant_lightningd_datadir:/root/.lightning" links: - bitcoind @@ -145,3 +169,6 @@ services: volumes: bitcoin_datadir: + customer_lightningd_datadir: + merchant_lightningd_datadir: + lightning_charge_datadir: diff --git a/BTCPayServer.Tests/docker-customer-lightning-cli.ps1 b/BTCPayServer.Tests/docker-customer-lightning-cli.ps1 new file mode 100644 index 000000000..64209bfe0 --- /dev/null +++ b/BTCPayServer.Tests/docker-customer-lightning-cli.ps1 @@ -0,0 +1 @@ +docker exec -ti btcpayservertests_customer_lightningd_1 lightning-cli $args diff --git a/BTCPayServer/Payments/Lightning/ChargeListener.cs b/BTCPayServer/Payments/Lightning/ChargeListener.cs index c2b7f6e6c..4f031409b 100644 --- a/BTCPayServer/Payments/Lightning/ChargeListener.cs +++ b/BTCPayServer/Payments/Lightning/ChargeListener.cs @@ -60,6 +60,8 @@ namespace BTCPayServer.Payments.Lightning private async Task EnsureListening(string invoiceId, bool poll) { + if (Listening(invoiceId)) + return; var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); foreach (var paymentMethod in invoice.GetPaymentMethods(_NetworkProvider) .Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike)) @@ -89,16 +91,13 @@ namespace BTCPayServer.Payments.Lightning var chargeInvoice = await charge.GetInvoice(lightningMethod.InvoiceId); if (chargeInvoice == null) continue; - if(chargeInvoice.Status == "paid") + if (chargeInvoice.Status == "paid") await AddPayment(network, chargeInvoice, listenedInvoice); if (chargeInvoice.Status == "paid" || chargeInvoice.Status == "expired") continue; } - if (!Listening(invoiceId)) - { - StartListening(listenedInvoice); - } + StartListening(listenedInvoice); } } @@ -157,6 +156,7 @@ namespace BTCPayServer.Payments.Lightning catch (Exception ex) { Logs.PayServer.LogError(ex, $"{supportedPaymentMethod.CryptoCode} (Lightning): Error while contacting {supportedPaymentMethod.GetLightningChargeUrl(false)}"); + DoneListening(supportedPaymentMethod.GetLightningChargeUrl(false)); } Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Stop listening {supportedPaymentMethod.GetLightningChargeUrl(false)}"); } @@ -202,9 +202,26 @@ namespace BTCPayServer.Payments.Lightning return false; } + /// + /// Stop listening all invoices on this server + /// + /// + private void DoneListening(Uri uri) + { + lock (_ListenedInvoiceByChargeInvoiceId) + { + foreach (var listenedInvoice in _ListenedInvoiceByLightningUrl[uri.AbsoluteUri]) + { + _ListenedInvoiceByChargeInvoiceId.Remove(listenedInvoice.PaymentMethodDetails.InvoiceId); + _InvoiceIds.Remove(listenedInvoice.InvoiceId); + } + _ListenedInvoiceByLightningUrl.Remove(uri.AbsoluteUri); + } + } + bool Listening(string invoiceId) { - lock(_ListenedInvoiceByLightningUrl) + lock (_ListenedInvoiceByLightningUrl) { return _InvoiceIds.Contains(invoiceId); } @@ -230,10 +247,6 @@ namespace BTCPayServer.Payments.Lightning { var listen = Listen(listenedInvoice.SupportedPaymentMethod, listenedInvoice.Network); _ListeningLightning.Add(listen); - listen.ContinueWith(_ => - { - DoneListening(listenedInvoice); - }, TaskScheduler.Default); } _ListenedInvoiceByLightningUrl.Add(listenedInvoice.Uri, listenedInvoice); _ListenedInvoiceByChargeInvoiceId.Add(listenedInvoice.PaymentMethodDetails.InvoiceId, listenedInvoice); diff --git a/BTCPayServer/Services/Wallets/BTCPayWallet.cs b/BTCPayServer/Services/Wallets/BTCPayWallet.cs index af8cfd749..2877c91c3 100644 --- a/BTCPayServer/Services/Wallets/BTCPayWallet.cs +++ b/BTCPayServer/Services/Wallets/BTCPayWallet.cs @@ -126,13 +126,13 @@ namespace BTCPayServer.Services.Wallets } catch { - Logs.PayServer.LogError("Call to NBXplorer GetUTXOsAsync timed out, this should never happen, please report this issue to NBXplorer developers"); + Logs.PayServer.LogError($"{Network.CryptoCode}: Call to NBXplorer GetUTXOsAsync timed out, this should never happen, please report this issue to NBXplorer developers"); throw; } var spentTime = DateTimeOffset.UtcNow - now; if (spentTime.TotalSeconds > 30) { - Logs.PayServer.LogWarning($"NBXplorer took {(int)spentTime.TotalSeconds} seconds to reply, there is something wrong, please report this issue to NBXplorer developers"); + Logs.PayServer.LogWarning($"{Network.CryptoCode}: NBXplorer took {(int)spentTime.TotalSeconds} seconds to reply, there is something wrong, please report this issue to NBXplorer developers"); } entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan; return result; From e2c4c913ffb9cf973ba941824fd709daad5bbdef Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 17 Mar 2018 19:33:36 +0900 Subject: [PATCH 09/14] Remove hard coded container names in test docker-compose --- BTCPayServer.Tests/README.md | 2 +- BTCPayServer.Tests/docker-bitcoin-cli.ps1 | 2 +- BTCPayServer.Tests/docker-compose.yml | 2 -- BTCPayServer.Tests/docker-litecoin-cli.ps1 | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/BTCPayServer.Tests/README.md b/BTCPayServer.Tests/README.md index 4b65f03a4..7b5b47c6b 100644 --- a/BTCPayServer.Tests/README.md +++ b/BTCPayServer.Tests/README.md @@ -41,7 +41,7 @@ docker-compose run --rm tests You can call bitcoin-cli inside the container with `docker exec`, for example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`: ``` -docker exec -ti btcpayserver_dev_bitcoind bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090 +docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090 ``` If you are using Powershell: diff --git a/BTCPayServer.Tests/docker-bitcoin-cli.ps1 b/BTCPayServer.Tests/docker-bitcoin-cli.ps1 index 6516d1011..7f1cac7c1 100644 --- a/BTCPayServer.Tests/docker-bitcoin-cli.ps1 +++ b/BTCPayServer.Tests/docker-bitcoin-cli.ps1 @@ -1 +1 @@ -docker exec -ti btcpayserver_dev_bitcoind bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" $args \ No newline at end of file +docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" $args diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 7672bbec9..810bf3c86 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -65,7 +65,6 @@ services: - litecoind bitcoind: - container_name: btcpayserver_dev_bitcoind image: nicolasdorier/docker-bitcoin:0.16.0 environment: BITCOIN_EXTRA_ARGS: | @@ -143,7 +142,6 @@ services: - bitcoind litecoind: - container_name: btcpayserver_dev_litecoind image: nicolasdorier/docker-litecoin:0.14.2 environment: BITCOIN_EXTRA_ARGS: | diff --git a/BTCPayServer.Tests/docker-litecoin-cli.ps1 b/BTCPayServer.Tests/docker-litecoin-cli.ps1 index 8e3038a95..cfd0801d2 100644 --- a/BTCPayServer.Tests/docker-litecoin-cli.ps1 +++ b/BTCPayServer.Tests/docker-litecoin-cli.ps1 @@ -1 +1 @@ -docker exec -ti btcpayserver_dev_litecoind litecoin-cli -regtest -conf="/data/litecoin.conf" -datadir="/data" $args +docker exec -ti btcpayservertests_litecoind_1 litecoin-cli -regtest -conf="/data/litecoin.conf" -datadir="/data" $args From e5d626e0fdced1fe858b859333283300a427e149 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 17 Mar 2018 19:35:37 +0900 Subject: [PATCH 10/14] Remove useless stuff in command line for tests --- BTCPayServer.Tests/README.md | 2 +- BTCPayServer.Tests/docker-bitcoin-cli.ps1 | 2 +- BTCPayServer.Tests/docker-litecoin-cli.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BTCPayServer.Tests/README.md b/BTCPayServer.Tests/README.md index 7b5b47c6b..156568046 100644 --- a/BTCPayServer.Tests/README.md +++ b/BTCPayServer.Tests/README.md @@ -41,7 +41,7 @@ docker-compose run --rm tests You can call bitcoin-cli inside the container with `docker exec`, for example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`: ``` -docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090 +docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -datadir="/data" sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090 ``` If you are using Powershell: diff --git a/BTCPayServer.Tests/docker-bitcoin-cli.ps1 b/BTCPayServer.Tests/docker-bitcoin-cli.ps1 index 7f1cac7c1..163f64a83 100644 --- a/BTCPayServer.Tests/docker-bitcoin-cli.ps1 +++ b/BTCPayServer.Tests/docker-bitcoin-cli.ps1 @@ -1 +1 @@ -docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" $args +docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -datadir="/data" $args diff --git a/BTCPayServer.Tests/docker-litecoin-cli.ps1 b/BTCPayServer.Tests/docker-litecoin-cli.ps1 index cfd0801d2..f79d6b58a 100644 --- a/BTCPayServer.Tests/docker-litecoin-cli.ps1 +++ b/BTCPayServer.Tests/docker-litecoin-cli.ps1 @@ -1 +1 @@ -docker exec -ti btcpayservertests_litecoind_1 litecoin-cli -regtest -conf="/data/litecoin.conf" -datadir="/data" $args +docker exec -ti btcpayservertests_litecoind_1 litecoin-cli -datadir="/data" $args From 57bb3b231c078589501e19bd7c64bab2f48eb917 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 17 Mar 2018 19:40:23 +0900 Subject: [PATCH 11/14] Add linux script for manual testing --- BTCPayServer.Tests/README.md | 7 ++++--- BTCPayServer.Tests/docker-bitcoin-cli.sh | 3 +++ BTCPayServer.Tests/docker-customer-lightning-cli.sh | 3 +++ BTCPayServer.Tests/docker-litecoin-cli.sh | 3 +++ 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100755 BTCPayServer.Tests/docker-bitcoin-cli.sh create mode 100755 BTCPayServer.Tests/docker-customer-lightning-cli.sh create mode 100755 BTCPayServer.Tests/docker-litecoin-cli.sh diff --git a/BTCPayServer.Tests/README.md b/BTCPayServer.Tests/README.md index 156568046..a9bb1a930 100644 --- a/BTCPayServer.Tests/README.md +++ b/BTCPayServer.Tests/README.md @@ -41,7 +41,7 @@ docker-compose run --rm tests You can call bitcoin-cli inside the container with `docker exec`, for example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`: ``` -docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -datadir="/data" sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090 +./docker-bitcoin-cli.sh sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090 ``` If you are using Powershell: @@ -51,12 +51,13 @@ If you are using Powershell: ### Using the test litecoin-cli -Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` instead. +Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` and `.\docker-litecoin-cli.sh` instead. ### Using the test lightning-cli +If you are using Linux: ``` -docker exec -ti btcpayservertests_customer_lightningd_1 lightning-cli pay lnbcrt100u1pd2e6uspp5ajnadvhazjrz55twd5k6yeg9u87wpw0q2fdr7g960yl5asv5fmnqdq9d3hkccqpxmedyrk0ehw5ueqx5e0r4qrrv74cewddfcvsxaawqz7634cmjj39sqwy5tvhz0hasktkk6t9pqfdh3edmf3z09zst5y7khv3rvxh8ctqqw6mwhh +./docker-customer-lightning-cli.sh pay lnbcrt100u1pd2e6uspp5ajnadvhazjrz55twd5k6yeg9u87wpw0q2fdr7g960yl5asv5fmnqdq9d3hkccqpxmedyrk0ehw5ueqx5e0r4qrrv74cewddfcvsxaawqz7634cmjj39sqwy5tvhz0hasktkk6t9pqfdh3edmf3z09zst5y7khv3rvxh8ctqqw6mwhh ``` If you are using Powershell: diff --git a/BTCPayServer.Tests/docker-bitcoin-cli.sh b/BTCPayServer.Tests/docker-bitcoin-cli.sh new file mode 100755 index 000000000..9d7a9215d --- /dev/null +++ b/BTCPayServer.Tests/docker-bitcoin-cli.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker exec -ti btcpayservertests_bitcoind_1 bitcoin-cli -datadir="/data" "$@" diff --git a/BTCPayServer.Tests/docker-customer-lightning-cli.sh b/BTCPayServer.Tests/docker-customer-lightning-cli.sh new file mode 100755 index 000000000..d2893ea77 --- /dev/null +++ b/BTCPayServer.Tests/docker-customer-lightning-cli.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker exec -ti btcpayservertests_customer_lightningd_1 lightning-cli "$@" diff --git a/BTCPayServer.Tests/docker-litecoin-cli.sh b/BTCPayServer.Tests/docker-litecoin-cli.sh new file mode 100755 index 000000000..584bbf22e --- /dev/null +++ b/BTCPayServer.Tests/docker-litecoin-cli.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker exec -ti btcpayservertests_litecoind_1 litecoin-cli -datadir="/data" "$@" From b8a4f0c0122e15da75841ffb7e2305728379eb47 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 18 Mar 2018 01:59:16 +0900 Subject: [PATCH 12/14] fix tests --- BTCPayServer.Tests/ServerTester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 04d526cd0..e001cddab 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -57,7 +57,7 @@ namespace BTCPayServer.Tests var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork; CustomerLightningD = new CLightningRPCClient(new Uri(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "http://127.0.0.1:30992/")), btc); - MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "http://api-token:foiewnccewuify@127.0.0.1:54938/", "lightning-charged", btc); + MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "http://api-token:foiewnccewuify@127.0.0.1:54938/", "merchant_lightningd", btc); PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay")) { From acb2407654cebde59a8258f6de6759a01c7ae94d Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 18 Mar 2018 02:26:33 +0900 Subject: [PATCH 13/14] Fix bug: Paying a lightning invoice might miss 1 satoshi due to rounding error --- BTCPayServer/BTCPayServer.csproj | 2 +- BTCPayServer/Extensions.cs | 13 ++++++++++++ .../Payments/Bitcoin/NBXplorerListener.cs | 6 ++++-- .../Payments/Lightning/ChargeListener.cs | 5 +++-- .../Lightning/LightningLikePaymentHandler.cs | 2 +- .../Services/Invoices/InvoiceEntity.cs | 21 ++++--------------- .../Services/Invoices/InvoiceRepository.cs | 15 ++++++++++++- 7 files changed, 40 insertions(+), 24 deletions(-) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 95181a9e3..32df3a015 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.1.49 + 1.0.1.50 NU1701,CA1816,CA1308,CA1810,CA2208 diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 5f24c943d..a93393c42 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -30,6 +30,19 @@ namespace BTCPayServer { public static class Extensions { + public static decimal RoundUp(decimal value, int precision) + { + for (int i = 0; i < precision; i++) + { + value = value * 10m; + } + value = Math.Ceiling(value); + for (int i = 0; i < precision; i++) + { + value = value / 10m; + } + return value; + } public static PaymentMethodId GetpaymentMethodId(this InvoiceCryptoInfo info) { return new PaymentMethodId(info.CryptoCode, Enum.Parse(info.PaymentType)); diff --git a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs index e33b9a9e7..d3f2856d5 100644 --- a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs +++ b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs @@ -160,7 +160,8 @@ namespace BTCPayServer.Payments.Bitcoin if (!alreadyExist) { var payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network.CryptoCode); - await ReceivedPayment(wallet, invoice.Id, payment, evt.DerivationStrategy); + if(payment != null) + await ReceivedPayment(wallet, invoice.Id, payment, evt.DerivationStrategy); } else { @@ -330,7 +331,8 @@ namespace BTCPayServer.Payments.Bitcoin var paymentData = new BitcoinLikePaymentData(coin.Coin, transaction.Transaction.RBF); var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network.CryptoCode).ConfigureAwait(false); alreadyAccounted.Add(coin.Coin.Outpoint); - invoice = await ReceivedPayment(wallet, invoice.Id, payment, strategy); + if (payment != null) + invoice = await ReceivedPayment(wallet, invoice.Id, payment, strategy); totalPayment++; } } diff --git a/BTCPayServer/Payments/Lightning/ChargeListener.cs b/BTCPayServer/Payments/Lightning/ChargeListener.cs index 4f031409b..e418a4633 100644 --- a/BTCPayServer/Payments/Lightning/ChargeListener.cs +++ b/BTCPayServer/Payments/Lightning/ChargeListener.cs @@ -163,12 +163,13 @@ namespace BTCPayServer.Payments.Lightning private async Task AddPayment(BTCPayNetwork network, ChargeInvoice notification, ListenedInvoice listenedInvoice) { - await _InvoiceRepository.AddPayment(listenedInvoice.InvoiceId, notification.PaidAt.Value, new LightningLikePaymentData() + var payment = await _InvoiceRepository.AddPayment(listenedInvoice.InvoiceId, notification.PaidAt.Value, new LightningLikePaymentData() { BOLT11 = notification.PaymentRequest, Amount = notification.MilliSatoshi }, network.CryptoCode, accounted: true); - _Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment")); + if(payment != null) + _Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment")); } private static ChargeClient GetChargeClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network) diff --git a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs index 61bc332a4..e8b852670 100644 --- a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs +++ b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs @@ -21,7 +21,7 @@ namespace BTCPayServer.Payments.Lightning public override async Task CreatePaymentMethodDetails(LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network) { var invoice = paymentMethod.ParentEntity; - var due = invoice.ProductInformation.Price / paymentMethod.Rate; + var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, 8); var client = GetClient(supportedPaymentMethod, network); var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow; var lightningInvoice = await client.CreateInvoiceAsync(new CreateInvoiceRequest() diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index d1eedaf5a..0730c6c18 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -646,8 +646,9 @@ namespace BTCPayServer.Services.Invoices var paid = 0m; var cryptoPaid = 0.0m; + int precision = 8; var paidTxFee = 0m; - bool paidEnough = paid >= RoundUp(totalDue, 8); + bool paidEnough = paid >= Extensions.RoundUp(totalDue, precision); int txRequired = 0; var payments = ParentEntity.GetPayments() @@ -662,7 +663,7 @@ namespace BTCPayServer.Services.Invoices totalDue += txFee; paidTxFee += txFee; } - paidEnough |= paid >= RoundUp(totalDue, 8); + paidEnough |= paid >= Extensions.RoundUp(totalDue, precision); if (GetId() == _.GetPaymentMethodId()) { cryptoPaid += _.GetCryptoPaymentData().GetValue(); @@ -681,7 +682,7 @@ namespace BTCPayServer.Services.Invoices paidTxFee += GetTxFee(); } - accounting.TotalDue = Money.Coins(RoundUp(totalDue, 8)); + accounting.TotalDue = Money.Coins(Extensions.RoundUp(totalDue, precision)); accounting.Paid = Money.Coins(paid); accounting.TxRequired = txRequired; accounting.CryptoPaid = Money.Coins(cryptoPaid); @@ -691,20 +692,6 @@ namespace BTCPayServer.Services.Invoices return accounting; } - private static decimal RoundUp(decimal value, int precision) - { - for (int i = 0; i < precision; i++) - { - value = value * 10m; - } - value = Math.Ceiling(value); - for (int i = 0; i < precision; i++) - { - value = value / 10m; - } - return value; - } - private decimal GetTxFee() { var method = GetPaymentMethodDetails(); diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index fafec2908..4dc28f7c6 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -461,6 +461,15 @@ namespace BTCPayServer.Services.Invoices AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray()); } + /// + /// Add a payment to an invoice + /// + /// + /// + /// + /// + /// + /// The PaymentEntity or null if already added public async Task AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, string cryptoCode, bool accounted = false) { using (var context = _ContextFactory.CreateContext()) @@ -486,7 +495,11 @@ namespace BTCPayServer.Services.Invoices context.Payments.Add(data); - await context.SaveChangesAsync().ConfigureAwait(false); + try + { + await context.SaveChangesAsync().ConfigureAwait(false); + } + catch(DbUpdateException) { return null; } // Already exists AddToTextSearch(invoiceId, paymentData.GetSearchTerms()); return entity; } From 8342ad91759867167753a2dee3a72313c6b04ee9 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 18 Mar 2018 12:58:14 +0900 Subject: [PATCH 14/14] Remove reference to browser mode of ledger --- BTCPayServer/Views/Stores/AddDerivationScheme.cshtml | 2 +- BTCPayServer/Views/Stores/Wallet.cshtml | 2 +- BTCPayServer/wwwroot/js/StoreWallet.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml index 8726a2494..6be492c03 100644 --- a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml +++ b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml @@ -31,7 +31,7 @@
    -
  • Activate the Browser support and refresh this page
  • +
  • Make sure you are running the Ledger Bitcoin or Litecoin app with version superior or equal to 1.2.4
  • Use a browser supporting the U2F protocol

Detecting hardware wallet...

diff --git a/BTCPayServer/wwwroot/js/StoreWallet.js b/BTCPayServer/wwwroot/js/StoreWallet.js index c5373fc11..dbd370d22 100644 --- a/BTCPayServer/wwwroot/js/StoreWallet.js +++ b/BTCPayServer/wwwroot/js/StoreWallet.js @@ -125,7 +125,7 @@ .catch(function (reason) { if (reason.message === "Sign failed") - reason = "Have you forgot to activate browser support in your ledger app?"; + reason = "Are you running the ledger app with version equals or above 1.2.4?"; Write('hw', 'error', reason); }) .then(function (result) {