fix issues

This commit is contained in:
Kukks
2023-04-14 15:04:45 +02:00
parent 86bb8b6110
commit d9dbee1347
4 changed files with 96 additions and 75 deletions

View File

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

View File

@@ -1,3 +1,3 @@
namespace BTCPayServer.Plugins.Prism; namespace BTCPayServer.Plugins.Prism;
public record PendingPayout(long BalanceAmount, long FeeCharged); public record PendingPayout(long PayoutAmount, long FeeCharged);

View File

@@ -33,6 +33,7 @@ namespace BTCPayServer.Plugins.Prism
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly LightningClientFactoryService _lightningClientFactoryService; private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions; private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private Dictionary<string, PrismSettings> _prismSettings; private Dictionary<string, PrismSettings> _prismSettings;
public SatBreaker(StoreRepository storeRepository, public SatBreaker(StoreRepository storeRepository,
@@ -43,7 +44,8 @@ namespace BTCPayServer.Plugins.Prism
LightningLikePayoutHandler lightningLikePayoutHandler, LightningLikePayoutHandler lightningLikePayoutHandler,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
LightningClientFactoryService lightningClientFactoryService, LightningClientFactoryService lightningClientFactoryService,
IOptions<LightningNetworkOptions> lightningNetworkOptions) : base(eventAggregator, logger) IOptions<LightningNetworkOptions> lightningNetworkOptions,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings) : base(eventAggregator, logger)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_logger = logger; _logger = logger;
@@ -53,6 +55,7 @@ namespace BTCPayServer.Plugins.Prism
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_lightningClientFactoryService = lightningClientFactoryService; _lightningClientFactoryService = lightningClientFactoryService;
_lightningNetworkOptions = lightningNetworkOptions; _lightningNetworkOptions = lightningNetworkOptions;
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
} }
public override async Task StartAsync(CancellationToken cancellationToken) public override async Task StartAsync(CancellationToken cancellationToken)
@@ -78,88 +81,101 @@ namespace BTCPayServer.Plugins.Prism
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var payoutsToCheck = _prismSettings.ToDictionary(pair => pair.Key, pair => pair.Value.PendingPayouts); try
var payoutIds = payoutsToCheck.SelectMany(pair => pair.Value.Keys).ToArray();
var payouts = (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{ {
PayoutIds = payoutIds, var payoutsToCheck =
States = new[] {PayoutState.Cancelled, PayoutState.Completed} _prismSettings.ToDictionary(pair => pair.Key, pair => pair.Value.PendingPayouts);
})); var payoutIds = payoutsToCheck
var lnClients = new Dictionary<string, ILightningClient>(); .SelectMany(pair => pair.Value?.Keys.ToArray() ?? Array.Empty<string>()).ToArray();
var res = new Dictionary<string, CreditDestination>(); var payouts = (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
foreach (var payout in payouts)
{
if (payoutsToCheck.TryGetValue(payout.StoreDataId, out var pendingPayouts) &&
pendingPayouts.TryGetValue(payout.Id, out var pendingPayout))
{ {
long toCredit = 0; PayoutIds = payoutIds,
switch (payout.State) States = new[] {PayoutState.Cancelled, PayoutState.Completed}
}));
var lnClients = new Dictionary<string, ILightningClient>();
var res = new Dictionary<string, CreditDestination>();
foreach (var payout in payouts)
{
if (payoutsToCheck.TryGetValue(payout.StoreDataId, out var pendingPayouts) &&
pendingPayouts.TryGetValue(payout.Id, out var pendingPayout))
{ {
case PayoutState.Completed: long toCredit = 0;
switch (payout.State)
{
case PayoutState.Completed:
var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob; var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob;
long? feePaid = null; long? feePaid = null;
if (!string.IsNullOrEmpty(proof?.PaymentHash)) if (!string.IsNullOrEmpty(proof?.PaymentHash))
{
if (!lnClients.TryGetValue(payout.StoreDataId, out var lnClient))
{ {
var store = await _storeRepository.FindStore(payout.StoreDataId); if (!lnClients.TryGetValue(payout.StoreDataId, out var lnClient))
{
var store = await _storeRepository.FindStore(payout.StoreDataId);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"); var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var id = new PaymentMethodId("BTC", PaymentTypes.LightningLike); var id = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider) var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>() .OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id); .FirstOrDefault(d => d.PaymentId == id);
if (existing?.GetExternalLightningUrl() is { } connectionString) if (existing?.GetExternalLightningUrl() is { } connectionString)
{ {
lnClient = _lightningClientFactoryService.Create(connectionString, network); lnClient = _lightningClientFactoryService.Create(connectionString,
} network);
else if (existing?.IsInternalNode is true && }
_lightningNetworkOptions.Value.InternalLightningByCryptoCode else if (existing?.IsInternalNode is true &&
.TryGetValue(network.CryptoCode, _lightningNetworkOptions.Value.InternalLightningByCryptoCode
out var internalLightningNode)) .TryGetValue(network.CryptoCode,
{ out var internalLightningNode))
lnClient = _lightningClientFactoryService.Create(internalLightningNode, {
network); lnClient = _lightningClientFactoryService.Create(internalLightningNode,
network);
}
lnClients.Add(payout.StoreDataId, lnClient);
} }
if (lnClient is not null)
lnClients.Add(payout.StoreDataId, lnClient); {
var p = await lnClient.GetPayment(proof.PaymentHash, CancellationToken);
feePaid = (long) p.Fee.ToUnit(LightMoneyUnit.Satoshi);
}
} }
if (lnClient is not null) if (feePaid is not null)
{ {
var p = await lnClient.GetPayment(proof.PaymentHash, CancellationToken); toCredit = pendingPayout.FeeCharged - feePaid.Value;
feePaid = (long) p.Fee.ToUnit(LightMoneyUnit.Satoshi);
} }
}
if (feePaid is not null) break;
{ case PayoutState.Cancelled:
toCredit = pendingPayout.FeeCharged - feePaid.Value; toCredit = pendingPayout.PayoutAmount + pendingPayout.FeeCharged;
} break;
}
break; res.TryAdd(payout.StoreDataId,
case PayoutState.Cancelled: new CreditDestination(payout.StoreDataId, new Dictionary<string, long>(),
toCredit = pendingPayout.BalanceAmount + pendingPayout.FeeCharged; new List<string>()));
break; var credDest = res[payout.StoreDataId];
credDest.PayoutsToRemove.Add(payout.Id);
credDest.Destcredits.Add(payout.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination,
toCredit);
} }
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.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");
} }
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;
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
} }
} }
@@ -175,11 +191,12 @@ namespace BTCPayServer.Plugins.Prism
return _prismSettings.TryGetValue(storeId, out var settings) ? settings : new PrismSettings(); return _prismSettings.TryGetValue(storeId, out var settings) ? settings : new PrismSettings();
} }
public async Task<bool> UpdatePrismSettingsForStore(string storeId, PrismSettings updatedSettings, bool skipLock = false) public async Task<bool> UpdatePrismSettingsForStore(string storeId, PrismSettings updatedSettings,
bool skipLock = false)
{ {
try try
{ {
if(!skipLock) if (!skipLock)
await _updateLock.WaitAsync(); await _updateLock.WaitAsync();
var currentSettings = await Get(storeId); var currentSettings = await Get(storeId);
@@ -199,7 +216,7 @@ namespace BTCPayServer.Plugins.Prism
finally finally
{ {
if(!skipLock) if (!skipLock)
_updateLock.Release(); _updateLock.Release();
} }
@@ -313,15 +330,18 @@ namespace BTCPayServer.Plugins.Prism
private async Task CreatePayouts(string storeId, PrismSettings prismSettings) private async Task CreatePayouts(string storeId, PrismSettings prismSettings)
{ {
var threshold = (long) Math.Max(1,
Math.Round(prismSettings.SatThreshold * 1.02, 0, MidpointRounding.AwayFromZero));
foreach (var (destination, amtMsats) in prismSettings.DestinationBalance) foreach (var (destination, amtMsats) in prismSettings.DestinationBalance)
{ {
var amt = amtMsats / 1000; var amt = amtMsats / 1000;
if (amt >= threshold) if (amt >= prismSettings.SatThreshold)
{ {
var reserveFee = (long) Math.Max(1, Math.Round(amt * 0.02, 0, MidpointRounding.AwayFromZero)); var reserveFee = (long) Math.Max(1, Math.Round(amt * 0.02, 0, MidpointRounding.AwayFromZero));
var payoutAmount = amt - reserveFee; var payoutAmount = amt - reserveFee;
if (payoutAmount <= 0)
{
continue;
}
var payout = await _pullPaymentHostedService.Claim(new ClaimRequest() var payout = await _pullPaymentHostedService.Claim(new ClaimRequest()
{ {
Destination = new LNURLPayClaimDestinaton(destination), Destination = new LNURLPayClaimDestinaton(destination),
@@ -332,8 +352,9 @@ namespace BTCPayServer.Plugins.Prism
}); });
if (payout.Result == ClaimRequest.ClaimResult.Ok) if (payout.Result == ClaimRequest.ClaimResult.Ok)
{ {
prismSettings.PendingPayouts??=new(); prismSettings.PendingPayouts ??= new();
prismSettings.PendingPayouts.Add(payout.PayoutData.Id, new PendingPayout(payoutAmount, reserveFee)); prismSettings.PendingPayouts.Add(payout.PayoutData.Id,
new PendingPayout(payoutAmount, reserveFee));
prismSettings.DestinationBalance.AddOrReplace(destination, prismSettings.DestinationBalance.AddOrReplace(destination,
amtMsats - (payoutAmount + reserveFee) * 1000); amtMsats - (payoutAmount + reserveFee) * 1000);
} }

View File

@@ -184,7 +184,7 @@
<tr> <tr>
<td>@payoutId</td> <td>@payoutId</td>
<td>@pendingPayout.FeeCharged</td> <td>@pendingPayout.FeeCharged</td>
<td>@pendingPayout.BalanceAmount</td> <td>@pendingPayout.PayoutAmount</td>
</tr> </tr>
} }
</table> </table>