4141import bisq .core .trade .TradeManager ;
4242import bisq .core .trade .bisq_v1 .FailedTradesManager ;
4343import bisq .core .trade .model .bisq_v1 .Trade ;
44+ import bisq .core .trade .protocol .bisq_v5 .model .StagedPayoutTxParameters ;
4445
4546import bisq .network .p2p .AckMessageSourceType ;
4647import bisq .network .p2p .NodeAddress ;
6465import com .google .inject .Singleton ;
6566
6667import java .util .ArrayList ;
68+ import java .util .Iterator ;
6769import java .util .List ;
6870import java .util .Optional ;
6971import java .util .concurrent .CompletableFuture ;
@@ -262,10 +264,7 @@ public NodeAddress getAgentNodeAddress(Dispute dispute) {
262264 return dispute .getContract ().getRefundAgentNodeAddress ();
263265 }
264266
265- public CompletableFuture <List <Transaction >> requestBlockchainTransactions (String makerFeeTxId ,
266- String takerFeeTxId ,
267- String depositTxId ,
268- String delayedPayoutTxId ) {
267+ public CompletableFuture <List <Transaction >> requestBlockchainTransactions (List <String > txIds ) {
269268 // in regtest mode, simulate a delay & failure obtaining the blockchain transactions
270269 // since we cannot request them in regtest anyway. this is useful for checking failure scenarios
271270 if (!Config .baseCurrencyNetwork ().isMainnet ()) {
@@ -276,28 +275,28 @@ public CompletableFuture<List<Transaction>> requestBlockchainTransactions(String
276275
277276 NetworkParameters params = btcWalletService .getParams ();
278277 List <Transaction > txs = new ArrayList <>();
279- return mempoolService .requestTxAsHex (makerFeeTxId )
280- .thenCompose (txAsHex -> {
281- txs .add (new Transaction (params , Hex .decode (txAsHex )));
282- return mempoolService .requestTxAsHex (takerFeeTxId );
283- }).thenCompose (txAsHex -> {
284- txs .add (new Transaction (params , Hex .decode (txAsHex )));
285- return mempoolService .requestTxAsHex (depositTxId );
286- }).thenCompose (txAsHex -> {
287- txs .add (new Transaction (params , Hex .decode (txAsHex )));
288- return mempoolService .requestTxAsHex (delayedPayoutTxId );
289- })
290- .thenApply (txAsHex -> {
291- txs .add (new Transaction (params , Hex .decode (txAsHex )));
292- return txs ;
293- });
278+ Iterator <String > txIdIterator = txIds .iterator ();
279+ if (!txIdIterator .hasNext ()) {
280+ return CompletableFuture .completedFuture (txs );
281+ }
282+ CompletableFuture <String > future = mempoolService .requestTxAsHex (txIdIterator .next ());
283+ while (txIdIterator .hasNext ()) {
284+ String txId = txIdIterator .next ();
285+ future = future .thenCompose (txAsHex -> {
286+ txs .add (new Transaction (params , Hex .decode (txAsHex )));
287+ return mempoolService .requestTxAsHex (txId );
288+ });
289+ }
290+ return future .thenApply (txAsHex -> {
291+ txs .add (new Transaction (params , Hex .decode (txAsHex )));
292+ return txs ;
293+ });
294294 }
295295
296296 public void verifyTradeTxChain (List <Transaction > txs ) {
297297 Transaction makerFeeTx = txs .get (0 );
298298 Transaction takerFeeTx = txs .get (1 );
299299 Transaction depositTx = txs .get (2 );
300- Transaction delayedPayoutTx = txs .get (3 );
301300
302301 // The order and number of buyer and seller inputs are not part of the trade protocol consensus.
303302 // In the current implementation buyer inputs come before seller inputs at depositTx and there is
@@ -316,38 +315,85 @@ public void verifyTradeTxChain(List<Transaction> txs) {
316315 }
317316 checkArgument (makerFeeTxFoundAtInputs , "makerFeeTx not found at depositTx inputs" );
318317 checkArgument (takerFeeTxFoundAtInputs , "takerFeeTx not found at depositTx inputs" );
319- checkArgument (depositTx .getInputs ().size () >= 2 ,
320- "DepositTx must have at least 2 inputs" );
321- checkArgument (delayedPayoutTx .getInputs ().size () == 1 ,
322- "DelayedPayoutTx must have 1 input" );
323- TransactionOutPoint delayedPayoutTxInputOutpoint = delayedPayoutTx .getInputs ().get (0 ).getOutpoint ();
324- String fundingTxId = delayedPayoutTxInputOutpoint .getHash ().toString ();
325- checkArgument (fundingTxId .equals (depositTx .getTxId ().toString ()),
326- "First input at delayedPayoutTx does not connect to depositTx" );
318+ checkArgument (depositTx .getInputs ().size () >= 2 , "depositTx must have at least 2 inputs" );
319+ if (txs .size () == 4 ) {
320+ Transaction delayedPayoutTx = txs .get (3 );
321+ checkArgument (delayedPayoutTx .getInputs ().size () == 1 , "delayedPayoutTx must have 1 input" );
322+ checkArgument (firstOutputConnectsToFirstInput (depositTx , delayedPayoutTx ),
323+ "First input at delayedPayoutTx does not connect to depositTx" );
324+ } else {
325+ Transaction warningTx = txs .get (3 );
326+ Transaction redirectTx = txs .get (4 );
327+
328+ checkArgument (warningTx .getInputs ().size () == 1 , "warningTx must have 1 input" );
329+ checkArgument (warningTx .getOutputs ().size () == 2 , "warningTx must have 2 outputs" );
330+ checkArgument (warningTx .getOutput (1 ).getValue ().value ==
331+ StagedPayoutTxParameters .WARNING_TX_FEE_BUMP_OUTPUT_VALUE ,
332+ "Second warningTx output is wrong amount for a fee bump output" );
333+
334+ checkArgument (redirectTx .getInputs ().size () == 1 , "redirectTx must have 1 input" );
335+ int numReceivers = redirectTx .getOutputs ().size () - 1 ;
336+ checkArgument (redirectTx .getOutput (numReceivers ).getValue ().value ==
337+ StagedPayoutTxParameters .REDIRECT_TX_FEE_BUMP_OUTPUT_VALUE ,
338+ "Last redirectTx output is wrong amount for a fee bump output" );
339+
340+ checkArgument (firstOutputConnectsToFirstInput (depositTx , warningTx ),
341+ "First input at warningTx does not connect to depositTx" );
342+ checkArgument (firstOutputConnectsToFirstInput (warningTx , redirectTx ),
343+ "First input at redirectTx does not connect to warningTx" );
344+ }
327345 }
328346
329- public void verifyDelayedPayoutTxReceivers (Transaction delayedPayoutTx , Dispute dispute ) {
330- Transaction depositTx = dispute .findDepositTx (btcWalletService ).orElseThrow ();
347+ private static boolean firstOutputConnectsToFirstInput (Transaction parent , Transaction child ) {
348+ TransactionOutPoint childTxInputOutpoint = child .getInput (0 ).getOutpoint ();
349+ String fundingTxId = childTxInputOutpoint .getHash ().toString ();
350+ return fundingTxId .equals (parent .getTxId ().toString ());
351+ }
352+
353+ public void verifyDelayedPayoutTxReceivers (Transaction depositTx , Transaction delayedPayoutTx , Dispute dispute ) {
331354 long inputAmount = depositTx .getOutput (0 ).getValue ().value ;
332355 int selectionHeight = dispute .getBurningManSelectionHeight ();
333356
334- List <Tuple2 <Long , String >> delayedPayoutTxReceivers = delayedPayoutTxReceiverService .getReceivers (
357+ List <Tuple2 <Long , String >> receivers = delayedPayoutTxReceiverService .getReceivers (
335358 selectionHeight ,
336359 inputAmount ,
337360 dispute .getTradeTxFee (),
338361 DelayedPayoutTxReceiverService .ReceiverFlag .flagsActivatedBy (dispute .getTradeDate ()));
339- log .info ("Verify delayedPayoutTx using selectionHeight {} and receivers {}" , selectionHeight , delayedPayoutTxReceivers );
340- checkArgument (delayedPayoutTx .getOutputs ().size () == delayedPayoutTxReceivers .size (),
341- "Size of outputs and delayedPayoutTxReceivers must be the same" );
362+ log .info ("Verify delayedPayoutTx using selectionHeight {} and receivers {}" , selectionHeight , receivers );
363+ checkArgument (delayedPayoutTx .getOutputs ().size () == receivers .size (),
364+ "Number of outputs must equal number of receivers" );
365+ checkOutputsPrefixMatchesReceivers (delayedPayoutTx , receivers );
366+ }
367+
368+ public void verifyRedirectTxReceivers (Transaction warningTx , Transaction redirectTx , Dispute dispute ) {
369+ long inputAmount = warningTx .getOutput (0 ).getValue ().value ;
370+ long inputAmountMinusFeeBumpAmount = inputAmount - StagedPayoutTxParameters .REDIRECT_TX_FEE_BUMP_OUTPUT_VALUE ;
371+ int selectionHeight = dispute .getBurningManSelectionHeight ();
372+
373+ List <Tuple2 <Long , String >> receivers = delayedPayoutTxReceiverService .getReceivers (
374+ selectionHeight ,
375+ inputAmountMinusFeeBumpAmount ,
376+ dispute .getTradeTxFee (),
377+ StagedPayoutTxParameters .REDIRECT_TX_MIN_WEIGHT ,
378+ DelayedPayoutTxReceiverService .ReceiverFlag .flagsActivatedBy (dispute .getTradeDate ()));
379+ log .info ("Verify redirectTx using selectionHeight {} and receivers {}" , selectionHeight , receivers );
380+ checkArgument (redirectTx .getOutputs ().size () == receivers .size () + 1 ,
381+ "Number of outputs must equal number of receivers plus 1" );
382+ checkOutputsPrefixMatchesReceivers (redirectTx , receivers );
383+ }
342384
385+ private void checkOutputsPrefixMatchesReceivers (Transaction delayedPayoutOrRedirectTx ,
386+ List <Tuple2 <Long , String >> receivers ) {
343387 NetworkParameters params = btcWalletService .getParams ();
344- for (int i = 0 ; i < delayedPayoutTx . getOutputs () .size (); i ++) {
345- TransactionOutput transactionOutput = delayedPayoutTx . getOutputs (). get (i );
346- Tuple2 <Long , String > receiverTuple = delayedPayoutTxReceivers .get (0 );
388+ for (int i = 0 ; i < receivers .size (); i ++) {
389+ TransactionOutput transactionOutput = delayedPayoutOrRedirectTx . getOutput (i );
390+ Tuple2 <Long , String > receiverTuple = receivers .get (i );
347391 checkArgument (transactionOutput .getScriptPubKey ().getToAddress (params ).toString ().equals (receiverTuple .second ),
348- "output address does not match delayedPayoutTxReceivers address. transactionOutput=" + transactionOutput );
392+ "Output address does not match receiver address (%s). transactionOutput=%s" ,
393+ receiverTuple .second , transactionOutput );
349394 checkArgument (transactionOutput .getValue ().value == receiverTuple .first ,
350- "output value does not match delayedPayoutTxReceivers value. transactionOutput=" + transactionOutput );
395+ "Output value does not match receiver value (%s). transactionOutput=%s" ,
396+ receiverTuple .first , transactionOutput );
351397 }
352398 }
353399}
0 commit comments