mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 06:21:44 +01:00
Payjoin label fixes (#3986)
* Payjoin label fixes * When a payjoin label was applied, coin selection filter would not work * When a payjoin happened with a receive address wallet, the payjoin label was not applied * Coin selection shows when a utxo is currently reserved for a payjoin. Applies both to UI and to GF API * remove reserved label * Update BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs
This commit is contained in:
parent
2e6246e385
commit
bec888da19
11 changed files with 82 additions and 71 deletions
13
BTCPayServer.Abstractions/Contracts/IUTXOLocker.cs
Normal file
13
BTCPayServer.Abstractions/Contracts/IUTXOLocker.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NBitcoin;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Payments.PayJoin;
|
||||||
|
|
||||||
|
public interface IUTXOLocker
|
||||||
|
{
|
||||||
|
Task<bool> TryLock(OutPoint outpoint);
|
||||||
|
Task<bool> TryUnlock(params OutPoint[] outPoints);
|
||||||
|
Task<bool> TryLockInputs(OutPoint[] outPoints);
|
||||||
|
Task<HashSet<OutPoint>> FindLocks(OutPoint[] outpoints);
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ namespace BTCPayServer.Tests
|
||||||
using var tester = CreateServerTester();
|
using var tester = CreateServerTester();
|
||||||
await tester.StartAsync();
|
await tester.StartAsync();
|
||||||
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
||||||
var repo = tester.PayTester.GetService<PayJoinRepository>();
|
var repo = tester.PayTester.GetService<UTXOLocker>();
|
||||||
var outpoint = RandomOutpoint();
|
var outpoint = RandomOutpoint();
|
||||||
|
|
||||||
// Should not be locked
|
// Should not be locked
|
||||||
|
@ -166,7 +166,7 @@ namespace BTCPayServer.Tests
|
||||||
using var tester = CreateServerTester();
|
using var tester = CreateServerTester();
|
||||||
await tester.StartAsync();
|
await tester.StartAsync();
|
||||||
var broadcaster = tester.PayTester.GetService<DelayedTransactionBroadcaster>();
|
var broadcaster = tester.PayTester.GetService<DelayedTransactionBroadcaster>();
|
||||||
var payjoinRepository = tester.PayTester.GetService<PayJoinRepository>();
|
var payjoinRepository = tester.PayTester.GetService<UTXOLocker>();
|
||||||
broadcaster.Disable();
|
broadcaster.Disable();
|
||||||
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
||||||
var btcPayWallet = tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
var btcPayWallet = tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
||||||
|
@ -633,7 +633,7 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
await tester.StartAsync();
|
await tester.StartAsync();
|
||||||
var broadcaster = tester.PayTester.GetService<DelayedTransactionBroadcaster>();
|
var broadcaster = tester.PayTester.GetService<DelayedTransactionBroadcaster>();
|
||||||
var payjoinRepository = tester.PayTester.GetService<PayJoinRepository>();
|
var payjoinRepository = tester.PayTester.GetService<UTXOLocker>();
|
||||||
broadcaster.Disable();
|
broadcaster.Disable();
|
||||||
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
||||||
var btcPayWallet = tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
var btcPayWallet = tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
||||||
|
@ -1155,7 +1155,7 @@ retry:
|
||||||
Assert.True(invoiceEntity.GetPayments(false).All(p => !p.Accounted));
|
Assert.True(invoiceEntity.GetPayments(false).All(p => !p.Accounted));
|
||||||
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData(false).First().PayjoinInformation.ContributedOutPoints[0];
|
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData(false).First().PayjoinInformation.ContributedOutPoints[0];
|
||||||
});
|
});
|
||||||
var payjoinRepository = tester.PayTester.GetService<PayJoinRepository>();
|
var payjoinRepository = tester.PayTester.GetService<UTXOLocker>();
|
||||||
// The outpoint should now be available for next pj selection
|
// The outpoint should now be available for next pj selection
|
||||||
Assert.False(await payjoinRepository.TryUnlock(ourOutpoint));
|
Assert.False(await payjoinRepository.TryUnlock(ourOutpoint));
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
private readonly WalletReceiveService _walletReceiveService;
|
private readonly WalletReceiveService _walletReceiveService;
|
||||||
private readonly IFeeProviderFactory _feeProviderFactory;
|
private readonly IFeeProviderFactory _feeProviderFactory;
|
||||||
private readonly LabelFactory _labelFactory;
|
private readonly LabelFactory _labelFactory;
|
||||||
|
private readonly UTXOLocker _utxoLocker;
|
||||||
|
|
||||||
public GreenfieldStoreOnChainWalletsController(
|
public GreenfieldStoreOnChainWalletsController(
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
|
@ -68,7 +69,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
WalletReceiveService walletReceiveService,
|
WalletReceiveService walletReceiveService,
|
||||||
IFeeProviderFactory feeProviderFactory,
|
IFeeProviderFactory feeProviderFactory,
|
||||||
LabelFactory labelFactory
|
LabelFactory labelFactory,
|
||||||
|
UTXOLocker utxoLocker
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
|
@ -85,6 +87,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
_walletReceiveService = walletReceiveService;
|
_walletReceiveService = walletReceiveService;
|
||||||
_feeProviderFactory = feeProviderFactory;
|
_feeProviderFactory = feeProviderFactory;
|
||||||
_labelFactory = labelFactory;
|
_labelFactory = labelFactory;
|
||||||
|
_utxoLocker = utxoLocker;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
@ -317,9 +320,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
var walletId = new WalletId(storeId, cryptoCode);
|
var walletId = new WalletId(storeId, cryptoCode);
|
||||||
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId);
|
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId);
|
||||||
var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);
|
var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);
|
||||||
|
|
||||||
return Ok(utxos.Select(coin =>
|
return Ok(utxos.Select(coin =>
|
||||||
{
|
{
|
||||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
|
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
|
||||||
|
var labels = info?.Labels ?? new Dictionary<string, LabelData>();
|
||||||
return new OnChainWalletUTXOData()
|
return new OnChainWalletUTXOData()
|
||||||
{
|
{
|
||||||
Outpoint = coin.OutPoint,
|
Outpoint = coin.OutPoint,
|
||||||
|
|
|
@ -65,11 +65,7 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly PayjoinClient _payjoinClient;
|
private readonly PayjoinClient _payjoinClient;
|
||||||
private readonly LabelFactory _labelFactory;
|
private readonly LabelFactory _labelFactory;
|
||||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
private readonly UTXOLocker _utxoLocker;
|
||||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
|
||||||
private readonly PullPaymentHostedService _pullPaymentService;
|
|
||||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
|
||||||
private readonly NBXplorerConnectionFactory _connectionFactory;
|
|
||||||
private readonly WalletHistogramService _walletHistogramService;
|
private readonly WalletHistogramService _walletHistogramService;
|
||||||
|
|
||||||
readonly CurrencyNameTable _currencyTable;
|
readonly CurrencyNameTable _currencyTable;
|
||||||
|
@ -79,10 +75,8 @@ namespace BTCPayServer.Controllers
|
||||||
CurrencyNameTable currencyTable,
|
CurrencyNameTable currencyTable,
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
MvcNewtonsoftJsonOptions mvcJsonOptions,
|
|
||||||
NBXplorerDashboard dashboard,
|
NBXplorerDashboard dashboard,
|
||||||
WalletHistogramService walletHistogramService,
|
WalletHistogramService walletHistogramService,
|
||||||
NBXplorerConnectionFactory connectionFactory,
|
|
||||||
RateFetcher rateProvider,
|
RateFetcher rateProvider,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
ExplorerClientProvider explorerProvider,
|
ExplorerClientProvider explorerProvider,
|
||||||
|
@ -94,12 +88,9 @@ namespace BTCPayServer.Controllers
|
||||||
DelayedTransactionBroadcaster broadcaster,
|
DelayedTransactionBroadcaster broadcaster,
|
||||||
PayjoinClient payjoinClient,
|
PayjoinClient payjoinClient,
|
||||||
LabelFactory labelFactory,
|
LabelFactory labelFactory,
|
||||||
ApplicationDbContextFactory dbContextFactory,
|
|
||||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings,
|
|
||||||
PullPaymentHostedService pullPaymentService,
|
|
||||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
PullPaymentHostedService pullPaymentHostedService)
|
PullPaymentHostedService pullPaymentHostedService,
|
||||||
|
UTXOLocker utxoLocker)
|
||||||
{
|
{
|
||||||
_currencyTable = currencyTable;
|
_currencyTable = currencyTable;
|
||||||
Repository = repo;
|
Repository = repo;
|
||||||
|
@ -119,12 +110,8 @@ namespace BTCPayServer.Controllers
|
||||||
_payjoinClient = payjoinClient;
|
_payjoinClient = payjoinClient;
|
||||||
_labelFactory = labelFactory;
|
_labelFactory = labelFactory;
|
||||||
_pullPaymentHostedService = pullPaymentHostedService;
|
_pullPaymentHostedService = pullPaymentHostedService;
|
||||||
_dbContextFactory = dbContextFactory;
|
_utxoLocker = utxoLocker;
|
||||||
_jsonSerializerSettings = jsonSerializerSettings;
|
|
||||||
_pullPaymentService = pullPaymentService;
|
|
||||||
_payoutHandlers = payoutHandlers;
|
|
||||||
ServiceProvider = serviceProvider;
|
ServiceProvider = serviceProvider;
|
||||||
_connectionFactory = connectionFactory;
|
|
||||||
_walletHistogramService = walletHistogramService;
|
_walletHistogramService = walletHistogramService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,15 +612,15 @@ namespace BTCPayServer.Controllers
|
||||||
vm.InputsAvailable = utxos.Select(coin =>
|
vm.InputsAvailable = utxos.Select(coin =>
|
||||||
{
|
{
|
||||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
|
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
|
||||||
|
var labels = info?.Labels == null
|
||||||
|
? new List<ColoredLabel>()
|
||||||
|
: _labelFactory.ColorizeTransactionLabels(walletBlobAsync, info, Request).ToList();
|
||||||
return new WalletSendModel.InputSelectionOption()
|
return new WalletSendModel.InputSelectionOption()
|
||||||
{
|
{
|
||||||
Outpoint = coin.OutPoint.ToString(),
|
Outpoint = coin.OutPoint.ToString(),
|
||||||
Amount = coin.Value.GetValue(network),
|
Amount = coin.Value.GetValue(network),
|
||||||
Comment = info?.Comment,
|
Comment = info?.Comment,
|
||||||
Labels =
|
Labels = labels,
|
||||||
info == null
|
|
||||||
? null
|
|
||||||
: _labelFactory.ColorizeTransactionLabels(walletBlobAsync, info, Request),
|
|
||||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
|
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
|
||||||
coin.OutPoint.Hash.ToString()),
|
coin.OutPoint.Hash.ToString()),
|
||||||
Confirmations = coin.Confirmations
|
Confirmations = coin.Confirmations
|
||||||
|
|
|
@ -14,8 +14,6 @@ using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Services.Labels;
|
using BTCPayServer.Services.Labels;
|
||||||
using BTCPayServer.Services.PaymentRequests;
|
using BTCPayServer.Services.PaymentRequests;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace BTCPayServer.HostedServices
|
namespace BTCPayServer.HostedServices
|
||||||
{
|
{
|
||||||
|
@ -48,14 +46,6 @@ namespace BTCPayServer.HostedServices
|
||||||
{
|
{
|
||||||
UpdateTransactionLabel.InvoiceLabelTemplate(invoiceEvent.Invoice.Id)
|
UpdateTransactionLabel.InvoiceLabelTemplate(invoiceEvent.Invoice.Id)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (invoiceEvent.Invoice.GetPayments(invoiceEvent.Payment.GetCryptoCode(), false).Any(entity =>
|
|
||||||
entity.GetCryptoPaymentData() is BitcoinLikePaymentData pData &&
|
|
||||||
pData.PayjoinInformation?.CoinjoinTransactionHash == transactionId))
|
|
||||||
{
|
|
||||||
labels.Add(UpdateTransactionLabel.PayjoinLabelTemplate());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var paymentId in PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice))
|
foreach (var paymentId in PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice))
|
||||||
{
|
{
|
||||||
labels.Add(UpdateTransactionLabel.PaymentRequestLabelTemplate(paymentId));
|
labels.Add(UpdateTransactionLabel.PaymentRequestLabelTemplate(paymentId));
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||||
public class NBXplorerListener : IHostedService
|
public class NBXplorerListener : IHostedService
|
||||||
{
|
{
|
||||||
readonly EventAggregator _Aggregator;
|
readonly EventAggregator _Aggregator;
|
||||||
private readonly PayJoinRepository _payJoinRepository;
|
private readonly UTXOLocker _utxoLocker;
|
||||||
readonly ExplorerClientProvider _ExplorerClients;
|
readonly ExplorerClientProvider _ExplorerClients;
|
||||||
private readonly PaymentService _paymentService;
|
private readonly PaymentService _paymentService;
|
||||||
readonly InvoiceRepository _InvoiceRepository;
|
readonly InvoiceRepository _InvoiceRepository;
|
||||||
|
@ -38,7 +38,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||||
BTCPayWalletProvider wallets,
|
BTCPayWalletProvider wallets,
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
EventAggregator aggregator,
|
EventAggregator aggregator,
|
||||||
PayJoinRepository payjoinRepository,
|
UTXOLocker payjoinRepository,
|
||||||
PaymentService paymentService,
|
PaymentService paymentService,
|
||||||
Logs logs)
|
Logs logs)
|
||||||
{
|
{
|
||||||
|
@ -48,7 +48,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||||
_InvoiceRepository = invoiceRepository;
|
_InvoiceRepository = invoiceRepository;
|
||||||
_ExplorerClients = explorerClients;
|
_ExplorerClients = explorerClients;
|
||||||
_Aggregator = aggregator;
|
_Aggregator = aggregator;
|
||||||
_payJoinRepository = payjoinRepository;
|
_utxoLocker = payjoinRepository;
|
||||||
_paymentService = paymentService;
|
_paymentService = paymentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||||
// reuse our outpoint for another PJ
|
// reuse our outpoint for another PJ
|
||||||
(originalPJBroadcastable is false && !cjPJBroadcasted))
|
(originalPJBroadcastable is false && !cjPJBroadcasted))
|
||||||
{
|
{
|
||||||
await _payJoinRepository.TryUnlock(payjoinInformation.ContributedOutPoints);
|
await _utxoLocker.TryUnlock(payjoinInformation.ContributedOutPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _paymentService.UpdatePayments(updatedPaymentEntities);
|
await _paymentService.UpdatePayments(updatedPaymentEntities);
|
||||||
|
|
|
@ -83,7 +83,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
private readonly InvoiceRepository _invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||||
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
||||||
private readonly PayJoinRepository _payJoinRepository;
|
private readonly UTXOLocker _utxoLocker;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly NBXplorerDashboard _dashboard;
|
private readonly NBXplorerDashboard _dashboard;
|
||||||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||||
|
@ -97,7 +97,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
public PayJoinEndpointController(BTCPayNetworkProvider btcPayNetworkProvider,
|
public PayJoinEndpointController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
InvoiceRepository invoiceRepository, ExplorerClientProvider explorerClientProvider,
|
InvoiceRepository invoiceRepository, ExplorerClientProvider explorerClientProvider,
|
||||||
BTCPayWalletProvider btcPayWalletProvider,
|
BTCPayWalletProvider btcPayWalletProvider,
|
||||||
PayJoinRepository payJoinRepository,
|
UTXOLocker utxoLocker,
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
NBXplorerDashboard dashboard,
|
NBXplorerDashboard dashboard,
|
||||||
DelayedTransactionBroadcaster broadcaster,
|
DelayedTransactionBroadcaster broadcaster,
|
||||||
|
@ -111,7 +111,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
_explorerClientProvider = explorerClientProvider;
|
_explorerClientProvider = explorerClientProvider;
|
||||||
_btcPayWalletProvider = btcPayWalletProvider;
|
_btcPayWalletProvider = btcPayWalletProvider;
|
||||||
_payJoinRepository = payJoinRepository;
|
_utxoLocker = utxoLocker;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_dashboard = dashboard;
|
_dashboard = dashboard;
|
||||||
_broadcaster = broadcaster;
|
_broadcaster = broadcaster;
|
||||||
|
@ -148,7 +148,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var ctx = new PayjoinReceiverContext(_invoiceRepository, _explorerClientProvider.GetExplorerClient(network), _payJoinRepository, Logs);
|
await using var ctx = new PayjoinReceiverContext(_invoiceRepository, _explorerClientProvider.GetExplorerClient(network), _utxoLocker, Logs);
|
||||||
ObjectResult CreatePayjoinErrorAndLog(int httpCode, PayjoinReceiverWellknownErrors err, string debug)
|
ObjectResult CreatePayjoinErrorAndLog(int httpCode, PayjoinReceiverWellknownErrors err, string debug)
|
||||||
{
|
{
|
||||||
ctx.Logs.Write($"Payjoin error: {debug}", InvoiceEventData.EventSeverity.Error);
|
ctx.Logs.Write($"Payjoin error: {debug}", InvoiceEventData.EventSeverity.Error);
|
||||||
|
@ -322,7 +322,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!await _payJoinRepository.TryLockInputs(ctx.OriginalTransaction.Inputs.Select(i => i.PrevOut).ToArray()))
|
if (!await _utxoLocker.TryLockInputs(ctx.OriginalTransaction.Inputs.Select(i => i.PrevOut).ToArray()))
|
||||||
{
|
{
|
||||||
// We do not broadcast, since we might double spend a delayed transaction of a previous payjoin
|
// We do not broadcast, since we might double spend a delayed transaction of a previous payjoin
|
||||||
ctx.DoNotBroadcast();
|
ctx.DoNotBroadcast();
|
||||||
|
@ -502,18 +502,23 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
_eventAggregator.Publish(new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) { Payment = payment });
|
_eventAggregator.Publish(new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) { Payment = payment });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(ctx.OriginalTransaction);
|
await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(ctx.OriginalTransaction);
|
||||||
|
var labels = selectedUTXOs.GroupBy(pair => pair.Key.Hash).Select(utxo =>
|
||||||
|
new KeyValuePair<uint256, List<(string color, Label label)>>(utxo.Key,
|
||||||
|
new List<(string color, Label label)>()
|
||||||
|
{
|
||||||
|
UpdateTransactionLabel.PayjoinExposedLabelTemplate(invoice?.Id)
|
||||||
|
}))
|
||||||
|
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||||
|
|
||||||
|
labels.Add(originalPaymentData.PayjoinInformation.CoinjoinTransactionHash, new List<(string color, Label label)>()
|
||||||
|
{
|
||||||
|
UpdateTransactionLabel.PayjoinLabelTemplate()
|
||||||
|
});
|
||||||
_eventAggregator.Publish(new UpdateTransactionLabel()
|
_eventAggregator.Publish(new UpdateTransactionLabel()
|
||||||
{
|
{
|
||||||
WalletId = walletId,
|
WalletId = walletId,
|
||||||
TransactionLabels = selectedUTXOs.GroupBy(pair => pair.Key.Hash).Select(utxo =>
|
TransactionLabels = labels
|
||||||
new KeyValuePair<uint256, List<(string color, Label label)>>(utxo.Key,
|
|
||||||
new List<(string color, Label label)>()
|
|
||||||
{
|
|
||||||
UpdateTransactionLabel.PayjoinExposedLabelTemplate(invoice?.Id)
|
|
||||||
}))
|
|
||||||
.ToDictionary(pair => pair.Key, pair => pair.Value)
|
|
||||||
});
|
});
|
||||||
ctx.Success();
|
ctx.Success();
|
||||||
// BTCPay Server support PSBT set as hex
|
// BTCPay Server support PSBT set as hex
|
||||||
|
@ -608,7 +613,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await _payJoinRepository.TryLock(availableUtxo.Outpoint))
|
if (await _utxoLocker.TryLock(availableUtxo.Outpoint))
|
||||||
{
|
{
|
||||||
return (new[] { availableUtxo }, PayjoinUtxoSelectionType.HeuristicBased);
|
return (new[] { availableUtxo }, PayjoinUtxoSelectionType.HeuristicBased);
|
||||||
}
|
}
|
||||||
|
@ -620,7 +625,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
{
|
{
|
||||||
if (currentTry >= maxTries)
|
if (currentTry >= maxTries)
|
||||||
break;
|
break;
|
||||||
if (await _payJoinRepository.TryLock(utxo.Outpoint))
|
if (await _utxoLocker.TryLock(utxo.Outpoint))
|
||||||
{
|
{
|
||||||
return (new[] { utxo }, PayjoinUtxoSelectionType.Ordered);
|
return (new[] { utxo }, PayjoinUtxoSelectionType.Ordered);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
{
|
{
|
||||||
services.AddSingleton<DelayedTransactionBroadcaster>();
|
services.AddSingleton<DelayedTransactionBroadcaster>();
|
||||||
services.AddSingleton<IHostedService, HostedServices.DelayedTransactionBroadcasterHostedService>();
|
services.AddSingleton<IHostedService, HostedServices.DelayedTransactionBroadcasterHostedService>();
|
||||||
services.AddSingleton<PayJoinRepository>();
|
services.AddSingleton<UTXOLocker>();
|
||||||
|
services.AddSingleton<IUTXOLocker>(provider => provider.GetRequiredService<UTXOLocker>());
|
||||||
services.AddSingleton<IPayjoinServerCommunicator, PayjoinServerCommunicator>();
|
services.AddSingleton<IPayjoinServerCommunicator, PayjoinServerCommunicator>();
|
||||||
services.AddSingleton<PayjoinClient>();
|
services.AddSingleton<PayjoinClient>();
|
||||||
services.AddTransient<Socks5HttpClientHandler>();
|
services.AddTransient<Socks5HttpClientHandler>();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
@ -6,21 +7,19 @@ using NBitcoin;
|
||||||
|
|
||||||
namespace BTCPayServer.Payments.PayJoin
|
namespace BTCPayServer.Payments.PayJoin
|
||||||
{
|
{
|
||||||
public class PayJoinRepository
|
public class UTXOLocker : IUTXOLocker
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
|
|
||||||
public PayJoinRepository(ApplicationDbContextFactory dbContextFactory)
|
public UTXOLocker(ApplicationDbContextFactory dbContextFactory)
|
||||||
{
|
{
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TryLock(OutPoint outpoint)
|
public async Task<bool> TryLock(OutPoint outpoint)
|
||||||
{
|
{
|
||||||
using var ctx = _dbContextFactory.CreateContext();
|
using var ctx = _dbContextFactory.CreateContext();
|
||||||
ctx.PayjoinLocks.Add(new PayjoinLock()
|
ctx.PayjoinLocks.Add(new PayjoinLock() {Id = outpoint.ToString()});
|
||||||
{
|
|
||||||
Id = outpoint.ToString()
|
|
||||||
});
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await ctx.SaveChangesAsync() == 1;
|
return await ctx.SaveChangesAsync() == 1;
|
||||||
|
@ -36,11 +35,9 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
using var ctx = _dbContextFactory.CreateContext();
|
using var ctx = _dbContextFactory.CreateContext();
|
||||||
foreach (OutPoint outPoint in outPoints)
|
foreach (OutPoint outPoint in outPoints)
|
||||||
{
|
{
|
||||||
ctx.PayjoinLocks.Remove(new PayjoinLock()
|
ctx.PayjoinLocks.Remove(new PayjoinLock() {Id = outPoint.ToString()});
|
||||||
{
|
|
||||||
Id = outPoint.ToString()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await ctx.SaveChangesAsync() == outPoints.Length;
|
return await ctx.SaveChangesAsync() == outPoints.Length;
|
||||||
|
@ -63,6 +60,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
Id = "K-" + outPoint.ToString()
|
Id = "K-" + outPoint.ToString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await ctx.SaveChangesAsync() == outPoints.Length;
|
return await ctx.SaveChangesAsync() == outPoints.Length;
|
||||||
|
@ -72,5 +70,13 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<HashSet<OutPoint>> FindLocks(OutPoint[] outpoints)
|
||||||
|
{
|
||||||
|
var outPointsStr = outpoints.Select(o => o.ToString());
|
||||||
|
await using var ctx = _dbContextFactory.CreateContext();
|
||||||
|
return (await ctx.PayjoinLocks.Where(l => outPointsStr.Contains(l.Id)).ToArrayAsync())
|
||||||
|
.Select(l => OutPoint.Parse(l.Id)).ToHashSet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,14 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
{
|
{
|
||||||
private readonly InvoiceRepository _invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
private readonly ExplorerClient _explorerClient;
|
private readonly ExplorerClient _explorerClient;
|
||||||
private readonly PayJoinRepository _payJoinRepository;
|
private readonly UTXOLocker _utxoLocker;
|
||||||
private readonly BTCPayServer.Logging.Logs BTCPayLogs;
|
private readonly BTCPayServer.Logging.Logs BTCPayLogs;
|
||||||
public PayjoinReceiverContext(InvoiceRepository invoiceRepository, ExplorerClient explorerClient, PayJoinRepository payJoinRepository, BTCPayServer.Logging.Logs logs)
|
public PayjoinReceiverContext(InvoiceRepository invoiceRepository, ExplorerClient explorerClient, UTXOLocker utxoLocker, BTCPayServer.Logging.Logs logs)
|
||||||
{
|
{
|
||||||
this.BTCPayLogs = logs;
|
this.BTCPayLogs = logs;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
_explorerClient = explorerClient;
|
_explorerClient = explorerClient;
|
||||||
_payJoinRepository = payJoinRepository;
|
_utxoLocker = utxoLocker;
|
||||||
}
|
}
|
||||||
public Invoice Invoice { get; set; }
|
public Invoice Invoice { get; set; }
|
||||||
public NBitcoin.Transaction OriginalTransaction { get; set; }
|
public NBitcoin.Transaction OriginalTransaction { get; set; }
|
||||||
|
@ -40,7 +40,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
}
|
}
|
||||||
if (!success && LockedUTXOs != null)
|
if (!success && LockedUTXOs != null)
|
||||||
{
|
{
|
||||||
disposing.Add(_payJoinRepository.TryUnlock(LockedUTXOs));
|
disposing.Add(_utxoLocker.TryUnlock(LockedUTXOs));
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -56,6 +56,7 @@ namespace BTCPayServer.Services.Labels
|
||||||
{
|
{
|
||||||
Text = uncoloredLabel.Text,
|
Text = uncoloredLabel.Text,
|
||||||
Color = color,
|
Color = color,
|
||||||
|
Tooltip = "",
|
||||||
TextColor = TextColor(color)
|
TextColor = TextColor(color)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -108,7 +109,10 @@ namespace BTCPayServer.Services.Labels
|
||||||
? null
|
? null
|
||||||
: _linkGenerator.PayoutLink(payoutLabel.WalletId, null, PayoutState.Completed, request.Scheme, request.Host,
|
: _linkGenerator.PayoutLink(payoutLabel.WalletId, null, PayoutState.Completed, request.Scheme, request.Host,
|
||||||
request.PathBase);
|
request.PathBase);
|
||||||
|
}
|
||||||
|
else if (uncoloredLabel.Text == "payjoin")
|
||||||
|
{
|
||||||
|
coloredLabel.Tooltip = $"This UTXO was part of a PayJoin transaction.";
|
||||||
}
|
}
|
||||||
return coloredLabel;
|
return coloredLabel;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue