upd prism

This commit is contained in:
Kukks
2023-08-14 14:56:32 +02:00
parent c4d07a5134
commit adbd1eebfd
4 changed files with 139 additions and 95 deletions

View File

@@ -11,7 +11,7 @@
<PropertyGroup>
<Product>LN Prism</Product>
<Description>Automated value splits for lightning.</Description>
<Version>1.1.11</Version>
<Version>1.1.12</Version>
</PropertyGroup>
<!-- Plugin development properties -->
<PropertyGroup>

View File

@@ -43,6 +43,7 @@
<th>Payout Id</th>
<th>Reserve fee</th>
<th>Amount</th>
<th>Actions</th>
</tr>
@foreach (var (payoutId, pendingPayout) in PendingPayouts)
{
@@ -50,6 +51,8 @@
<td>@payoutId</td>
<td>@pendingPayout.FeeCharged</td>
<td>@pendingPayout.PayoutAmount</td>
<td>
<button type="button" class="btn btn-sm btn-link" @onclick="() => OnCancelPayout.InvokeAsync(payoutId)">Update</button></td>
</tr>
}
</table>
@@ -70,6 +73,9 @@
[Parameter]
public EventCallback<(string destination, long newBalance)> OnUpdate { get; set; }
[Parameter]
public EventCallback<string> 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;
}
}

View File

@@ -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<IPayoutProcessorFactory> PayoutProcessorFactories
@inject SatBreaker SatBreaker
@inject LinkGenerator LinkGenerator
@inject PullPaymentHostedService PullPaymentHostedService
@inject IHttpContextAccessor HttpContextAccessor
@inject ILogger<PrismEdit> Logger
@implements IDisposable
@@ -134,7 +136,13 @@ else
}
</div>
</div>
<PrismBalances OnUpdate="OnUpdateBalance" DestinationBalance="Settings.DestinationBalance" PendingPayouts="Settings.PendingPayouts"></PrismBalances>
<PrismBalances
OnUpdate="OnUpdateBalance"
OnCancelPayout="CancelPayout"
DestinationBalance="Settings.DestinationBalance"
PendingPayouts="Settings.PendingPayouts"
></PrismBalances>
@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}));
}
}

View File

@@ -77,15 +77,24 @@ namespace BTCPayServer.Plugins.Prism
}
await base.StartAsync(cancellationToken);
_ = CheckPayouts(CancellationToken);
PushEvent(new CheckPayoutsEvt());
}
protected override void SubscribeToEvents()
{
base.SubscribeToEvents();
Subscribe<InvoiceEvent>();
Subscribe<CheckPayoutsEvt>();
Subscribe<PayoutEvent>();
}
class CheckPayoutsEvt
{
}
private TaskCompletionSource _checkPayoutTcs = new();
/// <summary>
/// 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
/// <param name="cancellationToken"></param>
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<string>()).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<string>()).ToArray();
var payouts = (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{
PayoutIds = payoutIds,
States = new[] {PayoutState.Cancelled, PayoutState.Completed}
}));
var lnClients = new Dictionary<string, ILightningClient>();
var res = new Dictionary<string, CreditDestination>();
PayoutIds = payoutIds,
States = new[] {PayoutState.Cancelled, PayoutState.Completed}
}));
var lnClients = new Dictionary<string, ILightningClient>();
var res = new Dictionary<string, CreditDestination>();
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<BTCPayNetwork>("BTC");
var id = new PaymentMethodId("BTC", LightningPaymentType.Instance);
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);
if (existing?.GetExternalLightningUrl() is { } connectionString)
{
var store = await _storeRepository.FindStore(payout.StoreDataId);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var id = new PaymentMethodId("BTC", LightningPaymentType.Instance);
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.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<string, long>(),
new List<string>()));
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<string, long>(),
new List<string>()));
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<string, long> Destcredits, List<string> 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<bool> CreatePayouts(string storeId, PrismSettings prismSettings)
{
var result = false;