From 0819df3d26c27227d36108341fc95971803f897e Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 15 Sep 2020 13:46:45 +0200 Subject: [PATCH 1/2] Ensure new bolt11 invoice on partial payment to btcpay invoice --- BTCPayServer.Tests/UnitTest1.cs | 27 +++++++++ .../Lightning/LightningLikePaymentHandler.cs | 11 +++- .../Payments/Lightning/LightningListener.cs | 59 ++++++++++++++++++- 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index ab03e923d..08f01b3fb 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -747,6 +747,33 @@ namespace BTCPayServer.Tests Assert.True(tor.Services.Where(t => t.ServiceType == TorServiceType.Other).Count() > 1); } + + [Fact(Timeout = 60 * 2 * 1000)] + [Trait("Integration", "Integration")] + [Trait("Lightning", "Lightning")] + public async Task EnsureNewLightningInvoiceOnPartialPayment() + { + using var tester = ServerTester.Create(); + tester.ActivateLightning(); + await tester.StartAsync(); + await tester.EnsureChannelsSetup(); + var user = tester.NewAccount(); + await user.GrantAccessAsync(); + await user.RegisterDerivationSchemeAsync("BTC"); + await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning); + + var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.01m, "BTC")); + await tester.WaitForEvent(async () => + { + await tester.ExplorerNode.SendToAddressAsync( + BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.005m)); + }); + + var localInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id); + Assert.NotEqual(invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11, + localInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11); + } + [Fact(Timeout = 60 * 2 * 1000)] [Trait("Integration", "Integration")] [Trait("Lightning", "Lightning")] diff --git a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs index 4fb7c869c..e3145791c 100644 --- a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs +++ b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs @@ -47,8 +47,17 @@ namespace BTCPayServer.Payments.Lightning //direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers var storeBlob = store.GetStoreBlob(); var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, network); + var invoice = paymentMethod.ParentEntity; - var due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility); + decimal due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility); + try + { + due = paymentMethod.Calculate().Due.ToDecimal(MoneyUnit.BTC); + } + catch (Exception) + { + // ignored + } var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network); var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow; if (expiry < TimeSpan.Zero) diff --git a/BTCPayServer/Payments/Lightning/LightningListener.cs b/BTCPayServer/Payments/Lightning/LightningListener.cs index 4c8da4d52..1cd2a6fd9 100644 --- a/BTCPayServer/Payments/Lightning/LightningListener.cs +++ b/BTCPayServer/Payments/Lightning/LightningListener.cs @@ -5,11 +5,14 @@ using System.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; +using BTCPayServer.Client.Models; +using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Lightning; using BTCPayServer.Logging; using BTCPayServer.Services; using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Stores; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -24,6 +27,8 @@ namespace BTCPayServer.Payments.Lightning private readonly IMemoryCache _memoryCache; readonly BTCPayNetworkProvider _NetworkProvider; private readonly LightningClientFactoryService lightningClientFactory; + private readonly LightningLikePaymentHandler _lightningLikePaymentHandler; + private readonly StoreRepository _storeRepository; readonly Channel _CheckInvoices = Channel.CreateUnbounded(); Task _CheckingInvoice; readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>(); @@ -32,13 +37,17 @@ namespace BTCPayServer.Payments.Lightning InvoiceRepository invoiceRepository, IMemoryCache memoryCache, BTCPayNetworkProvider networkProvider, - LightningClientFactoryService lightningClientFactory) + LightningClientFactoryService lightningClientFactory, + LightningLikePaymentHandler lightningLikePaymentHandler, + StoreRepository storeRepository) { _Aggregator = aggregator; _InvoiceRepository = invoiceRepository; _memoryCache = memoryCache; _NetworkProvider = networkProvider; this.lightningClientFactory = lightningClientFactory; + _lightningLikePaymentHandler = lightningLikePaymentHandler; + _storeRepository = storeRepository; } async Task CheckingInvoice(CancellationToken cancellation) @@ -54,7 +63,7 @@ namespace BTCPayServer.Payments.Lightning if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener) || !instanceListener.IsListening) { - instanceListener = instanceListener ?? new LightningInstanceListener(_InvoiceRepository, _Aggregator, listenedInvoice.SupportedPaymentMethod, lightningClientFactory, listenedInvoice.Network); + instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, listenedInvoice.SupportedPaymentMethod, lightningClientFactory, listenedInvoice.Network); var status = await instanceListener.PollPayment(listenedInvoice, cancellation); if (status is null || status is LightningInvoiceStatus.Paid || @@ -134,6 +143,51 @@ namespace BTCPayServer.Payments.Lightning _CheckInvoices.Writer.TryWrite(inv.Invoice.Id); } })); + leases.Add(_Aggregator.Subscribe(async inv => + { + if (inv.State.Status == InvoiceStatus.New && + inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial) + { + var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId); + var paymentMethods = invoice.GetPaymentMethods() + .Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike).ToArray(); + var store = await _storeRepository.FindStore(invoice.StoreId); + if (paymentMethods.Any()) + { + var logs = new InvoiceLogs(); + logs.Write("Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.", InvoiceEventData.EventSeverity.Info); + foreach (var paymentMethod in paymentMethods) + { + try + { + var supportedMethod = + invoice.GetSupportedPaymentMethod( + paymentMethod.GetId()).First(); + var prepObj = + _lightningLikePaymentHandler.PreparePayment(supportedMethod, store, + paymentMethod.Network); + var newPaymentMethodDetails = + await _lightningLikePaymentHandler.CreatePaymentMethodDetails( + logs, supportedMethod, + paymentMethod, store, paymentMethod.Network, prepObj); + + await _InvoiceRepository.NewAddress(invoice.Id, newPaymentMethodDetails, + paymentMethod.Network); + + _Aggregator.Publish(new InvoiceNewAddressEvent(invoice.Id, newPaymentMethodDetails.GetPaymentDestination(),paymentMethod.Network )); + } + catch (Exception e) + { + logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}", + InvoiceEventData.EventSeverity.Error); + } + } + + await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs); + _CheckInvoices.Writer.TryWrite(inv.InvoiceId); + } + } + })); _CheckingInvoice = CheckingInvoice(_Cts.Token); _ListenPoller = new Timer(async s => { @@ -168,7 +222,6 @@ namespace BTCPayServer.Payments.Lightning } readonly CancellationTokenSource _Cts = new CancellationTokenSource(); - readonly HashSet _InvoiceIds = new HashSet(); private Timer _ListenPoller; public async Task StopAsync(CancellationToken cancellationToken) From d334eed9bb5daf9296bdd5f2f79d1119f1541531 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 16 Sep 2020 14:50:06 +0200 Subject: [PATCH 2/2] improve test --- BTCPayServer.Tests/UnitTest1.cs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 08f01b3fb..271eaa000 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -761,7 +761,7 @@ namespace BTCPayServer.Tests await user.GrantAccessAsync(); await user.RegisterDerivationSchemeAsync("BTC"); await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning); - + user.SetNetworkFeeMode(NetworkFeeMode.Never); var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.01m, "BTC")); await tester.WaitForEvent(async () => { @@ -769,9 +769,27 @@ namespace BTCPayServer.Tests BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.005m)); }); - var localInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id); - Assert.NotEqual(invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11, - localInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11); + var newInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id); + var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11; + var oldBolt11= invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11; + Assert.NotEqual(newBolt11,oldBolt11); + Assert.Equal(newInvoice.BtcDue.GetValue(), BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC)); + var evt = await tester.WaitForEvent(async () => + { + await tester.SendLightningPaymentAsync(newInvoice); + }); + Assert.Equal(evt.InvoiceId, invoice.Id); + Assert.Equal(InvoiceStatus.Complete, evt.State.Status); + Assert.Equal(InvoiceExceptionStatus.None, evt.State.ExceptionStatus); + + evt = await tester.WaitForEvent(async () => + { + await tester.SendLightningPaymentAsync(invoice); + }); + + Assert.Equal(evt.InvoiceId, invoice.Id); + Assert.Equal(InvoiceStatus.Invalid, evt.State.Status); + Assert.Equal(InvoiceExceptionStatus.PaidOver, evt.State.ExceptionStatus); } [Fact(Timeout = 60 * 2 * 1000)]