From 5b4e5cc8ffa7060e2f055a56dab9eaa76d65aaf3 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Tue, 19 Nov 2024 14:08:00 -0300 Subject: [PATCH] Register spend transaction --- .../main/java/co/rsk/peg/BridgeSupport.java | 43 +++-- .../java/co/rsk/peg/BridgeSupportSvpTest.java | 172 ++++++++++++++---- 2 files changed, 162 insertions(+), 53 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java index 965e14f5c1..43f27f31cb 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -391,6 +391,13 @@ public void registerBtcTransaction( throw new RegisterBtcTransactionException("Transaction already processed"); } + if (svpIsOngoing() && isTheSvpSpendTransaction(btcTx)) { + markTxAsProcessed(btcTx); + provider.setSvpSpendTxHashUnsigned(null); + // proceed with svp success + return; + } + PegTxType pegTxType = PegUtils.getTransactionType( activations, provider, @@ -410,8 +417,8 @@ public void registerBtcTransaction( case PEGOUT_OR_MIGRATION: logger.debug("[registerBtcTransaction] This is a peg-out or migration tx {}", btcTx.getHash()); processPegoutOrMigration(btcTx); - if (svpIsOngoing()) { - updateSvpFundTransactionValuesIfPossible(btcTx); + if (svpIsOngoing() && isTheSvpFundTransaction(btcTx)) { + updateSvpFundTransactionValues(btcTx); } break; default: @@ -429,21 +436,13 @@ public void registerBtcTransaction( } } - private void updateSvpFundTransactionValuesIfPossible(BtcTransaction transaction) { - provider.getSvpFundTxHashUnsigned() - .filter(svpFundTxHashUnsigned -> isTheSvpFundTransaction(svpFundTxHashUnsigned, transaction)) - .ifPresent(isTheSvpFundTransaction -> updateSvpFundTransactionValues(transaction)); - } - - private boolean isTheSvpFundTransaction(Sha256Hash svpFundTransactionHashUnsigned, BtcTransaction transaction) { - Sha256Hash transactionHash = transaction.getHash(); - - if (!transaction.hasWitness()) { - BtcTransaction transactionCopyWithoutSignatures = new BtcTransaction(networkParameters, transaction.bitcoinSerialize()); // this is needed to not remove signatures from the actual tx - BitcoinUtils.removeSignaturesFromTransactionWithP2shMultiSigInputs(transactionCopyWithoutSignatures); - transactionHash = transactionCopyWithoutSignatures.getHash(); - } - return transactionHash.equals(svpFundTransactionHashUnsigned); + private boolean isTheSvpFundTransaction(BtcTransaction transaction) { + return provider.getSvpFundTxHashUnsigned() + .map(svpFundTransactionHashUnsigned -> { + Sha256Hash transactionHashWithoutSignatures = getMultiSigTransactionHashWithoutSignatures(networkParameters, transaction); + return transactionHashWithoutSignatures.equals(svpFundTransactionHashUnsigned); + }) + .orElse(false); } private void updateSvpFundTransactionValues(BtcTransaction transaction) { @@ -455,6 +454,15 @@ private void updateSvpFundTransactionValues(BtcTransaction transaction) { provider.setSvpFundTxHashUnsigned(null); } + private boolean isTheSvpSpendTransaction(BtcTransaction transaction) { + return provider.getSvpSpendTxHashUnsigned() + .map(svpSpendTransactionHashUnsigned -> { + Sha256Hash transactionHashWithoutSignatures = getMultiSigTransactionHashWithoutSignatures(networkParameters, transaction); + return transactionHashWithoutSignatures.equals(svpSpendTransactionHashUnsigned); + }) + .orElse(false); + } + private Script getLastRetiredFederationP2SHScript() { return federationSupport.getLastRetiredFederationP2SHScript().orElse(null); } @@ -1760,7 +1768,6 @@ private void addSvpSpendTxSignatures( provider.getSvpSpendTxWaitingForSignatures() // The svpSpendTxWFS should always be present at this point, since we already checked isTheSvpSpendTx. .ifPresent(svpSpendTxWFS -> { - Keccak256 svpSpendTxCreationRskTxHash = svpSpendTxWFS.getKey(); BtcTransaction svpSpendTx = svpSpendTxWFS.getValue(); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java index c2414dc678..fee9e2d30c 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java @@ -82,6 +82,9 @@ public class BridgeSupportSvpTest { private Sha256Hash svpSpendTransactionHashUnsigned; private BtcTransaction svpSpendTransaction; + private PartialMerkleTree pmtWithTransactions; + private int btcBlockWithPmtHeight; + @BeforeEach void setUp() { long rskExecutionBlockNumber = 1000L; @@ -467,8 +470,6 @@ private void assertSvpFundTransactionValuesWereNotUpdated() { @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tag("Fund transaction registration tests") class FundTxRegistrationTests { - private PartialMerkleTree pmtWithTransactions; - private int btcBlockWithPmtHeight; @Test void registerBtcTransaction_forSvpFundTransactionChange_whenProposedFederationDoesNotExist_shouldRegisterTransactionButNotUpdateSvpFundTransactionValues() throws Exception { @@ -595,43 +596,10 @@ void registerBtcTransaction_forSvpFundTransactionChange_whenSvpPeriodIsOngoing_s assertSvpFundTransactionValuesWereUpdated(); } - private void setUpForTransactionRegistration(BtcTransaction transaction) throws BlockStoreException { - // recreate a valid chain that has the tx, so it passes the previous checks in registerBtcTransaction - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(btcMainnetParams, 100, 100); - BtcBlockStoreWithCache btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(repository, bridgeMainNetConstants, bridgeStorageProvider, allActivations); - - pmtWithTransactions = createValidPmtForTransactions(Collections.singletonList(transaction.getHash()), btcMainnetParams); - btcBlockWithPmtHeight = bridgeMainNetConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeMainNetConstants.getPegoutTxIndexGracePeriodInBtcBlocks(); // we want pegout tx index to be activated - - int chainHeight = btcBlockWithPmtHeight + bridgeMainNetConstants.getBtc2RskMinimumAcceptableConfirmations(); - recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, btcMainnetParams); - - bridgeStorageProvider.save(); - - bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeMainNetConstants) - .withProvider(bridgeStorageProvider) - .withActivations(allActivations) - .withFederationSupport(federationSupport) - .withFeePerKbSupport(feePerKbSupport) - .withExecutionBlock(rskExecutionBlock) - .withBtcBlockStoreFactory(btcBlockStoreFactory) - .withRepository(repository) - .build(); - } - private void assertActiveFederationUtxosSize(int expectedActiveFederationUtxosSize) { assertEquals(expectedActiveFederationUtxosSize, federationSupport.getActiveFederationBtcUTXOs().size()); } - private void assertTransactionWasProcessed(Sha256Hash transactionHash) throws IOException { - Optional rskBlockHeightAtWhichBtcTxWasProcessed = bridgeStorageProvider.getHeightIfBtcTxhashIsAlreadyProcessed(transactionHash); - assertTrue(rskBlockHeightAtWhichBtcTxWasProcessed.isPresent()); - - long rskExecutionBlockNumber = rskExecutionBlock.getNumber(); - assertEquals(rskExecutionBlockNumber, rskBlockHeightAtWhichBtcTxWasProcessed.get()); - } - private void assertSvpFundTransactionValuesWereUpdated() { Optional svpFundTransactionSignedOpt = bridgeStorageProvider.getSvpFundTxSigned(); assertTrue(svpFundTransactionSignedOpt.isPresent()); @@ -982,6 +950,107 @@ private void assertFederatorSignedInputs(List inputs, List heightOfTransactionProcessing = bridgeStorageProvider.getHeightIfBtcTxhashIsAlreadyProcessed(svpSpendTransaction.getHash()); + assertFalse(heightOfTransactionProcessing.isPresent()); + + Optional svpSpendTxHashUnsigned = bridgeStorageProvider.getSvpSpendTxHashUnsigned(); + assertTrue(svpSpendTxHashUnsigned.isPresent()); + } + + @Test + void registerBtcTransaction_whenIsTheSpendTransaction_shouldMarkSpendTxAsProcessedAndClearValue() throws BlockStoreException, BridgeIllegalArgumentException, IOException { + // arrange + arrangeSvpSpendTransaction(); + setUpForTransactionRegistration(svpSpendTransaction); + + // act + bridgeSupport.registerBtcTransaction( + rskTx, + svpSpendTransaction.bitcoinSerialize(), + btcBlockWithPmtHeight, + pmtWithTransactions.bitcoinSerialize() + ); + bridgeStorageProvider.save(); + + // assert + assertTransactionWasProcessed(svpSpendTransaction.getHash()); + + Optional svpSpendTxHashUnsigned = bridgeStorageProvider.getSvpSpendTxHashUnsigned(); + assertFalse(svpSpendTxHashUnsigned.isPresent()); + } + + @Test + void registerBtcTransaction_whenIsNotTheSpendTransaction_shouldNotMarkSpendTxAsProcessed() throws BlockStoreException, BridgeIllegalArgumentException, IOException { + // arrange + arrangeSvpSpendTransaction(); + setUpForTransactionRegistration(svpSpendTransaction); + + BtcTransaction pegout = createPegout(proposedFederation.getRedeemScript()); + savePegoutIndex(pegout); + signInputs(pegout); + setUpForTransactionRegistration(pegout); + + // act + bridgeSupport.registerBtcTransaction( + rskTx, + pegout.bitcoinSerialize(), + btcBlockWithPmtHeight, + pmtWithTransactions.bitcoinSerialize() + ); + bridgeStorageProvider.save(); + + // assert + arrangeSvpSpendTransaction(); + Optional heightOfTransactionProcessing = bridgeStorageProvider.getHeightIfBtcTxhashIsAlreadyProcessed(svpSpendTransaction.getHash()); + assertFalse(heightOfTransactionProcessing.isPresent()); + + Optional svpSpendTxHashUnsigned = bridgeStorageProvider.getSvpSpendTxHashUnsigned(); + assertTrue(svpSpendTxHashUnsigned.isPresent()); + } + + @Test + void registerBtcTransaction_whenSpendTransactionHashIsNotSaved_shouldNotMarkSpendTxAsProcessed() throws BlockStoreException, BridgeIllegalArgumentException, IOException { + // arrange + recreateSvpSpendTransactionUnsigned(); + setUpForTransactionRegistration(svpSpendTransaction); + + // act + bridgeSupport.registerBtcTransaction( + rskTx, + svpSpendTransaction.bitcoinSerialize(), + btcBlockWithPmtHeight, + pmtWithTransactions.bitcoinSerialize() + ); + bridgeStorageProvider.save(); + + // assert + Optional heightOfTransactionProcessing = bridgeStorageProvider.getHeightIfBtcTxhashIsAlreadyProcessed(svpSpendTransaction.getHash()); + assertFalse(heightOfTransactionProcessing.isPresent()); + } + } + private void arrangeExecutionBlockIsAfterValidationPeriodEnded() { long validationPeriodEndBlock = proposedFederation.getCreationBlockNumber() + bridgeMainNetConstants.getFederationConstants().getValidationPeriodDurationInBlocks(); @@ -1081,6 +1150,39 @@ private void saveSvpSpendTransactionValues() { bridgeStorageProvider.save(); } + private void setUpForTransactionRegistration(BtcTransaction transaction) throws BlockStoreException { + // recreate a valid chain that has the tx, so it passes the previous checks in registerBtcTransaction + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(btcMainnetParams, 100, 100); + BtcBlockStoreWithCache btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(repository, bridgeMainNetConstants, bridgeStorageProvider, allActivations); + + pmtWithTransactions = createValidPmtForTransactions(Collections.singletonList(transaction.getHash()), btcMainnetParams); + btcBlockWithPmtHeight = bridgeMainNetConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeMainNetConstants.getPegoutTxIndexGracePeriodInBtcBlocks(); // we want pegout tx index to be activated + + int chainHeight = btcBlockWithPmtHeight + bridgeMainNetConstants.getBtc2RskMinimumAcceptableConfirmations(); + recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, btcMainnetParams); + + bridgeStorageProvider.save(); + + bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeMainNetConstants) + .withProvider(bridgeStorageProvider) + .withActivations(allActivations) + .withFederationSupport(federationSupport) + .withFeePerKbSupport(feePerKbSupport) + .withExecutionBlock(rskExecutionBlock) + .withBtcBlockStoreFactory(btcBlockStoreFactory) + .withRepository(repository) + .build(); + } + + private void assertTransactionWasProcessed(Sha256Hash transactionHash) throws IOException { + Optional rskBlockHeightAtWhichBtcTxWasProcessed = bridgeStorageProvider.getHeightIfBtcTxhashIsAlreadyProcessed(transactionHash); + assertTrue(rskBlockHeightAtWhichBtcTxWasProcessed.isPresent()); + + long rskExecutionBlockNumber = rskExecutionBlock.getNumber(); + assertEquals(rskExecutionBlockNumber, rskBlockHeightAtWhichBtcTxWasProcessed.get()); + } + private void assertNoSvpFundTxHashUnsigned() { assertFalse(bridgeStorageProvider.getSvpFundTxHashUnsigned().isPresent()); }