From adbd1eebfd06aa2a97fd38017a22f1fc575f9153 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 14 Aug 2023 14:56:32 +0200 Subject: [PATCH] upd prism --- .../BTCPayServer.Plugins.Prism.csproj | 2 +- .../Components/PrismBalances.razor | 7 +- .../Components/PrismEdit.razor | 20 +- .../BTCPayServer.Plugins.Prism/SatBreaker.cs | 205 ++++++++++-------- 4 files changed, 139 insertions(+), 95 deletions(-) diff --git a/Plugins/BTCPayServer.Plugins.Prism/BTCPayServer.Plugins.Prism.csproj b/Plugins/BTCPayServer.Plugins.Prism/BTCPayServer.Plugins.Prism.csproj index 636c27b..a2599a6 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/BTCPayServer.Plugins.Prism.csproj +++ b/Plugins/BTCPayServer.Plugins.Prism/BTCPayServer.Plugins.Prism.csproj @@ -11,7 +11,7 @@ LN Prism Automated value splits for lightning. - 1.1.11 + 1.1.12 diff --git a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor index 983f81c..35a1ea5 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor +++ b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor @@ -43,6 +43,7 @@ Payout Id Reserve fee Amount + Actions @foreach (var (payoutId, pendingPayout) in PendingPayouts) { @@ -50,6 +51,8 @@ @payoutId @pendingPayout.FeeCharged @pendingPayout.PayoutAmount + + } @@ -70,6 +73,9 @@ [Parameter] public EventCallback<(string destination, long newBalance)> OnUpdate { get; set; } + + [Parameter] + public EventCallback OnCancelPayout { get; set; } private EventCallback StartUpdate(string dest, long balance) { @@ -86,6 +92,5 @@ await OnUpdate.InvokeAsync((UpdatingDestination, Convert.ToInt64(UpdatingValue.Value * 1000m))); UpdatingDestination = null; } - } \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor index 99d1e1a..8911a76 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor +++ b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor @@ -1,6 +1,7 @@ @using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Models @using BTCPayServer.Client.Models +@using BTCPayServer.HostedServices @using BTCPayServer.Payments @using BTCPayServer.PayoutProcessors @using Microsoft.AspNetCore.Http @@ -14,6 +15,7 @@ @inject IEnumerable PayoutProcessorFactories @inject SatBreaker SatBreaker @inject LinkGenerator LinkGenerator +@inject PullPaymentHostedService PullPaymentHostedService @inject IHttpContextAccessor HttpContextAccessor @inject ILogger Logger @implements IDisposable @@ -134,7 +136,13 @@ else } - + @if (StatusMessageModel != null) @@ -467,6 +475,10 @@ else else { + if (string.IsNullOrEmpty(SelectedDestinationId)) + { + SelectedDestinationId = obj.Id; + } Settings.Destinations.AddOrReplace(SelectedDestinationId, obj.Destination); await SaveDestinations(); var needSave = false; @@ -509,4 +521,10 @@ else } + private async Task CancelPayout(string payoutId) + { + await PullPaymentHostedService.Cancel( + new PullPaymentHostedService.CancelRequest(new[] {payoutId}, new[] {StoreId})); + } + } \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs b/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs index 68cd046..38a8250 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs +++ b/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs @@ -77,15 +77,24 @@ namespace BTCPayServer.Plugins.Prism } await base.StartAsync(cancellationToken); - _ = CheckPayouts(CancellationToken); + PushEvent(new CheckPayoutsEvt()); } protected override void SubscribeToEvents() { base.SubscribeToEvents(); Subscribe(); + Subscribe(); + Subscribe(); } + class CheckPayoutsEvt + { + + } + + private TaskCompletionSource _checkPayoutTcs = new(); + /// /// Go through generated payouts and check if they are completed or cancelled, and then remove them from the list. /// If the fee can be fetched, we compute what the difference was from the original fee we computed (hardcoded 2% of the balance) @@ -94,117 +103,117 @@ namespace BTCPayServer.Plugins.Prism /// private async Task CheckPayouts(CancellationToken cancellationToken) { - while (!cancellationToken.IsCancellationRequested) + try { - try + _checkPayoutTcs = new TaskCompletionSource(); + var payoutsToCheck = + _prismSettings.ToDictionary(pair => pair.Key, pair => pair.Value.PendingPayouts); + var payoutIds = payoutsToCheck + .SelectMany(pair => pair.Value?.Keys.ToArray() ?? Array.Empty()).ToArray(); + var payouts = (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery() { - var payoutsToCheck = - _prismSettings.ToDictionary(pair => pair.Key, pair => pair.Value.PendingPayouts); - var payoutIds = payoutsToCheck - .SelectMany(pair => pair.Value?.Keys.ToArray() ?? Array.Empty()).ToArray(); - var payouts = (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery() - { - PayoutIds = payoutIds, - States = new[] {PayoutState.Cancelled, PayoutState.Completed} - })); - var lnClients = new Dictionary(); - var res = new Dictionary(); + PayoutIds = payoutIds, + States = new[] {PayoutState.Cancelled, PayoutState.Completed} + })); + var lnClients = new Dictionary(); + var res = new Dictionary(); - foreach (var payout in payouts) + foreach (var payout in payouts) + { + if (payoutsToCheck.TryGetValue(payout.StoreDataId, out var pendingPayouts) && + pendingPayouts.TryGetValue(payout.Id, out var pendingPayout)) { - if (payoutsToCheck.TryGetValue(payout.StoreDataId, out var pendingPayouts) && - pendingPayouts.TryGetValue(payout.Id, out var pendingPayout)) + long toCredit = 0; + switch (payout.State) { - long toCredit = 0; - switch (payout.State) - { - case PayoutState.Completed: + case PayoutState.Completed: - var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob; + var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob; - long? feePaid = null; - if (!string.IsNullOrEmpty(proof?.PaymentHash)) + long? feePaid = null; + if (!string.IsNullOrEmpty(proof?.PaymentHash)) + { + if (!lnClients.TryGetValue(payout.StoreDataId, out var lnClient)) { - if (!lnClients.TryGetValue(payout.StoreDataId, out var lnClient)) + var store = await _storeRepository.FindStore(payout.StoreDataId); + + var network = _btcPayNetworkProvider.GetNetwork("BTC"); + var id = new PaymentMethodId("BTC", LightningPaymentType.Instance); + var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider) + .OfType() + .FirstOrDefault(d => d.PaymentId == id); + if (existing?.GetExternalLightningUrl() is { } connectionString) { - var store = await _storeRepository.FindStore(payout.StoreDataId); - - var network = _btcPayNetworkProvider.GetNetwork("BTC"); - var id = new PaymentMethodId("BTC", LightningPaymentType.Instance); - var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider) - .OfType() - .FirstOrDefault(d => d.PaymentId == id); - if (existing?.GetExternalLightningUrl() is { } connectionString) - { - lnClient = _lightningClientFactoryService.Create(connectionString, - network); - } - else if (existing?.IsInternalNode is true && - _lightningNetworkOptions.Value.InternalLightningByCryptoCode - .TryGetValue(network.CryptoCode, - out var internalLightningNode)) - { - lnClient = _lightningClientFactoryService.Create(internalLightningNode, - network); - } - - - lnClients.Add(payout.StoreDataId, lnClient); + lnClient = _lightningClientFactoryService.Create(connectionString, + network); + } + else if (existing?.IsInternalNode is true && + _lightningNetworkOptions.Value.InternalLightningByCryptoCode + .TryGetValue(network.CryptoCode, + out var internalLightningNode)) + { + lnClient = _lightningClientFactoryService.Create(internalLightningNode, + network); } - if (lnClient is not null && proof?.PaymentHash is not null) - { - try - { - var p = await lnClient.GetPayment(proof.PaymentHash, CancellationToken); - feePaid = (long) p?.Fee?.ToUnit(LightMoneyUnit.Satoshi); - } - catch (Exception e) - { - _logger.LogError(e, - "The payment fee could not be fetched from the lightning node due to an error, so we will use the allocated 2% as the fee."); - } + lnClients.Add(payout.StoreDataId, lnClient); + } + + if (lnClient is not null && proof?.PaymentHash is not null) + { + try + { + + var p = await lnClient.GetPayment(proof.PaymentHash, CancellationToken); + feePaid = (long) p?.Fee?.ToUnit(LightMoneyUnit.Satoshi); + } + catch (Exception e) + { + _logger.LogError(e, + "The payment fee could not be fetched from the lightning node due to an error, so we will use the allocated 2% as the fee."); } } + } - if (feePaid is not null) - { - toCredit = pendingPayout.FeeCharged - feePaid.Value; - } + if (feePaid is not null) + { + toCredit = pendingPayout.FeeCharged - feePaid.Value; + } - break; - case PayoutState.Cancelled: - toCredit = pendingPayout.PayoutAmount + pendingPayout.FeeCharged; - break; - } - - res.TryAdd(payout.StoreDataId, - new CreditDestination(payout.StoreDataId, new Dictionary(), - new List())); - var credDest = res[payout.StoreDataId]; - credDest.PayoutsToRemove.Add(payout.Id); - - credDest.Destcredits.Add(payout.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination, - toCredit); + break; + case PayoutState.Cancelled: + toCredit = pendingPayout.PayoutAmount + pendingPayout.FeeCharged; + break; } + + res.TryAdd(payout.StoreDataId, + new CreditDestination(payout.StoreDataId, new Dictionary(), + new List())); + var credDest = res[payout.StoreDataId]; + credDest.PayoutsToRemove.Add(payout.Id); + + credDest.Destcredits.Add(payout.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination, + toCredit); } - - var tcs = new TaskCompletionSource(cancellationToken); - PushEvent(new PayoutCheckResult(res.Values.ToArray(), tcs)); - //we wait for ProcessEvent to handle this result so that we avoid race conditions. - await tcs.Task; - } - catch (Exception e) - { - _logger.LogError(e, "Error while checking payouts"); } - await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); + PushEvent(new PayoutCheckResult(res.Values.ToArray())); + + } + catch (Exception e) + { + _logger.LogError(e, "Error while checking payouts"); } - } - record PayoutCheckResult(CreditDestination[] CreditDestinations, TaskCompletionSource Tcs); + _ = Task.WhenAny(_checkPayoutTcs.Task, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken)).ContinueWith( + task => + { + if(!task.IsCanceled) + PushEvent(new CheckPayoutsEvt()); + }, cancellationToken); + } + record PayoutCheckResult(CreditDestination[] CreditDestinations); record CreditDestination(string StoreId, Dictionary Destcredits, List PayoutsToRemove); @@ -298,8 +307,6 @@ namespace BTCPayServer.Plugins.Prism } } } - - payoutCheckResult.Tcs.SetResult(); return; } @@ -379,6 +386,19 @@ namespace BTCPayServer.Plugins.Prism await UpdatePrismSettingsForStore(invoiceEvent.Invoice.StoreId, prismSettings, true); } } + + if(evt is CheckPayoutsEvt) + { + await CheckPayouts(cancellationToken); + } + if(evt is PayoutEvent payoutEvent) + { + if (!_prismSettings.TryGetValue(payoutEvent.Payout.StoreDataId, out var prismSettings)) + { + return; + } + //TODO: Trigger checking payouts, but we have to make sure we dont end up calling it multiple times at the same time as they schedule recursively. + } } catch (Exception e) { @@ -390,6 +410,7 @@ namespace BTCPayServer.Plugins.Prism } } + private async Task CreatePayouts(string storeId, PrismSettings prismSettings) { var result = false;