If an input already used in a payjoin is reused in another, we should not attempt to broadcast the original transaction.

This commit is contained in:
nicolas.dorier 2021-03-24 13:48:33 +09:00
parent 749d26fafa
commit a128685b83
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
3 changed files with 17 additions and 10 deletions

View file

@ -87,6 +87,19 @@ namespace BTCPayServer.Tests
Assert.True(await repo.TryLock(outpoint)); Assert.True(await repo.TryLock(outpoint));
Assert.True(await repo.TryUnlock(outpoint)); Assert.True(await repo.TryUnlock(outpoint));
Assert.False(await repo.TryUnlock(outpoint)); Assert.False(await repo.TryUnlock(outpoint));
// Make sure that if any can't be locked, all are not locked
var outpoint1 = RandomOutpoint();
var outpoint2 = RandomOutpoint();
Assert.True(await repo.TryLockInputs(new[] { outpoint1 }));
Assert.False(await repo.TryLockInputs(new[] { outpoint1, outpoint2 }));
Assert.True(await repo.TryLockInputs(new[] { outpoint2 }));
outpoint1 = RandomOutpoint();
outpoint2 = RandomOutpoint();
Assert.True(await repo.TryLockInputs(new[] { outpoint1 }));
Assert.False(await repo.TryLockInputs(new[] { outpoint2, outpoint1 }));
Assert.True(await repo.TryLockInputs(new[] { outpoint2 }));
} }
} }
@ -901,10 +914,6 @@ retry:
.SendEstimatedFees(new FeeRate(100m)) .SendEstimatedFees(new FeeRate(100m))
.BuildTransaction(true); .BuildTransaction(true);
//Attempt 1: Send a signed tx to invoice 1 that does not pay the invoice at all
//Result: reject
// Assert.False((await tester.PayTester.HttpClient.PostAsync(endpoint,
// new StringContent(Invoice2Coin1.ToHex(), Encoding.UTF8, "text/plain"))).IsSuccessStatusCode);
//Attempt 2: Create two transactions using different inputs and send them to the same invoice. //Attempt 2: Create two transactions using different inputs and send them to the same invoice.
//Result: Second Tx should be rejected. //Result: Second Tx should be rejected.

View file

@ -83,35 +83,30 @@ namespace BTCPayServer.Payments.PayJoin
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly InvoiceRepository _invoiceRepository; private readonly InvoiceRepository _invoiceRepository;
private readonly ExplorerClientProvider _explorerClientProvider; private readonly ExplorerClientProvider _explorerClientProvider;
private readonly StoreRepository _storeRepository;
private readonly BTCPayWalletProvider _btcPayWalletProvider; private readonly BTCPayWalletProvider _btcPayWalletProvider;
private readonly PayJoinRepository _payJoinRepository; private readonly PayJoinRepository _payJoinRepository;
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly NBXplorerDashboard _dashboard; private readonly NBXplorerDashboard _dashboard;
private readonly DelayedTransactionBroadcaster _broadcaster; private readonly DelayedTransactionBroadcaster _broadcaster;
private readonly WalletRepository _walletRepository;
private readonly BTCPayServerEnvironment _env; private readonly BTCPayServerEnvironment _env;
public PayJoinEndpointController(BTCPayNetworkProvider btcPayNetworkProvider, public PayJoinEndpointController(BTCPayNetworkProvider btcPayNetworkProvider,
InvoiceRepository invoiceRepository, ExplorerClientProvider explorerClientProvider, InvoiceRepository invoiceRepository, ExplorerClientProvider explorerClientProvider,
StoreRepository storeRepository, BTCPayWalletProvider btcPayWalletProvider, BTCPayWalletProvider btcPayWalletProvider,
PayJoinRepository payJoinRepository, PayJoinRepository payJoinRepository,
EventAggregator eventAggregator, EventAggregator eventAggregator,
NBXplorerDashboard dashboard, NBXplorerDashboard dashboard,
DelayedTransactionBroadcaster broadcaster, DelayedTransactionBroadcaster broadcaster,
WalletRepository walletRepository,
BTCPayServerEnvironment env) BTCPayServerEnvironment env)
{ {
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
_explorerClientProvider = explorerClientProvider; _explorerClientProvider = explorerClientProvider;
_storeRepository = storeRepository;
_btcPayWalletProvider = btcPayWalletProvider; _btcPayWalletProvider = btcPayWalletProvider;
_payJoinRepository = payJoinRepository; _payJoinRepository = payJoinRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_dashboard = dashboard; _dashboard = dashboard;
_broadcaster = broadcaster; _broadcaster = broadcaster;
_walletRepository = walletRepository;
_env = env; _env = env;
} }
@ -285,6 +280,8 @@ namespace BTCPayServer.Payments.PayJoin
if (!await _payJoinRepository.TryLockInputs(ctx.OriginalTransaction.Inputs.Select(i => i.PrevOut).ToArray())) if (!await _payJoinRepository.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
ctx.DoNotBroadcast();
return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "Some of those inputs have already been used to make another payjoin transaction"); return CreatePayjoinErrorAndLog(503, PayjoinReceiverWellknownErrors.Unavailable, "Some of those inputs have already been used to make another payjoin transaction");
} }

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;