From d0e50e168c114b17ce853a2d0736e92c9d203298 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Wed, 25 Sep 2024 20:44:58 -0300 Subject: [PATCH 01/44] Move searchForOutput method to BitcoinUtils to reuse it --- .../main/java/co/rsk/peg/bitcoin/BitcoinUtils.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index d96797f763a..d041acb8f68 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -1,9 +1,6 @@ package co.rsk.peg.bitcoin; -import co.rsk.bitcoinj.core.BtcTransaction; -import co.rsk.bitcoinj.core.ScriptException; -import co.rsk.bitcoinj.core.Sha256Hash; -import co.rsk.bitcoinj.core.TransactionInput; +import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.Script; import co.rsk.bitcoinj.script.ScriptBuilder; import co.rsk.bitcoinj.script.ScriptChunk; @@ -81,4 +78,10 @@ public static void removeSignaturesFromTransactionWithP2shMultiSigInputs(BtcTran input.setScriptSig(emptyInputScript); } } + + public static Optional searchForOutput(List transactionOutputs, Script outputScriptPubKey) { + return transactionOutputs.stream() + .filter(output -> output.getScriptPubKey().equals(outputScriptPubKey)) + .findFirst(); + } } From cb0e2bca1bc00fae81b524fdd035e7e2d9920ab4 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Wed, 25 Sep 2024 20:48:53 -0300 Subject: [PATCH 02/44] Create and process svp spend transaction --- .../main/java/co/rsk/peg/BridgeSupport.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) 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 5a119f24686..a83f869ede7 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -17,9 +17,11 @@ */ package co.rsk.peg; +import static co.rsk.peg.BridgeUtils.calculatePegoutTxSize; import static co.rsk.peg.PegUtils.getFlyoverAddress; import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; +import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static java.util.Objects.isNull; @@ -1048,6 +1050,92 @@ private SendRequest createSvpFundTransactionSendRequest(BtcTransaction transacti return sendRequest; } + protected void processSvpSpendTransactionUnsigned(Transaction rskTx) { + federationSupport.getProposedFederation() + .ifPresent(proposedFederation -> provider.getSvpFundTxSigned() + .ifPresent(svpFundTxSigned -> { + BtcTransaction svpSpendTransactionUnsigned = createSvpSpendTransaction(svpFundTxSigned, proposedFederation); + updateSvpSpendTransactionValues(rskTx.getHash(), svpSpendTransactionUnsigned); + })); + } + + private BtcTransaction createSvpSpendTransaction(BtcTransaction svpFundTxSigned, Federation proposedFederation) { + BtcTransaction svpSpendTransaction = new BtcTransaction(networkParameters); + svpSpendTransaction.setVersion(BTC_TX_VERSION_2); + + addSvpSpendTransactionInputs(svpSpendTransaction, svpFundTxSigned, proposedFederation); + + svpSpendTransaction.addOutput( + calculateAmountToSendToActiveFederation(proposedFederation), + federationSupport.getActiveFederationAddress() + ); + + return svpSpendTransaction; + } + + private Coin calculateAmountToSendToActiveFederation(Federation proposedFederation) { + int svpSpendTransactionSize = calculatePegoutTxSize(activations, proposedFederation, 2, 1); + long backupSizePercentage = (long) 1.2; // just to be sure the amount sent will be enough + + return feePerKbSupport.getFeePerKb() + .multiply(svpSpendTransactionSize * backupSizePercentage) + .divide(1000); + } + + private void addSvpSpendTransactionInputs(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Federation proposedFederation) { + // TODO update when segwit since the scriptSig changes + addInputSentToProposedFederation(svpSpendTransaction, svpFundTxSigned, proposedFederation); + addInputSentToFlyoverProposedFederation(svpSpendTransaction, svpFundTxSigned, proposedFederation.getRedeemScript()); + } + + private void addInputSentToProposedFederation(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Federation proposedFederation) { + List svpFundTxSignedOutputs = svpFundTxSigned.getOutputs(); + Script proposedFederationOutputScript = proposedFederation.getP2SHScript(); + TransactionOutput outputToProposedFederation = + searchForOutput(svpFundTxSignedOutputs, proposedFederationOutputScript) + .orElseThrow(() -> + new IllegalStateException("Svp fund transaction must have an output to the proposed federation.") + ); + + Script proposedFederationRedeemScript = proposedFederation.getRedeemScript(); + Script proposedFederationScriptSig = proposedFederationOutputScript.createEmptyInputScript(null, proposedFederationRedeemScript); + svpSpendTransaction.addInput( + svpFundTxSigned.getHash(), + svpFundTxSignedOutputs.indexOf(outputToProposedFederation), + proposedFederationScriptSig + ); + } + + private void addInputSentToFlyoverProposedFederation(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Script proposedFederationRedeemScript) { + Keccak256 flyoverPrefix = bridgeConstants.getProposedFederationFlyoverPrefix(); + Script flyoverProposedFederationRedeemScript = FlyoverRedeemScriptBuilderImpl.builder().of(flyoverPrefix, proposedFederationRedeemScript); + Script flyoverProposedFederationScriptPubKey = ScriptBuilder.createP2SHOutputScript(flyoverProposedFederationRedeemScript); + + List svpFundTxSignedOutputs = svpFundTxSigned.getOutputs(); + TransactionOutput outputToFlyoverProposedFederation = + searchForOutput(svpFundTxSignedOutputs, flyoverProposedFederationScriptPubKey) + .orElseThrow(() -> + new IllegalStateException("Svp fund transaction must have an output to the flyover proposed federation.") + ); + + Script flyoverProposedFederationEmptyScriptSig = + flyoverProposedFederationScriptPubKey.createEmptyInputScript(null, flyoverProposedFederationRedeemScript); + svpSpendTransaction.addInput( + svpFundTxSigned.getHash(), + svpFundTxSignedOutputs.indexOf(outputToFlyoverProposedFederation), + flyoverProposedFederationEmptyScriptSig + ); + } + + private void updateSvpSpendTransactionValues(Keccak256 rskTxHash, BtcTransaction svpSpendTransactionUnsigned) { + provider.setSvpSpendTxHashUnsigned(svpSpendTransactionUnsigned.getHash()); + provider.setSvpSpendTxWaitingForSignatures( + new AbstractMap.SimpleEntry<>(rskTxHash, svpSpendTransactionUnsigned) + ); + + provider.setSvpFundTxSigned(null); + } + protected void updateFederationCreationBlockHeights() { federationSupport.updateFederationCreationBlockHeights(); } From 5f23da46dc05702bde4c63c755d6138441a958c7 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 26 Sep 2024 09:22:15 -0300 Subject: [PATCH 03/44] Add tests to spend transaction process. Refactor some methods to avoid code duplication --- .../java/co/rsk/peg/BridgeSupportSvpTest.java | 280 ++++++++++++------ 1 file changed, 195 insertions(+), 85 deletions(-) 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 2e465741cc7..74e6907bcae 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java @@ -29,8 +29,10 @@ import java.io.IOException; import java.util.*; +import java.util.stream.IntStream; import static co.rsk.peg.BridgeSupportTestUtil.*; +import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -228,31 +230,14 @@ private void assertOneOutputIsChange(List transactionOutputs) private void assertOneOutputIsToProposedFederationWithExpectedAmount(List svpFundTransactionUnsignedOutputs) { Script proposedFederationScriptPubKey = proposedFederation.getP2SHScript(); - Optional outputToProposedFederationOpt = searchForOutput(svpFundTransactionUnsignedOutputs, proposedFederationScriptPubKey); - assertTrue(outputToProposedFederationOpt.isPresent()); - - TransactionOutput outputToProposedFederation = outputToProposedFederationOpt.get(); - assertEquals(spendableValueFromProposedFederation, outputToProposedFederation.getValue()); + assertOutputWasSentToExpectedScriptWithExpectedAmount(svpFundTransactionUnsignedOutputs, proposedFederationScriptPubKey, spendableValueFromProposedFederation); } private void assertOneOutputIsToProposedFederationWithFlyoverPrefixWithExpectedAmount(List svpFundTransactionUnsignedOutputs) { Script proposedFederationWithFlyoverPrefixScriptPubKey = PegUtils.getFlyoverScriptPubKey(bridgeMainNetConstants.getProposedFederationFlyoverPrefix(), proposedFederation.getRedeemScript()); - Optional outputToProposedFederationWithFlyoverPrefixOpt = searchForOutput( - svpFundTransactionUnsignedOutputs, - proposedFederationWithFlyoverPrefixScriptPubKey - ); - assertTrue(outputToProposedFederationWithFlyoverPrefixOpt.isPresent()); - - TransactionOutput outputToProposedFederationWithFlyoverPrefix = outputToProposedFederationWithFlyoverPrefixOpt.get(); - assertEquals(spendableValueFromProposedFederation, outputToProposedFederationWithFlyoverPrefix.getValue()); - } - - private Optional searchForOutput(List transactionOutputs, Script outputScriptPubKey) { - return transactionOutputs.stream() - .filter(output -> output.getScriptPubKey().equals(outputScriptPubKey)) - .findFirst(); + assertOutputWasSentToExpectedScriptWithExpectedAmount(svpFundTransactionUnsignedOutputs, proposedFederationWithFlyoverPrefixScriptPubKey, spendableValueFromProposedFederation); } private void assertPegoutWasAddedToPegoutsWaitingForConfirmations(PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations, Sha256Hash pegoutTransactionHash, Keccak256 releaseCreationTxHash) { @@ -320,6 +305,7 @@ private void assertEventWasEmittedWithExpectedData(byte[] expectedData) { assertTrue(data.isPresent()); } } + @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tag("svp fund transaction registration tests") @@ -342,7 +328,7 @@ void setUp() { @Test void registerBtcTransaction_forSvpFundTransactionChange_whenProposedFederationDoesNotExist_shouldRegisterTransactionButNotUpdateSvpFundTransactionValues() throws Exception { // arrange - BtcTransaction svpFundTransaction = arrangeSvpFundTransactionWithChange(); + BtcTransaction svpFundTransaction = arrangeSvpFundTransactionUnsignedWithChange(); signInputs(svpFundTransaction); // a transaction trying to be registered should be signed setUpForTransactionRegistration(svpFundTransaction); @@ -378,7 +364,7 @@ void registerBtcTransaction_forSvpFundTransactionChange_whenValidationPeriodEnde .build(); rskExecutionBlock = Block.createBlockFromHeader(blockHeader, true); - BtcTransaction svpFundTransaction = arrangeSvpFundTransactionWithChange(); + BtcTransaction svpFundTransaction = arrangeSvpFundTransactionUnsignedWithChange(); signInputs(svpFundTransaction); // a transaction trying to be registered should be signed setUpForTransactionRegistration(svpFundTransaction); @@ -401,7 +387,7 @@ void registerBtcTransaction_forSvpFundTransactionChange_whenValidationPeriodEnde @Test void registerBtcTransaction_forNormalPegout_whenSvpPeriodIsOngoing_shouldRegisterTransactionButNotUpdateSvpFundTransactionValues() throws Exception { // Arrange - arrangeSvpFundTransactionWithChange(); + arrangeSvpFundTransactionUnsignedWithChange(); BtcTransaction pegout = createPegout(); savePegoutIndex(pegout); @@ -427,7 +413,7 @@ void registerBtcTransaction_forNormalPegout_whenSvpPeriodIsOngoing_shouldRegiste @Test void registerBtcTransaction_forSvpFundTransactionWithoutChangeOutput_whenSvpPeriodIsOngoing_shouldJustUpdateSvpFundTransactionValues() throws Exception { // Arrange - BtcTransaction svpFundTransaction = recreateSvpFundTransaction(); + BtcTransaction svpFundTransaction = recreateSvpFundTransactionUnsigned(); saveSvpFundTransactionUnsigned(svpFundTransaction); signInputs(svpFundTransaction); // a transaction trying to be registered should be signed @@ -452,7 +438,7 @@ void registerBtcTransaction_forSvpFundTransactionWithoutChangeOutput_whenSvpPeri @Test void registerBtcTransaction_forSvpFundTransactionChange_whenSvpPeriodIsOngoing_shouldRegisterTransactionAndUpdateSvpFundTransactionValues() throws Exception { // Arrange - BtcTransaction svpFundTransaction = arrangeSvpFundTransactionWithChange(); + BtcTransaction svpFundTransaction = arrangeSvpFundTransactionUnsignedWithChange(); signInputs(svpFundTransaction); // a transaction trying to be registered should be signed setUpForTransactionRegistration(svpFundTransaction); @@ -473,33 +459,6 @@ void registerBtcTransaction_forSvpFundTransactionChange_whenSvpPeriodIsOngoing_s assertSvpFundTransactionValuesWereUpdated(); } - private BtcTransaction arrangeSvpFundTransactionWithChange() { - BtcTransaction svpFundTransaction = recreateSvpFundTransaction(); - addOutputChange(svpFundTransaction); - saveSvpFundTransactionUnsigned(svpFundTransaction); - - return svpFundTransaction; - } - - private BtcTransaction recreateSvpFundTransaction() { - BtcTransaction svpFundTransaction = new BtcTransaction(btcMainnetParams); - - Sha256Hash parentTxHash = BitcoinTestUtils.createHash(1); - addInput(svpFundTransaction, parentTxHash); - - svpFundTransaction.addOutput(spendableValueFromProposedFederation, proposedFederation.getAddress()); - Address flyoverProposedFederationAddress = - PegUtils.getFlyoverAddress(btcMainnetParams, bridgeMainNetConstants.getProposedFederationFlyoverPrefix(), proposedFederation.getRedeemScript()); - svpFundTransaction.addOutput(spendableValueFromProposedFederation, flyoverProposedFederationAddress); - - return svpFundTransaction; - } - - private void saveSvpFundTransactionUnsigned(BtcTransaction svpFundTransaction) { - savePegoutIndex(svpFundTransaction); - saveSvpFundTransactionHashUnsigned(svpFundTransaction.getHash()); - } - private BtcTransaction createPegout() { BtcTransaction pegout = new BtcTransaction(btcMainnetParams); Sha256Hash parentTxHash = BitcoinTestUtils.createHash(2); @@ -509,40 +468,6 @@ private BtcTransaction createPegout() { return pegout; } - private void addInput(BtcTransaction transaction, Sha256Hash parentTxHash) { - // we need to add an input that we can actually sign, - // and we know the private keys for the following scriptSig - Federation federation = P2shErpFederationBuilder.builder().build(); - Script scriptSig = federation.getP2SHScript().createEmptyInputScript(null, federation.getRedeemScript()); - - transaction.addInput(parentTxHash, 0, scriptSig); - } - - private void signInputs(BtcTransaction transaction) { - List keysToSign = - BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"member01", "member02", "member03", "member04", "member05"}, true); - List inputs = transaction.getInputs(); - for (TransactionInput input : inputs) { - BitcoinTestUtils.signTransactionInputFromP2shMultiSig(transaction, inputs.indexOf(input), keysToSign); - } - } - - private void addOutputChange(BtcTransaction transaction) { - // add output to the active fed - Script activeFederationP2SHScript = activeFederation.getP2SHScript(); - transaction.addOutput(Coin.COIN.multiply(10), activeFederationP2SHScript); - } - - private void savePegoutIndex(BtcTransaction pegout) { - BitcoinUtils.getFirstInputSigHash(pegout) - .ifPresent(inputSigHash -> bridgeStorageProvider.setPegoutTxSigHash(inputSigHash)); - } - - private void saveSvpFundTransactionHashUnsigned(Sha256Hash svpFundTransactionHashUnsigned) { - bridgeStorageProvider.setSvpFundTxHashUnsigned(svpFundTransactionHashUnsigned); - bridgeSupport.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); @@ -592,4 +517,189 @@ private void assertSvpFundTransactionValuesWereUpdated() { assertFalse(bridgeStorageProvider.getSvpFundTxHashUnsigned().isPresent()); } } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @Tag("svp spend transaction creation and processing tests") + class SvpSpendTxCreationAndProcessingTests { + private BtcTransaction svpFundTransaction; + private Sha256Hash svpSpendTransactionHashUnsigned; + private BtcTransaction svpSpendTransactionUnsigned; + + @BeforeEach + void setUp() { + bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeMainNetConstants) + .withProvider(bridgeStorageProvider) + .withActivations(allActivations) + .withFederationSupport(federationSupport) + .withFeePerKbSupport(feePerKbSupport) + .withExecutionBlock(rskExecutionBlock) + .build(); + } + + @Test + void processSvpSpendTransaction_whenThereIsNoProposedFederation_shouldNotCreateNorProcessSpendTx() { + // act + when(federationSupport.getProposedFederation()).thenReturn(Optional.empty()); + bridgeSupport.processSvpSpendTransactionUnsigned(rskTx); + bridgeStorageProvider.save(); + + // assert + assertSvpSpendTransactionValuesWereNotSaved(); + } + + @Test + void processSvpSpendTransaction_whenThereIsNoFundTxSigned_shouldNotCreateNorProcessSpendTx() { + // act + bridgeSupport.processSvpSpendTransactionUnsigned(rskTx); + bridgeStorageProvider.save(); + + // assert + assertSvpSpendTransactionValuesWereNotSaved(); + } + + private void assertSvpSpendTransactionValuesWereNotSaved() { + Optional svpSpendTxHashUnsigned = bridgeStorageProvider.getSvpSpendTxHashUnsigned(); + assertFalse(svpSpendTxHashUnsigned.isPresent()); + + Optional> svpSpendTxWaitingForSignatures = bridgeStorageProvider.getSvpSpendTxWaitingForSignatures(); + assertFalse(svpSpendTxWaitingForSignatures.isPresent()); + } + + @Test + void processSvpSpendTransaction_createsExpectedTransactionAndSavesTheHashInStorageEntryAndInWaitingForSignaturesMapEntry() { + // arrange + arrangeSvpFundTransactionSigned(); + + // act + bridgeSupport.processSvpSpendTransactionUnsigned(rskTx); + bridgeStorageProvider.save(); + + // assert + assertSvpSpendTxHashUnsignedWasSavedInStorage(); + assertSvpSpendTxIsWaitingForSignatures(); + assertSvpFundTxSignedWasRemoved(); + assertSvpSpendTxHasExpectedInputsAndOutputs(); + } + + private void arrangeSvpFundTransactionSigned() { + svpFundTransaction = recreateSvpFundTransactionUnsigned(); + signInputs(svpFundTransaction); + + bridgeStorageProvider.setSvpFundTxSigned(svpFundTransaction); + bridgeStorageProvider.save(); + } + + private void assertSvpSpendTxHashUnsignedWasSavedInStorage() { + Optional svpSpendTransactionHashUnsignedOpt = bridgeStorageProvider.getSvpSpendTxHashUnsigned(); + assertTrue(svpSpendTransactionHashUnsignedOpt.isPresent()); + + svpSpendTransactionHashUnsigned = svpSpendTransactionHashUnsignedOpt.get(); + } + + private void assertSvpSpendTxIsWaitingForSignatures() { + Optional> svpSpendTxWaitingForSignaturesOpt = bridgeStorageProvider.getSvpSpendTxWaitingForSignatures(); + assertTrue(svpSpendTxWaitingForSignaturesOpt.isPresent()); + Map.Entry svpSpendTxWaitingForSignatures = svpSpendTxWaitingForSignaturesOpt.get(); + + Keccak256 svpSpendTxWaitingForSignaturesRskTxHash = svpSpendTxWaitingForSignatures.getKey(); + assertEquals(rskTx.getHash(), svpSpendTxWaitingForSignaturesRskTxHash); + + BtcTransaction svpSpendTxWaitingForSignaturesSpendTx = svpSpendTxWaitingForSignatures.getValue(); + assertEquals(svpSpendTransactionHashUnsigned, svpSpendTxWaitingForSignaturesSpendTx.getHash()); + svpSpendTransactionUnsigned = svpSpendTxWaitingForSignaturesSpendTx; + } + + private void assertSvpFundTxSignedWasRemoved() { + Optional svpFundTxSigned = bridgeStorageProvider.getSvpFundTxSigned(); + assertFalse(svpFundTxSigned.isPresent()); + } + + private void assertSvpSpendTxHasExpectedInputsAndOutputs() { + assertInputsOutpointHashIsFundTxHash(svpSpendTransactionUnsigned.getInputs(), svpFundTransaction.getHash()); + + List outputs = svpSpendTransactionUnsigned.getOutputs(); + assertEquals(1, outputs.size()); + + Coin expectedAmount = Coin.valueOf(1_762L); + assertOutputWasSentToExpectedScriptWithExpectedAmount(outputs, activeFederation.getP2SHScript(), expectedAmount); + } + + private void assertInputsOutpointHashIsFundTxHash(List inputs, Sha256Hash svpFundTxHashSigned) { + for (TransactionInput input : inputs) { + Sha256Hash outpointHash = input.getOutpoint().getHash(); + assertEquals(svpFundTxHashSigned, outpointHash); + } + } + } + + private void assertOutputWasSentToExpectedScriptWithExpectedAmount(List transactionOutputs, Script expectedScriptPubKey, Coin expectedAmount) { + Optional expectedOutput = searchForOutput(transactionOutputs, expectedScriptPubKey); + assertTrue(expectedOutput.isPresent()); + + TransactionOutput output = expectedOutput.get(); + assertEquals(expectedAmount, output.getValue()); + } + + private BtcTransaction arrangeSvpFundTransactionUnsignedWithChange() { + BtcTransaction svpFundTransaction = recreateSvpFundTransactionUnsigned(); + addOutputChange(svpFundTransaction); + saveSvpFundTransactionUnsigned(svpFundTransaction); + + return svpFundTransaction; + } + + private BtcTransaction recreateSvpFundTransactionUnsigned() { + BtcTransaction svpFundTransaction = new BtcTransaction(btcMainnetParams); + + Sha256Hash parentTxHash = BitcoinTestUtils.createHash(1); + addInput(svpFundTransaction, parentTxHash); + + svpFundTransaction.addOutput(spendableValueFromProposedFederation, proposedFederation.getAddress()); + Address flyoverProposedFederationAddress = + PegUtils.getFlyoverAddress(btcMainnetParams, bridgeMainNetConstants.getProposedFederationFlyoverPrefix(), proposedFederation.getRedeemScript()); + svpFundTransaction.addOutput(spendableValueFromProposedFederation, flyoverProposedFederationAddress); + + return svpFundTransaction; + } + + private void addInput(BtcTransaction transaction, Sha256Hash parentTxHash) { + // we need to add an input that we can actually sign, + // and we know the private keys for the following scriptSig + Federation federation = P2shErpFederationBuilder.builder().build(); + Script scriptSig = federation.getP2SHScript().createEmptyInputScript(null, federation.getRedeemScript()); + + transaction.addInput(parentTxHash, 0, scriptSig); + } + + private void signInputs(BtcTransaction transaction) { + List keysToSign = + BitcoinTestUtils.getBtcEcKeysFromSeeds(new String[]{"member01", "member02", "member03", "member04", "member05"}, true); + List inputs = transaction.getInputs(); + IntStream.range(0, inputs.size()).forEach(i -> + BitcoinTestUtils.signTransactionInputFromP2shMultiSig(transaction, i, keysToSign) + ); + } + + private void addOutputChange(BtcTransaction transaction) { + // add output to the active fed + Script activeFederationP2SHScript = activeFederation.getP2SHScript(); + transaction.addOutput(Coin.COIN.multiply(10), activeFederationP2SHScript); + } + + private void saveSvpFundTransactionUnsigned(BtcTransaction svpFundTransaction) { + savePegoutIndex(svpFundTransaction); + saveSvpFundTransactionHashUnsigned(svpFundTransaction.getHash()); + } + + private void savePegoutIndex(BtcTransaction pegout) { + BitcoinUtils.getFirstInputSigHash(pegout) + .ifPresent(inputSigHash -> bridgeStorageProvider.setPegoutTxSigHash(inputSigHash)); + } + + private void saveSvpFundTransactionHashUnsigned(Sha256Hash svpFundTransactionHashUnsigned) { + bridgeStorageProvider.setSvpFundTxHashUnsigned(svpFundTransactionHashUnsigned); + bridgeSupport.save(); + } } From 8f188b2d9a17163543d2d642c3c3aa0fa5da7686 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 26 Sep 2024 11:53:58 -0300 Subject: [PATCH 04/44] Create new method in BitcoinUtils to remove duplicated code from BridgeSupport --- .../java/co/rsk/peg/bitcoin/BitcoinUtils.java | 13 ++++++++++++ .../co/rsk/peg/bitcoin/BitcoinUtilsTest.java | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index d041acb8f68..01a290140bf 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -79,6 +79,19 @@ public static void removeSignaturesFromTransactionWithP2shMultiSigInputs(BtcTran } } + public static void addInputFromOutputSentToScript(BtcTransaction transaction, BtcTransaction sourceTransaction, Script expectedOutputScript) { + List outputs = sourceTransaction.getOutputs(); + TransactionOutput matchingOutput = searchForOutput(outputs, expectedOutputScript) + .orElseThrow( + () -> { + String message = String.format("Transaction must have an output to expected script %s", expectedOutputScript); + logger.error("[addInputFromExpectedOutput] {}", message); + return new IllegalStateException(message); + } + ); + transaction.addInput(matchingOutput); + } + public static Optional searchForOutput(List transactionOutputs, Script outputScriptPubKey) { return transactionOutputs.stream() .filter(output -> output.getScriptPubKey().equals(outputScriptPubKey)) diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java index bb40abbfeb3..9bbc009890d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java @@ -19,6 +19,8 @@ import java.util.*; import java.util.stream.Stream; +import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromOutputSentToScript; + class BitcoinUtilsTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); @@ -397,4 +399,23 @@ void removeSignaturesFromTransaction_whenTransactionIsLegacyAndInputsHaveP2shMul // assert assertEquals(transactionWithoutSignaturesHash, transaction.getHash()); } + + @Test + void addInputFromOutputSentToScript_shouldAddInputWithOutpointFromOutput() { + // arrange + Script redeemScript = new Script(Hex.decode("645521020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db21026b472f7d59d201ff1f540f111b6eb329e071c30a9d23e3d2bcd128fe73dc254c210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf93210357f7ed4c118e581f49cd3b4d9dd1edb4295f4def49d6dcf2faaaaac87a1a0a422103ae72827d25030818c4947a800187b1fbcc33ae751e248ae60094cc989fb880f62103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b358887719062103e05bf6002b62651378b1954820539c36ca405cbb778c225395dd9ebff678029959ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); + Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); + + BtcTransaction sourceTransaction = new BtcTransaction(btcMainnetParams); + sourceTransaction.addOutput(Coin.valueOf(1000L), outputScript); + + // act + BtcTransaction newTransaction = new BtcTransaction(btcMainnetParams); + addInputFromOutputSentToScript(newTransaction, sourceTransaction, outputScript); + + // assert + TransactionInput newTransactionInput = newTransaction.getInput(0); + TransactionOutput sourceTransactionOutput = sourceTransaction.getOutput(0); + Assertions.assertEquals(newTransactionInput.getOutpoint().getHash(), sourceTransactionOutput.getParentTransactionHash()); + } } From 3f6c1e5d5f5b98d6b7a5c7e2c80368057c7db2b6 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 26 Sep 2024 11:57:08 -0300 Subject: [PATCH 05/44] Add hsm-needed pegout logging for svp spend tx. Remove duplicated code --- .../main/java/co/rsk/peg/BridgeSupport.java | 54 +++++-------------- 1 file changed, 12 insertions(+), 42 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 a83f869ede7..2a4da0705d6 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -20,8 +20,9 @@ import static co.rsk.peg.BridgeUtils.calculatePegoutTxSize; import static co.rsk.peg.PegUtils.getFlyoverAddress; import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize; +import static co.rsk.peg.PegUtils.getFlyoverScriptPubKey; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; -import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; +import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromOutputSentToScript; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static java.util.Objects.isNull; @@ -1055,7 +1056,13 @@ protected void processSvpSpendTransactionUnsigned(Transaction rskTx) { .ifPresent(proposedFederation -> provider.getSvpFundTxSigned() .ifPresent(svpFundTxSigned -> { BtcTransaction svpSpendTransactionUnsigned = createSvpSpendTransaction(svpFundTxSigned, proposedFederation); - updateSvpSpendTransactionValues(rskTx.getHash(), svpSpendTransactionUnsigned); + + Keccak256 rskTxHash = rskTx.getHash(); + updateSvpSpendTransactionValues(rskTxHash, svpSpendTransactionUnsigned); + + Coin requestedAmount = svpSpendTransactionUnsigned.getOutput(0).getValue(); + logReleaseRequested(rskTxHash, svpSpendTransactionUnsigned, requestedAmount); + logPegoutTransactionCreated(svpSpendTransactionUnsigned); })); } @@ -1083,48 +1090,11 @@ private Coin calculateAmountToSendToActiveFederation(Federation proposedFederati } private void addSvpSpendTransactionInputs(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Federation proposedFederation) { - // TODO update when segwit since the scriptSig changes - addInputSentToProposedFederation(svpSpendTransaction, svpFundTxSigned, proposedFederation); - addInputSentToFlyoverProposedFederation(svpSpendTransaction, svpFundTxSigned, proposedFederation.getRedeemScript()); - } - - private void addInputSentToProposedFederation(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Federation proposedFederation) { - List svpFundTxSignedOutputs = svpFundTxSigned.getOutputs(); Script proposedFederationOutputScript = proposedFederation.getP2SHScript(); - TransactionOutput outputToProposedFederation = - searchForOutput(svpFundTxSignedOutputs, proposedFederationOutputScript) - .orElseThrow(() -> - new IllegalStateException("Svp fund transaction must have an output to the proposed federation.") - ); - - Script proposedFederationRedeemScript = proposedFederation.getRedeemScript(); - Script proposedFederationScriptSig = proposedFederationOutputScript.createEmptyInputScript(null, proposedFederationRedeemScript); - svpSpendTransaction.addInput( - svpFundTxSigned.getHash(), - svpFundTxSignedOutputs.indexOf(outputToProposedFederation), - proposedFederationScriptSig - ); - } - - private void addInputSentToFlyoverProposedFederation(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Script proposedFederationRedeemScript) { - Keccak256 flyoverPrefix = bridgeConstants.getProposedFederationFlyoverPrefix(); - Script flyoverProposedFederationRedeemScript = FlyoverRedeemScriptBuilderImpl.builder().of(flyoverPrefix, proposedFederationRedeemScript); - Script flyoverProposedFederationScriptPubKey = ScriptBuilder.createP2SHOutputScript(flyoverProposedFederationRedeemScript); + addInputFromOutputSentToScript(svpSpendTransaction, svpFundTxSigned, proposedFederationOutputScript); - List svpFundTxSignedOutputs = svpFundTxSigned.getOutputs(); - TransactionOutput outputToFlyoverProposedFederation = - searchForOutput(svpFundTxSignedOutputs, flyoverProposedFederationScriptPubKey) - .orElseThrow(() -> - new IllegalStateException("Svp fund transaction must have an output to the flyover proposed federation.") - ); - - Script flyoverProposedFederationEmptyScriptSig = - flyoverProposedFederationScriptPubKey.createEmptyInputScript(null, flyoverProposedFederationRedeemScript); - svpSpendTransaction.addInput( - svpFundTxSigned.getHash(), - svpFundTxSignedOutputs.indexOf(outputToFlyoverProposedFederation), - flyoverProposedFederationEmptyScriptSig - ); + Script flyoverProposedFederationOutputScript = getFlyoverScriptPubKey(bridgeConstants.getProposedFederationFlyoverPrefix(), proposedFederation.getRedeemScript()); + addInputFromOutputSentToScript(svpSpendTransaction, svpFundTxSigned, flyoverProposedFederationOutputScript); } private void updateSvpSpendTransactionValues(Keccak256 rskTxHash, BtcTransaction svpSpendTransactionUnsigned) { From 5d288e1aa34fde16af0a6837c36391da750143ad Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 26 Sep 2024 11:59:07 -0300 Subject: [PATCH 06/44] Add assertions to new logged events. Reorder methods to be more organized and focussed --- .../java/co/rsk/peg/BridgeSupportSvpTest.java | 181 ++++++++++-------- 1 file changed, 98 insertions(+), 83 deletions(-) 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 74e6907bcae..524abd67502 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java @@ -40,6 +40,8 @@ public class BridgeSupportSvpTest { private static final RskAddress bridgeContractAddress = PrecompiledContracts.BRIDGE_ADDR; + private final CallTransaction.Function releaseRequestedEvent = BridgeEvents.RELEASE_REQUESTED.getEvent(); + private final CallTransaction.Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); private static final ActivationConfig.ForBlock allActivations = ActivationConfigsForTest.all().forBlock(0); private static final BridgeConstants bridgeMainNetConstants = BridgeMainNetConstants.getInstance(); @@ -105,8 +107,6 @@ void setUp() { @Tag("svp fund transaction creation and processing tests") class SvpFundTxCreationAndProcessingTests { private final List logs = new ArrayList<>(); - private final CallTransaction.Function releaseRequestedEvent = BridgeEvents.RELEASE_REQUESTED.getEvent(); - private final CallTransaction.Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); private BtcTransaction svpFundTransactionUnsigned; private Sha256Hash svpFundTransactionHashUnsigned; @@ -186,8 +186,8 @@ private void assertSvpReleaseWasSettled() throws IOException { svpFundTransactionUnsigned = getSvpFundTransactionFromPegoutsMap(pegoutsWaitingForConfirmations); assertPegoutTxSigHashWasSaved(svpFundTransactionUnsigned); - assertLogReleaseRequested(rskTx.getHash(), svpFundTransactionHashUnsigned); - assertLogPegoutTransactionCreated(svpFundTransactionUnsigned); + assertLogReleaseRequested(logs, rskTx.getHash(), svpFundTransactionHashUnsigned, spendableValueFromProposedFederation); + assertLogPegoutTransactionCreated(logs, svpFundTransactionUnsigned); } private BtcTransaction getSvpFundTransactionFromPegoutsMap(PegoutsWaitingForConfirmations pegoutsWaitingForConfirmations) { @@ -257,53 +257,6 @@ private void assertPegoutTxSigHashWasSaved(BtcTransaction pegoutTransaction) { Sha256Hash pegoutTxSigHash = pegoutTxSigHashOpt.get(); assertTrue(bridgeStorageProvider.hasPegoutTxSigHash(pegoutTxSigHash)); } - - private void assertLogReleaseRequested(Keccak256 releaseCreationTxHash, Sha256Hash pegoutTransactionHash) { - byte[] releaseCreationTxHashSerialized = releaseCreationTxHash.getBytes(); - byte[] pegoutTransactionHashSerialized = pegoutTransactionHash.getBytes(); - List encodedTopics = getEncodedTopics(releaseRequestedEvent, releaseCreationTxHashSerialized, pegoutTransactionHashSerialized); - - byte[] encodedData = getEncodedData(releaseRequestedEvent, spendableValueFromProposedFederation.getValue()); - - assertEventWasEmittedWithExpectedTopics(encodedTopics); - assertEventWasEmittedWithExpectedData(encodedData); - } - - private void assertLogPegoutTransactionCreated(BtcTransaction pegoutTransaction) { - Sha256Hash pegoutTransactionHash = pegoutTransaction.getHash(); - byte[] pegoutTransactionHashSerialized = pegoutTransactionHash.getBytes(); - List encodedTopics = getEncodedTopics(pegoutTransactionCreatedEvent, pegoutTransactionHashSerialized); - - List outpointValues = extractOutpointValues(pegoutTransaction); - byte[] serializedOutpointValues = UtxoUtils.encodeOutpointValues(outpointValues); - byte[] encodedData = getEncodedData(pegoutTransactionCreatedEvent, serializedOutpointValues); - - assertEventWasEmittedWithExpectedTopics(encodedTopics); - assertEventWasEmittedWithExpectedData(encodedData); - } - - private List getEncodedTopics(CallTransaction.Function bridgeEvent, Object... args) { - byte[][] encodedTopicsInBytes = bridgeEvent.encodeEventTopics(args); - return LogInfo.byteArrayToList(encodedTopicsInBytes); - } - - private byte[] getEncodedData(CallTransaction.Function bridgeEvent, Object... args) { - return bridgeEvent.encodeEventData(args); - } - - private void assertEventWasEmittedWithExpectedTopics(List expectedTopics) { - Optional topicOpt = logs.stream() - .filter(log -> log.getTopics().equals(expectedTopics)) - .findFirst(); - assertTrue(topicOpt.isPresent()); - } - - private void assertEventWasEmittedWithExpectedData(byte[] expectedData) { - Optional data = logs.stream() - .filter(log -> Arrays.equals(log.getData(), expectedData)) - .findFirst(); - assertTrue(data.isPresent()); - } } @Nested @@ -351,6 +304,11 @@ void registerBtcTransaction_forSvpFundTransactionChange_whenProposedFederationDo assertSvpFundTransactionValuesWereNotUpdated(); } + private void assertSvpFundTransactionValuesWereNotUpdated() { + assertTrue(bridgeStorageProvider.getSvpFundTxHashUnsigned().isPresent()); + assertFalse(bridgeStorageProvider.getSvpFundTxSigned().isPresent()); + } + @Test void registerBtcTransaction_forSvpFundTransactionChange_whenValidationPeriodEnded_shouldRegisterTransactionButNotUpdateSvpFundTransactionValues() throws Exception { // arrange @@ -459,6 +417,24 @@ void registerBtcTransaction_forSvpFundTransactionChange_whenSvpPeriodIsOngoing_s assertSvpFundTransactionValuesWereUpdated(); } + private BtcTransaction arrangeSvpFundTransactionUnsignedWithChange() { + BtcTransaction svpFundTransaction = recreateSvpFundTransactionUnsigned(); + addOutputChange(svpFundTransaction); + saveSvpFundTransactionUnsigned(svpFundTransaction); + + return svpFundTransaction; + } + + private void saveSvpFundTransactionUnsigned(BtcTransaction svpFundTransaction) { + savePegoutIndex(svpFundTransaction); + saveSvpFundTransactionHashUnsigned(svpFundTransaction.getHash()); + } + + private void saveSvpFundTransactionHashUnsigned(Sha256Hash svpFundTransactionHashUnsigned) { + bridgeStorageProvider.setSvpFundTxHashUnsigned(svpFundTransactionHashUnsigned); + bridgeSupport.save(); + } + private BtcTransaction createPegout() { BtcTransaction pegout = new BtcTransaction(btcMainnetParams); Sha256Hash parentTxHash = BitcoinTestUtils.createHash(2); @@ -468,6 +444,11 @@ private BtcTransaction createPegout() { return pegout; } + private void savePegoutIndex(BtcTransaction pegout) { + BitcoinUtils.getFirstInputSigHash(pegout) + .ifPresent(inputSigHash -> bridgeStorageProvider.setPegoutTxSigHash(inputSigHash)); + } + 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); @@ -505,11 +486,6 @@ private void assertTransactionWasProcessed(Sha256Hash transactionHash) throws IO assertEquals(rskExecutionBlockNumber, rskBlockHeightAtWhichBtcTxWasProcessed.get()); } - private void assertSvpFundTransactionValuesWereNotUpdated() { - assertTrue(bridgeStorageProvider.getSvpFundTxHashUnsigned().isPresent()); - assertFalse(bridgeStorageProvider.getSvpFundTxSigned().isPresent()); - } - private void assertSvpFundTransactionValuesWereUpdated() { Optional svpFundTransactionSignedOpt = bridgeStorageProvider.getSvpFundTxSigned(); assertTrue(svpFundTransactionSignedOpt.isPresent()); @@ -522,15 +498,25 @@ private void assertSvpFundTransactionValuesWereUpdated() { @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tag("svp spend transaction creation and processing tests") class SvpSpendTxCreationAndProcessingTests { + private final List logs = new ArrayList<>(); private BtcTransaction svpFundTransaction; private Sha256Hash svpSpendTransactionHashUnsigned; private BtcTransaction svpSpendTransactionUnsigned; @BeforeEach void setUp() { + SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); + BridgeEventLogger bridgeEventLogger = new BridgeEventLoggerImpl( + bridgeMainNetConstants, + allActivations, + logs, + signatureCache + ); + bridgeSupport = bridgeSupportBuilder .withBridgeConstants(bridgeMainNetConstants) .withProvider(bridgeStorageProvider) + .withEventLogger(bridgeEventLogger) .withActivations(allActivations) .withFederationSupport(federationSupport) .withFeePerKbSupport(feePerKbSupport) @@ -568,7 +554,7 @@ private void assertSvpSpendTransactionValuesWereNotSaved() { } @Test - void processSvpSpendTransaction_createsExpectedTransactionAndSavesTheHashInStorageEntryAndInWaitingForSignaturesMapEntry() { + void processSvpSpendTransaction_createsExpectedTransactionAndSavesTheValuesAndLogsExpectedEvents() { // arrange arrangeSvpFundTransactionSigned(); @@ -579,8 +565,13 @@ void processSvpSpendTransaction_createsExpectedTransactionAndSavesTheHashInStora // assert assertSvpSpendTxHashUnsignedWasSavedInStorage(); assertSvpSpendTxIsWaitingForSignatures(); - assertSvpFundTxSignedWasRemoved(); assertSvpSpendTxHasExpectedInputsAndOutputs(); + + assertSvpFundTxSignedWasRemoved(); + + assertLogPegoutTransactionCreated(logs, svpSpendTransactionUnsigned); + Coin requestedAmount = Coin.valueOf(1762); + assertLogReleaseRequested(logs, rskTx.getHash(), svpSpendTransactionHashUnsigned, requestedAmount); } private void arrangeSvpFundTransactionSigned() { @@ -634,22 +625,6 @@ private void assertInputsOutpointHashIsFundTxHash(List inputs, } } - private void assertOutputWasSentToExpectedScriptWithExpectedAmount(List transactionOutputs, Script expectedScriptPubKey, Coin expectedAmount) { - Optional expectedOutput = searchForOutput(transactionOutputs, expectedScriptPubKey); - assertTrue(expectedOutput.isPresent()); - - TransactionOutput output = expectedOutput.get(); - assertEquals(expectedAmount, output.getValue()); - } - - private BtcTransaction arrangeSvpFundTransactionUnsignedWithChange() { - BtcTransaction svpFundTransaction = recreateSvpFundTransactionUnsigned(); - addOutputChange(svpFundTransaction); - saveSvpFundTransactionUnsigned(svpFundTransaction); - - return svpFundTransaction; - } - private BtcTransaction recreateSvpFundTransactionUnsigned() { BtcTransaction svpFundTransaction = new BtcTransaction(btcMainnetParams); @@ -688,18 +663,58 @@ private void addOutputChange(BtcTransaction transaction) { transaction.addOutput(Coin.COIN.multiply(10), activeFederationP2SHScript); } - private void saveSvpFundTransactionUnsigned(BtcTransaction svpFundTransaction) { - savePegoutIndex(svpFundTransaction); - saveSvpFundTransactionHashUnsigned(svpFundTransaction.getHash()); + private void assertOutputWasSentToExpectedScriptWithExpectedAmount(List transactionOutputs, Script expectedScriptPubKey, Coin expectedAmount) { + Optional expectedOutput = searchForOutput(transactionOutputs, expectedScriptPubKey); + assertTrue(expectedOutput.isPresent()); + + TransactionOutput output = expectedOutput.get(); + assertEquals(expectedAmount, output.getValue()); + } + + private void assertLogReleaseRequested(List logs, Keccak256 releaseCreationTxHash, Sha256Hash pegoutTransactionHash, Coin requestedAmount) { + byte[] releaseCreationTxHashSerialized = releaseCreationTxHash.getBytes(); + byte[] pegoutTransactionHashSerialized = pegoutTransactionHash.getBytes(); + List encodedTopics = getEncodedTopics(releaseRequestedEvent, releaseCreationTxHashSerialized, pegoutTransactionHashSerialized); + + byte[] encodedData = getEncodedData(releaseRequestedEvent, requestedAmount.getValue()); + + assertEventWasEmittedWithExpectedTopics(logs, encodedTopics); + assertEventWasEmittedWithExpectedData(logs, encodedData); + } + + private void assertLogPegoutTransactionCreated(List logs, BtcTransaction pegoutTransaction) { + Sha256Hash pegoutTransactionHash = pegoutTransaction.getHash(); + byte[] pegoutTransactionHashSerialized = pegoutTransactionHash.getBytes(); + List encodedTopics = getEncodedTopics(pegoutTransactionCreatedEvent, pegoutTransactionHashSerialized); + + List outpointValues = extractOutpointValues(pegoutTransaction); + byte[] serializedOutpointValues = UtxoUtils.encodeOutpointValues(outpointValues); + byte[] encodedData = getEncodedData(pegoutTransactionCreatedEvent, serializedOutpointValues); + + assertEventWasEmittedWithExpectedTopics(logs, encodedTopics); + assertEventWasEmittedWithExpectedData(logs, encodedData); + } + + private List getEncodedTopics(CallTransaction.Function bridgeEvent, Object... args) { + byte[][] encodedTopicsInBytes = bridgeEvent.encodeEventTopics(args); + return LogInfo.byteArrayToList(encodedTopicsInBytes); + } + + private byte[] getEncodedData(CallTransaction.Function bridgeEvent, Object... args) { + return bridgeEvent.encodeEventData(args); } - private void savePegoutIndex(BtcTransaction pegout) { - BitcoinUtils.getFirstInputSigHash(pegout) - .ifPresent(inputSigHash -> bridgeStorageProvider.setPegoutTxSigHash(inputSigHash)); + private void assertEventWasEmittedWithExpectedTopics(List logs, List expectedTopics) { + Optional topicOpt = logs.stream() + .filter(log -> log.getTopics().equals(expectedTopics)) + .findFirst(); + assertTrue(topicOpt.isPresent()); } - private void saveSvpFundTransactionHashUnsigned(Sha256Hash svpFundTransactionHashUnsigned) { - bridgeStorageProvider.setSvpFundTxHashUnsigned(svpFundTransactionHashUnsigned); - bridgeSupport.save(); + private void assertEventWasEmittedWithExpectedData(List logs, byte[] expectedData) { + Optional data = logs.stream() + .filter(log -> Arrays.equals(log.getData(), expectedData)) + .findFirst(); + assertTrue(data.isPresent()); } } From 70fd49eb7b0d3131a4f6e800c3f64fa90c85395d Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 26 Sep 2024 13:17:03 -0300 Subject: [PATCH 07/44] Refactor method to avoid throwing exception unnecessary. Rename method. Add test case for no matching script. Reorder methods. Add log assertions for when svp spend tx is not processed. Rename variables and methods --- .../main/java/co/rsk/peg/BridgeSupport.java | 22 ++++++------- .../java/co/rsk/peg/bitcoin/BitcoinUtils.java | 13 ++------ .../java/co/rsk/peg/BridgeSupportSvpTest.java | 32 ++++++++++++------- .../co/rsk/peg/bitcoin/BitcoinUtilsTest.java | 25 +++++++++++++-- 4 files changed, 56 insertions(+), 36 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 2a4da0705d6..f2cea87d7cf 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -22,7 +22,7 @@ import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize; import static co.rsk.peg.PegUtils.getFlyoverScriptPubKey; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; -import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromOutputSentToScript; +import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromMatchingOutputScript; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static java.util.Objects.isNull; @@ -1060,8 +1060,8 @@ protected void processSvpSpendTransactionUnsigned(Transaction rskTx) { Keccak256 rskTxHash = rskTx.getHash(); updateSvpSpendTransactionValues(rskTxHash, svpSpendTransactionUnsigned); - Coin requestedAmount = svpSpendTransactionUnsigned.getOutput(0).getValue(); - logReleaseRequested(rskTxHash, svpSpendTransactionUnsigned, requestedAmount); + Coin amountSentToActiveFed = svpSpendTransactionUnsigned.getOutput(0).getValue(); + logReleaseRequested(rskTxHash, svpSpendTransactionUnsigned, amountSentToActiveFed); logPegoutTransactionCreated(svpSpendTransactionUnsigned); })); } @@ -1080,6 +1080,14 @@ private BtcTransaction createSvpSpendTransaction(BtcTransaction svpFundTxSigned, return svpSpendTransaction; } + private void addSvpSpendTransactionInputs(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Federation proposedFederation) { + Script proposedFederationOutputScript = proposedFederation.getP2SHScript(); + addInputFromMatchingOutputScript(svpSpendTransaction, svpFundTxSigned, proposedFederationOutputScript); + + Script flyoverProposedFederationOutputScript = getFlyoverScriptPubKey(bridgeConstants.getProposedFederationFlyoverPrefix(), proposedFederation.getRedeemScript()); + addInputFromMatchingOutputScript(svpSpendTransaction, svpFundTxSigned, flyoverProposedFederationOutputScript); + } + private Coin calculateAmountToSendToActiveFederation(Federation proposedFederation) { int svpSpendTransactionSize = calculatePegoutTxSize(activations, proposedFederation, 2, 1); long backupSizePercentage = (long) 1.2; // just to be sure the amount sent will be enough @@ -1089,14 +1097,6 @@ private Coin calculateAmountToSendToActiveFederation(Federation proposedFederati .divide(1000); } - private void addSvpSpendTransactionInputs(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Federation proposedFederation) { - Script proposedFederationOutputScript = proposedFederation.getP2SHScript(); - addInputFromOutputSentToScript(svpSpendTransaction, svpFundTxSigned, proposedFederationOutputScript); - - Script flyoverProposedFederationOutputScript = getFlyoverScriptPubKey(bridgeConstants.getProposedFederationFlyoverPrefix(), proposedFederation.getRedeemScript()); - addInputFromOutputSentToScript(svpSpendTransaction, svpFundTxSigned, flyoverProposedFederationOutputScript); - } - private void updateSvpSpendTransactionValues(Keccak256 rskTxHash, BtcTransaction svpSpendTransactionUnsigned) { provider.setSvpSpendTxHashUnsigned(svpSpendTransactionUnsigned.getHash()); provider.setSvpSpendTxWaitingForSignatures( diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index 01a290140bf..35a2c534360 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -79,17 +79,10 @@ public static void removeSignaturesFromTransactionWithP2shMultiSigInputs(BtcTran } } - public static void addInputFromOutputSentToScript(BtcTransaction transaction, BtcTransaction sourceTransaction, Script expectedOutputScript) { + public static void addInputFromMatchingOutputScript(BtcTransaction transaction, BtcTransaction sourceTransaction, Script expectedOutputScript) { List outputs = sourceTransaction.getOutputs(); - TransactionOutput matchingOutput = searchForOutput(outputs, expectedOutputScript) - .orElseThrow( - () -> { - String message = String.format("Transaction must have an output to expected script %s", expectedOutputScript); - logger.error("[addInputFromExpectedOutput] {}", message); - return new IllegalStateException(message); - } - ); - transaction.addInput(matchingOutput); + searchForOutput(outputs, expectedOutputScript) + .ifPresent(transaction::addInput); } public static Optional searchForOutput(List transactionOutputs, Script outputScriptPubKey) { 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 524abd67502..41356543661 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java @@ -106,13 +106,14 @@ void setUp() { @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tag("svp fund transaction creation and processing tests") class SvpFundTxCreationAndProcessingTests { - private final List logs = new ArrayList<>(); + private List logs; private BtcTransaction svpFundTransactionUnsigned; private Sha256Hash svpFundTransactionHashUnsigned; @BeforeEach void setUp() { + logs = new ArrayList<>(); SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); BridgeEventLogger bridgeEventLogger = new BridgeEventLoggerImpl( bridgeMainNetConstants, @@ -444,6 +445,12 @@ private BtcTransaction createPegout() { return pegout; } + private void addOutputChange(BtcTransaction transaction) { + // add output to the active fed + Script activeFederationP2SHScript = activeFederation.getP2SHScript(); + transaction.addOutput(Coin.COIN.multiply(10), activeFederationP2SHScript); + } + private void savePegoutIndex(BtcTransaction pegout) { BitcoinUtils.getFirstInputSigHash(pegout) .ifPresent(inputSigHash -> bridgeStorageProvider.setPegoutTxSigHash(inputSigHash)); @@ -498,13 +505,14 @@ private void assertSvpFundTransactionValuesWereUpdated() { @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tag("svp spend transaction creation and processing tests") class SvpSpendTxCreationAndProcessingTests { - private final List logs = new ArrayList<>(); + private List logs; private BtcTransaction svpFundTransaction; private Sha256Hash svpSpendTransactionHashUnsigned; private BtcTransaction svpSpendTransactionUnsigned; @BeforeEach void setUp() { + logs = new ArrayList<>(); SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); BridgeEventLogger bridgeEventLogger = new BridgeEventLoggerImpl( bridgeMainNetConstants, @@ -533,6 +541,7 @@ void processSvpSpendTransaction_whenThereIsNoProposedFederation_shouldNotCreateN // assert assertSvpSpendTransactionValuesWereNotSaved(); + assertSvpSpendTransactionReleaseWasNotLogged(); } @Test @@ -543,6 +552,7 @@ void processSvpSpendTransaction_whenThereIsNoFundTxSigned_shouldNotCreateNorProc // assert assertSvpSpendTransactionValuesWereNotSaved(); + assertSvpSpendTransactionReleaseWasNotLogged(); } private void assertSvpSpendTransactionValuesWereNotSaved() { @@ -553,6 +563,10 @@ private void assertSvpSpendTransactionValuesWereNotSaved() { assertFalse(svpSpendTxWaitingForSignatures.isPresent()); } + private void assertSvpSpendTransactionReleaseWasNotLogged() { + assertEquals(0, logs.size()); + } + @Test void processSvpSpendTransaction_createsExpectedTransactionAndSavesTheValuesAndLogsExpectedEvents() { // arrange @@ -567,11 +581,11 @@ void processSvpSpendTransaction_createsExpectedTransactionAndSavesTheValuesAndLo assertSvpSpendTxIsWaitingForSignatures(); assertSvpSpendTxHasExpectedInputsAndOutputs(); - assertSvpFundTxSignedWasRemoved(); + assertSvpFundTxSignedWasRemovedFromStorage(); assertLogPegoutTransactionCreated(logs, svpSpendTransactionUnsigned); - Coin requestedAmount = Coin.valueOf(1762); - assertLogReleaseRequested(logs, rskTx.getHash(), svpSpendTransactionHashUnsigned, requestedAmount); + Coin valueSentToActiveFed = Coin.valueOf(1762); + assertLogReleaseRequested(logs, rskTx.getHash(), svpSpendTransactionHashUnsigned, valueSentToActiveFed); } private void arrangeSvpFundTransactionSigned() { @@ -602,7 +616,7 @@ private void assertSvpSpendTxIsWaitingForSignatures() { svpSpendTransactionUnsigned = svpSpendTxWaitingForSignaturesSpendTx; } - private void assertSvpFundTxSignedWasRemoved() { + private void assertSvpFundTxSignedWasRemovedFromStorage() { Optional svpFundTxSigned = bridgeStorageProvider.getSvpFundTxSigned(); assertFalse(svpFundTxSigned.isPresent()); } @@ -657,12 +671,6 @@ private void signInputs(BtcTransaction transaction) { ); } - private void addOutputChange(BtcTransaction transaction) { - // add output to the active fed - Script activeFederationP2SHScript = activeFederation.getP2SHScript(); - transaction.addOutput(Coin.COIN.multiply(10), activeFederationP2SHScript); - } - private void assertOutputWasSentToExpectedScriptWithExpectedAmount(List transactionOutputs, Script expectedScriptPubKey, Coin expectedAmount) { Optional expectedOutput = searchForOutput(transactionOutputs, expectedScriptPubKey); assertTrue(expectedOutput.isPresent()); diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java index 9bbc009890d..b659cc0a9ba 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java @@ -19,7 +19,7 @@ import java.util.*; import java.util.stream.Stream; -import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromOutputSentToScript; +import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromMatchingOutputScript; class BitcoinUtilsTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); @@ -401,7 +401,26 @@ void removeSignaturesFromTransaction_whenTransactionIsLegacyAndInputsHaveP2shMul } @Test - void addInputFromOutputSentToScript_shouldAddInputWithOutpointFromOutput() { + void addInputFromOutputSentToScript_withNoMatchingOutputScript_shouldNotAddInput() { + // arrange + Script redeemScript = new Script(Hex.decode("645521020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db21026b472f7d59d201ff1f540f111b6eb329e071c30a9d23e3d2bcd128fe73dc254c210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf93210357f7ed4c118e581f49cd3b4d9dd1edb4295f4def49d6dcf2faaaaac87a1a0a422103ae72827d25030818c4947a800187b1fbcc33ae751e248ae60094cc989fb880f62103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b358887719062103e05bf6002b62651378b1954820539c36ca405cbb778c225395dd9ebff678029959ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); + Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); + BtcTransaction sourceTransaction = new BtcTransaction(btcMainnetParams); + sourceTransaction.addOutput(Coin.valueOf(1000L), outputScript); + + // act + BtcTransaction newTransaction = new BtcTransaction(btcMainnetParams); + + Script anotherRedeemScript = new Script(Hex.decode("556421020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db21026b472f7d59d201ff1f540f111b6eb329e071c30a9d23e3d2bcd128fe73dc254c210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf93210357f7ed4c118e581f49cd3b4d9dd1edb4295f4def49d6dcf2faaaaac87a1a0a422103ae72827d25030818c4947a800187b1fbcc33ae751e248ae60094cc989fb880f62103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b358887719062103e05bf6002b62651378b1954820539c36ca405cbb778c225395dd9ebff678029959ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); + Script anotherOutputScript = ScriptBuilder.createP2SHOutputScript(anotherRedeemScript); + addInputFromMatchingOutputScript(newTransaction, sourceTransaction, anotherOutputScript); + + // assert + Assertions.assertEquals(0, newTransaction.getInputs().size()); + } + + @Test + void addInputFromOutputSentToScript_withMatchingOutputScript_shouldAddInputWithOutpointFromOutput() { // arrange Script redeemScript = new Script(Hex.decode("645521020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db21026b472f7d59d201ff1f540f111b6eb329e071c30a9d23e3d2bcd128fe73dc254c210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf93210357f7ed4c118e581f49cd3b4d9dd1edb4295f4def49d6dcf2faaaaac87a1a0a422103ae72827d25030818c4947a800187b1fbcc33ae751e248ae60094cc989fb880f62103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b358887719062103e05bf6002b62651378b1954820539c36ca405cbb778c225395dd9ebff678029959ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); @@ -411,7 +430,7 @@ void addInputFromOutputSentToScript_shouldAddInputWithOutpointFromOutput() { // act BtcTransaction newTransaction = new BtcTransaction(btcMainnetParams); - addInputFromOutputSentToScript(newTransaction, sourceTransaction, outputScript); + addInputFromMatchingOutputScript(newTransaction, sourceTransaction, outputScript); // assert TransactionInput newTransactionInput = newTransaction.getInput(0); From 729fca996a8a799206503287cefb7cc105e93bdc Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 26 Sep 2024 16:50:13 -0300 Subject: [PATCH 08/44] Rename method that calculates amount to be sent in svp spend tx. Replace redeem scripts being used in tests for some less shady and add tests for public method. Fix typo after rebase --- .../main/java/co/rsk/peg/BridgeSupport.java | 4 +- .../co/rsk/peg/bitcoin/BitcoinUtilsTest.java | 50 +++++++++++++++++-- 2 files changed, 49 insertions(+), 5 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 f2cea87d7cf..9989e784d26 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1073,7 +1073,7 @@ private BtcTransaction createSvpSpendTransaction(BtcTransaction svpFundTxSigned, addSvpSpendTransactionInputs(svpSpendTransaction, svpFundTxSigned, proposedFederation); svpSpendTransaction.addOutput( - calculateAmountToSendToActiveFederation(proposedFederation), + calculateSvpSpendTxAmount(proposedFederation), federationSupport.getActiveFederationAddress() ); @@ -1088,7 +1088,7 @@ private void addSvpSpendTransactionInputs(BtcTransaction svpSpendTransaction, Bt addInputFromMatchingOutputScript(svpSpendTransaction, svpFundTxSigned, flyoverProposedFederationOutputScript); } - private Coin calculateAmountToSendToActiveFederation(Federation proposedFederation) { + private Coin calculateSvpSpendTxAmount(Federation proposedFederation) { int svpSpendTransactionSize = calculatePegoutTxSize(activations, proposedFederation, 2, 1); long backupSizePercentage = (long) 1.2; // just to be sure the amount sent will be enough diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java index b659cc0a9ba..a4bd89d1e23 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java @@ -8,6 +8,7 @@ import co.rsk.peg.constants.BridgeMainNetConstants; import co.rsk.peg.federation.Federation; import co.rsk.peg.federation.P2shErpFederationBuilder; +import co.rsk.peg.federation.StandardMultiSigFederationBuilder; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -20,6 +21,7 @@ import java.util.stream.Stream; import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromMatchingOutputScript; +import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; class BitcoinUtilsTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); @@ -403,7 +405,8 @@ void removeSignaturesFromTransaction_whenTransactionIsLegacyAndInputsHaveP2shMul @Test void addInputFromOutputSentToScript_withNoMatchingOutputScript_shouldNotAddInput() { // arrange - Script redeemScript = new Script(Hex.decode("645521020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db21026b472f7d59d201ff1f540f111b6eb329e071c30a9d23e3d2bcd128fe73dc254c210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf93210357f7ed4c118e581f49cd3b4d9dd1edb4295f4def49d6dcf2faaaaac87a1a0a422103ae72827d25030818c4947a800187b1fbcc33ae751e248ae60094cc989fb880f62103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b358887719062103e05bf6002b62651378b1954820539c36ca405cbb778c225395dd9ebff678029959ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); + Federation federation = P2shErpFederationBuilder.builder().build(); + Script redeemScript = federation.getRedeemScript(); Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); BtcTransaction sourceTransaction = new BtcTransaction(btcMainnetParams); sourceTransaction.addOutput(Coin.valueOf(1000L), outputScript); @@ -411,7 +414,8 @@ void addInputFromOutputSentToScript_withNoMatchingOutputScript_shouldNotAddInput // act BtcTransaction newTransaction = new BtcTransaction(btcMainnetParams); - Script anotherRedeemScript = new Script(Hex.decode("556421020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db21026b472f7d59d201ff1f540f111b6eb329e071c30a9d23e3d2bcd128fe73dc254c210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf93210357f7ed4c118e581f49cd3b4d9dd1edb4295f4def49d6dcf2faaaaac87a1a0a422103ae72827d25030818c4947a800187b1fbcc33ae751e248ae60094cc989fb880f62103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b358887719062103e05bf6002b62651378b1954820539c36ca405cbb778c225395dd9ebff678029959ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); + Federation anotherFederation = StandardMultiSigFederationBuilder.builder().build(); + Script anotherRedeemScript = anotherFederation.getRedeemScript(); Script anotherOutputScript = ScriptBuilder.createP2SHOutputScript(anotherRedeemScript); addInputFromMatchingOutputScript(newTransaction, sourceTransaction, anotherOutputScript); @@ -422,7 +426,8 @@ void addInputFromOutputSentToScript_withNoMatchingOutputScript_shouldNotAddInput @Test void addInputFromOutputSentToScript_withMatchingOutputScript_shouldAddInputWithOutpointFromOutput() { // arrange - Script redeemScript = new Script(Hex.decode("645521020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db21026b472f7d59d201ff1f540f111b6eb329e071c30a9d23e3d2bcd128fe73dc254c210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf93210357f7ed4c118e581f49cd3b4d9dd1edb4295f4def49d6dcf2faaaaac87a1a0a422103ae72827d25030818c4947a800187b1fbcc33ae751e248ae60094cc989fb880f62103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b358887719062103e05bf6002b62651378b1954820539c36ca405cbb778c225395dd9ebff678029959ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); + Federation federation = P2shErpFederationBuilder.builder().build(); + Script redeemScript = federation.getRedeemScript(); Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); BtcTransaction sourceTransaction = new BtcTransaction(btcMainnetParams); @@ -437,4 +442,43 @@ void addInputFromOutputSentToScript_withMatchingOutputScript_shouldAddInputWithO TransactionOutput sourceTransactionOutput = sourceTransaction.getOutput(0); Assertions.assertEquals(newTransactionInput.getOutpoint().getHash(), sourceTransactionOutput.getParentTransactionHash()); } + + @Test + void searchForOutput_whenTheOutputIsThere_shouldReturnOutputSentToExpectedScript() { + // arrange + Federation federation = P2shErpFederationBuilder.builder().build(); + Script redeemScript = federation.getRedeemScript(); + Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); + + BtcTransaction sourceTransaction = new BtcTransaction(btcMainnetParams); + sourceTransaction.addOutput(Coin.valueOf(1000L), outputScript); + + // act + Optional transactionOutput = searchForOutput(sourceTransaction.getOutputs(), outputScript); + + // assert + Assertions.assertTrue(transactionOutput.isPresent()); + Assertions.assertEquals(outputScript, transactionOutput.get().getScriptPubKey()); + } + + @Test + void searchForOutput_whenTheOutputIsNotThere_shouldReturnEmpty() { + // arrange + Federation federation = P2shErpFederationBuilder.builder().build(); + Script redeemScript = federation.getRedeemScript(); + Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); + + BtcTransaction sourceTransaction = new BtcTransaction(btcMainnetParams); + sourceTransaction.addOutput(Coin.valueOf(1000L), outputScript); + + Federation anotherFederation = StandardMultiSigFederationBuilder.builder().build(); + Script anotherRedeemScript = anotherFederation.getRedeemScript(); + Script anotherOutputScript = ScriptBuilder.createP2SHOutputScript(anotherRedeemScript); + + // act + Optional transactionOutput = searchForOutput(sourceTransaction.getOutputs(), anotherOutputScript); + + // assert + Assertions.assertFalse(transactionOutput.isPresent()); + } } From ab6c7fd91ef34bda5cef09668c1e6d552fceeef9 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Mon, 30 Sep 2024 15:26:00 -0300 Subject: [PATCH 09/44] Add missing empty p2sh input script to svp spend transaction Rename method to be more accurate and reuse it. Correct test --- .../main/java/co/rsk/peg/BridgeSupport.java | 18 ++++++---- .../src/main/java/co/rsk/peg/PegUtils.java | 12 +++---- .../java/co/rsk/peg/bitcoin/BitcoinUtils.java | 5 +++ .../java/co/rsk/peg/BridgeSupportSvpTest.java | 9 +++-- .../test/java/co/rsk/peg/PegUtilsTest.java | 11 +++++-- .../co/rsk/peg/bitcoin/BitcoinUtilsTest.java | 33 +++++++++++++++++-- 6 files changed, 68 insertions(+), 20 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 9989e784d26..2f3fc99a69c 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -18,11 +18,10 @@ package co.rsk.peg; import static co.rsk.peg.BridgeUtils.calculatePegoutTxSize; -import static co.rsk.peg.PegUtils.getFlyoverAddress; import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize; -import static co.rsk.peg.PegUtils.getFlyoverScriptPubKey; +import static co.rsk.peg.PegUtils.*; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; -import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromMatchingOutputScript; +import static co.rsk.peg.bitcoin.BitcoinUtils.*; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static java.util.Objects.isNull; @@ -1081,11 +1080,18 @@ private BtcTransaction createSvpSpendTransaction(BtcTransaction svpFundTxSigned, } private void addSvpSpendTransactionInputs(BtcTransaction svpSpendTransaction, BtcTransaction svpFundTxSigned, Federation proposedFederation) { + Script proposedFederationRedeemScript = proposedFederation.getRedeemScript(); Script proposedFederationOutputScript = proposedFederation.getP2SHScript(); addInputFromMatchingOutputScript(svpSpendTransaction, svpFundTxSigned, proposedFederationOutputScript); - - Script flyoverProposedFederationOutputScript = getFlyoverScriptPubKey(bridgeConstants.getProposedFederationFlyoverPrefix(), proposedFederation.getRedeemScript()); - addInputFromMatchingOutputScript(svpSpendTransaction, svpFundTxSigned, flyoverProposedFederationOutputScript); + svpSpendTransaction.getInput(0) + .setScriptSig(createBaseP2SHInputScriptThatSpendsFromRedeemScript(proposedFederationRedeemScript)); + + Script flyoverRedeemScript = + getFlyoverRedeemScript(bridgeConstants.getProposedFederationFlyoverPrefix(), proposedFederationRedeemScript); + Script flyoverOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript); + addInputFromMatchingOutputScript(svpSpendTransaction, svpFundTxSigned, flyoverOutputScript); + svpSpendTransaction.getInput(1) + .setScriptSig(createBaseP2SHInputScriptThatSpendsFromRedeemScript(flyoverRedeemScript)); } private Coin calculateSvpSpendTxAmount(Federation proposedFederation) { diff --git a/rskj-core/src/main/java/co/rsk/peg/PegUtils.java b/rskj-core/src/main/java/co/rsk/peg/PegUtils.java index 74b04f90281..64283c476e1 100644 --- a/rskj-core/src/main/java/co/rsk/peg/PegUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/PegUtils.java @@ -199,16 +199,16 @@ private static PeginEvaluationResult evaluateLegacyPeginSender(TxSenderAddressTy public static Address getFlyoverAddress(NetworkParameters networkParameters, Keccak256 flyoverDerivationHash, Script redeemScript) { Script flyoverScriptPubKey = getFlyoverScriptPubKey(flyoverDerivationHash, redeemScript); - return Address.fromP2SHScript(networkParameters, flyoverScriptPubKey); } public static Script getFlyoverScriptPubKey(Keccak256 flyoverDerivationHash, Script redeemScript) { - Script flyoverRedeemScript = FlyoverRedeemScriptBuilderImpl.builder().of( - flyoverDerivationHash, - redeemScript - ); - + Script flyoverRedeemScript = getFlyoverRedeemScript(flyoverDerivationHash, redeemScript); return ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript); } + + public static Script getFlyoverRedeemScript(Keccak256 flyoverDerivationHash, Script redeemScript) { + return FlyoverRedeemScriptBuilderImpl.builder() + .of(flyoverDerivationHash, redeemScript); + } } diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index 35a2c534360..86134f5b350 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -85,6 +85,11 @@ public static void addInputFromMatchingOutputScript(BtcTransaction transaction, .ifPresent(transaction::addInput); } + public static Script createBaseP2SHInputScriptThatSpendsFromRedeemScript(Script redeemScript) { + Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); + return outputScript.createEmptyInputScript(null, redeemScript); + } + public static Optional searchForOutput(List transactionOutputs, Script outputScriptPubKey) { return transactionOutputs.stream() .filter(output -> output.getScriptPubKey().equals(outputScriptPubKey)) 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 41356543661..6495c2ef1df 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java @@ -32,6 +32,7 @@ import java.util.stream.IntStream; import static co.rsk.peg.BridgeSupportTestUtil.*; +import static co.rsk.peg.bitcoin.BitcoinUtils.createBaseP2SHInputScriptThatSpendsFromRedeemScript; import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static org.junit.jupiter.api.Assertions.*; @@ -657,9 +658,11 @@ private void addInput(BtcTransaction transaction, Sha256Hash parentTxHash) { // we need to add an input that we can actually sign, // and we know the private keys for the following scriptSig Federation federation = P2shErpFederationBuilder.builder().build(); - Script scriptSig = federation.getP2SHScript().createEmptyInputScript(null, federation.getRedeemScript()); - - transaction.addInput(parentTxHash, 0, scriptSig); + transaction.addInput( + parentTxHash, + 0, + createBaseP2SHInputScriptThatSpendsFromRedeemScript(federation.getRedeemScript()) + ); } private void signInputs(BtcTransaction transaction) { diff --git a/rskj-core/src/test/java/co/rsk/peg/PegUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/PegUtilsTest.java index 5b3e27e1513..3bc853e4738 100644 --- a/rskj-core/src/test/java/co/rsk/peg/PegUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/PegUtilsTest.java @@ -29,8 +29,7 @@ import java.util.stream.Stream; import static co.rsk.peg.PegTestUtils.createFederation; -import static co.rsk.peg.PegUtils.getFlyoverAddress; -import static co.rsk.peg.PegUtils.getFlyoverScriptPubKey; +import static co.rsk.peg.PegUtils.*; import static co.rsk.peg.federation.FederationTestUtils.createP2shErpFederation; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -66,6 +65,14 @@ void init() { activeFederation = createP2shErpFederation(federationMainNetConstants, activeFedSigners); } + @ParameterizedTest + @MethodSource("derivationHashAndRedeemScriptArgs") + void getFlyoverRedeemScript_fromRealValues_shouldReturnSameRealRedeemScript(Keccak256 flyoverDerivationHash, Script redeemScript) { + Script flyoverRedeemScript = new Script(Hex.decode("20fc2bb93810d3d2332fed0b291c03822100a813eceaa0665896e0c82a8d50043975645521020ace50bab1230f8002a0bfe619482af74b338cc9e4c956add228df47e6adae1c21025093f439fb8006fd29ab56605ffec9cdc840d16d2361004e1337a2f86d8bd2db210275d473555de2733c47125f9702b0f870df1d817379f5587f09b6c40ed2c6c9492102a95f095d0ce8cb3b9bf70cc837e3ebe1d107959b1fa3f9b2d8f33446f9c8cbdb2103250c11be0561b1d7ae168b1f59e39cbc1fd1ba3cf4d2140c1a365b2723a2bf9321034851379ec6b8a701bd3eef8a0e2b119abb4bdde7532a3d6bcbff291b0daf3f25210350179f143a632ce4e6ac9a755b82f7f4266cfebb116a42cadb104c2c2a3350f92103b04fbd87ef5e2c0946a684c8c93950301a45943bbe56d979602038698facf9032103b58a5da144f5abab2e03e414ad044b732300de52fa25c672a7f7b3588877190659ae670350cd00b275532102370a9838e4d15708ad14a104ee5606b36caaaaf739d833e67770ce9fd9b3ec80210257c293086c4d4fe8943deda5f890a37d11bebd140e220faa76258a41d077b4d42103c2660a46aa73078ee6016dee953488566426cf55fc8011edd0085634d75395f92103cd3e383ec6e12719a6c69515e5559bcbe037d0aa24c187e1e26ce932e22ad7b354ae68")); + + assertEquals(flyoverRedeemScript, getFlyoverRedeemScript(flyoverDerivationHash, redeemScript)); + } + @ParameterizedTest @MethodSource("derivationHashAndRedeemScriptArgs") void getFlyoverScriptPubKey_fromRealValues_shouldReturnSameRealOutputScript(Keccak256 flyoverDerivationHash, Script redeemScript) { diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java index a4bd89d1e23..cbaff1473ff 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java @@ -1,5 +1,6 @@ package co.rsk.peg.bitcoin; +import static co.rsk.peg.bitcoin.BitcoinUtils.*; import static org.junit.jupiter.api.Assertions.*; import co.rsk.bitcoinj.core.*; @@ -20,9 +21,6 @@ import java.util.*; import java.util.stream.Stream; -import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromMatchingOutputScript; -import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; - class BitcoinUtilsTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); @@ -443,6 +441,35 @@ void addInputFromOutputSentToScript_withMatchingOutputScript_shouldAddInputWithO Assertions.assertEquals(newTransactionInput.getOutpoint().getHash(), sourceTransactionOutput.getParentTransactionHash()); } + @Test + void createBaseP2SHInputScriptThatSpendsFromRedeemScript_shouldCreateExpectedScriptSig() { + // arrange + Federation federation = P2shErpFederationBuilder.builder().build(); + Script redeemScript = federation.getRedeemScript(); + + BtcTransaction fundTransaction = new BtcTransaction(btcMainnetParams); + fundTransaction.addOutput(Coin.valueOf(1000), federation.getAddress()); + + BtcTransaction transaction = new BtcTransaction(btcMainnetParams); + TransactionOutput outpoint = fundTransaction.getOutput(0); + transaction.addInput(outpoint); + + // act + TransactionInput input = transaction.getInput(0); + input.setScriptSig(createBaseP2SHInputScriptThatSpendsFromRedeemScript(redeemScript)); + + // assert + List scriptSigChunks = input.getScriptSig().getChunks(); + int redeemScriptChunkIndex = scriptSigChunks.size() - 1; + + assertArrayEquals(redeemScript.getProgram(), scriptSigChunks.get(redeemScriptChunkIndex).data); // last chunk should be the redeem script + + for (ScriptChunk chunk : scriptSigChunks.subList(0, redeemScriptChunkIndex)) { // all the other chunks should be zero + assertEquals(0, chunk.opcode); + } + + } + @Test void searchForOutput_whenTheOutputIsThere_shouldReturnOutputSentToExpectedScript() { // arrange From 407f086eaa50d2e0750eeb550ce7c35961d752fc Mon Sep 17 00:00:00 2001 From: julia-zack Date: Tue, 1 Oct 2024 09:49:22 -0300 Subject: [PATCH 10/44] Add tests to check if svp spend tx inputs have expected script sig --- .../java/co/rsk/peg/BridgeSupportSvpTest.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) 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 6495c2ef1df..76bb38d81a5 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java @@ -2,6 +2,7 @@ import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.Script; +import co.rsk.bitcoinj.script.ScriptChunk; import co.rsk.bitcoinj.store.BlockStoreException; import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; @@ -32,6 +33,7 @@ import java.util.stream.IntStream; import static co.rsk.peg.BridgeSupportTestUtil.*; +import static co.rsk.peg.PegUtils.getFlyoverRedeemScript; import static co.rsk.peg.bitcoin.BitcoinUtils.createBaseP2SHInputScriptThatSpendsFromRedeemScript; import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; @@ -623,7 +625,10 @@ private void assertSvpFundTxSignedWasRemovedFromStorage() { } private void assertSvpSpendTxHasExpectedInputsAndOutputs() { - assertInputsOutpointHashIsFundTxHash(svpSpendTransactionUnsigned.getInputs(), svpFundTransaction.getHash()); + List inputs = svpSpendTransactionUnsigned.getInputs(); + assertEquals(2, inputs.size()); + assertInputsHaveExpectedScriptSig(inputs); + assertInputsOutpointHashIsFundTxHash(inputs, svpFundTransaction.getHash()); List outputs = svpSpendTransactionUnsigned.getOutputs(); assertEquals(1, outputs.size()); @@ -632,12 +637,33 @@ private void assertSvpSpendTxHasExpectedInputsAndOutputs() { assertOutputWasSentToExpectedScriptWithExpectedAmount(outputs, activeFederation.getP2SHScript(), expectedAmount); } + private void assertInputsHaveExpectedScriptSig(List inputs) { + TransactionInput inputToProposedFederation = inputs.get(0); + Script proposedFederationRedeemScript = proposedFederation.getRedeemScript(); + assertInputHasExpectedScriptSig(inputToProposedFederation, proposedFederationRedeemScript); + + TransactionInput inputToFlyoverProposedFederation = inputs.get(1); + Script flyoverRedeemScript = + getFlyoverRedeemScript(bridgeMainNetConstants.getProposedFederationFlyoverPrefix(), proposedFederationRedeemScript); + assertInputHasExpectedScriptSig(inputToFlyoverProposedFederation, flyoverRedeemScript); + } + private void assertInputsOutpointHashIsFundTxHash(List inputs, Sha256Hash svpFundTxHashSigned) { for (TransactionInput input : inputs) { Sha256Hash outpointHash = input.getOutpoint().getHash(); assertEquals(svpFundTxHashSigned, outpointHash); } } + + private void assertInputHasExpectedScriptSig(TransactionInput input, Script redeemScript) { + List scriptSigChunks = input.getScriptSig().getChunks(); + int redeemScriptChunkIndex = scriptSigChunks.size() - 1; + + assertArrayEquals(redeemScript.getProgram(), scriptSigChunks.get(redeemScriptChunkIndex).data); // last chunk should be the redeem script + for (ScriptChunk chunk : scriptSigChunks.subList(0, redeemScriptChunkIndex)) { // all the other chunks should be zero + assertEquals(0, chunk.opcode); + } + } } private BtcTransaction recreateSvpFundTransactionUnsigned() { From 7c961d42d53be84eb1fc610e4220f73f82a4a7d3 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Fri, 27 Sep 2024 13:47:08 -0300 Subject: [PATCH 11/44] Refactor BridgeEventLoggerImpl Add blank line to improve readability Replace usage of Collectors.toList with .toList to fix sonar complain Remove extra blank line rename to be consistent with other namings Remove global variable --- .../rsk/peg/utils/BridgeEventLoggerImpl.java | 161 ++++++++++++------ 1 file changed, 105 insertions(+), 56 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java index b5ead3941e8..b7023ce630e 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java @@ -41,7 +41,6 @@ import java.util.List; import java.util.function.Function; -import java.util.stream.Collectors; /** * Responsible for logging events triggered by BridgeContract. @@ -49,9 +48,6 @@ * @author martin.medina */ public class BridgeEventLoggerImpl implements BridgeEventLogger { - - private static final byte[] BRIDGE_CONTRACT_ADDRESS = PrecompiledContracts.BRIDGE_ADDR.getBytes(); - private final BridgeConstants bridgeConstants; private final SignatureCache signatureCache; private final List logs; @@ -67,17 +63,21 @@ public BridgeEventLoggerImpl(BridgeConstants bridgeConstants, ActivationConfig.F @Override public void logUpdateCollections(Transaction rskTx) { CallTransaction.Function event = BridgeEvents.UPDATE_COLLECTIONS.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); - byte[] encodedData = event.encodeEventData(rskTx.getSender(signatureCache).toString()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + + String rskTxSenderAddress = rskTx.getSender(signatureCache).toString(); + byte[] encodedData = event.encodeEventData(rskTxSenderAddress); + + addLog(encodedTopics, encodedData); } @Override public void logAddSignature(FederationMember federationMember, BtcTransaction btcTx, byte[] rskTxHash) { ECKey federatorPublicKey = getFederatorPublicKey(federationMember); String federatorRskAddress = ByteUtil.toHexString(federatorPublicKey.getAddress()); + logAddSignatureInSolidityFormat(rskTxHash, federatorRskAddress, federationMember.getBtcPublicKey()); } @@ -95,37 +95,45 @@ private boolean shouldUseRskPublicKey() { private void logAddSignatureInSolidityFormat(byte[] rskTxHash, String federatorRskAddress, BtcECKey federatorPublicKey) { CallTransaction.Function event = BridgeEvents.ADD_SIGNATURE.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(rskTxHash, federatorRskAddress); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); - byte[] encodedData = event.encodeEventData(federatorPublicKey.getPubKey()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(rskTxHash, federatorRskAddress); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + + byte[] federatorPublicKeySerialized = federatorPublicKey.getPubKey(); + byte[] encodedData = event.encodeEventData(federatorPublicKeySerialized); + + addLog(encodedTopics, encodedData); } @Override public void logReleaseBtc(BtcTransaction btcTx, byte[] rskTxHash) { CallTransaction.Function event = BridgeEvents.RELEASE_BTC.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(rskTxHash); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); - byte[] encodedData = event.encodeEventData(btcTx.bitcoinSerialize()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(rskTxHash); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + + byte[] rawBtcTxSerialized = btcTx.bitcoinSerialize(); + byte[] encodedData = event.encodeEventData(rawBtcTxSerialized); + + addLog(encodedTopics, encodedData); } @Override public void logCommitFederation(Block executionBlock, Federation oldFederation, Federation newFederation) { + CallTransaction.Function event = BridgeEvents.COMMIT_FEDERATION.getEvent(); + // Convert old federation public keys in bytes array byte[] oldFederationFlatPubKeys = flatKeysAsByteArray(oldFederation.getBtcPublicKeys()); String oldFederationBtcAddress = oldFederation.getAddress().toBase58(); + byte[] newFederationFlatPubKeys = flatKeysAsByteArray(newFederation.getBtcPublicKeys()); String newFederationBtcAddress = newFederation.getAddress().toBase58(); FederationConstants federationConstants = bridgeConstants.getFederationConstants(); long newFedActivationBlockNumber = executionBlock.getNumber() + federationConstants.getFederationActivationAge(activations); - CallTransaction.Function event = BridgeEvents.COMMIT_FEDERATION.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); byte[] encodedData = event.encodeEventData( oldFederationFlatPubKeys, @@ -135,61 +143,75 @@ public void logCommitFederation(Block executionBlock, Federation oldFederation, newFedActivationBlockNumber ); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logLockBtc(RskAddress receiver, BtcTransaction btcTx, Address senderBtcAddress, Coin amount) { CallTransaction.Function event = BridgeEvents.LOCK_BTC.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(receiver.toString()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); - byte[] encodedData = event.encodeEventData(btcTx.getHash().getBytes(), senderBtcAddress.toString(), amount.getValue()); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(receiver.toString()); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + + byte[] btcTxHashSerialized = btcTx.getHash().getBytes(); + byte[] encodedData = event.encodeEventData( + btcTxHashSerialized, + senderBtcAddress.toString(), + amount.getValue() + ); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logPeginBtc(RskAddress receiver, BtcTransaction btcTx, Coin amount, int protocolVersion) { CallTransaction.Function event = BridgeEvents.PEGIN_BTC.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(receiver.toString(), btcTx.getHash().getBytes()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[][] encodedTopicsSerialized = event.encodeEventTopics(receiver.toString(), btcTx.getHash().getBytes()); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); byte[] encodedData = event.encodeEventData(amount.getValue(), protocolVersion); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logReleaseBtcRequested(byte[] rskTransactionHash, BtcTransaction btcTx, Coin amount) { CallTransaction.Function event = BridgeEvents.RELEASE_REQUESTED.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(rskTransactionHash, btcTx.getHash().getBytes()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[] btcTxHashSerialized = btcTx.getHash().getBytes(); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(rskTransactionHash, btcTxHashSerialized); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + byte[] encodedData = event.encodeEventData(amount.getValue()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logRejectedPegin(BtcTransaction btcTx, RejectedPeginReason reason) { CallTransaction.Function event = BridgeEvents.REJECTED_PEGIN.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(btcTx.getHash().getBytes()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[] btcTxHashSerialized = btcTx.getHash().getBytes(); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(btcTxHashSerialized); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); byte[] encodedData = event.encodeEventData(reason.getValue()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logUnrefundablePegin(BtcTransaction btcTx, UnrefundablePeginReason reason) { CallTransaction.Function event = BridgeEvents.UNREFUNDABLE_PEGIN.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(btcTx.getHash().getBytes()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[] btcTxHashSerialized = btcTx.getHash().getBytes(); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(btcTxHashSerialized); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); byte[] encodedData = event.encodeEventData(reason.getValue()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override @@ -203,50 +225,63 @@ public void logReleaseBtcRequestReceived(String sender, Address btcDestinationAd private void logReleaseBtcRequestReceived(String sender, byte[] btcDestinationAddress, Coin amount) { CallTransaction.Function event = BridgeEvents.RELEASE_REQUEST_RECEIVED_LEGACY.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(sender); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[][] encodedTopicsSerialized = event.encodeEventTopics(sender); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + byte[] encodedData = event.encodeEventData(btcDestinationAddress, amount.getValue()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + + addLog(encodedTopics, encodedData); } private void logReleaseBtcRequestReceived(String sender, String btcDestinationAddress, Coin amount) { CallTransaction.Function event = BridgeEvents.RELEASE_REQUEST_RECEIVED.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(sender); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[][] encodedTopicsSerialized = event.encodeEventTopics(sender); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + byte[] encodedData = event.encodeEventData(btcDestinationAddress, amount.getValue()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logReleaseBtcRequestRejected(String sender, Coin amount, RejectedPegoutReason reason) { CallTransaction.Function event = BridgeEvents.RELEASE_REQUEST_REJECTED.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(sender); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[][] encodedTopicsSerialized = event.encodeEventTopics(sender); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + byte[] encodedData = event.encodeEventData(amount.getValue(), reason.getValue()); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logBatchPegoutCreated(Sha256Hash btcTxHash, List rskTxHashes) { CallTransaction.Function event = BridgeEvents.BATCH_PEGOUT_CREATED.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(btcTxHash.getBytes()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[] btcTxHashSerialized = btcTxHash.getBytes(); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(btcTxHashSerialized); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); byte[] serializedRskTxHashes = serializeRskTxHashes(rskTxHashes); byte[] encodedData = event.encodeEventData(serializedRskTxHashes); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + addLog(encodedTopics, encodedData); } @Override public void logPegoutConfirmed(Sha256Hash btcTxHash, long pegoutCreationRskBlockNumber) { CallTransaction.Function event = BridgeEvents.PEGOUT_CONFIRMED.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(btcTxHash.getBytes()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[] btcTxHashSerialized = btcTxHash.getBytes(); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(btcTxHashSerialized); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + byte[] encodedData = event.encodeEventData(pegoutCreationRskBlockNumber); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + + addLog(encodedTopics, encodedData); } @Override @@ -256,18 +291,21 @@ public void logPegoutTransactionCreated(Sha256Hash btcTxHash, List outpoin } CallTransaction.Function event = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); - byte[][] encodedTopicsInBytes = event.encodeEventTopics(btcTxHash.getBytes()); - List encodedTopics = LogInfo.byteArrayToList(encodedTopicsInBytes); + + byte[] btcTxHashSerialized = btcTxHash.getBytes(); + byte[][] encodedTopicsSerialized = event.encodeEventTopics(btcTxHashSerialized); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); byte[] serializedOutpointValues = UtxoUtils.encodeOutpointValues(outpointValues); byte[] encodedData = event.encodeEventData(serializedOutpointValues); - this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, encodedTopics, encodedData)); + + addLog(encodedTopics, encodedData); } private byte[] flatKeys(List keys, Function parser) { List pubKeys = keys.stream() .map(parser) - .collect(Collectors.toList()); + .toList(); int pubKeysLength = pubKeys.stream().mapToInt(key -> key.length).sum(); byte[] flatPubKeys = new byte[pubKeysLength]; @@ -287,7 +325,7 @@ private byte[] flatKeysAsByteArray(List keys) { private byte[] serializeRskTxHashes(List rskTxHashes) { List rskTxHashesList = rskTxHashes.stream() .map(Keccak256::getBytes) - .collect(Collectors.toList()); + .toList(); int rskTxHashesLength = rskTxHashesList.stream().mapToInt(key -> key.length).sum(); byte[] serializedRskTxHashes = new byte[rskTxHashesLength]; @@ -299,4 +337,15 @@ private byte[] serializeRskTxHashes(List rskTxHashes) { return serializedRskTxHashes; } + + private List getEncodedTopics(byte[][] encodedTopicsSerialized) { + return LogInfo.byteArrayToList(encodedTopicsSerialized); + } + + private void addLog(List eventEncodedTopics, byte[] eventEncodedData) { + RskAddress bridgeContractAddress = PrecompiledContracts.BRIDGE_ADDR; + LogInfo newLog = new LogInfo(bridgeContractAddress.getBytes(), eventEncodedTopics, eventEncodedData); + + this.logs.add(newLog); + } } From 2048c49719e2cddd08e786cd3d8a02c3fa38f02a Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 27 Sep 2024 17:07:24 -0300 Subject: [PATCH 12/44] Refactor BridgeEventLoggerImpl to not receive SignatureCache object Variable name and test assertion update Co-authored-by: jeremy-then <94636474+jeremy-then@users.noreply.github.com> Co-authored-by: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> --- .../main/java/co/rsk/peg/BridgeSupport.java | 7 +- .../java/co/rsk/peg/BridgeSupportFactory.java | 4 +- .../co/rsk/peg/utils/BridgeEventLogger.java | 3 +- .../rsk/peg/utils/BridgeEventLoggerImpl.java | 9 +- .../peg/utils/BrigeEventLoggerLegacyImpl.java | 9 +- .../src/test/java/co/rsk/RskTestUtils.java | 12 ++- .../peg/BridgeSupportAddSignatureTest.java | 88 ++++++++--------- .../test/java/co/rsk/peg/BridgeSupportIT.java | 8 +- ...ridgeSupportProcessFundsMigrationTest.java | 4 + .../rsk/peg/BridgeSupportReleaseBtcTest.java | 41 ++++---- .../java/co/rsk/peg/BridgeSupportSvpTest.java | 8 +- .../java/co/rsk/peg/BridgeSupportTest.java | 95 +++++++++++++------ .../federation/VoteFederationChangeTest.java | 6 +- .../peg/utils/BridgeEventLoggerImplTest.java | 23 ++--- .../BridgeEventLoggerLegacyImplTest.java | 15 ++- 15 files changed, 182 insertions(+), 150 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 2f3fc99a69c..6a473531499 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -979,7 +979,7 @@ private void requestRelease(Address destinationAddress, Coin value, Transaction public void updateCollections(Transaction rskTx) throws IOException { Context.propagate(btcContext); - eventLogger.logUpdateCollections(rskTx); + logUpdateCollections(rskTx); processFundsMigration(rskTx); @@ -990,6 +990,11 @@ public void updateCollections(Transaction rskTx) throws IOException { updateFederationCreationBlockHeights(); } + private void logUpdateCollections(Transaction rskTx) { + RskAddress sender = rskTx.getSender(signatureCache); + eventLogger.logUpdateCollections(sender); + } + private boolean svpIsOngoing() { Optional proposedFederationOpt = federationSupport.getProposedFederation(); diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java index d6783f8e4ae..0a9a6fa3da5 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupportFactory.java @@ -94,9 +94,9 @@ public BridgeSupport newInstance( BridgeEventLogger eventLogger = null; if (logs != null) { if (activations.isActive(ConsensusRule.RSKIP146)) { - eventLogger = new BridgeEventLoggerImpl(bridgeConstants, activations, logs, signatureCache); + eventLogger = new BridgeEventLoggerImpl(bridgeConstants, activations, logs); } else { - eventLogger = new BrigeEventLoggerLegacyImpl(bridgeConstants, activations, logs, signatureCache); + eventLogger = new BrigeEventLoggerLegacyImpl(bridgeConstants, activations, logs); } } diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java index 6184db4d756..355aad58367 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java @@ -25,7 +25,6 @@ import co.rsk.peg.federation.FederationMember; import co.rsk.peg.pegin.RejectedPeginReason; import org.ethereum.core.Block; -import org.ethereum.core.Transaction; import java.util.List; /** @@ -35,7 +34,7 @@ */ public interface BridgeEventLogger { - void logUpdateCollections(Transaction rskTx); + void logUpdateCollections(RskAddress sender); void logAddSignature(FederationMember federationMember, BtcTransaction btcTx, byte[] rskTxHash); diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java index b7023ce630e..dc714a20458 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java @@ -49,26 +49,23 @@ */ public class BridgeEventLoggerImpl implements BridgeEventLogger { private final BridgeConstants bridgeConstants; - private final SignatureCache signatureCache; private final List logs; private final ActivationConfig.ForBlock activations; - public BridgeEventLoggerImpl(BridgeConstants bridgeConstants, ActivationConfig.ForBlock activations, List logs, SignatureCache signatureCache) { + public BridgeEventLoggerImpl(BridgeConstants bridgeConstants, ActivationConfig.ForBlock activations, List logs) { this.activations = activations; this.bridgeConstants = bridgeConstants; - this.signatureCache = signatureCache; this.logs = logs; } @Override - public void logUpdateCollections(Transaction rskTx) { + public void logUpdateCollections(RskAddress sender) { CallTransaction.Function event = BridgeEvents.UPDATE_COLLECTIONS.getEvent(); byte[][] encodedTopicsSerialized = event.encodeEventTopics(); List encodedTopics = getEncodedTopics(encodedTopicsSerialized); - String rskTxSenderAddress = rskTx.getSender(signatureCache).toString(); - byte[] encodedData = event.encodeEventData(rskTxSenderAddress); + byte[] encodedData = event.encodeEventData(sender.toHexString()); addLog(encodedTopics, encodedData); } diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java index 7f3b04ab9db..d151ca3974e 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java @@ -19,6 +19,7 @@ package co.rsk.peg.utils; import co.rsk.bitcoinj.core.*; +import co.rsk.core.RskAddress; import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.Bridge; import co.rsk.peg.DeprecatedMethodCallException; @@ -53,18 +54,16 @@ public class BrigeEventLoggerLegacyImpl implements BridgeEventLogger { private final BridgeConstants bridgeConstants; private final ActivationConfig.ForBlock activations; - private final SignatureCache signatureCache; private final List logs; - public BrigeEventLoggerLegacyImpl(BridgeConstants bridgeConstants, ActivationConfig.ForBlock activations, List logs, SignatureCache signatureCache) { + public BrigeEventLoggerLegacyImpl(BridgeConstants bridgeConstants, ActivationConfig.ForBlock activations, List logs) { this.bridgeConstants = bridgeConstants; this.activations = activations; - this.signatureCache = signatureCache; this.logs = logs; } @Override - public void logUpdateCollections(Transaction rskTx) { + public void logUpdateCollections(RskAddress sender) { if (activations.isActive(ConsensusRule.RSKIP146)) { throw new DeprecatedMethodCallException( "Calling BrigeEventLoggerLegacyImpl.logUpdateCollections method after RSKIP146 activation" @@ -73,7 +72,7 @@ public void logUpdateCollections(Transaction rskTx) { this.logs.add( new LogInfo(BRIDGE_CONTRACT_ADDRESS, Collections.singletonList(Bridge.UPDATE_COLLECTIONS_TOPIC), - RLP.encodeElement(rskTx.getSender(signatureCache).getBytes()) + RLP.encodeElement(sender.getBytes()) ) ); } diff --git a/rskj-core/src/test/java/co/rsk/RskTestUtils.java b/rskj-core/src/test/java/co/rsk/RskTestUtils.java index c383f54b171..eb1db7f0299 100644 --- a/rskj-core/src/test/java/co/rsk/RskTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/RskTestUtils.java @@ -6,7 +6,6 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; public class RskTestUtils { @@ -18,11 +17,14 @@ public static Keccak256 createHash(int nHash) { return new Keccak256(bytes); } + public static ECKey getEcKeyFromSeed(String seed) { + byte[] seedHash = HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8)); + return ECKey.fromPrivate(seedHash); + } + public static List getEcKeysFromSeeds(String[] seeds) { return Arrays.stream(seeds) - .map(seed -> seed.getBytes(StandardCharsets.UTF_8)) - .map(HashUtil::keccak256) - .map(ECKey::fromPrivate) - .collect(Collectors.toList()); + .map(RskTestUtils::getEcKeyFromSeed) + .toList(); } } diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java index ff78acb4576..58995a0843d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java @@ -1,67 +1,62 @@ package co.rsk.peg; +import static co.rsk.peg.BridgeSupportTestUtil.createRepository; +import static co.rsk.peg.PegTestUtils.createBaseInputScriptThatSpendsFromTheFederation; +import static co.rsk.peg.PegTestUtils.createHash3; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + import co.rsk.RskTestUtils; +import co.rsk.bitcoinj.core.*; +import co.rsk.bitcoinj.script.Script; +import co.rsk.bitcoinj.script.ScriptChunk; +import co.rsk.core.RskAddress; +import co.rsk.crypto.Keccak256; import co.rsk.peg.bitcoin.BitcoinTestUtils; +import co.rsk.peg.btcLockSender.BtcLockSenderProvider; +import co.rsk.peg.constants.*; +import co.rsk.peg.federation.*; import co.rsk.peg.federation.constants.FederationConstants; import co.rsk.peg.feeperkb.FeePerKbSupport; import co.rsk.peg.lockingcap.LockingCapSupport; +import co.rsk.peg.pegininstructions.PeginInstructionsProvider; +import co.rsk.peg.storage.BridgeStorageAccessorImpl; +import co.rsk.peg.utils.*; import co.rsk.peg.whitelist.WhitelistSupport; -import java.time.Instant; +import co.rsk.test.builders.BridgeSupportBuilder; import java.math.BigInteger; +import java.time.Instant; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; - -import co.rsk.peg.constants.BridgeConstants; -import co.rsk.peg.constants.BridgeMainNetConstants; -import co.rsk.peg.federation.*; -import co.rsk.peg.storage.BridgeStorageAccessorImpl; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.util.encoders.Hex; import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.CallTransaction; +import org.ethereum.core.Repository; import org.ethereum.crypto.ECKey; import org.ethereum.util.RLP; import org.ethereum.util.RLPList; import org.ethereum.vm.LogInfo; import org.ethereum.vm.PrecompiledContracts; - -import co.rsk.bitcoinj.core.*; -import co.rsk.bitcoinj.script.Script; -import co.rsk.bitcoinj.script.ScriptChunk; -import co.rsk.peg.constants.BridgeRegTestConstants; -import co.rsk.crypto.Keccak256; -import co.rsk.peg.btcLockSender.BtcLockSenderProvider; -import co.rsk.peg.pegininstructions.PeginInstructionsProvider; -import co.rsk.core.RskAddress; -import co.rsk.peg.utils.*; -import co.rsk.test.builders.BridgeSupportBuilder; - -import org.bouncycastle.util.encoders.Hex; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.crypto.ec.CustomNamedCurves; -import org.bouncycastle.crypto.params.ECDomainParameters; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; - -import static co.rsk.peg.BridgeSupportTestUtil.createRepository; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import static co.rsk.peg.PegTestUtils.*; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import static org.hamcrest.collection.IsEmptyCollection.empty; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNot.not; -import static org.mockito.Mockito.*; - class BridgeSupportAddSignatureTest { private static final RskAddress bridgeAddress = PrecompiledContracts.BRIDGE_ADDR; @@ -75,7 +70,7 @@ class BridgeSupportAddSignatureTest { "0362634ab57dae9cb373a5d536e66a8c4f67468bbcfb063809bab643072d78a124", "03c5946b3fbae03a654237da863c9ed534e0878657175b132b8ca630f245df04db", "02cd53fc53a07f211641a677d250f6de99caf620e8e77071e811a28b3bcddf0be1" - ).map(hex -> BtcECKey.fromPublicOnly(Hex.decode(hex))).collect(Collectors.toList()); + ).map(hex -> BtcECKey.fromPublicOnly(Hex.decode(hex))).toList(); private final NetworkParameters btcRegTestParams = bridgeRegTestConstants.getBtcParams(); private final Instant creationTime = Instant.ofEpochMilli(1000L); private final long creationBlockNumber = 0L; @@ -549,11 +544,9 @@ void addSignatureMultipleInputsPartiallyValid() throws Exception { provider.getPegoutsWaitingForSignatures().put(keccak256, t); provider.save(); - SignatureCache signatureCache = new ReceivedTxSignatureCache(); - ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); List logs = new ArrayList<>(); - BridgeEventLogger eventLogger = new BrigeEventLoggerLegacyImpl(bridgeRegTestConstants, activations, logs, signatureCache); + BridgeEventLogger eventLogger = new BrigeEventLoggerLegacyImpl(bridgeRegTestConstants, activations, logs); FederationSupport federationSupport = createDefaultFederationSupport(bridgeRegTestConstants.getFederationConstants()); @@ -685,8 +678,7 @@ void addSignature(ActivationConfig.ForBlock activations, FederationMember federa FederationSupport federationSupport = createFederationSupport(bridgeMainNetConstants.getFederationConstants(), federationStorageProvider, mock(Block.class), activations); LinkedList eventLogs = new LinkedList<>(); - BlockTxSignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); - BridgeEventLogger eventLogger = new BridgeEventLoggerImpl(bridgeMainNetConstants, activations, eventLogs, signatureCache); + BridgeEventLogger eventLogger = new BridgeEventLoggerImpl(bridgeMainNetConstants, activations, eventLogs); // Build prev btc tx BtcTransaction prevTx = new BtcTransaction(btcParams); @@ -793,12 +785,10 @@ private void addSignatureFromValidFederator(List privateKeysToSignWith provider.save(); track.commit(); - SignatureCache signatureCache = new ReceivedTxSignatureCache(); - track = repository.startTracking(); ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); List logs = new ArrayList<>(); - BridgeEventLogger eventLogger = new BrigeEventLoggerLegacyImpl(bridgeRegTestConstants, activations, logs, signatureCache); + BridgeEventLogger eventLogger = new BrigeEventLoggerLegacyImpl(bridgeRegTestConstants, activations, logs); BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( track, bridgeAddress, diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java index c78d444191c..2842c77f568 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import co.rsk.RskTestUtils; import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.crypto.TransactionSignature; import co.rsk.bitcoinj.script.Script; @@ -446,11 +447,13 @@ void callUpdateCollectionsGenerateEventLog() throws IOException { .chainId(Constants.REGTEST_CHAIN_ID) .value(BigIntegers.asUnsignedByteArray(DUST_AMOUNT)) .build(); + ECKey key = new ECKey(); + RskAddress sender = new RskAddress(key.getAddress()); tx.sign(key.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); - verify(eventLogger, times(1)).logUpdateCollections(tx); + verify(eventLogger).logUpdateCollections(sender); } @Test @@ -763,6 +766,9 @@ void minimumProcessFundsMigrationValue() throws IOException { .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); + Repository repository = createRepository(); Repository track = repository.startTracking(); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportProcessFundsMigrationTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportProcessFundsMigrationTest.java index dd4bdbd38f3..17ebd166a15 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportProcessFundsMigrationTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportProcessFundsMigrationTest.java @@ -1,5 +1,6 @@ package co.rsk.peg; +import co.rsk.RskTestUtils; import co.rsk.bitcoinj.core.BtcTransaction; import co.rsk.bitcoinj.core.Coin; import co.rsk.bitcoinj.core.UTXO; @@ -19,6 +20,7 @@ import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.Transaction; +import org.ethereum.crypto.ECKey; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -183,6 +185,8 @@ void test_processFundsMigration( when(federationStorageProvider.getOldFederationBtcUTXOs()).thenReturn(sufficientUTXOsForMigration); Transaction updateCollectionsTx = buildUpdateCollectionsTransaction(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + updateCollectionsTx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(updateCollectionsTx); assertEquals(activations.isActive(ConsensusRule.RSKIP146)? 0 : 1, provider.getPegoutsWaitingForConfirmations().getEntriesWithoutHash().size()); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportReleaseBtcTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportReleaseBtcTest.java index 6628e71992d..f248d40752f 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportReleaseBtcTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportReleaseBtcTest.java @@ -17,6 +17,7 @@ */ package co.rsk.peg; +import co.rsk.RskTestUtils; import co.rsk.bitcoinj.core.*; import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.constants.BridgeRegTestConstants; @@ -231,7 +232,7 @@ void handmade_release_after_rskip_146() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP185)).thenReturn(false); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); bridgeSupport.releaseBtc(releaseTx); @@ -257,7 +258,7 @@ void handmade_release_after_rskip_146_185() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP326)).thenReturn(false); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); bridgeSupport.releaseBtc(releaseTx); @@ -294,7 +295,7 @@ void handmade_release_after_rskip_146_185_326() throws IOException { List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); bridgeSupport.releaseBtc(releaseTx); @@ -331,7 +332,7 @@ void handmade_release_after_rskip_146_rejected_lowAmount() throws IOException { List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); releaseTx = buildReleaseRskTx(Coin.ZERO); @@ -358,7 +359,7 @@ void handmade_release_after_rskip_146_185_rejected_lowAmount() throws IOExceptio when(activationMock.isActive(ConsensusRule.RSKIP185)).thenReturn(true); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); releaseTx = buildReleaseRskTx(Coin.ZERO); @@ -391,7 +392,7 @@ void handmade_release_after_rskip_146_185_rejected_contractCaller() throws IOExc List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); releaseTx = buildReleaseRskTx_fromContract(Coin.COIN); @@ -422,7 +423,7 @@ void handmade_release_after_rskip_146_rejected_contractCaller() throws IOExcepti when(activationMock.isActive(ConsensusRule.RSKIP185)).thenReturn(false); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache); + BridgeEventLoggerImpl bridgeEventLogger = new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); releaseTx = buildReleaseRskTx_fromContract(Coin.COIN); @@ -441,7 +442,7 @@ void release_after_rskip_219() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP219)).thenReturn(true); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); // Get a value between old and new minimum pegout values @@ -475,7 +476,7 @@ void release_after_rskip_219_326() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP326)).thenReturn(true); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); // Get a value between old and new minimum pegout values @@ -508,7 +509,7 @@ void release_before_rskip_219() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP219)).thenReturn(false); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); // Get a value between old and new minimum pegout values @@ -535,7 +536,7 @@ void release_before_rskip_219_minimum_exclusive() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP219)).thenReturn(false); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); // Get a value exactly to legacy minimum @@ -559,7 +560,7 @@ void release_after_rskip_219_minimum_inclusive() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP219)).thenReturn(true); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); // Get a value exactly to current minimum @@ -590,7 +591,7 @@ void release_after_rskip_219_326_minimum_inclusive() throws IOException { when(activationMock.isActive(ConsensusRule.RSKIP326)).thenReturn(true); List logInfo = new ArrayList<>(); - BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(bridgeEventLogger, activationMock); // Get a value exactly to current minimum @@ -1172,7 +1173,7 @@ private void testPegoutMinimumWithFeeVerificationPass(Coin feePerKB, Coin value) when(activationMock.isActive(ConsensusRule.RSKIP219)).thenReturn(true); List logInfo = new ArrayList<>(); - eventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + eventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(eventLogger, activationMock); when(feePerKbSupport.getFeePerKb()).thenReturn(feePerKB); @@ -1206,7 +1207,7 @@ private void testPegoutMinimumWithFeeVerificationRejectedByLowAmount(Coin feePer RskAddress bridgeAddress = PrecompiledContracts.BRIDGE_ADDR; List logInfo = new ArrayList<>(); - eventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + eventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(eventLogger, activationMock); when(feePerKbSupport.getFeePerKb()).thenReturn(feePerKB); @@ -1247,7 +1248,7 @@ private void testPegoutMinimumWithFeeVerificationRejectedByFeeAboveValue(Coin fe RskAddress bridgeAddress = PrecompiledContracts.BRIDGE_ADDR; List logInfo = new ArrayList<>(); - eventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo, signatureCache)); + eventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activationMock, logInfo)); bridgeSupport = initBridgeSupport(eventLogger, activationMock); when(feePerKbSupport.getFeePerKb()).thenReturn(feePerKB); @@ -1327,8 +1328,7 @@ private Transaction buildReleaseRskTx_fromContract(Coin coin) { } private Transaction buildUpdateTx() { - return Transaction - .builder() + Transaction updateCollectionsTx = Transaction.builder() .nonce(NONCE) .gasPrice(GAS_PRICE) .gasLimit(GAS_LIMIT) @@ -1337,6 +1337,11 @@ private Transaction buildUpdateTx() { .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + updateCollectionsTx.sign(senderKey.getPrivKeyBytes()); + + return updateCollectionsTx; } private BridgeSupport initBridgeSupport(BridgeEventLogger eventLogger, ActivationConfig.ForBlock activationMock) { 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 76bb38d81a5..bbce67d2ada 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportSvpTest.java @@ -117,12 +117,10 @@ class SvpFundTxCreationAndProcessingTests { @BeforeEach void setUp() { logs = new ArrayList<>(); - SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); BridgeEventLogger bridgeEventLogger = new BridgeEventLoggerImpl( bridgeMainNetConstants, allActivations, - logs, - signatureCache + logs ); bridgeSupport = bridgeSupportBuilder @@ -516,12 +514,10 @@ class SvpSpendTxCreationAndProcessingTests { @BeforeEach void setUp() { logs = new ArrayList<>(); - SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); BridgeEventLogger bridgeEventLogger = new BridgeEventLoggerImpl( bridgeMainNetConstants, allActivations, - logs, - signatureCache + logs ); bridgeSupport = bridgeSupportBuilder diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java index edcb52e2eae..e4888860b5d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -18,6 +18,7 @@ */ package co.rsk.peg; +import co.rsk.RskTestUtils; import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.Script; import co.rsk.bitcoinj.script.ScriptBuilder; @@ -1845,6 +1846,9 @@ void callProcessFundsMigration_is_migrating_before_rskip_146_activation() throws .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); + federationSupport = federationSupportBuilder .withFederationConstants(federationConstantsRegtest) .withFederationStorageProvider(federationStorageProviderMock) @@ -1991,6 +1995,8 @@ void callProcessFundsMigration_is_migrated_before_rskip_146_activation() throws .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); federationSupport = federationSupportBuilder .withFederationConstants(federationConstantsRegtest) @@ -2062,6 +2068,9 @@ void callProcessFundsMigration_is_migrated_after_rskip_146_activation() throws I .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); + federationSupport = federationSupportBuilder .withFederationConstants(federationConstantsRegtest) .withFederationStorageProvider(federationStorageProviderMock) @@ -2142,6 +2151,9 @@ void updateFederationCreationBlockHeights_before_rskip_186_activation() throws I .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); + federationSupport = federationSupportBuilder .withFederationConstants(federationConstantsRegtest) .withFederationStorageProvider(federationStorageProviderMock) @@ -2229,6 +2241,9 @@ void updateFederationCreationBlockHeights_after_rskip_186_activation() throws IO .withFeePerKbSupport(feePerKbSupport) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); + List sufficientUTXOsForMigration1 = new ArrayList<>(); sufficientUTXOsForMigration1.add(createUTXO(Coin.COIN, oldFederation.getAddress())); when(federationStorageProviderMock.getOldFederationBtcUTXOs()) @@ -2294,6 +2309,8 @@ void rskTxWaitingForSignature_uses_updateCollection_rskTxHash_before_rskip_146_a .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); assertEquals(btcTx, provider.getPegoutsWaitingForSignatures().get(tx.getHash())); @@ -2338,15 +2355,17 @@ void rskTxWaitingForSignature_postRSKIP326_emitNewPegoutConfirmedEvent() throws .build(); Transaction tx = Transaction - .builder() - .nonce(NONCE) - .gasPrice(GAS_PRICE) - .gasLimit(GAS_LIMIT) - .destination(Hex.decode(TO_ADDRESS)) - .data(Hex.decode(DATA)) - .chainId(Constants.REGTEST_CHAIN_ID) - .value(DUST_AMOUNT) - .build(); + .builder() + .nonce(NONCE) + .gasPrice(GAS_PRICE) + .gasLimit(GAS_LIMIT) + .destination(Hex.decode(TO_ADDRESS)) + .data(Hex.decode(DATA)) + .chainId(Constants.REGTEST_CHAIN_ID) + .value(DUST_AMOUNT) + .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); verify(eventLogger, times(1)).logPegoutConfirmed(btcTx.getHash(), rskBlockNumber); @@ -2391,15 +2410,17 @@ void rskTxWaitingForSignature_preRSKIP326_noPegoutConfirmedEventEmitted() throws .build(); Transaction tx = Transaction - .builder() - .nonce(NONCE) - .gasPrice(GAS_PRICE) - .gasLimit(GAS_LIMIT) - .destination(Hex.decode(TO_ADDRESS)) - .data(Hex.decode(DATA)) - .chainId(Constants.REGTEST_CHAIN_ID) - .value(DUST_AMOUNT) - .build(); + .builder() + .nonce(NONCE) + .gasPrice(GAS_PRICE) + .gasLimit(GAS_LIMIT) + .destination(Hex.decode(TO_ADDRESS)) + .data(Hex.decode(DATA)) + .chainId(Constants.REGTEST_CHAIN_ID) + .value(DUST_AMOUNT) + .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); verify(eventLogger, times(0)).logPegoutConfirmed(btcTx.getHash(), rskBlockNumber); @@ -2441,15 +2462,17 @@ void rskTxWaitingForSignature_postRSKIP326NoTxWithEnoughConfirmation_pegoutConfi .build(); Transaction tx = Transaction - .builder() - .nonce(NONCE) - .gasPrice(GAS_PRICE) - .gasLimit(GAS_LIMIT) - .destination(Hex.decode(TO_ADDRESS)) - .data(Hex.decode(DATA)) - .chainId(Constants.REGTEST_CHAIN_ID) - .value(DUST_AMOUNT) - .build(); + .builder() + .nonce(NONCE) + .gasPrice(GAS_PRICE) + .gasLimit(GAS_LIMIT) + .destination(Hex.decode(TO_ADDRESS)) + .data(Hex.decode(DATA)) + .chainId(Constants.REGTEST_CHAIN_ID) + .value(DUST_AMOUNT) + .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); verify(eventLogger, times(0)).logPegoutConfirmed(btcTx.getHash(), 1L); @@ -2498,6 +2521,8 @@ void rskTxWaitingForSignature_uses_updateCollection_rskTxHash_after_rskip_146_ac .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); assertEquals(btcTx, provider.getPegoutsWaitingForSignatures().get(tx.getHash())); @@ -2548,6 +2573,8 @@ void rskTxWaitingForSignature_uses_release_transaction_rstTxHash_after_rskip_146 .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); assertEquals(btcTx, provider.getPegoutsWaitingForSignatures().get(rskTxHash)); @@ -2599,6 +2626,8 @@ void rskTxWaitingForSignature_uses_updateCollection_rskTxHash_after_rskip_176_ac .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); assertEquals(btcTx, provider.getPegoutsWaitingForSignatures().get(tx.getHash())); @@ -2683,6 +2712,8 @@ void rskTxWaitingForSignature_fail_adding_an_already_existing_key_after_rskip_37 .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); // Assert two transactions are added to pegoutsWaitingForConfirmations, one pegout batch and one migration tx @@ -2735,6 +2766,7 @@ void rskTxWaitingForSignature_fail_adding_an_already_existing_key_after_rskip_37 .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); // Get the transaction that was confirmed and the one that stayed unconfirmed @@ -2770,6 +2802,7 @@ void rskTxWaitingForSignature_fail_adding_an_already_existing_key_after_rskip_37 .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + throwsExceptionTx.sign(senderKey.getPrivKeyBytes()); assertThrows(IllegalStateException.class, () -> bridgeSupportForFailingTx.updateCollections(throwsExceptionTx)); @@ -2802,6 +2835,7 @@ void rskTxWaitingForSignature_fail_adding_an_already_existing_key_after_rskip_37 .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); @@ -2854,6 +2888,8 @@ void rskTxWaitingForSignature_uses_pegoutCreation_rskTxHash_after_rskip_375_acti .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); bridgeSupport.updateCollections(tx); assertNull(provider.getPegoutsWaitingForSignatures().get(tx.getHash())); @@ -2905,6 +2941,9 @@ void rskTxWaitingForSignature_override_entry_is_allowed_before_rskip_375_activat .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); + TreeMap txsWaitingForSignatures = new TreeMap<>(); BtcTransaction existingBtcTxEntryValue = mock(BtcTransaction.class); txsWaitingForSignatures.put(tx.getHash(), existingBtcTxEntryValue); @@ -2971,6 +3010,8 @@ void rskTxWaitingForSignature_override_entry_attempt_after_rskip_375_activation( .chainId(Constants.REGTEST_CHAIN_ID) .value(DUST_AMOUNT) .build(); + ECKey senderKey = RskTestUtils.getEcKeyFromSeed("sender"); + tx.sign(senderKey.getPrivKeyBytes()); assertThrows(IllegalStateException.class, () -> bridgeSupport.updateCollections(tx)); } diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/VoteFederationChangeTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/VoteFederationChangeTest.java index 9d97cf1f48c..610720b6bb1 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/VoteFederationChangeTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/VoteFederationChangeTest.java @@ -64,7 +64,7 @@ void setUp() { activations = ActivationConfigsForTest.all().forBlock(0L); logs = new ArrayList<>(); - bridgeEventLogger = new BridgeEventLoggerImpl(BridgeMainNetConstants.getInstance(), activations, logs, signatureCache); + bridgeEventLogger = new BridgeEventLoggerImpl(BridgeMainNetConstants.getInstance(), activations, logs); StorageAccessor inMemoryStorageAccessor = new InMemoryStorage(); storageProvider = new FederationStorageProviderImpl(inMemoryStorageAccessor); @@ -360,7 +360,7 @@ void voteCommitFederation_commitFederationWithOver10Members_throwsException() { void voteCommitFederation_preRSKIP186_whenPendingFederationIsSet_shouldPerformLegacyCommitFederationActionsButNotSetFederationChangeInfo() { // arrange activations = ActivationConfigsForTest.papyrus200().forBlock(0L); - bridgeEventLogger = new BridgeEventLoggerImpl(BridgeMainNetConstants.getInstance(), activations, logs, signatureCache); + bridgeEventLogger = new BridgeEventLoggerImpl(BridgeMainNetConstants.getInstance(), activations, logs); federationSupport = federationSupportBuilder .withFederationConstants(federationMainnetConstants) .withFederationStorageProvider(storageProvider) @@ -393,7 +393,7 @@ void voteCommitFederation_preRSKIP186_whenPendingFederationIsSet_shouldPerformLe void voteCommitFederation_postRSKIP186_preRSKIP419_whenPendingFederationIsSet_shouldPerformLegacyCommitFederationActions() { // arrange activations = ActivationConfigsForTest.arrowhead631().forBlock(0L); - bridgeEventLogger = new BridgeEventLoggerImpl(BridgeMainNetConstants.getInstance(), activations, logs, signatureCache); + bridgeEventLogger = new BridgeEventLoggerImpl(BridgeMainNetConstants.getInstance(), activations, logs); federationSupport = federationSupportBuilder .withFederationConstants(federationMainnetConstants) .withFederationStorageProvider(storageProvider) diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index bff03605651..f8aa1ea2243 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -20,7 +20,6 @@ import static co.rsk.peg.bitcoin.BitcoinTestUtils.coinListOf; import static co.rsk.peg.bitcoin.BitcoinTestUtils.flatKeysAsByteArray; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -39,7 +38,6 @@ import java.math.BigInteger; import java.time.Instant; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.bouncycastle.util.encoders.Hex; import org.ethereum.config.blockchain.upgrades.*; @@ -67,15 +65,13 @@ class BridgeEventLoggerImplTest { private List eventLogs; private BridgeEventLogger eventLogger; - private SignatureCache signatureCache; @BeforeEach void setup() { ActivationConfig.ForBlock activations = ActivationConfigsForTest.all().forBlock(0L); - signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); eventLogs = new LinkedList<>(); - eventLogger = new BridgeEventLoggerImpl(BRIDGE_CONSTANTS, activations, eventLogs, signatureCache); + eventLogger = new BridgeEventLoggerImpl(BRIDGE_CONSTANTS, activations, eventLogs); } @Test @@ -151,12 +147,8 @@ void logPeginBtc() { @Test void logUpdateCollections() { - // Setup Rsk transaction - Transaction tx = mock(Transaction.class); - when(tx.getSender(any(SignatureCache.class))).thenReturn(RSK_ADDRESS); - // Act - eventLogger.logUpdateCollections(tx); + eventLogger.logUpdateCollections(RSK_ADDRESS); commonAssertLogs(eventLogs); assertTopics(1, eventLogs); @@ -165,7 +157,7 @@ void logUpdateCollections() { 0, BridgeEvents.UPDATE_COLLECTIONS.getEvent(), new Object[]{}, - new Object[]{tx.getSender(signatureCache).toString()} + new Object[]{RSK_ADDRESS.toHexString()} ); } @@ -207,8 +199,7 @@ void logAddSignature(ActivationConfig.ForBlock activations, FederationMember fed BridgeEventLogger bridgeEventLogger = new BridgeEventLoggerImpl( BRIDGE_CONSTANTS, activations, - eventLogs, - signatureCache + eventLogs ); BtcECKey federatorBtcPubKey = federationMember.getBtcPublicKey(); @@ -248,7 +239,7 @@ void logCommitFederation(boolean isRSKIP383Active) { ActivationConfig.ForBlock hopActivations = ActivationConfigsForTest.hop400().forBlock(0); ActivationConfig.ForBlock allActivations = ActivationConfigsForTest.all().forBlock(0L); if (!isRSKIP383Active) { - eventLogger = new BridgeEventLoggerImpl(BRIDGE_CONSTANTS, hopActivations, eventLogs, signatureCache); + eventLogger = new BridgeEventLoggerImpl(BRIDGE_CONSTANTS, hopActivations, eventLogs); } long federationActivationAgePreRskip383 = FEDERATION_CONSTANTS.getFederationActivationAge(hopActivations); @@ -413,7 +404,7 @@ void logUnrefundablePegin() { @Test void testLogReleaseBtcRequestReceivedBeforeRSKIP326HardFork() { ActivationConfig.ForBlock hopActivations = ActivationConfigsForTest.hop400().forBlock(0); - eventLogger = new BridgeEventLoggerImpl(BRIDGE_CONSTANTS, hopActivations, eventLogs, signatureCache); + eventLogger = new BridgeEventLoggerImpl(BRIDGE_CONSTANTS, hopActivations, eventLogs); Address btcRecipientAddress = new Address( NETWORK_PARAMETERS, @@ -613,7 +604,7 @@ private void commonAssertLogs(List logs) { private byte[] serializeRskTxHashes(List rskTxHashes) { List rskTxHashesList = rskTxHashes.stream() .map(Keccak256::getBytes) - .collect(Collectors.toList()); + .toList(); int rskTxHashesLength = rskTxHashesList.stream().mapToInt(key -> key.length).sum(); byte[] serializedRskTxHashes = new byte[rskTxHashesLength]; diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java index fa0e9d175ae..31c8cf8024b 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java @@ -73,7 +73,7 @@ void setup() { activations = mock(ActivationConfig.ForBlock.class); eventLogs = new LinkedList<>(); constantsMock = mock(BridgeConstants.class); - eventLogger = new BrigeEventLoggerLegacyImpl(constantsMock, activations, eventLogs, new BlockTxSignatureCache(new ReceivedTxSignatureCache())); + eventLogger = new BrigeEventLoggerLegacyImpl(constantsMock, activations, eventLogs); btcTxMock = mock(BtcTransaction.class); rskTxHash = PegTestUtils.createHash3(1); } @@ -83,13 +83,10 @@ void testLogUpdateCollectionsBeforeRskip146() { when(activations.isActive(ConsensusRule.RSKIP146)).thenReturn(false); // Setup Rsk transaction - Transaction tx = mock(Transaction.class); - RskAddress sender = mock(RskAddress.class); - when(sender.toString()).thenReturn("0x0000000000000000000000000000000000000001"); - when(tx.getSender(any(SignatureCache.class))).thenReturn(sender); + RskAddress sender = new RskAddress("0000000000000000000000000000000000001001"); // Act - eventLogger.logUpdateCollections(tx); + eventLogger.logUpdateCollections(sender); commonAssertLogs(eventLogs); assertTopics(1, eventLogs); @@ -101,15 +98,15 @@ void testLogUpdateCollectionsBeforeRskip146() { } // Assert log data - byte[] encodedData = RLP.encodeElement(tx.getSender(new BlockTxSignatureCache(new ReceivedTxSignatureCache())).getBytes()); + byte[] encodedData = RLP.encodeElement(sender.getBytes()); Assertions.assertArrayEquals(encodedData, logResult.getData()); } @Test void testLogUpdateCollectionsAfterRskip146() { when(activations.isActive(ConsensusRule.RSKIP146)).thenReturn(true); - Transaction anyTx = any(); - assertThrows(DeprecatedMethodCallException.class, () -> eventLogger.logUpdateCollections(anyTx)); + + assertThrows(DeprecatedMethodCallException.class, () -> eventLogger.logUpdateCollections(any(RskAddress.class))); } @Test From fd62cb842882d0e270ef73a62ad06a4092b00182 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Fri, 27 Sep 2024 15:36:16 -0300 Subject: [PATCH 13/44] Remove unnecessary (since we are unit-testing) arguments from assertion methods Reuse already declared variable Remove unused import --- .../peg/utils/BridgeEventLoggerImplTest.java | 226 +++++++++--------- 1 file changed, 114 insertions(+), 112 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index f8aa1ea2243..c40153cbf33 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -21,7 +21,6 @@ import static co.rsk.peg.bitcoin.BitcoinTestUtils.flatKeysAsByteArray; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import co.rsk.RskTestUtils; import co.rsk.bitcoinj.core.*; @@ -44,6 +43,7 @@ import org.ethereum.core.*; import org.ethereum.core.CallTransaction.Function; import org.ethereum.crypto.ECKey; +import org.ethereum.vm.DataWord; import org.ethereum.vm.LogInfo; import org.ethereum.vm.PrecompiledContracts; import org.junit.jupiter.api.*; @@ -56,6 +56,8 @@ * @author martin.medina */ class BridgeEventLoggerImplTest { + private static final RskAddress BRIDGE_ADDRESS = PrecompiledContracts.BRIDGE_ADDR; + private static final byte[] BRIDGE_ADDRESS_SERIALIZED = BRIDGE_ADDRESS.getBytes(); private static final BridgeConstants BRIDGE_CONSTANTS = BridgeMainNetConstants.getInstance(); private static final FederationConstants FEDERATION_CONSTANTS = BRIDGE_CONSTANTS.getFederationConstants(); private static final NetworkParameters NETWORK_PARAMETERS = BRIDGE_CONSTANTS.getBtcParams(); @@ -82,11 +84,9 @@ void logLockBtc() { // Act eventLogger.logLockBtc(RSK_ADDRESS, BTC_TRANSACTION, senderAddress, amount); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.LOCK_BTC.getEvent(), new Object[]{RSK_ADDRESS.toString()}, new Object[]{BTC_TRANSACTION.getHash().getBytes(), senderAddress.toString(), amount.getValue()} @@ -105,11 +105,9 @@ void logLockBtc_with_segwit_address() { // Act eventLogger.logLockBtc(RSK_ADDRESS, BTC_TRANSACTION, senderAddress, amount); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.LOCK_BTC.getEvent(), new Object[]{RSK_ADDRESS.toString()}, new Object[]{BTC_TRANSACTION.getHash().getBytes(), "3L4zu4GWVVSfAWCPbP3RqkJUvxpiiQebPX", amount.getValue()} @@ -131,7 +129,7 @@ void logPeginBtc() { CallTransaction.Function event = BridgeEvents.PEGIN_BTC.getEvent(); // Assert address that made the log - assertEquals(PrecompiledContracts.BRIDGE_ADDR, new RskAddress(logResult.getAddress())); + assertEquals(BRIDGE_ADDRESS, new RskAddress(logResult.getAddress())); // Assert log topics assertEquals(3, logResult.getTopics().size()); @@ -150,11 +148,9 @@ void logUpdateCollections() { // Act eventLogger.logUpdateCollections(RSK_ADDRESS); - commonAssertLogs(eventLogs); - assertTopics(1, eventLogs); + commonAssertLogs(); + assertTopics(1); assertEvent( - eventLogs, - 0, BridgeEvents.UPDATE_COLLECTIONS.getEvent(), new Object[]{}, new Object[]{RSK_ADDRESS.toHexString()} @@ -207,14 +203,14 @@ void logAddSignature(ActivationConfig.ForBlock activations, FederationMember fed bridgeEventLogger.logAddSignature(federationMember, BTC_TRANSACTION, RSK_TX_HASH.getBytes()); // Assert - commonAssertLogs(eventLogs); - assertTopics(3, eventLogs); + commonAssertLogs(); + assertTopics(3); CallTransaction.Function bridgeEvent = BridgeEvents.ADD_SIGNATURE.getEvent(); Object[] eventTopics = new Object[]{RSK_TX_HASH.getBytes(), expectedRskAddress.toString()}; Object[] eventParams = new Object[]{federatorBtcPubKey.getPubKey()}; - assertEvent(eventLogs, 0, bridgeEvent, eventTopics, eventParams); + assertEvent(bridgeEvent, eventTopics, eventParams); } @Test @@ -222,11 +218,9 @@ void logReleaseBtc() { // Act eventLogger.logReleaseBtc(BTC_TRANSACTION, RSK_TX_HASH.getBytes()); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.RELEASE_BTC.getEvent(), new Object[]{RSK_TX_HASH.getBytes()}, new Object[]{BTC_TRANSACTION.bitcoinSerialize()} @@ -246,48 +240,17 @@ void logCommitFederation(boolean isRSKIP383Active) { long federationActivationAgePostRskip383 = FEDERATION_CONSTANTS.getFederationActivationAge(allActivations); // Setup parameters for test method call - Instant creationTime = Instant.ofEpochMilli(15005L); - Block executionBlock = mock(Block.class); - when(executionBlock.getTimestamp()).thenReturn(15005L); - when(executionBlock.getNumber()).thenReturn(15L); - - List oldFederationKeys = Arrays.asList( - BtcECKey.fromPublicOnly(Hex.decode("036bb9eab797eadc8b697f0e82a01d01cabbfaaca37e5bafc06fdc6fdd38af894a")), - BtcECKey.fromPublicOnly(Hex.decode("031da807c71c2f303b7f409dd2605b297ac494a563be3b9ca5f52d95a43d183cc5")), - BtcECKey.fromPublicOnly(Hex.decode("025eefeeeed5cdc40822880c7db1d0a88b7b986945ed3fc05a0b45fe166fe85e12")), - BtcECKey.fromPublicOnly(Hex.decode("03c67ad63527012fd4776ae892b5dc8c56f80f1be002dc65cd520a2efb64e37b49")) - ); - - List oldFederationMembers = FederationTestUtils.getFederationMembersWithBtcKeys(oldFederationKeys); - NetworkParameters btcParams = BRIDGE_CONSTANTS.getBtcParams(); - FederationArgs oldFedArgs = new FederationArgs( - oldFederationMembers, - creationTime, - 15L, - btcParams - ); + Block rskExecutionBlock = arrangeRskExecutionBlock(); + long rskExecutionBlockNumber = rskExecutionBlock.getNumber(); - Federation oldFederation = FederationFactory.buildStandardMultiSigFederation(oldFedArgs); - - List newFederationKeys = Arrays.asList( - BtcECKey.fromPublicOnly(Hex.decode("0346cb6b905e4dee49a862eeb2288217d06afcd4ace4b5ca77ebedfbc6afc1c19d")), - BtcECKey.fromPublicOnly(Hex.decode("0269a0dbe7b8f84d1b399103c466fb20531a56b1ad3a7b44fe419e74aad8c46db7")), - BtcECKey.fromPublicOnly(Hex.decode("026192d8ab41bd402eb0431457f6756a3f3ce15c955c534d2b87f1e0372d8ba338")) - ); - List newFederationMembers = FederationTestUtils.getFederationMembersWithBtcKeys(newFederationKeys); - FederationArgs newFedArgs = new FederationArgs( - newFederationMembers, - creationTime, - 15L, - btcParams - ); - - Federation newFederation = FederationFactory.buildStandardMultiSigFederation(newFedArgs); + Instant creationTime = Instant.ofEpochMilli(rskExecutionBlockNumber); + Federation oldFederation = getOldFederation(creationTime); + Federation newFederation = getNewFederation(creationTime); // Act - eventLogger.logCommitFederation(executionBlock, oldFederation, newFederation); - commonAssertLogs(eventLogs); - assertTopics(1, eventLogs); + eventLogger.logCommitFederation(rskExecutionBlock, oldFederation, newFederation); + commonAssertLogs(); + assertTopics(1); // Assert log data byte[] oldFederationFlatPubKeys = flatKeysAsByteArray(oldFederation.getBtcPublicKeys()); @@ -295,8 +258,8 @@ void logCommitFederation(boolean isRSKIP383Active) { byte[] newFederationFlatPubKeys = flatKeysAsByteArray(newFederation.getBtcPublicKeys()); String newFederationBtcAddress = newFederation.getAddress().toBase58(); long newFedActivationBlockNumber = isRSKIP383Active ? - executionBlock.getNumber() + federationActivationAgePostRskip383 : - executionBlock.getNumber() + federationActivationAgePreRskip383; + rskExecutionBlockNumber + federationActivationAgePostRskip383 : + rskExecutionBlockNumber + federationActivationAgePreRskip383; Object[] data = new Object[]{ oldFederationFlatPubKeys, @@ -308,7 +271,7 @@ void logCommitFederation(boolean isRSKIP383Active) { CallTransaction.Function event = BridgeEvents.COMMIT_FEDERATION.getEvent(); Object[] topics = {}; - assertEvent(eventLogs, 0, event, topics, data); + assertEvent(event, topics, data); final LogInfo log = eventLogs.get(0); Object[] decodeEventData = event.decodeEventData(log.getData()); @@ -318,25 +281,69 @@ void logCommitFederation(boolean isRSKIP383Active) { // assert fed activation has different values before and after RSKIP383 respectively if (isRSKIP383Active){ - long legacyFedActivationBlockNumber = executionBlock.getNumber() + federationActivationAgePreRskip383; + long legacyFedActivationBlockNumber = rskExecutionBlockNumber + federationActivationAgePreRskip383; assertNotEquals(loggedFedActivationBlockNumber, legacyFedActivationBlockNumber); } else { - long defaultFedActivationBlockNumber = executionBlock.getNumber() + federationActivationAgePostRskip383; + long defaultFedActivationBlockNumber = rskExecutionBlockNumber + federationActivationAgePostRskip383; assertNotEquals(loggedFedActivationBlockNumber, defaultFedActivationBlockNumber); } } + private Federation getOldFederation(Instant creationTime) { + List oldFederationKeys = Arrays.asList( + BtcECKey.fromPublicOnly(Hex.decode("036bb9eab797eadc8b697f0e82a01d01cabbfaaca37e5bafc06fdc6fdd38af894a")), + BtcECKey.fromPublicOnly(Hex.decode("031da807c71c2f303b7f409dd2605b297ac494a563be3b9ca5f52d95a43d183cc5")), + BtcECKey.fromPublicOnly(Hex.decode("025eefeeeed5cdc40822880c7db1d0a88b7b986945ed3fc05a0b45fe166fe85e12")), + BtcECKey.fromPublicOnly(Hex.decode("03c67ad63527012fd4776ae892b5dc8c56f80f1be002dc65cd520a2efb64e37b49")) + ); + + return getFederationFromBtcKeys(oldFederationKeys, creationTime); + } + + private Federation getNewFederation(Instant creationTime) { + List newFederationKeys = Arrays.asList( + BtcECKey.fromPublicOnly(Hex.decode("0346cb6b905e4dee49a862eeb2288217d06afcd4ace4b5ca77ebedfbc6afc1c19d")), + BtcECKey.fromPublicOnly(Hex.decode("0269a0dbe7b8f84d1b399103c466fb20531a56b1ad3a7b44fe419e74aad8c46db7")), + BtcECKey.fromPublicOnly(Hex.decode("026192d8ab41bd402eb0431457f6756a3f3ce15c955c534d2b87f1e0372d8ba338")) + ); + + return getFederationFromBtcKeys(newFederationKeys, creationTime); + } + + private Federation getFederationFromBtcKeys(List federationKeys, Instant creationTime) { + NetworkParameters btcParams = BRIDGE_CONSTANTS.getBtcParams(); + + List federationMembers = FederationTestUtils.getFederationMembersWithBtcKeys(federationKeys); + FederationArgs federationArgs = new FederationArgs( + federationMembers, + creationTime, + 15L, + btcParams + ); + + return FederationFactory.buildStandardMultiSigFederation(federationArgs); + } + + private Block arrangeRskExecutionBlock() { + long rskExecutionBlockNumber = 15005L; + long rskExecutionBlockTimestamp = 15L; + BlockHeader blockHeader = new BlockHeaderBuilder(mock(ActivationConfig.class)) + .setNumber(rskExecutionBlockNumber) + .setTimestamp(rskExecutionBlockTimestamp) + .build(); + + return Block.createBlockFromHeader(blockHeader, true); + } + @Test void logReleaseBtcRequested() { Coin amount = Coin.SATOSHI; eventLogger.logReleaseBtcRequested(RSK_TX_HASH.getBytes(), BTC_TRANSACTION, amount); - commonAssertLogs(eventLogs); - assertTopics(3, eventLogs); + commonAssertLogs(); + assertTopics(3); assertEvent( - eventLogs, - 0, BridgeEvents.RELEASE_REQUESTED.getEvent(), new Object[]{RSK_TX_HASH.getBytes(), BTC_TRANSACTION.getHash().getBytes()}, new Object[]{amount.getValue()} @@ -350,11 +357,11 @@ void logRejectedPegin() { assertEquals(1, eventLogs.size()); LogInfo entry = eventLogs.get(0); - assertEquals(PrecompiledContracts.BRIDGE_ADDR, new RskAddress(entry.getAddress())); + assertEquals(BRIDGE_ADDRESS, new RskAddress(entry.getAddress())); // Assert address that made the log LogInfo result = eventLogs.get(0); - assertArrayEquals(PrecompiledContracts.BRIDGE_ADDR.getBytes(), result.getAddress()); + assertArrayEquals(BRIDGE_ADDRESS_SERIALIZED, result.getAddress()); // Assert log topics assertEquals(2, result.getTopics().size()); @@ -378,11 +385,11 @@ void logUnrefundablePegin() { assertEquals(1, eventLogs.size()); LogInfo entry = eventLogs.get(0); - assertEquals(PrecompiledContracts.BRIDGE_ADDR, new RskAddress(entry.getAddress())); + assertEquals(BRIDGE_ADDRESS, new RskAddress(entry.getAddress())); // Assert address that made the log LogInfo result = eventLogs.get(0); - assertArrayEquals(PrecompiledContracts.BRIDGE_ADDR.getBytes(), result.getAddress()); + assertArrayEquals(BRIDGE_ADDRESS_SERIALIZED, result.getAddress()); // Assert log topics assertEquals(2, result.getTopics().size()); @@ -415,11 +422,9 @@ void testLogReleaseBtcRequestReceivedBeforeRSKIP326HardFork() { eventLogger.logReleaseBtcRequestReceived(RSK_ADDRESS.toString(), btcRecipientAddress, amount); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.RELEASE_REQUEST_RECEIVED_LEGACY.getEvent(), new Object[]{RSK_ADDRESS.toString()}, new Object[]{btcRecipientAddress.getHash160(), amount.value} @@ -437,11 +442,9 @@ void testLogReleaseBtcRequestReceivedAfterRSKIP326HardFork() { eventLogger.logReleaseBtcRequestReceived(RSK_ADDRESS.toString(), btcRecipientAddress, amount); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.RELEASE_REQUEST_RECEIVED.getEvent(), new Object[]{RSK_ADDRESS.toString()}, new Object[]{btcRecipientAddress.toString(), amount.value} @@ -455,11 +458,9 @@ void testLogReleaseBtcRequestRejected() { eventLogger.logReleaseBtcRequestRejected(RSK_ADDRESS.toString(), amount, reason); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.RELEASE_REQUEST_REJECTED.getEvent(), new Object[]{RSK_ADDRESS.toString()}, new Object[]{amount.value, reason.getValue()} @@ -476,11 +477,9 @@ void logBatchPegoutCreated() { eventLogger.logBatchPegoutCreated(BTC_TRANSACTION.getHash(), rskTxHashes); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.BATCH_PEGOUT_CREATED.getEvent(), new Object[]{BTC_TRANSACTION.getHash().getBytes()}, new Object[]{serializeRskTxHashes(rskTxHashes)} @@ -507,11 +506,9 @@ void logBatchPegoutCreatedWithWitness() { BTC_TRANSACTION.setWitness(0, txWitness); eventLogger.logBatchPegoutCreated(BTC_TRANSACTION.getHash(true), rskTxHashes); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.BATCH_PEGOUT_CREATED.getEvent(), new Object[]{BTC_TRANSACTION.getHash(true).getBytes()}, new Object[]{serializeRskTxHashes(rskTxHashes)} @@ -523,11 +520,9 @@ void logPegoutConfirmed() { long pegoutCreationRskBlockNumber = 50; eventLogger.logPegoutConfirmed(BTC_TRANSACTION.getHash(), pegoutCreationRskBlockNumber); - commonAssertLogs(eventLogs); - assertTopics(2, eventLogs); + commonAssertLogs(); + assertTopics(2); assertEvent( - eventLogs, - 0, BridgeEvents.PEGOUT_CONFIRMED.getEvent(), new Object[]{BTC_TRANSACTION.getHash().getBytes()}, new Object[]{pegoutCreationRskBlockNumber} @@ -550,15 +545,14 @@ private static Stream logPegoutTransactionCreatedValidArgProvider() { @MethodSource("logPegoutTransactionCreatedValidArgProvider") void logPegoutTransactionCreated_ok(Sha256Hash btcTxHash, List outpointValues) { eventLogger.logPegoutTransactionCreated(btcTxHash, outpointValues); - commonAssertLogs(eventLogs); + commonAssertLogs(); - assertTopics(2, eventLogs); + assertTopics(2); - int index = 0; Function expectedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); Object[] topics = {btcTxHash.getBytes()}; Object[] params = {UtxoUtils.encodeOutpointValues(outpointValues)}; - assertEvent(eventLogs, index, expectedEvent, topics, params); + assertEvent(expectedEvent, topics, params); } private static Stream logPegoutTransactionCreatedInvalidArgProvider() { @@ -582,23 +576,31 @@ void logPegoutTransactionCreated_invalidBtcTxHashOrOutpointValues_shouldFail(Sha /********************************** * ------- UTILS ------- * *********************************/ - private static void assertEvent(List logs, int index, CallTransaction.Function event, Object[] topics, Object[] params) { - final LogInfo log = logs.get(index); - assertEquals(LogInfo.byteArrayToList(event.encodeEventTopics(topics)), log.getTopics()); - assertArrayEquals(event.encodeEventData(params), log.getData()); + private void assertEvent(CallTransaction.Function event, Object[] topics, Object[] data) { + LogInfo log = eventLogs.get(0); + + byte[][] expectedEventTopicsSerialized = event.encodeEventTopics(topics); + List expectedEventTopics = LogInfo.byteArrayToList(expectedEventTopicsSerialized); + assertEquals(expectedEventTopics, log.getTopics()); + + byte[] expectedEventData = event.encodeEventData(data); + assertArrayEquals(expectedEventData, log.getData()); } - private void assertTopics(int topics, List logs) { - assertEquals(topics, logs.get(0).getTopics().size()); + private void assertTopics(int expectedTopicsSize) { + int topicsSize = eventLogs.get(0).getTopics().size(); + + assertEquals(expectedTopicsSize, topicsSize); } - private void commonAssertLogs(List logs) { - assertEquals(1, logs.size()); - LogInfo entry = logs.get(0); + private void commonAssertLogs() { + assertEquals(1, eventLogs.size()); // Assert address that made the log - assertEquals(PrecompiledContracts.BRIDGE_ADDR, new RskAddress(entry.getAddress())); - assertArrayEquals(PrecompiledContracts.BRIDGE_ADDR.getBytes(), entry.getAddress()); + LogInfo entry = eventLogs.get(0); + byte[] entryAddressSerialized = entry.getAddress(); + assertArrayEquals(BRIDGE_ADDRESS_SERIALIZED, entryAddressSerialized); + assertEquals(BRIDGE_ADDRESS, new RskAddress(entryAddressSerialized)); } private byte[] serializeRskTxHashes(List rskTxHashes) { From 5fe0e32196687548cbca422535b8820bcf2412c5 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:40:35 +0000 Subject: [PATCH 14/44] feat(federation): add getProposedFederationAddress method to FederationSupport feat(federation): add unit tests for getProposedFederationAddress in FederationSupport --- .../rsk/peg/federation/FederationSupport.java | 1 + .../peg/federation/FederationSupportImpl.java | 6 ++++ .../federation/FederationSupportImplTest.java | 33 ++++++++++++++++--- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index 099b86a2965..ddb442da342 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -47,6 +47,7 @@ public interface FederationSupport { byte[] getPendingFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); Optional getProposedFederation(); + Optional
getProposedFederationAddress(); int voteFederationChange( Transaction tx, diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 1d381429a38..d2c8100ea8d 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -375,6 +375,12 @@ public Optional getProposedFederation() { return provider.getProposedFederation(constants, activations); } + @Override + public Optional
getProposedFederationAddress() { + return getProposedFederation() + .map(Federation::getAddress); + } + @Override public int voteFederationChange(Transaction tx, ABICallSpec callSpec, SignatureCache signatureCache, BridgeEventLogger eventLogger) { String calledFunction = callSpec.getFunction(); diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java index c2a6ffc6643..22aa43b6fc9 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java @@ -2188,27 +2188,50 @@ void save_callsStorageProviderSave() { @Test void getProposedFederation_whenStorageProviderReturnsEmpty_shouldReturnEmpty() { - // act + // Act Optional actualProposedFederation = federationSupport.getProposedFederation(); - // assert + // Assert assertFalse(actualProposedFederation.isPresent()); } @Test void getProposedFederation_whenStorageProviderReturnsProposedFederation_shouldReturnProposedFederation() { - // arrange + // Arrange Federation proposedFederation = P2shErpFederationBuilder.builder().build(); storageProvider.setProposedFederation(proposedFederation); - //act + // Act Optional actualProposedFederation = federationSupport.getProposedFederation(); - // assert + // Assert assertTrue(actualProposedFederation.isPresent()); assertEquals(proposedFederation, actualProposedFederation.get()); } + @Test + void getProposedFederationAddress_whenStorageProviderReturnsEmpty_shouldReturnEmpty() { + // Act + Optional
actualProposedFederationAddress = federationSupport.getProposedFederationAddress(); + + // Assert + assertFalse(actualProposedFederationAddress.isPresent()); + } + + @Test + void getProposedFederationAddress_whenStorageProviderReturnsProposedFederation_shouldReturnProposedFederationAddress() { + // Arrange + Federation proposedFederation = P2shErpFederationBuilder.builder().build(); + storageProvider.setProposedFederation(proposedFederation); + + // Act + Optional
actualProposedFederationAddress = federationSupport.getProposedFederationAddress(); + + // Assert + assertTrue(actualProposedFederationAddress.isPresent()); + assertEquals(proposedFederation.getAddress(), actualProposedFederationAddress.get()); + } + private List getRskPublicKeysFromFederationMembers(List members) { return members.stream() .map(FederationMember::getRskPublicKey) From c8ce076c62cecc996dce9cb953bf87e3bfc22816 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:58:16 +0000 Subject: [PATCH 15/44] feat(federation): add getProposedFederationSize to Federation Support feat(federation): add unit tests for getProposedFederationSize in FederationSupport feat(federation): change getProposedFederationSize to return Optional and wrap into all into FEDERATION_NON_EXISTENT code Update test assertion Co-authored-by: julia zack <83707069+julia-zack@users.noreply.github.com> --- .../FederationChangeResponseCode.java | 3 +- .../rsk/peg/federation/FederationSupport.java | 1 + .../peg/federation/FederationSupportImpl.java | 20 +++++--- .../federation/FederationSupportImplTest.java | 46 +++++++++++++++---- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationChangeResponseCode.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationChangeResponseCode.java index f64bdd7a0ec..fd29faba0c7 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationChangeResponseCode.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationChangeResponseCode.java @@ -6,8 +6,7 @@ public enum FederationChangeResponseCode { EXISTING_FEDERATION_AWAITING_ACTIVATION(-2), RETIRING_FEDERATION_ALREADY_EXISTS(-3), PROPOSED_FEDERATION_ALREADY_EXISTS(-4), - PENDING_FEDERATION_NON_EXISTENT(-1), - RETIRING_FEDERATION_NON_EXISTENT(-1), + FEDERATION_NON_EXISTENT(-1), FEDERATOR_ALREADY_PRESENT(-2), INSUFFICIENT_MEMBERS(-2), PENDING_FEDERATION_MISMATCHED_HASH(-3), diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index ddb442da342..38b58b91060 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -48,6 +48,7 @@ public interface FederationSupport { Optional getProposedFederation(); Optional
getProposedFederationAddress(); + Optional getProposedFederationSize(); int voteFederationChange( Transaction tx, diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index d2c8100ea8d..18eb04514d0 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -242,7 +242,7 @@ public Address getRetiringFederationAddress() { public int getRetiringFederationSize() { Federation retiringFederation = getRetiringFederation(); if (retiringFederation == null) { - return FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode(); + return FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode(); } return retiringFederation.getSize(); @@ -252,7 +252,7 @@ public int getRetiringFederationSize() { public int getRetiringFederationThreshold() { Federation retiringFederation = getRetiringFederation(); if (retiringFederation == null) { - return FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode(); + return FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode(); } return retiringFederation.getNumberOfSignaturesRequired(); @@ -272,7 +272,7 @@ public Instant getRetiringFederationCreationTime() { public long getRetiringFederationCreationBlockNumber() { Federation retiringFederation = getRetiringFederation(); if (retiringFederation == null) { - return FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode(); + return FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode(); } return retiringFederation.getCreationBlockNumber(); } @@ -340,7 +340,7 @@ public int getPendingFederationSize() { PendingFederation currentPendingFederation = getPendingFederation(); if (currentPendingFederation == null) { - return FederationChangeResponseCode.PENDING_FEDERATION_NON_EXISTENT.getCode(); + return FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode(); } return currentPendingFederation.getSize(); @@ -381,6 +381,12 @@ public Optional
getProposedFederationAddress() { .map(Federation::getAddress); } + @Override + public Optional getProposedFederationSize() { + return getProposedFederation() + .map(Federation::getSize); + } + @Override public int voteFederationChange(Transaction tx, ABICallSpec callSpec, SignatureCache signatureCache, BridgeEventLogger eventLogger) { String calledFunction = callSpec.getFunction(); @@ -592,7 +598,7 @@ private Integer addFederatorPublicKeyMultikey(boolean dryRun, BtcECKey btcKey, E if (currentPendingFederation == null) { logger.warn("[addFederatorPublicKeyMultikey] Pending federation does not exist."); - return FederationChangeResponseCode.PENDING_FEDERATION_NON_EXISTENT.getCode(); + return FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode(); } if (currentPendingFederation.getBtcPublicKeys().contains(btcKey) || @@ -632,7 +638,7 @@ private FederationChangeResponseCode commitFederation(boolean dryRun, Keccak256 if (currentPendingFederation == null) { logger.warn("[commitFederation] Pending federation does not exist."); - return FederationChangeResponseCode.PENDING_FEDERATION_NON_EXISTENT; + return FederationChangeResponseCode.FEDERATION_NON_EXISTENT; } if (!currentPendingFederation.isComplete()) { @@ -771,7 +777,7 @@ private Integer rollbackFederation(boolean dryRun) { if (!pendingFederationExists()) { logger.warn("[rollbackFederation] Pending federation does not exist."); - return FederationChangeResponseCode.PENDING_FEDERATION_NON_EXISTENT.getCode(); + return FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode(); } if (dryRun) { diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java index 22aa43b6fc9..c3ad36ac644 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java @@ -1136,14 +1136,14 @@ void getRetiringFederationAddress_returnsNull() { @Tag("getRetiringFederationSize") void getRetiringFederationSize_returnsRetiringFederationNonExistentResponseCode() { int retiringFederationSize = federationSupport.getRetiringFederationSize(); - assertThat(retiringFederationSize, is(FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationSize, is(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @Test @Tag("getRetiringFederationThreshold") void getRetiringFederationThreshold_returnsRetiringFederationNonExistentResponseCode() { int retiringFederationThreshold = federationSupport.getRetiringFederationThreshold(); - assertThat(retiringFederationThreshold, is(FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationThreshold, is(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @Test @@ -1157,7 +1157,7 @@ void getRetiringFederationCreationTime_returnsNull() { @Tag("getRetiringFederationCreationBlockNumber") void getRetiringFederationCreationBlockNumber_returnsRetiringFederationNonExistentResponseCode() { long retiringFederationCreationBlockNumber = federationSupport.getRetiringFederationCreationBlockNumber(); - assertThat(retiringFederationCreationBlockNumber, is((long) FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationCreationBlockNumber, is((long) FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @Test @@ -1225,14 +1225,14 @@ void getRetiringFederationAddress_returnsNull() { @Tag("getRetiringFederationSize") void getRetiringFederationSize_returnsRetiringFederationNonExistentResponseCode() { int retiringFederationSize = federationSupport.getRetiringFederationSize(); - assertThat(retiringFederationSize, is(FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationSize, is(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @Test @Tag("getRetiringFederationThreshold") void getRetiringFederationThreshold_returnsRetiringFederationNonExistentResponseCode() { int retiringFederationThreshold = federationSupport.getRetiringFederationThreshold(); - assertThat(retiringFederationThreshold, is(FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationThreshold, is(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @Test @@ -1246,7 +1246,7 @@ void getRetiringFederationCreationTime_returnsNull() { @Tag("getRetiringFederationCreationBlockNumber") void getRetiringFederationCreationBlockNumber_returnsRetiringFederationNonExistentResponseCode() { long retiringFederationCreationBlockNumber = federationSupport.getRetiringFederationCreationBlockNumber(); - assertThat(retiringFederationCreationBlockNumber, is((long) FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationCreationBlockNumber, is((long) FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @Test @@ -1424,7 +1424,7 @@ void getRetiringFederationSize_withNewFederationNotActive_returnsRetiringFederat .build(); int retiringFederationSize = federationSupport.getRetiringFederationSize(); - assertThat(retiringFederationSize, is(FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationSize, is(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @ParameterizedTest @@ -1466,7 +1466,7 @@ void getRetiringFederationThreshold_withNewFederationNotActive_returnsRetiringFe .build(); int retiringFederationThreshold = federationSupport.getRetiringFederationThreshold(); - assertThat(retiringFederationThreshold, is(FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationThreshold, is(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @ParameterizedTest @@ -1550,7 +1550,7 @@ void getRetiringFederationCreationBlockNumber_withNewFederationNotActive_returns .build(); long retiringFederationCreationBlockNumber = federationSupport.getRetiringFederationCreationBlockNumber(); - assertThat(retiringFederationCreationBlockNumber, is((long) FederationChangeResponseCode.RETIRING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(retiringFederationCreationBlockNumber, is((long) FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @ParameterizedTest @@ -1905,7 +1905,7 @@ void setUp() { @Tag("getPendingFederationSize") void getPendingFederationSize_returnsPendingFederationNonExistentResponseCode() { int pendingFederationSize = federationSupport.getPendingFederationSize(); - assertThat(pendingFederationSize, is(FederationChangeResponseCode.PENDING_FEDERATION_NON_EXISTENT.getCode())); + assertThat(pendingFederationSize, is(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode())); } @Test @@ -2208,6 +2208,32 @@ void getProposedFederation_whenStorageProviderReturnsProposedFederation_shouldRe assertTrue(actualProposedFederation.isPresent()); assertEquals(proposedFederation, actualProposedFederation.get()); } + + @Test + void getProposedFederationSize_whenStorageProviderReturnsEmpty_shouldReturnEmpty() { + // Act + Optional actualProposedFederationSize = federationSupport.getProposedFederationSize(); + + // Assert + assertFalse(actualProposedFederationSize.isPresent()); + } + + @Test + void getProposedFederationSize_whenStorageProviderReturnsProposedFederation_shouldReturnProposedFederationSize() { + // Arrange + List federationKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[] { "fa01", "fa02", "fa03", "fa04", "fa05", "fa06", "fa07", "fa08", "fa09" }, true); + Federation proposedFederation = P2shErpFederationBuilder.builder().withMembersBtcPublicKeys(federationKeys).build(); + storageProvider.setProposedFederation(proposedFederation); + int expectedSize = federationKeys.size(); + + // Act + Optional actualProposedFederationSize = federationSupport.getProposedFederationSize(); + + // Assert + assertTrue(actualProposedFederationSize.isPresent()); + assertEquals(expectedSize, actualProposedFederationSize.get()); + } @Test void getProposedFederationAddress_whenStorageProviderReturnsEmpty_shouldReturnEmpty() { From 94b87a7ea6b0066a3f04952109cc6557c9ea7cbb Mon Sep 17 00:00:00 2001 From: julia-zack Date: Fri, 27 Sep 2024 15:47:39 -0300 Subject: [PATCH 16/44] Create new commitFederationFailed Bridge event Make fields private to avoid sonar complain Use var to improve readability --- .../main/java/co/rsk/peg/BridgeEvents.java | 10 ++++++-- .../co/rsk/peg/utils/BridgeEventLogger.java | 2 ++ .../rsk/peg/utils/BridgeEventLoggerImpl.java | 18 +++++++++++++ .../peg/utils/BrigeEventLoggerLegacyImpl.java | 18 ++++++++----- .../peg/utils/BridgeEventLoggerImplTest.java | 25 +++++++++++++++++++ 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java b/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java index 4683068e18c..7579cfd79d0 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java @@ -60,6 +60,12 @@ public enum BridgeEvents { new CallTransaction.Param(false, "activationHeight", SolidityType.getType(SolidityType.INT256)) } ), + COMMIT_FEDERATION_FAILED("commit_federation_failed", + new CallTransaction.Param[]{ + new CallTransaction.Param(false, "proposedFederationRedeemScriptSerialized", SolidityType.getType(SolidityType.BYTES)), + new CallTransaction.Param(false, "blockNumber", SolidityType.getType(SolidityType.INT256)) + } + ), RELEASE_REQUESTED("release_requested", new CallTransaction.Param[]{ new CallTransaction.Param(true, "rskTxHash", SolidityType.getType(SolidityType.BYTES32)), @@ -107,8 +113,8 @@ public enum BridgeEvents { } ); - private String eventName; - private CallTransaction.Param[] params; + private final String eventName; + private final CallTransaction.Param[] params; BridgeEvents(String eventName, CallTransaction.Param[] params) { this.eventName = eventName; diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java index 355aad58367..8f3713b981b 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLogger.java @@ -42,6 +42,8 @@ public interface BridgeEventLogger { void logCommitFederation(Block executionBlock, Federation oldFederation, Federation newFederation); + void logCommitFederationFailure(Block executionBlock, Federation proposedFederation); + default void logLockBtc(RskAddress rskReceiver, BtcTransaction btcTx, Address senderBtcAddress, Coin amount) { throw new UnsupportedOperationException(); } diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java index dc714a20458..fd387ac76d1 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java @@ -143,6 +143,24 @@ public void logCommitFederation(Block executionBlock, Federation oldFederation, addLog(encodedTopics, encodedData); } + @Override + public void logCommitFederationFailure(Block executionBlock, Federation proposedFederation) { + CallTransaction.Function event = BridgeEvents.COMMIT_FEDERATION_FAILED.getEvent(); + + byte[][] encodedTopicsSerialized = event.encodeEventTopics(); + List encodedTopics = getEncodedTopics(encodedTopicsSerialized); + + byte[] proposedFederationRedeemScriptSerialized = proposedFederation.getRedeemScript().getProgram(); + long executionBlockNumber = executionBlock.getNumber(); + + byte[] encodedData = event.encodeEventData( + proposedFederationRedeemScriptSerialized, + executionBlockNumber + ); + + addLog(encodedTopics, encodedData); + } + @Override public void logLockBtc(RskAddress receiver, BtcTransaction btcTx, Address senderBtcAddress, Coin amount) { CallTransaction.Function event = BridgeEvents.LOCK_BTC.getEvent(); diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java index d151ca3974e..c18a2fe19d5 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BrigeEventLoggerLegacyImpl.java @@ -39,7 +39,6 @@ import java.util.Collections; import java.util.List; import java.util.function.Function; -import java.util.stream.Collectors; /** * Responsible for logging events triggered by BridgeContract in RLP format. @@ -66,7 +65,7 @@ public BrigeEventLoggerLegacyImpl(BridgeConstants bridgeConstants, ActivationCon public void logUpdateCollections(RskAddress sender) { if (activations.isActive(ConsensusRule.RSKIP146)) { throw new DeprecatedMethodCallException( - "Calling BrigeEventLoggerLegacyImpl.logUpdateCollections method after RSKIP146 activation" + "Calling BridgeEventLoggerLegacyImpl.logUpdateCollections method after RSKIP146 activation" ); } this.logs.add( @@ -81,7 +80,7 @@ public void logUpdateCollections(RskAddress sender) { public void logAddSignature(FederationMember federationMember, BtcTransaction btcTx, byte[] rskTxHash) { if (activations.isActive(ConsensusRule.RSKIP146)) { throw new DeprecatedMethodCallException( - "Calling BrigeEventLoggerLegacyImpl.logAddSignature method after RSKIP146 activation" + "Calling BridgeEventLoggerLegacyImpl.logAddSignature method after RSKIP146 activation" ); } List topics = Collections.singletonList(Bridge.ADD_SIGNATURE_TOPIC); @@ -96,7 +95,7 @@ public void logAddSignature(FederationMember federationMember, BtcTransaction bt public void logReleaseBtc(BtcTransaction btcTx, byte[] rskTxHash) { if (activations.isActive(ConsensusRule.RSKIP146)) { throw new DeprecatedMethodCallException( - "Calling BrigeEventLoggerLegacyImpl.logReleaseBtc method after RSKIP146 activation" + "Calling BridgeEventLoggerLegacyImpl.logReleaseBtc method after RSKIP146 activation" ); } List topics = Collections.singletonList(Bridge.RELEASE_BTC_TOPIC); @@ -109,7 +108,7 @@ public void logReleaseBtc(BtcTransaction btcTx, byte[] rskTxHash) { public void logCommitFederation(Block executionBlock, Federation oldFederation, Federation newFederation) { if (activations.isActive(ConsensusRule.RSKIP146)) { throw new DeprecatedMethodCallException( - "Calling BrigeEventLoggerLegacyImpl.logCommitFederation method after RSKIP146 activation" + "Calling BridgeEventLoggerLegacyImpl.logCommitFederation method after RSKIP146 activation" ); } List topics = Collections.singletonList(Bridge.COMMIT_FEDERATION_TOPIC); @@ -128,6 +127,13 @@ public void logCommitFederation(Block executionBlock, Federation oldFederation, this.logs.add(new LogInfo(BRIDGE_CONTRACT_ADDRESS, topics, data)); } + @Override + public void logCommitFederationFailure(Block executionBlock, Federation proposedFederation) { + throw new UnsupportedOperationException( + "Calling BridgeEventLoggerLegacyImpl.logCommitFederationFailure is not allowed, since this event was created after deprecating the class." + ); + } + private byte[] flatKeysAsRlpCollection(List keys) { return flatKeys(keys, (k -> RLP.encodeElement(k.getPubKey()))); } @@ -135,7 +141,7 @@ private byte[] flatKeysAsRlpCollection(List keys) { private byte[] flatKeys(List keys, Function parser) { List pubKeys = keys.stream() .map(parser) - .collect(Collectors.toList()); + .toList(); int pubKeysLength = pubKeys.stream().mapToInt(key -> key.length).sum(); byte[] flatPubKeys = new byte[pubKeysLength]; diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index c40153cbf33..c547abb400c 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -324,6 +324,31 @@ private Federation getFederationFromBtcKeys(List federationKeys, Insta return FederationFactory.buildStandardMultiSigFederation(federationArgs); } + @Test + void logCommitFederationFailed() { + // arrange + Block rskExecutionBlock = arrangeRskExecutionBlock(); + long executionBlockNumber = rskExecutionBlock.getNumber(); + + Federation proposedFederation = P2shErpFederationBuilder.builder().build(); + byte[] proposedFederationRedeemScriptSerialized = proposedFederation.getRedeemScript().getProgram(); + + CallTransaction.Function event = BridgeEvents.COMMIT_FEDERATION_FAILED.getEvent(); + var topics = new Object[]{}; + var data = new Object[]{ + proposedFederationRedeemScriptSerialized, + executionBlockNumber + }; + + // act + eventLogger.logCommitFederationFailure(rskExecutionBlock, proposedFederation); + + // assert + commonAssertLogs(); + assertTopics(1); + assertEvent(event, topics, data); + } + private Block arrangeRskExecutionBlock() { long rskExecutionBlockNumber = 15005L; long rskExecutionBlockTimestamp = 15L; From b707b0c25e483e74d9c6755510d94e84c2785bac Mon Sep 17 00:00:00 2001 From: julia-zack Date: Tue, 1 Oct 2024 09:55:24 -0300 Subject: [PATCH 17/44] Add test for logCommitFederationFailure from deprecated logger --- .../rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java index 31c8cf8024b..80640d9a5e9 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerLegacyImplTest.java @@ -29,6 +29,7 @@ import co.rsk.peg.federation.constants.FederationConstants; import org.bouncycastle.util.encoders.Hex; import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.*; import org.ethereum.util.RLP; @@ -284,6 +285,15 @@ void testLogCommitFederationAfterRskip146() { assertThrows(DeprecatedMethodCallException.class, () -> eventLogger.logCommitFederation(mock(Block.class), mock(Federation.class), mock(Federation.class))); } + @Test + void testLogCommitFederationFailure_throwsUnsupportedOperationException() { + // Setup event logger + activations = ActivationConfigsForTest.all().forBlock(0); + + // Act + assertThrows(UnsupportedOperationException.class, () -> eventLogger.logCommitFederationFailure(mock(Block.class), mock(Federation.class))); + } + /********************************** * ------- UTILS ------- * *********************************/ From 144ea6e8b50304e927a4de05efca793148818fc8 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 3 Oct 2024 10:39:02 -0300 Subject: [PATCH 18/44] Move rsk block creation method to RskTestUtils --- rskj-core/src/test/java/co/rsk/RskTestUtils.java | 15 +++++++++++++++ .../rsk/peg/utils/BridgeEventLoggerImplTest.java | 9 ++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/RskTestUtils.java b/rskj-core/src/test/java/co/rsk/RskTestUtils.java index eb1db7f0299..af816ca28cd 100644 --- a/rskj-core/src/test/java/co/rsk/RskTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/RskTestUtils.java @@ -1,12 +1,18 @@ package co.rsk; import co.rsk.crypto.Keccak256; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.BlockHeaderBuilder; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import static org.mockito.Mockito.mock; + public class RskTestUtils { public static Keccak256 createHash(int nHash) { @@ -27,4 +33,13 @@ public static List getEcKeysFromSeeds(String[] seeds) { .map(RskTestUtils::getEcKeyFromSeed) .toList(); } + + public static Block createRskExecutionBlock(long rskExecutionBlockNumber, long rskExecutionBlockTimestamp) { + BlockHeader blockHeader = new BlockHeaderBuilder(mock(ActivationConfig.class)) + .setNumber(rskExecutionBlockNumber) + .setTimestamp(rskExecutionBlockTimestamp) + .build(); + + return Block.createBlockFromHeader(blockHeader, true); + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index c547abb400c..9b3773b7a5b 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -17,10 +17,10 @@ */ package co.rsk.peg.utils; +import static co.rsk.RskTestUtils.createRskExecutionBlock; import static co.rsk.peg.bitcoin.BitcoinTestUtils.coinListOf; import static co.rsk.peg.bitcoin.BitcoinTestUtils.flatKeysAsByteArray; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; import co.rsk.RskTestUtils; import co.rsk.bitcoinj.core.*; @@ -352,12 +352,7 @@ void logCommitFederationFailed() { private Block arrangeRskExecutionBlock() { long rskExecutionBlockNumber = 15005L; long rskExecutionBlockTimestamp = 15L; - BlockHeader blockHeader = new BlockHeaderBuilder(mock(ActivationConfig.class)) - .setNumber(rskExecutionBlockNumber) - .setTimestamp(rskExecutionBlockTimestamp) - .build(); - - return Block.createBlockFromHeader(blockHeader, true); + return createRskExecutionBlock(rskExecutionBlockNumber, rskExecutionBlockTimestamp); } @Test From 82f0c9b03d104ff5c2bab0712bd1b5829f6e0570 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 3 Oct 2024 10:40:37 -0300 Subject: [PATCH 19/44] Remove unnecessary suffix from variable name --- rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java b/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java index 7579cfd79d0..cc53bff53f3 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeEvents.java @@ -62,7 +62,7 @@ public enum BridgeEvents { ), COMMIT_FEDERATION_FAILED("commit_federation_failed", new CallTransaction.Param[]{ - new CallTransaction.Param(false, "proposedFederationRedeemScriptSerialized", SolidityType.getType(SolidityType.BYTES)), + new CallTransaction.Param(false, "proposedFederationRedeemScript", SolidityType.getType(SolidityType.BYTES)), new CallTransaction.Param(false, "blockNumber", SolidityType.getType(SolidityType.INT256)) } ), From d9c4942802f13092526ec3909fe165a069e902f5 Mon Sep 17 00:00:00 2001 From: Marcos Date: Thu, 3 Oct 2024 13:08:15 -0300 Subject: [PATCH 20/44] Create default implementation of createRskBlock util method --- .../src/test/java/co/rsk/RskTestUtils.java | 29 ++++++++++++------- .../peg/utils/BridgeEventLoggerImplTest.java | 4 +-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/RskTestUtils.java b/rskj-core/src/test/java/co/rsk/RskTestUtils.java index af816ca28cd..9f6fb61d315 100644 --- a/rskj-core/src/test/java/co/rsk/RskTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/RskTestUtils.java @@ -1,17 +1,17 @@ package co.rsk; +import static org.mockito.Mockito.mock; + import co.rsk.crypto.Keccak256; -import org.ethereum.config.blockchain.upgrades.ActivationConfig; -import org.ethereum.core.Block; -import org.ethereum.core.BlockHeader; -import org.ethereum.core.BlockHeaderBuilder; -import org.ethereum.crypto.ECKey; -import org.ethereum.crypto.HashUtil; import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.List; - -import static org.mockito.Mockito.mock; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.core.*; +import org.ethereum.crypto.ECKey; +import org.ethereum.crypto.HashUtil; public class RskTestUtils { @@ -34,10 +34,17 @@ public static List getEcKeysFromSeeds(String[] seeds) { .toList(); } - public static Block createRskExecutionBlock(long rskExecutionBlockNumber, long rskExecutionBlockTimestamp) { + public static Block createRskBlock() { + final int defaultBlockNumber = 1001; + final Instant defaultBlockTimestamp = ZonedDateTime.parse("2020-01-20T12:00:08.400Z").toInstant(); + + return createRskBlock(defaultBlockNumber, defaultBlockTimestamp.toEpochMilli()); + } + + public static Block createRskBlock(long blockNumber, long blockTimestamp) { BlockHeader blockHeader = new BlockHeaderBuilder(mock(ActivationConfig.class)) - .setNumber(rskExecutionBlockNumber) - .setTimestamp(rskExecutionBlockTimestamp) + .setNumber(blockNumber) + .setTimestamp(blockTimestamp) .build(); return Block.createBlockFromHeader(blockHeader, true); diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index 9b3773b7a5b..9c04650a157 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -17,7 +17,7 @@ */ package co.rsk.peg.utils; -import static co.rsk.RskTestUtils.createRskExecutionBlock; +import static co.rsk.RskTestUtils.createRskBlock; import static co.rsk.peg.bitcoin.BitcoinTestUtils.coinListOf; import static co.rsk.peg.bitcoin.BitcoinTestUtils.flatKeysAsByteArray; import static org.junit.jupiter.api.Assertions.*; @@ -352,7 +352,7 @@ void logCommitFederationFailed() { private Block arrangeRskExecutionBlock() { long rskExecutionBlockNumber = 15005L; long rskExecutionBlockTimestamp = 15L; - return createRskExecutionBlock(rskExecutionBlockNumber, rskExecutionBlockTimestamp); + return createRskBlock(rskExecutionBlockNumber, rskExecutionBlockTimestamp); } @Test From f18dac854b9e3977274f285db3625aafe6f6b55e Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:09:59 +0000 Subject: [PATCH 21/44] feat(federation): add getProposedFederationCreationTime in FederationSupport feat(federation): add unit tests for getProposedFederationCreationTime in FederationSupport --- .../rsk/peg/federation/FederationSupport.java | 1 + .../peg/federation/FederationSupportImpl.java | 30 +++++++++---------- .../federation/FederationSupportImplTest.java | 26 ++++++++++++++++ 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index 38b58b91060..ce353f581f0 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -49,6 +49,7 @@ public interface FederationSupport { Optional getProposedFederation(); Optional
getProposedFederationAddress(); Optional getProposedFederationSize(); + Optional getProposedFederationCreationTime(); int voteFederationChange( Transaction tx, diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 18eb04514d0..879131123fd 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -1,8 +1,8 @@ package co.rsk.peg.federation; -import co.rsk.bitcoinj.core.Address; -import co.rsk.bitcoinj.core.BtcECKey; -import co.rsk.bitcoinj.core.UTXO; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; + +import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.Script; import co.rsk.core.RskAddress; import co.rsk.core.types.bytes.Bytes; @@ -10,27 +10,19 @@ import co.rsk.peg.BridgeIllegalArgumentException; import co.rsk.peg.federation.constants.FederationConstants; import co.rsk.peg.utils.BridgeEventLogger; -import co.rsk.peg.vote.ABICallElection; -import co.rsk.peg.vote.ABICallSpec; -import co.rsk.peg.vote.ABICallVoteResult; -import co.rsk.peg.vote.AddressBasedAuthorizer; +import co.rsk.peg.vote.*; import co.rsk.util.StringUtils; +import java.time.Instant; +import java.util.*; +import javax.annotation.Nullable; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; -import org.ethereum.core.Block; -import org.ethereum.core.SignatureCache; -import org.ethereum.core.Transaction; +import org.ethereum.core.*; import org.ethereum.crypto.ECKey; import org.ethereum.util.ByteUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; -import java.time.Instant; -import java.util.*; - -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; - public class FederationSupportImpl implements FederationSupport { private static final Logger logger = LoggerFactory.getLogger(FederationSupportImpl.class); @@ -387,6 +379,12 @@ public Optional getProposedFederationSize() { .map(Federation::getSize); } + @Override + public Optional getProposedFederationCreationTime() { + return getProposedFederation() + .map(Federation::getCreationTime); + } + @Override public int voteFederationChange(Transaction tx, ABICallSpec callSpec, SignatureCache signatureCache, BridgeEventLogger eventLogger) { String calledFunction = callSpec.getFunction(); diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java index c3ad36ac644..65a06b76937 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java @@ -2258,6 +2258,32 @@ void getProposedFederationAddress_whenStorageProviderReturnsProposedFederation_s assertEquals(proposedFederation.getAddress(), actualProposedFederationAddress.get()); } + @Test + void getProposedFederationCreationTime_whenStorageProviderReturnsEmpty_shouldReturnEmpty() { + // Act + Optional actualCreationTime = federationSupport.getProposedFederationCreationTime(); + + // Assert + assertFalse(actualCreationTime.isPresent()); + } + + @Test + void getProposedFederationCreationTime_whenStorageProviderReturnsProposedFederation_shouldReturnCreationTime() { + // Arrange + Instant creationTime = Instant.EPOCH; + Federation proposedFederation = P2shErpFederationBuilder.builder() + .withCreationTime(creationTime) + .build(); + storageProvider.setProposedFederation(proposedFederation); + + // Act + Optional actualCreationTime = federationSupport.getProposedFederationCreationTime(); + + // Assert + assertTrue(actualCreationTime.isPresent()); + assertEquals(creationTime, actualCreationTime.get()); + } + private List getRskPublicKeysFromFederationMembers(List members) { return members.stream() .map(FederationMember::getRskPublicKey) From f8e925bc126fcfb6e8f20f59d5696f6e09ab99fe Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:22:38 +0000 Subject: [PATCH 22/44] feat(federation): add getProposedFederationCreationBlockNumber method in FederationSupport feat(federation): add unit tests for getProposedFederationCreationBlockNumber in FederationSupport --- .../rsk/peg/federation/FederationSupport.java | 1 + .../peg/federation/FederationSupportImpl.java | 6 ++++ .../federation/FederationSupportImplTest.java | 33 ++++++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index ce353f581f0..17b42a470ac 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -50,6 +50,7 @@ public interface FederationSupport { Optional
getProposedFederationAddress(); Optional getProposedFederationSize(); Optional getProposedFederationCreationTime(); + Optional getProposedFederationCreationBlockNumber(); int voteFederationChange( Transaction tx, diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 879131123fd..63417caeb3e 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -373,6 +373,12 @@ public Optional
getProposedFederationAddress() { .map(Federation::getAddress); } + @Override + public Optional getProposedFederationCreationBlockNumber() { + return getProposedFederation() + .map(Federation::getCreationBlockNumber); + } + @Override public Optional getProposedFederationSize() { return getProposedFederation() diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java index 65a06b76937..2032c980224 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java @@ -37,7 +37,6 @@ import co.rsk.peg.storage.InMemoryStorage; import java.time.Instant; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; import co.rsk.peg.storage.StorageAccessor; import co.rsk.test.builders.FederationSupportBuilder; @@ -55,10 +54,10 @@ class FederationSupportImplTest { private static final FederationConstants federationMainnetConstants = FederationMainNetConstants.getInstance(); private final Federation genesisFederation = FederationTestUtils.getGenesisFederation(federationMainnetConstants); + private final FederationSupportBuilder federationSupportBuilder = FederationSupportBuilder.builder(); private ErpFederation newFederation; private StorageAccessor storageAccessor; private FederationStorageProvider storageProvider; - private final FederationSupportBuilder federationSupportBuilder = FederationSupportBuilder.builder(); private FederationSupport federationSupport; @BeforeEach @@ -2284,15 +2283,41 @@ void getProposedFederationCreationTime_whenStorageProviderReturnsProposedFederat assertEquals(creationTime, actualCreationTime.get()); } + @Test + void getProposedFederationCreationBlockNumber_whenStorageProviderReturnsEmpty_shouldReturnErrorCode() { + // Act + Optional actualCreationBlockNumber = federationSupport.getProposedFederationCreationBlockNumber(); + + // Assert + assertFalse(actualCreationBlockNumber.isPresent()); + } + + @Test + void getProposedFederationCreationBlockNumber_whenStorageProviderReturnsProposedFederation_shouldReturnCreationBlockNumber() { + // Arrange + long creationBlockNumber = 12345L; + Federation proposedFederation = P2shErpFederationBuilder.builder() + .withCreationBlockNumber(creationBlockNumber) + .build(); + storageProvider.setProposedFederation(proposedFederation); + + // Act + Optional actualCreationBlockNumber = federationSupport.getProposedFederationCreationBlockNumber(); + + // Assert + assertTrue(actualCreationBlockNumber.isPresent()); + assertEquals(creationBlockNumber, actualCreationBlockNumber.get()); + } + private List getRskPublicKeysFromFederationMembers(List members) { return members.stream() .map(FederationMember::getRskPublicKey) - .collect(Collectors.toList()); + .toList(); } private List getMstPublicKeysFromFederationMembers(List members) { return members.stream() .map(FederationMember::getMstPublicKey) - .collect(Collectors.toList()); + .toList(); } } From 1a95c23bd92b9feec0b742f282c425e5faaabb05 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:56:49 +0000 Subject: [PATCH 23/44] feat(peg): add getProposedFederationAddress Bridge method feat(peg): add unit tests for getProposedFederationAddress Bridge method feat(peg): move javadocs and simplify test --- .../src/main/java/co/rsk/peg/Bridge.java | 24 +++++++++++++ .../main/java/co/rsk/peg/BridgeMethods.java | 12 +++++++ .../main/java/co/rsk/peg/BridgeSupport.java | 4 +++ .../peg/federation/FederationSupportImpl.java | 12 +++++++ .../java/co/rsk/peg/BridgeSupportTest.java | 23 +++++++++++++ .../src/test/java/co/rsk/peg/BridgeTest.java | 34 +++++++++++++++++++ 6 files changed, 109 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index 8c0d53e6d68..d6f7d06bbd1 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -173,6 +173,9 @@ public class Bridge extends PrecompiledContracts.PrecompiledContract { public static final CallTransaction.Function GET_PENDING_FEDERATOR_PUBLIC_KEY = BridgeMethods.GET_PENDING_FEDERATOR_PUBLIC_KEY.getFunction(); // Returns the public key of given type the federator at the specified index for the current pending federation public static final CallTransaction.Function GET_PENDING_FEDERATOR_PUBLIC_KEY_OF_TYPE = BridgeMethods.GET_PENDING_FEDERATOR_PUBLIC_KEY_OF_TYPE.getFunction(); + + // Returns the proposed federation bitcoin address + public static final CallTransaction.Function GET_PROPOSED_FEDERATION_ADDRESS = BridgeMethods.GET_PROPOSED_FEDERATION_ADDRESS.getFunction(); // Returns the lock whitelist size public static final CallTransaction.Function GET_LOCK_WHITELIST_SIZE = BridgeMethods.GET_LOCK_WHITELIST_SIZE.getFunction(); @@ -1040,6 +1043,27 @@ public byte[] getPendingFederatorPublicKeyOfType(Object[] args) throws VMExcepti return publicKey; } + /** + * Retrieves the proposed federation Bitcoin address as a Base58 string. + * + *

+ * This method attempts to fetch the address of the proposed federation. If the + * proposed federation is present, it converts the address to its Base58 representation. + * If not, an empty string is returned. + *

+ * + * @param args Additional arguments (currently unused) + * @return The Base58 encoded Bitcoin address of the proposed federation, or an empty + * string if no proposed federation is present. + */ + public String getProposedFederationAddress(Object[] args) { + logger.trace("getProposedFederationAddress"); + + return bridgeSupport.getProposedFederationAddress() + .map(Address::toBase58) + .orElse(""); + } + public Integer getLockWhitelistSize(Object[] args) { logger.trace("getLockWhitelistSize"); diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java index 5c128236d56..c5f12fdbd5a 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java @@ -454,6 +454,18 @@ public enum BridgeMethods { fixedPermission(true), CallTypeHelper.ALLOW_STATIC_CALL ), + GET_PROPOSED_FEDERATION_ADDRESS( + CallTransaction.Function.fromSignature( + "getProposedFederationAddress", + new String[]{}, + new String[]{ "string" } + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getProposedFederationAddress, + activations -> activations.isActive(RSKIP419), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL + ), GET_STATE_FOR_BTC_RELEASE_CLIENT( CallTransaction.Function.fromSignature( "getStateForBtcReleaseClient", 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 6a473531499..a22ca0e35cf 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -2247,6 +2247,10 @@ public byte[] getPendingFederatorPublicKeyOfType(int index, FederationMember.Key return federationSupport.getPendingFederatorPublicKeyOfType(index, keyType); } + public Optional

getProposedFederationAddress() { + return federationSupport.getProposedFederationAddress(); + } + public Integer getLockWhitelistSize() { return whitelistSupport.getLockWhitelistSize(); } diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 63417caeb3e..16252fcbde6 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -367,6 +367,18 @@ public Optional getProposedFederation() { return provider.getProposedFederation(constants, activations); } + /** + * Retrieves the Bitcoin address of the proposed federation, if it exists. + * + *

+ * This method checks if there is a proposed federation available and + * returns its associated Bitcoin address. The proposed federation is typically + * a federation that is awaiting approval or activation. + *

+ * + * @return an {@link Optional} containing the Bitcoin {@link Address} of the proposed federation, + * or an empty {@link Optional} if no proposed federation is available. + */ @Override public Optional
getProposedFederationAddress() { return getProposedFederation() diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java index e4888860b5d..654ef458d6f 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -513,6 +513,29 @@ void getPendingFederatorPublicKeyOfType() { assertThat(bridgeSupport.getPendingFederatorPublicKeyOfType(0, FederationMember.KeyType.MST), is(mstKey.getPubKey())); } + @Test + void getProposedFederationAddress_whenBridgeSupportReturnsEmpty_shouldReturnEmpty() { + // Act + var actualProposedFederationAddress = bridgeSupport.getProposedFederationAddress(); + + // Assert + assertFalse(actualProposedFederationAddress.isPresent()); + } + + @Test + void getProposedFederationAddress_whenProposedFederationExists_shouldReturnAddress() { + // Arrange + var expectedAddress = federation.getAddress(); + when(federationSupport.getProposedFederationAddress()).thenReturn(Optional.of(expectedAddress)); + + // Act + var actualProposedFederationAddress = bridgeSupport.getProposedFederationAddress(); + + // Assert + assertTrue(actualProposedFederationAddress.isPresent()); + assertEquals(expectedAddress, actualProposedFederationAddress.get()); + } + @Test void voteFederationChange() { Transaction tx = mock(Transaction.class); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java index 0b93794d9ee..58e2a24a7e0 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java @@ -2232,6 +2232,40 @@ void getRetiringFederatorPublicKeyOfType(MessageCall.MsgType msgType, Activation } } + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getProposedFederationAddress(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Address expectedAddress = Address.fromBase58(networkParameters, "32Bhwee9FzQbuaG29RcXpdrvYnvZeMk11M"); + when(bridgeSupportMock.getProposedFederationAddress()).thenReturn(Optional.of(expectedAddress)); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PROPOSED_FEDERATION_ADDRESS.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP419, 0)) { + if (!(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock).getProposedFederationAddress(); + } + } else { + // Pre RSKIP419 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + @ParameterizedTest() @MethodSource("msgTypesAndActivations") void getStateForBtcReleaseClient(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { From 7732178d329b1a8d922398f0c26640ae071d64f8 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:18:23 +0200 Subject: [PATCH 24/44] feat(peg): Update Bridge Update Bridge.java --- rskj-core/src/main/java/co/rsk/peg/Bridge.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index d6f7d06bbd1..5d1e9d3a17b 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -173,9 +173,6 @@ public class Bridge extends PrecompiledContracts.PrecompiledContract { public static final CallTransaction.Function GET_PENDING_FEDERATOR_PUBLIC_KEY = BridgeMethods.GET_PENDING_FEDERATOR_PUBLIC_KEY.getFunction(); // Returns the public key of given type the federator at the specified index for the current pending federation public static final CallTransaction.Function GET_PENDING_FEDERATOR_PUBLIC_KEY_OF_TYPE = BridgeMethods.GET_PENDING_FEDERATOR_PUBLIC_KEY_OF_TYPE.getFunction(); - - // Returns the proposed federation bitcoin address - public static final CallTransaction.Function GET_PROPOSED_FEDERATION_ADDRESS = BridgeMethods.GET_PROPOSED_FEDERATION_ADDRESS.getFunction(); // Returns the lock whitelist size public static final CallTransaction.Function GET_LOCK_WHITELIST_SIZE = BridgeMethods.GET_LOCK_WHITELIST_SIZE.getFunction(); From fd6bd6b8f2c8f45bac6147d83a432e8860c41234 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:46:56 +0000 Subject: [PATCH 25/44] feat(federation): add getProposedFederatorPublicKeyOfType method in FederationSupport feat(federation): add unit tests for getProposedFederatorPublicKeyOfType in FederatorSupport feat(federation): misc comments feat(federation): add unit tests for key type rsk and mst for getProposedFederatorPublicOfType --- .../rsk/peg/federation/FederationSupport.java | 1 + .../peg/federation/FederationSupportImpl.java | 28 +++++--- .../federation/FederationSupportImplTest.java | 71 +++++++++++++++++++ 3 files changed, 91 insertions(+), 9 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index 17b42a470ac..acebc836cea 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -51,6 +51,7 @@ public interface FederationSupport { Optional getProposedFederationSize(); Optional getProposedFederationCreationTime(); Optional getProposedFederationCreationBlockNumber(); + Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); int voteFederationChange( Transaction tx, diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 16252fcbde6..5ad5ef92efc 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -366,6 +366,13 @@ public byte[] getPendingFederatorPublicKeyOfType(int index, FederationMember.Key public Optional getProposedFederation() { return provider.getProposedFederation(constants, activations); } + + @Override + public Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType) { + return getProposedFederation() + .map(Federation::getMembers) + .map(members -> getFederationMemberPublicKeyOfType(members, index, keyType, "Proposed Federator")); + } /** * Retrieves the Bitcoin address of the proposed federation, if it exists. @@ -830,17 +837,20 @@ public long getActiveFederationCreationBlockHeight() { } /** - * Returns the compressed public key of given type of the member list at the given index - * Throws a custom index out of bounds exception when appropiate - * @param members the list of federation members - * @param index the federator's index (zero-based) - * @param keyType the key type - * @param errorPrefix the index out of bounds error prefix - * @return the federation member's public key + * Returns the compressed public key of given type of the member list at the + * given index. Throws a custom index out of bounds exception when appropiate. + * + * @param members list of federation members + * @param index federator's index (zero-based) + * @param keyType key type + * @param errorPrefix index out of bounds error prefix + * @return federation member's public key */ - private byte[] getFederationMemberPublicKeyOfType(List members, int index, FederationMember.KeyType keyType, String errorPrefix) { + private byte[] getFederationMemberPublicKeyOfType( + List members, int index, FederationMember.KeyType keyType, String errorPrefix) { if (index < 0 || index >= members.size()) { - throw new IndexOutOfBoundsException(String.format("%s index must be between 0 and %d", errorPrefix, members.size() - 1)); + throw new IndexOutOfBoundsException( + String.format("%s index must be between 0 and %d (found: %d)", errorPrefix, members.size() - 1, index)); } return members.get(index).getPublicKey(keyType).getPubKey(true); diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java index 2032c980224..53fad863964 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java @@ -2309,6 +2309,77 @@ void getProposedFederationCreationBlockNumber_whenStorageProviderReturnsProposed assertEquals(creationBlockNumber, actualCreationBlockNumber.get()); } + @Test + void getProposedFederatorPublicKeyOfType_whenFederationIsEmpty_shouldReturnEmpty() { + // Act + Optional actualPublicKey = federationSupport.getProposedFederatorPublicKeyOfType(0, FederationMember.KeyType.BTC); + + // Assert + assertFalse(actualPublicKey.isPresent()); + } + + @Test + void getProposedFederatorPublicKeyOfType_whenStorageProviderReturnsProposedFederationAndKeyTypeIsBTC_shouldReturnPublicKey() { + // Arrange + List federationKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[] { "fa01", "fa02", "fa03", "fa04", "fa05", "fa06", "fa07", "fa08", "fa09" }, true); + Federation proposedFederation = P2shErpFederationBuilder.builder().withMembersBtcPublicKeys(federationKeys).build(); + FederationMember.KeyType keyType = FederationMember.KeyType.BTC; + byte[] expectedPublicKey = proposedFederation.getMembers().get(0).getPublicKey(keyType).getPubKey(true); + storageProvider.setProposedFederation(proposedFederation); + + // Act + Optional actualPublicKey = federationSupport.getProposedFederatorPublicKeyOfType(0, keyType); + + // Assert + assertTrue(actualPublicKey.isPresent()); + assertArrayEquals(expectedPublicKey, actualPublicKey.get()); + } + + @Test + void getProposedFederatorPublicKeyOfType_whenStorageProviderReturnsProposedFederationAndKeyTypeIsRSK_shouldReturnPublicKey() { + // Arrange + List federationKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[] { "fa01", "fa02", "fa03", "fa04", "fa05", "fa06", "fa07", "fa08", "fa09" }, true); + List federationRskKeys = federationKeys.stream() + .map(BtcECKey::getPubKey) + .map(ECKey::fromPublicOnly) + .toList(); + Federation proposedFederation = P2shErpFederationBuilder.builder().withMembersRskPublicKeys(federationRskKeys).build(); + FederationMember.KeyType keyType = FederationMember.KeyType.RSK; + byte[] expectedPublicKey = proposedFederation.getMembers().get(0).getPublicKey(keyType).getPubKey(true); + storageProvider.setProposedFederation(proposedFederation); + + // Act + Optional actualPublicKey = federationSupport.getProposedFederatorPublicKeyOfType(0, keyType); + + // Assert + assertTrue(actualPublicKey.isPresent()); + assertArrayEquals(expectedPublicKey, actualPublicKey.get()); + } + + @Test + void getProposedFederatorPublicKeyOfType_whenStorageProviderReturnsProposedFederationAndKeyTypeIsMst_shouldReturnPublicKey() { + // Arrange + List federationKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[] { "fa01", "fa02", "fa03", "fa04", "fa05", "fa06", "fa07", "fa08", "fa09" }, true); + List federationMstKeys = federationKeys.stream() + .map(BtcECKey::getPubKey) + .map(ECKey::fromPublicOnly) + .toList(); + Federation proposedFederation = P2shErpFederationBuilder.builder().withMembersMstPublicKeys(federationMstKeys).build(); + FederationMember.KeyType keyType = FederationMember.KeyType.MST; + byte[] expectedPublicKey = proposedFederation.getMembers().get(0).getPublicKey(keyType).getPubKey(true); + storageProvider.setProposedFederation(proposedFederation); + + // Act + Optional actualPublicKey = federationSupport.getProposedFederatorPublicKeyOfType(0, keyType); + + // Assert + assertTrue(actualPublicKey.isPresent()); + assertArrayEquals(expectedPublicKey, actualPublicKey.get()); + } + private List getRskPublicKeysFromFederationMembers(List members) { return members.stream() .map(FederationMember::getRskPublicKey) From fae9175795b9a79094ee4c334cc120fe1466d107 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:52:45 +0000 Subject: [PATCH 26/44] refactor(federation): tidy up federation builder --- .../federation/P2shErpFederationBuilder.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationBuilder.java b/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationBuilder.java index fe247efbafe..4821c8531d6 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationBuilder.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/P2shErpFederationBuilder.java @@ -5,11 +5,9 @@ import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.federation.constants.FederationMainNetConstants; import org.ethereum.crypto.ECKey; - import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.IntStream; public class P2shErpFederationBuilder { @@ -96,18 +94,21 @@ public ErpFederation build() { private List getFederationMembers() { if (membersRskPublicKeys.isEmpty()) { - membersRskPublicKeys = membersBtcPublicKeys.stream() - .map(btcKey -> ECKey.fromPublicOnly(btcKey.getPubKey())) - .collect(Collectors.toList()); + this.membersRskPublicKeys = membersBtcPublicKeys.stream() + .map(BtcECKey::getPubKey) + .map(ECKey::fromPublicOnly) + .toList(); } + if (membersMstPublicKeys.isEmpty()) { - membersMstPublicKeys = new ArrayList<>(membersRskPublicKeys); + this.membersMstPublicKeys = new ArrayList<>(membersRskPublicKeys); } - return IntStream.range(0, membersBtcPublicKeys.size()).mapToObj(i -> new FederationMember( - membersBtcPublicKeys.get(i), - membersRskPublicKeys.get(i), - membersMstPublicKeys.get(i) - )).collect(Collectors.toList()); + return IntStream.range(0, membersBtcPublicKeys.size()) + .mapToObj(i -> new FederationMember( + membersBtcPublicKeys.get(i), + membersRskPublicKeys.get(i), + membersMstPublicKeys.get(i))) + .toList(); } } From aaa4fb88b620156dff3ad641e088ad52dc003abc Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:43:38 +0000 Subject: [PATCH 27/44] feat(federation): add more unit tests per key type when empty federation --- .../co/rsk/peg/federation/FederationSupportImplTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java index 53fad863964..788108b3688 100644 --- a/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/federation/FederationSupportImplTest.java @@ -48,6 +48,7 @@ import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; class FederationSupportImplTest { @@ -2309,10 +2310,11 @@ void getProposedFederationCreationBlockNumber_whenStorageProviderReturnsProposed assertEquals(creationBlockNumber, actualCreationBlockNumber.get()); } - @Test - void getProposedFederatorPublicKeyOfType_whenFederationIsEmpty_shouldReturnEmpty() { + @ParameterizedTest + @EnumSource(FederationMember.KeyType.class) + void getProposedFederatorPublicKeyOfType_whenFederationIsEmpty_shouldReturnEmpty(FederationMember.KeyType keyType) { // Act - Optional actualPublicKey = federationSupport.getProposedFederatorPublicKeyOfType(0, FederationMember.KeyType.BTC); + Optional actualPublicKey = federationSupport.getProposedFederatorPublicKeyOfType(0, keyType); // Assert assertFalse(actualPublicKey.isPresent()); From e0f52f5dc225a569c3da5700549dbb0217a2db2e Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:14:50 +0000 Subject: [PATCH 28/44] feat(federation): add javadoc for getProposedFederationCreationTime in FederationSupport --- .../co/rsk/peg/federation/FederationSupportImpl.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 5ad5ef92efc..7208e041391 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -404,6 +404,18 @@ public Optional getProposedFederationSize() { .map(Federation::getSize); } + /** + * Retrieves the creation time of the proposed federation, if available. + * + *

+ * This method checks if a proposed federation exists and, if present, + * returns the time at which it was created. The proposed federation is + * typically one that is awaiting approval or activation. + *

+ * + * @return an {@link Optional} containing the {@link Instant} of the proposed federation's creation, + * or an empty {@link Optional} if no proposed federation exists. + */ @Override public Optional getProposedFederationCreationTime() { return getProposedFederation() From 2e405e63fd71904682b6d13abe7373229af48d83 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:15:15 +0000 Subject: [PATCH 29/44] feat(peg): add getProposedFederationCreationTime Bridge method feat(peg): add unit tests for getProposedFederationCreationTime Bridge method refactor(federation): move javadocs --- .../src/main/java/co/rsk/peg/Bridge.java | 20 +++++++++++ .../main/java/co/rsk/peg/BridgeMethods.java | 12 +++++++ .../main/java/co/rsk/peg/BridgeSupport.java | 4 +++ .../rsk/peg/federation/FederationSupport.java | 30 ++++++++++++++++ .../peg/federation/FederationSupportImpl.java | 24 ------------- .../java/co/rsk/peg/BridgeSupportTest.java | 23 +++++++++++++ .../src/test/java/co/rsk/peg/BridgeTest.java | 34 +++++++++++++++++++ 7 files changed, 123 insertions(+), 24 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index 5d1e9d3a17b..338fb8ca7c1 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -1061,6 +1061,26 @@ public String getProposedFederationAddress(Object[] args) { .orElse(""); } + /** + * Retrieves the creation time of the proposed federation in milliseconds since the epoch. + * + *

+ * This method checks if a proposed federation exists and returns its creation time in + * milliseconds since the Unix epoch. If no proposed federation exists, it returns -1. + *

+ * + * @param args unused arguments for this method (can be null or empty). + * @return the creation time of the proposed federation in milliseconds since the epoch, + * or -1 if no proposed federation exists. + */ + public Long getProposedFederationCreationTime(Object[] args) { + logger.trace("getProposedFederationCreationTime"); + + return bridgeSupport.getProposedFederationCreationTime() + .map(Instant::toEpochMilli) + .orElse(-1L); + } + public Integer getLockWhitelistSize(Object[] args) { logger.trace("getLockWhitelistSize"); diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java index c5f12fdbd5a..b00ada24404 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java @@ -466,6 +466,18 @@ public enum BridgeMethods { fixedPermission(true), CallTypeHelper.ALLOW_STATIC_CALL ), + GET_PROPOSED_FEDERATION_CREATION_TIME( + CallTransaction.Function.fromSignature( + "getProposedFederationCreationTime", + new String[]{}, + new String[]{ "int256" } + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getProposedFederationCreationTime, + activations -> activations.isActive(RSKIP419), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL + ), GET_STATE_FOR_BTC_RELEASE_CLIENT( CallTransaction.Function.fromSignature( "getStateForBtcReleaseClient", 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 a22ca0e35cf..e3e2de1a25f 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -2251,6 +2251,10 @@ public Optional
getProposedFederationAddress() { return federationSupport.getProposedFederationAddress(); } + public Optional getProposedFederationCreationTime() { + return federationSupport.getProposedFederationCreationTime(); + } + public Integer getLockWhitelistSize() { return whitelistSupport.getLockWhitelistSize(); } diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index acebc836cea..26e3197ccaa 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -47,10 +47,40 @@ public interface FederationSupport { byte[] getPendingFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); Optional getProposedFederation(); + + /** + * Retrieves the Bitcoin address of the proposed federation, if it exists. + * + *

+ * This method checks if there is a proposed federation available and + * returns its associated Bitcoin address. The proposed federation is typically + * a federation that is awaiting approval or activation. + *

+ * + * @return an {@link Optional} containing the Bitcoin {@link Address} of the proposed federation, + * or an empty {@link Optional} if no proposed federation is available. + */ Optional
getProposedFederationAddress(); + Optional getProposedFederationSize(); + + /** + * Retrieves the creation time of the proposed federation, if available. + * + *

+ * This method checks if a proposed federation exists and, if present, + * returns the time at which it was created. The proposed federation is + * typically one that is awaiting approval or activation. + *

+ * + * @return an {@link Optional} containing the {@link Instant} of the + * proposed federation's creation, or an empty {@link Optional} + * if no proposed federation exists. + */ Optional getProposedFederationCreationTime(); + Optional getProposedFederationCreationBlockNumber(); + Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); int voteFederationChange( diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 7208e041391..ba1aa28f499 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -374,18 +374,6 @@ public Optional getProposedFederatorPublicKeyOfType(int index, Federatio .map(members -> getFederationMemberPublicKeyOfType(members, index, keyType, "Proposed Federator")); } - /** - * Retrieves the Bitcoin address of the proposed federation, if it exists. - * - *

- * This method checks if there is a proposed federation available and - * returns its associated Bitcoin address. The proposed federation is typically - * a federation that is awaiting approval or activation. - *

- * - * @return an {@link Optional} containing the Bitcoin {@link Address} of the proposed federation, - * or an empty {@link Optional} if no proposed federation is available. - */ @Override public Optional
getProposedFederationAddress() { return getProposedFederation() @@ -404,18 +392,6 @@ public Optional getProposedFederationSize() { .map(Federation::getSize); } - /** - * Retrieves the creation time of the proposed federation, if available. - * - *

- * This method checks if a proposed federation exists and, if present, - * returns the time at which it was created. The proposed federation is - * typically one that is awaiting approval or activation. - *

- * - * @return an {@link Optional} containing the {@link Instant} of the proposed federation's creation, - * or an empty {@link Optional} if no proposed federation exists. - */ @Override public Optional getProposedFederationCreationTime() { return getProposedFederation() diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java index 654ef458d6f..67830efba55 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -536,6 +536,29 @@ void getProposedFederationAddress_whenProposedFederationExists_shouldReturnAddre assertEquals(expectedAddress, actualProposedFederationAddress.get()); } + @Test + void getProposedFederationCreationTime_whenBridgeSupportReturnsEmpty_shouldReturnEmpty() { + // Act + var actualProposedFederationCreationTime = bridgeSupport.getProposedFederationCreationTime(); + + // Assert + assertFalse(actualProposedFederationCreationTime.isPresent()); + } + + @Test + void getProposedFederationCreationTime_whenProposedFederationExists_shouldReturnCreationTime() { + // Arrange + var expectedCreationTime = federation.getCreationTime(); + when(federationSupport.getProposedFederationCreationTime()).thenReturn(Optional.of(expectedCreationTime)); + + // Act + var actualProposedFederationCreationTime = bridgeSupport.getProposedFederationCreationTime(); + + // Assert + assertTrue(actualProposedFederationCreationTime.isPresent()); + assertEquals(expectedCreationTime, actualProposedFederationCreationTime.get()); + } + @Test void voteFederationChange() { Transaction tx = mock(Transaction.class); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java index 58e2a24a7e0..f4d9e7cec81 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java @@ -2266,6 +2266,40 @@ void getProposedFederationAddress(MessageCall.MsgType msgType, ActivationConfig } } + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getProposedFederationCreationTime(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Instant expectedCreationTime = Instant.EPOCH; + when(bridgeSupportMock.getProposedFederationCreationTime()).thenReturn(Optional.of(expectedCreationTime)); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PROPOSED_FEDERATION_CREATION_TIME.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP419, 0)) { + if (!(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock).getProposedFederationCreationTime(); + } + } else { + // Pre RSKIP419 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + @ParameterizedTest() @MethodSource("msgTypesAndActivations") void getStateForBtcReleaseClient(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { From 7825529473bf6d4c307199a673f8806094cdf95f Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:35:35 +0000 Subject: [PATCH 30/44] feat(federation): add javadoc for getProposedFederationCreationBlockNumber in FederationSupport --- .../co/rsk/peg/federation/FederationSupportImpl.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index ba1aa28f499..2f872020b35 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -380,6 +380,18 @@ public Optional
getProposedFederationAddress() { .map(Federation::getAddress); } + /** + * Retrieves the block number at which the proposed federation was created, if available. + * + *

+ * This method checks if there is a proposed federation and, if present, + * returns the block number during which it was created. The proposed federation + * is typically a federation awaiting approval or activation. + *

+ * + * @return an {@link Optional} containing the block number of the proposed federation's creation, + * or an empty {@link Optional} if no proposed federation exists. + */ @Override public Optional getProposedFederationCreationBlockNumber() { return getProposedFederation() From 6e76150b6fefb56284d6f494b4839ca6b17160a4 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:35:47 +0000 Subject: [PATCH 31/44] feat(peg): add getProposedFederationCreationBlockNumber Bridge Method feat(peg): add unit tests for getProposedFederationCreationBlockNumber Bridge Method refactor(federation): move javadocs --- .../src/main/java/co/rsk/peg/Bridge.java | 24 +++- .../main/java/co/rsk/peg/BridgeMethods.java | 132 ++++++++++-------- .../main/java/co/rsk/peg/BridgeSupport.java | 4 + .../rsk/peg/federation/FederationSupport.java | 19 ++- .../peg/federation/FederationSupportImpl.java | 12 -- .../java/co/rsk/peg/BridgeSupportTest.java | 23 +++ .../src/test/java/co/rsk/peg/BridgeTest.java | 34 +++++ 7 files changed, 172 insertions(+), 76 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index 338fb8ca7c1..5e700d86201 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -33,6 +33,7 @@ import co.rsk.peg.vote.ABICallSpec; import co.rsk.peg.bitcoin.MerkleBranch; import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationChangeResponseCode; import co.rsk.peg.federation.FederationMember; import co.rsk.peg.flyover.FlyoverTxResponseCodes; import co.rsk.peg.utils.BtcTransactionFormatUtils; @@ -1075,12 +1076,33 @@ public String getProposedFederationAddress(Object[] args) { */ public Long getProposedFederationCreationTime(Object[] args) { logger.trace("getProposedFederationCreationTime"); - + return bridgeSupport.getProposedFederationCreationTime() .map(Instant::toEpochMilli) .orElse(-1L); } + /** + * Retrieves the block number of the proposed federation's creation. + * + *

+ * This method checks if a proposed federation exists and returns the block number at which it was created. + * If no proposed federation exists, it returns the default code defined in + * {@link FederationChangeResponseCode#FEDERATION_NON_EXISTENT}. + *

+ * + * @param args unused arguments for this method (can be null or empty). + * @return the block number of the proposed federation's creation, or + * the code from {@link FederationChangeResponseCode#FEDERATION_NON_EXISTENT} + * if no proposed federation exists. + */ + public long getProposedFederationCreationBlockNumber(Object[] args) { + logger.trace("getProposedFederationCreationBlockNumber"); + + return bridgeSupport.getProposedFederationCreationBlockNumber() + .orElse((long) FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode()); + } + public Integer getLockWhitelistSize(Object[] args) { logger.trace("getLockWhitelistSize"); diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java index b00ada24404..999a245dddb 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java @@ -353,9 +353,9 @@ public enum BridgeMethods { ), GET_PENDING_FEDERATOR_PUBLIC_KEY( CallTransaction.Function.fromSignature( - "getPendingFederatorPublicKey", - new String[]{"int256"}, - new String[]{"bytes"} + "getPendingFederatorPublicKey", + new String[]{"int256"}, + new String[]{"bytes"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getPendingFederatorPublicKey, @@ -365,9 +365,9 @@ public enum BridgeMethods { ), GET_PENDING_FEDERATOR_PUBLIC_KEY_OF_TYPE( CallTransaction.Function.fromSignature( - "getPendingFederatorPublicKeyOfType", - new String[]{"int256", "string"}, - new String[]{"bytes"} + "getPendingFederatorPublicKeyOfType", + new String[]{"int256", "string"}, + new String[]{"bytes"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getPendingFederatorPublicKeyOfType, @@ -377,9 +377,9 @@ public enum BridgeMethods { ), GET_RETIRING_FEDERATION_ADDRESS( CallTransaction.Function.fromSignature( - "getRetiringFederationAddress", - new String[]{}, - new String[]{"string"} + "getRetiringFederationAddress", + new String[]{}, + new String[]{"string"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getRetiringFederationAddress, @@ -388,9 +388,9 @@ public enum BridgeMethods { ), GET_RETIRING_FEDERATION_CREATION_BLOCK_NUMBER( CallTransaction.Function.fromSignature( - "getRetiringFederationCreationBlockNumber", - new String[]{}, - new String[]{"int256"} + "getRetiringFederationCreationBlockNumber", + new String[]{}, + new String[]{"int256"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getRetiringFederationCreationBlockNumber, @@ -399,9 +399,9 @@ public enum BridgeMethods { ), GET_RETIRING_FEDERATION_CREATION_TIME( CallTransaction.Function.fromSignature( - "getRetiringFederationCreationTime", - new String[]{}, - new String[]{"int256"} + "getRetiringFederationCreationTime", + new String[]{}, + new String[]{"int256"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getRetiringFederationCreationTime, @@ -410,9 +410,9 @@ public enum BridgeMethods { ), GET_RETIRING_FEDERATION_SIZE( CallTransaction.Function.fromSignature( - "getRetiringFederationSize", - new String[]{}, - new String[]{"int256"} + "getRetiringFederationSize", + new String[]{}, + new String[]{"int256"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getRetiringFederationSize, @@ -421,9 +421,9 @@ public enum BridgeMethods { ), GET_RETIRING_FEDERATION_THRESHOLD( CallTransaction.Function.fromSignature( - "getRetiringFederationThreshold", - new String[]{}, - new String[]{"int256"} + "getRetiringFederationThreshold", + new String[]{}, + new String[]{"int256"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getRetiringFederationThreshold, @@ -432,9 +432,9 @@ public enum BridgeMethods { ), GET_RETIRING_FEDERATOR_PUBLIC_KEY( CallTransaction.Function.fromSignature( - "getRetiringFederatorPublicKey", - new String[]{"int256"}, - new String[]{"bytes"} + "getRetiringFederatorPublicKey", + new String[]{"int256"}, + new String[]{"bytes"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getRetiringFederatorPublicKey, @@ -444,9 +444,9 @@ public enum BridgeMethods { ), GET_RETIRING_FEDERATOR_PUBLIC_KEY_OF_TYPE( CallTransaction.Function.fromSignature( - "getRetiringFederatorPublicKeyOfType", - new String[]{"int256", "string"}, - new String[]{"bytes"} + "getRetiringFederatorPublicKeyOfType", + new String[]{"int256", "string"}, + new String[]{"bytes"} ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getRetiringFederatorPublicKeyOfType, @@ -456,9 +456,9 @@ public enum BridgeMethods { ), GET_PROPOSED_FEDERATION_ADDRESS( CallTransaction.Function.fromSignature( - "getProposedFederationAddress", - new String[]{}, - new String[]{ "string" } + "getProposedFederationAddress", + new String[]{}, + new String[]{ "string" } ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getProposedFederationAddress, @@ -468,9 +468,9 @@ public enum BridgeMethods { ), GET_PROPOSED_FEDERATION_CREATION_TIME( CallTransaction.Function.fromSignature( - "getProposedFederationCreationTime", - new String[]{}, - new String[]{ "int256" } + "getProposedFederationCreationTime", + new String[]{}, + new String[]{ "int256" } ), fixedCost(3000L), (BridgeMethodExecutorTyped) Bridge::getProposedFederationCreationTime, @@ -478,11 +478,23 @@ public enum BridgeMethods { fixedPermission(true), CallTypeHelper.ALLOW_STATIC_CALL ), + GET_PROPOSED_FEDERATION_CREATION_BLOCK_NUMBER( + CallTransaction.Function.fromSignature( + "getProposedFederationCreationBlockNumber", + new String[]{}, + new String[]{ "int256" } + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getProposedFederationCreationBlockNumber, + activations -> activations.isActive(RSKIP419), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL + ), GET_STATE_FOR_BTC_RELEASE_CLIENT( CallTransaction.Function.fromSignature( - "getStateForBtcReleaseClient", - new String[]{}, - new String[]{"bytes"} + "getStateForBtcReleaseClient", + new String[]{}, + new String[]{"bytes"} ), fixedCost(4000L), (BridgeMethodExecutorTyped) Bridge::getStateForBtcReleaseClient, @@ -491,9 +503,9 @@ public enum BridgeMethods { ), GET_STATE_FOR_SVP_CLIENT( CallTransaction.Function.fromSignature( - "getStateForSvpClient", - new String[]{}, - new String[]{"bytes"} + "getStateForSvpClient", + new String[]{}, + new String[]{"bytes"} ), fixedCost(4000L), // TODO: check fixed cost value (BridgeMethodExecutorTyped) Bridge::getStateForSvpClient, @@ -503,9 +515,9 @@ public enum BridgeMethods { ), GET_STATE_FOR_DEBUGGING( CallTransaction.Function.fromSignature( - "getStateForDebugging", - new String[]{}, - new String[]{"bytes"} + "getStateForDebugging", + new String[]{}, + new String[]{"bytes"} ), fixedCost(3_000_000L), (BridgeMethodExecutorTyped) Bridge::getStateForDebugging, @@ -514,9 +526,9 @@ public enum BridgeMethods { ), GET_LOCKING_CAP( CallTransaction.Function.fromSignature( - "getLockingCap", - new String[]{}, - new String[]{"int256"} + "getLockingCap", + new String[]{}, + new String[]{"int256"} ), fixedCost(3_000L), (BridgeMethodExecutorTyped) Bridge::getLockingCap, @@ -526,9 +538,9 @@ public enum BridgeMethods { ), GET_ACTIVE_POWPEG_REDEEM_SCRIPT( CallTransaction.Function.fromSignature( - "getActivePowpegRedeemScript", - new String[]{}, - new String[]{"bytes"} + "getActivePowpegRedeemScript", + new String[]{}, + new String[]{"bytes"} ), fixedCost(30_000L), (BridgeMethodExecutorTyped) Bridge::getActivePowpegRedeemScript, @@ -538,9 +550,9 @@ public enum BridgeMethods { ), GET_ACTIVE_FEDERATION_CREATION_BLOCK_HEIGHT( CallTransaction.Function.fromSignature( - "getActiveFederationCreationBlockHeight", - new String[]{}, - new String[]{"uint256"} + "getActiveFederationCreationBlockHeight", + new String[]{}, + new String[]{"uint256"} ), fixedCost(3_000L), (BridgeMethodExecutorTyped) Bridge::getActiveFederationCreationBlockHeight, @@ -550,9 +562,9 @@ public enum BridgeMethods { ), INCREASE_LOCKING_CAP( CallTransaction.Function.fromSignature( - "increaseLockingCap", - new String[]{"int256"}, - new String[]{"bool"} + "increaseLockingCap", + new String[]{"int256"}, + new String[]{"bool"} ), fixedCost(8_000L), (BridgeMethodExecutorTyped) Bridge::increaseLockingCap, @@ -561,9 +573,9 @@ public enum BridgeMethods { ), IS_BTC_TX_HASH_ALREADY_PROCESSED( CallTransaction.Function.fromSignature( - "isBtcTxHashAlreadyProcessed", - new String[]{"string"}, - new String[]{"bool"} + "isBtcTxHashAlreadyProcessed", + new String[]{"string"}, + new String[]{"bool"} ), fixedCost(23000L), (BridgeMethodExecutorTyped) Bridge::isBtcTxHashAlreadyProcessed, @@ -572,9 +584,9 @@ public enum BridgeMethods { ), RECEIVE_HEADERS( CallTransaction.Function.fromSignature( - "receiveHeaders", - new String[]{"bytes[]"}, - new String[]{} + "receiveHeaders", + new String[]{"bytes[]"}, + new String[]{} ), fromMethod(Bridge::receiveHeadersGetCost), Bridge.executeIfElse( 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 e3e2de1a25f..289593e5707 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -2255,6 +2255,10 @@ public Optional getProposedFederationCreationTime() { return federationSupport.getProposedFederationCreationTime(); } + public Optional getProposedFederationCreationBlockNumber() { + return federationSupport.getProposedFederationCreationBlockNumber(); + } + public Integer getLockWhitelistSize() { return whitelistSupport.getLockWhitelistSize(); } diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index 26e3197ccaa..9a17adcf160 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -52,11 +52,11 @@ public interface FederationSupport { * Retrieves the Bitcoin address of the proposed federation, if it exists. * *

- * This method checks if there is a proposed federation available and - * returns its associated Bitcoin address. The proposed federation is typically + * This method checks if there is a proposed federation available and + * returns its associated Bitcoin address. The proposed federation is typically * a federation that is awaiting approval or activation. *

- * + * * @return an {@link Optional} containing the Bitcoin {@link Address} of the proposed federation, * or an empty {@link Optional} if no proposed federation is available. */ @@ -79,6 +79,19 @@ public interface FederationSupport { */ Optional getProposedFederationCreationTime(); + /** + * Retrieves the block number at which the proposed federation was created, if available. + * + *

+ * This method checks if there is a proposed federation and, if present, + * returns the block number during which it was created. The proposed federation + * is typically a federation awaiting approval or activation. + *

+ * + * @return an {@link Optional} containing the block number of the proposed + * federation's creation, or an empty {@link Optional} if no proposed + * federation exists. + */ Optional getProposedFederationCreationBlockNumber(); Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 2f872020b35..ba1aa28f499 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -380,18 +380,6 @@ public Optional
getProposedFederationAddress() { .map(Federation::getAddress); } - /** - * Retrieves the block number at which the proposed federation was created, if available. - * - *

- * This method checks if there is a proposed federation and, if present, - * returns the block number during which it was created. The proposed federation - * is typically a federation awaiting approval or activation. - *

- * - * @return an {@link Optional} containing the block number of the proposed federation's creation, - * or an empty {@link Optional} if no proposed federation exists. - */ @Override public Optional getProposedFederationCreationBlockNumber() { return getProposedFederation() diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java index 67830efba55..d02bf126d45 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -559,6 +559,29 @@ void getProposedFederationCreationTime_whenProposedFederationExists_shouldReturn assertEquals(expectedCreationTime, actualProposedFederationCreationTime.get()); } + @Test + void getProposedFederationCreationBlockNumber_whenBridgeSupportReturnsEmpty_shouldReturnEmpty() { + // Act + var actualProposedFederationCreationBlockNumber = bridgeSupport.getProposedFederationCreationBlockNumber(); + + // Assert + assertFalse(actualProposedFederationCreationBlockNumber.isPresent()); + } + + @Test + void getProposedFederationCreationBlockNumber_whenProposedFederationExists_shouldReturnCreationBlockNumber() { + // Arrange + var expectedCreationBlockNumber = federation.getCreationBlockNumber(); + when(federationSupport.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(expectedCreationBlockNumber)); + + // Act + var actualProposedFederationCreationBlockNumber = bridgeSupport.getProposedFederationCreationBlockNumber(); + + // Assert + assertTrue(actualProposedFederationCreationBlockNumber.isPresent()); + assertEquals(expectedCreationBlockNumber, actualProposedFederationCreationBlockNumber.get()); + } + @Test void voteFederationChange() { Transaction tx = mock(Transaction.class); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java index f4d9e7cec81..77d6df5afb1 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java @@ -2300,6 +2300,40 @@ void getProposedFederationCreationTime(MessageCall.MsgType msgType, ActivationCo } } + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getProposedFederationCreationBlockNumber(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + long expectedCreationBlockNumber = 123456L; + when(bridgeSupportMock.getProposedFederationCreationBlockNumber()).thenReturn(Optional.of(expectedCreationBlockNumber)); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PROPOSED_FEDERATION_CREATION_BLOCK_NUMBER.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP419, 0)) { + if (!(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock).getProposedFederationCreationBlockNumber(); + } + } else { + // Pre RSKIP419 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + @ParameterizedTest() @MethodSource("msgTypesAndActivations") void getStateForBtcReleaseClient(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { From 26ef5b55d88dbe69d29184ae8ab8488b3279d7e1 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:05:49 +0000 Subject: [PATCH 32/44] feat(federation): add javadoc for getProposedFederationSize in FederationSupport --- .../co/rsk/peg/federation/FederationSupportImpl.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index ba1aa28f499..6dc6573f19e 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -386,6 +386,17 @@ public Optional getProposedFederationCreationBlockNumber() { .map(Federation::getCreationBlockNumber); } + /** + * Retrieves the size of the proposed federation, if it exists. + * + *

+ * This method checks if a proposed federation is available and returns the number of members + * in the proposed federation. If no proposed federation exists, it returns an empty {@link Optional}. + *

+ * + * @return an {@link Optional} containing the size of the proposed federation (i.e., the number of members), + * or an empty {@link Optional} if no proposed federation is available. + */ @Override public Optional getProposedFederationSize() { return getProposedFederation() From af872b314c59dea64428f71727c0ba4af5d0c49f Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:06:08 +0000 Subject: [PATCH 33/44] feat(peg): add getProposedFederationSize Bridge Method feat(peg): add unit tests for getProposedFederationSize Bridge Method refactor(federation): move javadocs --- .../src/main/java/co/rsk/peg/Bridge.java | 20 +++++++++++ .../main/java/co/rsk/peg/BridgeMethods.java | 12 +++++++ .../main/java/co/rsk/peg/BridgeSupport.java | 4 +++ .../rsk/peg/federation/FederationSupport.java | 12 +++++++ .../peg/federation/FederationSupportImpl.java | 11 ------ .../java/co/rsk/peg/BridgeSupportTest.java | 23 +++++++++++++ .../src/test/java/co/rsk/peg/BridgeTest.java | 34 +++++++++++++++++++ 7 files changed, 105 insertions(+), 11 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index 5e700d86201..67e9e55e777 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -1062,6 +1062,26 @@ public String getProposedFederationAddress(Object[] args) { .orElse(""); } + /** + * Retrieves the size of the proposed federation, if it exists. + * + *

+ * This method returns the number of members in the proposed federation. If no proposed federation exists, + * it returns a default response code {@link FederationChangeResponseCode#FEDERATION_NON_EXISTENT} that indicates + * the federation does not exist. + *

+ * + * @param args unused arguments for this method (can be null or empty). + * @return the size of the proposed federation (number of members), or the default code from + * {@link FederationChangeResponseCode#FEDERATION_NON_EXISTENT} if no proposed federation is available. + */ + public int getProposedFederationSize(Object[] args) { + logger.trace("getProposedFederationSize"); + + return bridgeSupport.getProposedFederationSize() + .orElse(FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode()); + } + /** * Retrieves the creation time of the proposed federation in milliseconds since the epoch. * diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java index 999a245dddb..5bd6bdc3c87 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java @@ -466,6 +466,18 @@ public enum BridgeMethods { fixedPermission(true), CallTypeHelper.ALLOW_STATIC_CALL ), + GET_PROPOSED_FEDERATION_SIZE( + CallTransaction.Function.fromSignature( + "getProposedFederationSize", + new String[]{}, + new String[]{ "int256" } + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getProposedFederationSize, + activations -> activations.isActive(RSKIP419), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL + ), GET_PROPOSED_FEDERATION_CREATION_TIME( CallTransaction.Function.fromSignature( "getProposedFederationCreationTime", 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 289593e5707..4d6a7631bd3 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -2251,6 +2251,10 @@ public Optional
getProposedFederationAddress() { return federationSupport.getProposedFederationAddress(); } + public Optional getProposedFederationSize() { + return federationSupport.getProposedFederationSize(); + } + public Optional getProposedFederationCreationTime() { return federationSupport.getProposedFederationCreationTime(); } diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index 9a17adcf160..bd0f9a67581 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -62,6 +62,18 @@ public interface FederationSupport { */ Optional
getProposedFederationAddress(); + /** + * Retrieves the size of the proposed federation, if it exists. + * + *

+ * This method checks if a proposed federation is available and returns the number of members + * in the proposed federation. If no proposed federation exists, it returns an empty {@link Optional}. + *

+ * + * @return an {@link Optional} containing the size of the proposed federation + * (i.e., the number of members), or an empty {@link Optional} if no + * proposed federation is available. + */ Optional getProposedFederationSize(); /** diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java index 6dc6573f19e..ba1aa28f499 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupportImpl.java @@ -386,17 +386,6 @@ public Optional getProposedFederationCreationBlockNumber() { .map(Federation::getCreationBlockNumber); } - /** - * Retrieves the size of the proposed federation, if it exists. - * - *

- * This method checks if a proposed federation is available and returns the number of members - * in the proposed federation. If no proposed federation exists, it returns an empty {@link Optional}. - *

- * - * @return an {@link Optional} containing the size of the proposed federation (i.e., the number of members), - * or an empty {@link Optional} if no proposed federation is available. - */ @Override public Optional getProposedFederationSize() { return getProposedFederation() diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java index d02bf126d45..9d8177ca3ec 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -536,6 +536,29 @@ void getProposedFederationAddress_whenProposedFederationExists_shouldReturnAddre assertEquals(expectedAddress, actualProposedFederationAddress.get()); } + @Test + void getProposedFederationSize_whenBridgeSupportReturnsEmpty_shouldReturnEmpty() { + // Act + var actualProposedFederationSize = bridgeSupport.getProposedFederationSize(); + + // Assert + assertFalse(actualProposedFederationSize.isPresent()); + } + + @Test + void getProposedFederationSize_whenProposedFederationExists_shouldReturnSize() { + // Arrange + var expectedSize = federation.getSize(); + when(federationSupport.getProposedFederationSize()).thenReturn(Optional.of(expectedSize)); + + // Act + var actualProposedFederationSize = bridgeSupport.getProposedFederationSize(); + + // Assert + assertTrue(actualProposedFederationSize.isPresent()); + assertEquals(expectedSize, actualProposedFederationSize.get()); + } + @Test void getProposedFederationCreationTime_whenBridgeSupportReturnsEmpty_shouldReturnEmpty() { // Act diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java index 77d6df5afb1..a9d24493c48 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java @@ -2266,6 +2266,40 @@ void getProposedFederationAddress(MessageCall.MsgType msgType, ActivationConfig } } + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getProposedFederationSize(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Integer expectedSize = 9; + when(bridgeSupportMock.getProposedFederationSize()).thenReturn(Optional.of(expectedSize)); + + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PROPOSED_FEDERATION_SIZE.getFunction(); + byte[] data = function.encode(); + + if (activationConfig.isActive(ConsensusRule.RSKIP419, 0)) { + if (!(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock).getProposedFederationSize(); + } + } else { + // Pre RSKIP419 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + @ParameterizedTest() @MethodSource("msgTypesAndActivations") void getProposedFederationCreationTime(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { From 25e7437165dd14538298e0e3da8bb1004a82de07 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 26 Sep 2024 16:50:13 -0300 Subject: [PATCH 34/44] Rename method that calculates amount to be sent in svp spend tx. Replace redeem scripts being used in tests for some less shady and add tests for public method. Fix typo after rebase --- .../co/rsk/peg/bitcoin/BitcoinUtilsTest.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java index cbaff1473ff..670d5f20d06 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java @@ -1,15 +1,17 @@ package co.rsk.peg.bitcoin; -import static co.rsk.peg.bitcoin.BitcoinUtils.*; +import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromMatchingOutputScript; +import static co.rsk.peg.bitcoin.BitcoinUtils.createBaseP2SHInputScriptThatSpendsFromRedeemScript; +import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; import static org.junit.jupiter.api.Assertions.*; import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.*; import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.constants.BridgeMainNetConstants; -import co.rsk.peg.federation.Federation; -import co.rsk.peg.federation.P2shErpFederationBuilder; -import co.rsk.peg.federation.StandardMultiSigFederationBuilder; +import co.rsk.peg.federation.*; +import java.util.*; +import java.util.stream.Stream; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -18,9 +20,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.*; -import java.util.stream.Stream; - class BitcoinUtilsTest { private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); private static final NetworkParameters btcMainnetParams = bridgeMainnetConstants.getBtcParams(); @@ -418,7 +417,7 @@ void addInputFromOutputSentToScript_withNoMatchingOutputScript_shouldNotAddInput addInputFromMatchingOutputScript(newTransaction, sourceTransaction, anotherOutputScript); // assert - Assertions.assertEquals(0, newTransaction.getInputs().size()); + assertEquals(0, newTransaction.getInputs().size()); } @Test @@ -438,7 +437,7 @@ void addInputFromOutputSentToScript_withMatchingOutputScript_shouldAddInputWithO // assert TransactionInput newTransactionInput = newTransaction.getInput(0); TransactionOutput sourceTransactionOutput = sourceTransaction.getOutput(0); - Assertions.assertEquals(newTransactionInput.getOutpoint().getHash(), sourceTransactionOutput.getParentTransactionHash()); + assertEquals(newTransactionInput.getOutpoint().getHash(), sourceTransactionOutput.getParentTransactionHash()); } @Test @@ -467,7 +466,6 @@ void createBaseP2SHInputScriptThatSpendsFromRedeemScript_shouldCreateExpectedScr for (ScriptChunk chunk : scriptSigChunks.subList(0, redeemScriptChunkIndex)) { // all the other chunks should be zero assertEquals(0, chunk.opcode); } - } @Test @@ -484,8 +482,8 @@ void searchForOutput_whenTheOutputIsThere_shouldReturnOutputSentToExpectedScript Optional transactionOutput = searchForOutput(sourceTransaction.getOutputs(), outputScript); // assert - Assertions.assertTrue(transactionOutput.isPresent()); - Assertions.assertEquals(outputScript, transactionOutput.get().getScriptPubKey()); + assertTrue(transactionOutput.isPresent()); + assertEquals(outputScript, transactionOutput.get().getScriptPubKey()); } @Test @@ -506,6 +504,6 @@ void searchForOutput_whenTheOutputIsNotThere_shouldReturnEmpty() { Optional transactionOutput = searchForOutput(sourceTransaction.getOutputs(), anotherOutputScript); // assert - Assertions.assertFalse(transactionOutput.isPresent()); + assertFalse(transactionOutput.isPresent()); } } From f044914d56463ad4bce44d36fa89e6ad88ba921b Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:46:56 +0000 Subject: [PATCH 35/44] feat(federation): add getProposedFederatorPublicKeyOfType method in FederationSupport feat(federation): add javadoc for getProposedFederatorPublicKeyOfType in FederationSupport --- .../rsk/peg/federation/FederationSupport.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index bd0f9a67581..f2f2969eb52 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -105,7 +105,25 @@ public interface FederationSupport { * federation exists. */ Optional getProposedFederationCreationBlockNumber(); + Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); + /** + * Retrieves the public key of the specified type for a federator at the given index + * from the proposed federation, if it exists. + * + *

+ * This method checks whether a proposed federation is available and retrieves the + * public key for a federator at the specified index. The key type can be of various + * types (e.g., BTC, RSK), depending on the key used by the federation member. + *

+ * + * @param index the zero-based index of the federator in the federation's member list. + * @param keyType the type of public key to retrieve (e.g., {@link FederationMember.KeyType#BTC}). + * @return an {@link Optional} containing the public key as a byte array if the proposed + * federation and federator at the specified index exist, or an empty {@link Optional} + * if no proposed federation or member at the given index is found. + * @throws IndexOutOfBoundsException if the index is out of the federation member list bounds. + */ Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); int voteFederationChange( From 6ca9beca9564b455252666216a89c7a9e63037f5 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:04:10 +0000 Subject: [PATCH 36/44] feat(peg): add getProposedFederatorPublicKeyOfType Bridge method feat(peg): add unit tests for getProposedFederatorPublicKeyOfType Bridge method feat(peg): add index out of bounds test for getting proposed fed pub key --- .../src/main/java/co/rsk/peg/Bridge.java | 37 ++++++++++++ .../main/java/co/rsk/peg/BridgeMethods.java | 12 ++++ .../main/java/co/rsk/peg/BridgeSupport.java | 4 ++ .../java/co/rsk/peg/BridgeSupportTest.java | 58 +++++++++++++++++++ .../src/test/java/co/rsk/peg/BridgeTest.java | 33 +++++++++++ 5 files changed, 144 insertions(+) diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index 67e9e55e777..189efe4a724 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -1123,6 +1123,43 @@ public long getProposedFederationCreationBlockNumber(Object[] args) { .orElse((long) FederationChangeResponseCode.FEDERATION_NON_EXISTENT.getCode()); } + /** + * Retrieves the public key of the proposed federator at the specified index and key type. + * + *

+ * This method extracts the index and key type from the provided arguments, retrieves the + * public key of the proposed federator, and returns it. If no public key is found, an empty byte + * array is returned. + *

+ * + *

+ * The first argument in the {@code args} array is expected to be a {@link BigInteger} representing + * the federator's index. The second argument is expected to be a {@link String} representing + * the key type, which is converted into a {@link FederationMember.KeyType}. + *

+ * + * @param args an array of arguments, where {@code args[0]} is a {@link BigInteger} for the federator's index, + * and {@code args[1]} is a {@link String} for the key type. + * @return a byte array containing the federator's public key, or an empty byte array if not found. + * @throws VMException if an error occurs while processing the key type. + */ + public byte[] getProposedFederatorPublicKeyOfType(Object[] args) throws VMException { + logger.trace("getProposedFederatorPublicKeyOfType"); + + int index = ((BigInteger) args[0]).intValue(); + + FederationMember.KeyType keyType; + try { + keyType = FederationMember.KeyType.byValue((String) args[1]); + } catch (Exception e) { + logger.warn("Exception in getProposedFederatorPublicKeyOfType", e); + throw new VMException("Exception in getProposedFederatorPublicKeyOfType", e); + } + + return bridgeSupport.getProposedFederatorPublicKeyOfType(index, keyType) + .orElse(new byte[]{}); + } + public Integer getLockWhitelistSize(Object[] args) { logger.trace("getLockWhitelistSize"); diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java index 5bd6bdc3c87..62a9881f1bb 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeMethods.java @@ -502,6 +502,18 @@ public enum BridgeMethods { fixedPermission(true), CallTypeHelper.ALLOW_STATIC_CALL ), + GET_PROPOSED_FEDERATOR_PUBLIC_KEY_OF_TYPE( + CallTransaction.Function.fromSignature( + "getProposedFederatorPublicKeyOfType", + new String[]{ "int256", "string" }, + new String[]{ "bytes" } + ), + fixedCost(3000L), + (BridgeMethodExecutorTyped) Bridge::getProposedFederatorPublicKeyOfType, + activations -> activations.isActive(RSKIP419), + fixedPermission(true), + CallTypeHelper.ALLOW_STATIC_CALL + ), GET_STATE_FOR_BTC_RELEASE_CLIENT( CallTransaction.Function.fromSignature( "getStateForBtcReleaseClient", 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 4d6a7631bd3..f26d0ded3af 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -2263,6 +2263,10 @@ public Optional getProposedFederationCreationBlockNumber() { return federationSupport.getProposedFederationCreationBlockNumber(); } + public Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType) { + return federationSupport.getProposedFederatorPublicKeyOfType(index, keyType); + } + public Integer getLockWhitelistSize() { return whitelistSupport.getLockWhitelistSize(); } diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java index 9d8177ca3ec..de2383e6f05 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -63,6 +63,7 @@ import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -536,6 +537,50 @@ void getProposedFederationAddress_whenProposedFederationExists_shouldReturnAddre assertEquals(expectedAddress, actualProposedFederationAddress.get()); } + @ParameterizedTest + @EnumSource(FederationMember.KeyType.class) + void getProposedFederatorPublicKeyOfType_whenBridgeSupportReturnsEmpty_shouldReturnEmpty(FederationMember.KeyType keyType) { + // Arrange + var index = 1; + + // Act + var actualProposedFederatorPublicKey = bridgeSupport.getProposedFederatorPublicKeyOfType(index, keyType); + + // Assert + assertFalse(actualProposedFederatorPublicKey.isPresent()); + } + + @ParameterizedTest + @EnumSource(FederationMember.KeyType.class) + void getProposedFederatorPublicKeyOfType_whenProposedFederationExists_shouldReturnAddress(FederationMember.KeyType keyType) { + var index = 0; + var member = federation.getMembers().get(index); + + // Set up public keys based on the keyType + byte[] expectedPublicKey; + switch (keyType) { + case BTC: + expectedPublicKey = member.getBtcPublicKey().getPubKey(); + when(federationSupport.getProposedFederatorPublicKeyOfType(index, FederationMember.KeyType.BTC)) + .thenReturn(Optional.of(expectedPublicKey)); + break; + case RSK: + expectedPublicKey = member.getRskPublicKey().getPubKey(); + when(federationSupport.getProposedFederatorPublicKeyOfType(index, FederationMember.KeyType.RSK)) + .thenReturn(Optional.of(expectedPublicKey)); + break; + case MST: + expectedPublicKey = member.getMstPublicKey().getPubKey(); + when(federationSupport.getProposedFederatorPublicKeyOfType(index, FederationMember.KeyType.MST)) + .thenReturn(Optional.of(expectedPublicKey)); + break; + default: + throw new IllegalArgumentException("Unknown KeyType"); + } + + assertArrayEquals(expectedPublicKey, bridgeSupport.getProposedFederatorPublicKeyOfType(index, keyType).get()); + } + @Test void getProposedFederationSize_whenBridgeSupportReturnsEmpty_shouldReturnEmpty() { // Act @@ -605,6 +650,19 @@ void getProposedFederationCreationBlockNumber_whenProposedFederationExists_shoul assertEquals(expectedCreationBlockNumber, actualProposedFederationCreationBlockNumber.get()); } + @Test + void getProposedFederatorPublicKeyOfType_whenIndexOutOfBoundsForMemberList_shouldThrowException() { + // Arrange + var index = 1; + var keyType = FederationMember.KeyType.BTC; + when(federationSupport.getProposedFederatorPublicKeyOfType(index, keyType)) + .thenThrow(new IndexOutOfBoundsException()); + + // Act & Assert + assertThrows(IndexOutOfBoundsException.class, + () -> bridgeSupport.getProposedFederatorPublicKeyOfType(index, keyType)); + } + @Test void voteFederationChange() { Transaction tx = mock(Transaction.class); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java index a9d24493c48..9883aca561e 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeTest.java @@ -2368,6 +2368,39 @@ void getProposedFederationCreationBlockNumber(MessageCall.MsgType msgType, Activ } } + @ParameterizedTest() + @MethodSource("msgTypesAndActivations") + void getProposedFederatorPublicKeyOfType(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException { + Transaction rskTxMock = mock(Transaction.class); + doReturn(true).when(rskTxMock).isLocalCallTransaction(); + + int federatorIndex = 1; + KeyType keyType = KeyType.BTC; + BridgeSupport bridgeSupportMock = mock(BridgeSupport.class); + Bridge bridge = bridgeBuilder + .transaction(rskTxMock) + .activationConfig(activationConfig) + .bridgeSupport(bridgeSupportMock) + .msgType(msgType) + .build(); + + CallTransaction.Function function = BridgeMethods.GET_PROPOSED_FEDERATOR_PUBLIC_KEY_OF_TYPE.getFunction(); + byte[] data = function.encode(federatorIndex, keyType.getValue()); + + if (activationConfig.isActive(ConsensusRule.RSKIP419, 0)) { + if (!(msgType.equals(MessageCall.MsgType.CALL) || msgType.equals(MessageCall.MsgType.STATICCALL))) { + // Post arrowhead should fail for any msg type != CALL or STATIC CALL + assertThrows(VMException.class, () -> bridge.execute(data)); + } else { + bridge.execute(data); + verify(bridgeSupportMock).getProposedFederatorPublicKeyOfType(federatorIndex, keyType); + } + } else { + // Pre RSKIP419 this method is not enabled, should fail for all message types + assertThrows(VMException.class, () -> bridge.execute(data)); + } + } + @ParameterizedTest() @MethodSource("msgTypesAndActivations") void getStateForBtcReleaseClient(MessageCall.MsgType msgType, ActivationConfig activationConfig) throws VMException, IOException { From cf7f2b04af78358d4d73d62e4077617b97c1a75f Mon Sep 17 00:00:00 2001 From: Marcos Date: Tue, 8 Oct 2024 13:27:29 -0300 Subject: [PATCH 37/44] Remove duplicated method declaration in FederationSupport --- .../src/main/java/co/rsk/peg/federation/FederationSupport.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java index f2f2969eb52..d6e8ca7522c 100644 --- a/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/federation/FederationSupport.java @@ -105,7 +105,6 @@ public interface FederationSupport { * federation exists. */ Optional getProposedFederationCreationBlockNumber(); - Optional getProposedFederatorPublicKeyOfType(int index, FederationMember.KeyType keyType); /** * Retrieves the public key of the specified type for a federator at the given index From 2a0aafcc2baa5efd630b21341362fa22616102d6 Mon Sep 17 00:00:00 2001 From: Antonio Pancorbo <48168255+apancorb@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:29:31 +0200 Subject: [PATCH 38/44] feat(peg): update test name in BridgeSupportTest --- rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java index de2383e6f05..3aefede4362 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -552,7 +552,7 @@ void getProposedFederatorPublicKeyOfType_whenBridgeSupportReturnsEmpty_shouldRet @ParameterizedTest @EnumSource(FederationMember.KeyType.class) - void getProposedFederatorPublicKeyOfType_whenProposedFederationExists_shouldReturnAddress(FederationMember.KeyType keyType) { + void getProposedFederatorPublicKeyOfType_whenProposedFederationExists_shouldReturnExpectedPublicKey(FederationMember.KeyType keyType) { var index = 0; var member = federation.getMembers().get(index); From e8f58ed26e76f91a06ebd30a62fb715e3237af66 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Thu, 3 Oct 2024 15:07:48 -0300 Subject: [PATCH 39/44] Refactor addSignature as previous step to create addSvpSpendTxSignature method Remove addSvpSpendTxSignature method that would be part of another pr Remove optional logic to verify signatures Catch specific SignatureException Make getDecodedSignatures throw signature exception to handle just that type Avoid using isPresent / get and make code more functional. Align javadoc. Replace times(0) for never(). Change boolean method name to be affirmative. Change method responsibility to be more accurate. Change btcTx name for releaseTx name. Return false when catching exception when signing --- .../main/java/co/rsk/peg/BridgeSupport.java | 271 ++++++++++-------- .../java/co/rsk/peg/bitcoin/BitcoinUtils.java | 7 + .../rsk/peg/utils/BridgeEventLoggerImpl.java | 11 +- .../peg/BridgeSupportAddSignatureTest.java | 8 +- 4 files changed, 171 insertions(+), 126 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 f26d0ded3af..6da7074dd90 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -60,6 +60,7 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; +import java.security.SignatureException; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -996,21 +997,17 @@ private void logUpdateCollections(Transaction rskTx) { } private boolean svpIsOngoing() { - Optional proposedFederationOpt = federationSupport.getProposedFederation(); - - if (!proposedFederationOpt.isPresent()) { - return false; - } - Federation proposedFederation = proposedFederationOpt.get(); - - long validationPeriodEndBlock = proposedFederation.getCreationBlockNumber() - + bridgeConstants.getFederationConstants().getValidationPeriodDurationInBlocks(); - return rskExecutionBlock.getNumber() <= validationPeriodEndBlock; + return federationSupport.getProposedFederation() + .map(Federation::getCreationBlockNumber) + .map(proposedFederationCreationBlockNumber -> + proposedFederationCreationBlockNumber + bridgeConstants.getFederationConstants().getValidationPeriodDurationInBlocks()) + .filter(validationPeriodEndBlock -> rskExecutionBlock.getNumber() <= validationPeriodEndBlock) + .isPresent(); } protected void processSvpFundTransactionUnsigned(Transaction rskTx) throws IOException, InsufficientMoneyException { Optional proposedFederationOpt = federationSupport.getProposedFederation(); - if (!proposedFederationOpt.isPresent()) { + if (proposedFederationOpt.isEmpty()) { String message = "Proposed federation should be present when processing SVP fund transaction."; logger.warn(message); throw new IllegalStateException(message); @@ -1570,15 +1567,50 @@ private void adjustBalancesIfChangeOutputWasDust(BtcTransaction btcTx, Coin sent * The hash for the signature must be calculated with Transaction.SigHash.ALL and anyoneCanPay=false. The signature must be canonical. * If enough signatures were added, ask federators to broadcast the btc release tx. * - * @param federatorPublicKey Federator who is signing - * @param signatures 1 signature per btc tx input - * @param rskTxHash The id of the rsk tx + * @param federatorBtcPublicKey Federator who is signing + * @param signatures 1 signature per btc tx input + * @param rskTxHashSerialized The id of the rsk tx */ - public void addSignature(BtcECKey federatorPublicKey, List signatures, byte[] rskTxHash) throws Exception { + public void addSignature(BtcECKey federatorBtcPublicKey, List signatures, byte[] rskTxHashSerialized) throws Exception { + if (signatures == null || signatures.isEmpty()) { + return; + } + Context.propagate(btcContext); + Keccak256 rskTxHash = new Keccak256(rskTxHashSerialized); + + BtcTransaction releaseTx = provider.getPegoutsWaitingForSignatures().get(rskTxHash); + if (releaseTx == null) { + logger.warn("No tx waiting for signature for hash {}. Probably fully signed already.", rskTxHash); + return; + } + if (!enoughSignatures(releaseTx, signatures)) { + return; + } + + addReleaseSignatures(federatorBtcPublicKey, signatures, rskTxHashSerialized, releaseTx); + } + + private boolean enoughSignatures(BtcTransaction releaseTx, List signatures) { + int inputsSize = releaseTx.getInputs().size(); + int signaturesSize = signatures.size(); + + if (inputsSize != signaturesSize) { + logger.warn("Expected {} signatures but received {}.", inputsSize, signaturesSize); + return false; + } + return true; + } + + private void addReleaseSignatures( + BtcECKey federatorPublicKey, + List signatures, + byte[] rskTxHashSerialized, + BtcTransaction btcTx + ) throws IOException { Optional optionalFederation = getFederationFromPublicKey(federatorPublicKey); - if (!optionalFederation.isPresent()) { + if (optionalFederation.isEmpty()) { logger.warn( "[addSignature] Supplied federator btc public key {} does not belong to any of the federators.", federatorPublicKey @@ -1588,7 +1620,7 @@ public void addSignature(BtcECKey federatorPublicKey, List signatures, b Federation federation = optionalFederation.get(); Optional federationMember = federation.getMemberByBtcPublicKey(federatorPublicKey); - if (!federationMember.isPresent()){ + if (federationMember.isEmpty()){ logger.warn( "[addSignature] Supplied federator btc public key {} doest not match any of the federator member btc public keys {}.", federatorPublicKey, federation.getBtcPublicKeys() @@ -1597,28 +1629,20 @@ public void addSignature(BtcECKey federatorPublicKey, List signatures, b } FederationMember signingFederationMember = federationMember.get(); - BtcTransaction btcTx = provider.getPegoutsWaitingForSignatures().get(new Keccak256(rskTxHash)); - if (btcTx == null) { - logger.warn( - "No tx waiting for signature for hash {}. Probably fully signed already.", - new Keccak256(rskTxHash) - ); - return; - } - if (btcTx.getInputs().size() != signatures.size()) { - logger.warn( - "Expected {} signatures but received {}.", - btcTx.getInputs().size(), - signatures.size() - ); - return; + if (!activations.isActive(ConsensusRule.RSKIP326)) { + eventLogger.logAddSignature(signingFederationMember, btcTx, rskTxHashSerialized); } - if (!activations.isActive(ConsensusRule.RSKIP326)) { - eventLogger.logAddSignature(signingFederationMember, btcTx, rskTxHash); + processSigning(signingFederationMember, signatures, rskTxHashSerialized, btcTx); + + Keccak256 rskTxHash = new Keccak256(rskTxHashSerialized); + if (!BridgeUtils.hasEnoughSignatures(btcContext, btcTx) && logger.isDebugEnabled()) { + logMissingSignatures(btcTx, rskTxHash, federation); + return; } - processSigning(signingFederationMember, signatures, rskTxHash, btcTx, federation); + logReleaseBtc(btcTx, rskTxHashSerialized); + provider.getPegoutsWaitingForSignatures().remove(rskTxHash); } private Optional getFederationFromPublicKey(BtcECKey federatorPublicKey) { @@ -1635,119 +1659,134 @@ private Optional getFederationFromPublicKey(BtcECKey federatorPublic return Optional.empty(); } + private void logMissingSignatures(BtcTransaction btcTx, Keccak256 rskTxHash, Federation federation) { + int missingSignatures = BridgeUtils.countMissingSignatures(btcContext, btcTx); + int neededSignatures = federation.getNumberOfSignaturesRequired(); + int signaturesCount = neededSignatures - missingSignatures; + + logger.debug("Tx {} not yet fully signed. Requires {}/{} signatures but has {}", + rskTxHash, neededSignatures, federation.getSize(), signaturesCount); + } + + private void logReleaseBtc(BtcTransaction btcTx, byte[] rskTxHashSerialized) { + logger.info("Tx fully signed {}. Hex: {}", btcTx, Bytes.of(btcTx.bitcoinSerialize())); + eventLogger.logReleaseBtc(btcTx, rskTxHashSerialized); + } + private void processSigning( FederationMember federatorMember, List signatures, - byte[] rskTxHash, - BtcTransaction btcTx, - Federation federation) throws IOException { + byte[] rskTxHashSerialized, + BtcTransaction btcTx) { - BtcECKey federatorBtcPublicKey = federatorMember.getBtcPublicKey(); // Build input hashes for signatures - int numInputs = btcTx.getInputs().size(); - - List sighashes = new ArrayList<>(); - List txSigs = new ArrayList<>(); - for (int i = 0; i < numInputs; i++) { - TransactionInput txIn = btcTx.getInput(i); - Script inputScript = txIn.getScriptSig(); - List chunks = inputScript.getChunks(); - byte[] program = chunks.get(chunks.size() - 1).data; - Script redeemScript = new Script(program); - sighashes.add(btcTx.hashForSignature(i, redeemScript, BtcTransaction.SigHash.ALL, false)); + List sigHashes = new ArrayList<>(); + for (int i = 0; i < btcTx.getInputs().size(); i++) { + Sha256Hash sigHash = generateSigHashForP2SHInput(btcTx, i); + sigHashes.add(sigHash); } // Verify given signatures are correct before proceeding - for (int i = 0; i < numInputs; i++) { - BtcECKey.ECDSASignature sig; - try { - sig = BtcECKey.ECDSASignature.decodeFromDER(signatures.get(i)); - } catch (RuntimeException e) { - logger.warn( - "Malformed signature for input {} of tx {}: {}", - i, - new Keccak256(rskTxHash), - Bytes.of(signatures.get(i)) - ); - return; - } + BtcECKey federatorBtcPublicKey = federatorMember.getBtcPublicKey(); + List txSigs; + try { + txSigs = getTransactionSignatures(federatorBtcPublicKey, sigHashes, signatures); + } catch (SignatureException e) { + return; + } - Sha256Hash sighash = sighashes.get(i); + // All signatures are correct. Proceed to signing + Keccak256 rskTxHash = new Keccak256(rskTxHashSerialized); + boolean signed = sign(federatorBtcPublicKey, txSigs, sigHashes, rskTxHash, btcTx); - if (!federatorBtcPublicKey.verify(sighash, sig)) { + if (signed && activations.isActive(ConsensusRule.RSKIP326)) { + eventLogger.logAddSignature(federatorMember, btcTx, rskTxHashSerialized); + } + } + + private List getTransactionSignatures(BtcECKey federatorBtcPublicKey, List sigHashes, List signatures) throws SignatureException { + List decodedSignatures = getDecodedSignatures(signatures); + List txSigs = new ArrayList<>(); + + for (int i = 0; i < decodedSignatures.size(); i++) { + BtcECKey.ECDSASignature decodedSignature = decodedSignatures.get(i); + Sha256Hash sigHash = sigHashes.get(i); + + if (!federatorBtcPublicKey.verify(sigHash, decodedSignature)) { logger.warn( "Signature {} {} is not valid for hash {} and public key {}", i, - Bytes.of(sig.encodeToDER()), - sighash, + Bytes.of(decodedSignature.encodeToDER()), + sigHashes, federatorBtcPublicKey ); - return; + throw new SignatureException(); } - TransactionSignature txSig = new TransactionSignature(sig, BtcTransaction.SigHash.ALL, false); - txSigs.add(txSig); + TransactionSignature txSig = new TransactionSignature(decodedSignature, BtcTransaction.SigHash.ALL, false); if (!txSig.isCanonical()) { - logger.warn("Signature {} {} is not canonical.", i, Bytes.of(signatures.get(i))); - return; + logger.warn("Signature {} {} is not canonical.", i, Bytes.of(decodedSignature.encodeToDER())); + throw new SignatureException(); } + txSigs.add(txSig); } + return txSigs; + } - boolean signed = false; + private List getDecodedSignatures(List signatures) throws SignatureException { + List decodedSignatures = new ArrayList<>(); + for (byte[] signature : signatures) { + try { + decodedSignatures.add(BtcECKey.ECDSASignature.decodeFromDER(signature)); + } catch (RuntimeException e) { + int index = signatures.indexOf(signature); + logger.warn("Malformed signature for input {} : {}", index, Bytes.of(signature)); + throw new SignatureException(); + } + } + return decodedSignatures; + } - // All signatures are correct. Proceed to signing - for (int i = 0; i < numInputs; i++) { - Sha256Hash sighash = sighashes.get(i); + private boolean sign( + BtcECKey federatorBtcPublicKey, + List txSigs, + List sigHashes, + Keccak256 rskTxHash, + BtcTransaction btcTx) { + + boolean signed = false; + for (int i = 0; i < sigHashes.size(); i++) { + Sha256Hash sighash = sigHashes.get(i); TransactionInput input = btcTx.getInput(i); Script inputScript = input.getScriptSig(); - boolean alreadySignedByThisFederator = BridgeUtils.isInputSignedByThisFederator( - federatorBtcPublicKey, - sighash, - input); + boolean alreadySignedByThisFederator = + BridgeUtils.isInputSignedByThisFederator(federatorBtcPublicKey, sighash, input); // Sign the input if it wasn't already - if (!alreadySignedByThisFederator) { - try { - int sigIndex = inputScript.getSigInsertionIndex(sighash, federatorBtcPublicKey); - inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex, 1, 1); - input.setScriptSig(inputScript); - logger.debug("Tx input {} for tx {} signed.", i, new Keccak256(rskTxHash)); - signed = true; - } catch (IllegalStateException e) { - Federation retiringFederation = getRetiringFederation(); - if (getActiveFederation().hasBtcPublicKey(federatorBtcPublicKey)) { - logger.debug("A member of the active federation is trying to sign a tx of the retiring one"); - return; - } else if (retiringFederation != null && retiringFederation.hasBtcPublicKey(federatorBtcPublicKey)) { - logger.debug("A member of the retiring federation is trying to sign a tx of the active one"); - return; - } - throw e; - } - } else { - logger.warn("Input {} of tx {} already signed by this federator.", i, new Keccak256(rskTxHash)); + if (alreadySignedByThisFederator) { + logger.warn("Input {} of tx {} already signed by this federator.", i, rskTxHash); break; } - } - if(signed && activations.isActive(ConsensusRule.RSKIP326)) { - eventLogger.logAddSignature(federatorMember, btcTx, rskTxHash); + try { + int sigIndex = inputScript.getSigInsertionIndex(sighash, federatorBtcPublicKey); + inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex, 1, 1); + input.setScriptSig(inputScript); + logger.debug("Tx input {} for tx {} signed.", i, rskTxHash); + signed = true; + } catch (IllegalStateException e) { + Federation retiringFederation = getRetiringFederation(); + if (getActiveFederation().hasBtcPublicKey(federatorBtcPublicKey)) { + logger.debug("A member of the active federation is trying to sign a tx of the retiring one"); + } else if (retiringFederation != null && retiringFederation.hasBtcPublicKey(federatorBtcPublicKey)) { + logger.debug("A member of the retiring federation is trying to sign a tx of the active one"); + } + return false; + } } - if (BridgeUtils.hasEnoughSignatures(btcContext, btcTx)) { - logger.info("Tx fully signed {}. Hex: {}", btcTx, Bytes.of(btcTx.bitcoinSerialize())); - provider.getPegoutsWaitingForSignatures().remove(new Keccak256(rskTxHash)); - - eventLogger.logReleaseBtc(btcTx, rskTxHash); - } else if (logger.isDebugEnabled()) { - int missingSignatures = BridgeUtils.countMissingSignatures(btcContext, btcTx); - int neededSignatures = federation.getNumberOfSignaturesRequired(); - int signaturesCount = neededSignatures - missingSignatures; - - logger.debug("Tx {} not yet fully signed. Requires {}/{} signatures but has {}", - new Keccak256(rskTxHash), neededSignatures, getActiveFederationSize(), signaturesCount); - } + return signed; } /** diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index 86134f5b350..c789d2e8137 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -95,4 +95,11 @@ public static Optional searchForOutput(List output.getScriptPubKey().equals(outputScriptPubKey)) .findFirst(); } + + public static Sha256Hash generateSigHashForP2SHInput(BtcTransaction btcTx, int inputIndex) { + return Optional.ofNullable(btcTx.getInput(inputIndex)) + .flatMap(BitcoinUtils::extractRedeemScriptFromInput) + .map(redeemScript -> btcTx.hashForSignature(inputIndex, redeemScript, BtcTransaction.SigHash.ALL, false)) + .orElseThrow(() -> new IllegalArgumentException("Couldn't extract redeem script from p2sh input")); + } } diff --git a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java index fd387ac76d1..72f811e66c0 100644 --- a/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java +++ b/rskj-core/src/main/java/co/rsk/peg/utils/BridgeEventLoggerImpl.java @@ -31,8 +31,6 @@ import org.ethereum.config.blockchain.upgrades.ConsensusRule; import org.ethereum.core.Block; import org.ethereum.core.CallTransaction; -import org.ethereum.core.SignatureCache; -import org.ethereum.core.Transaction; import org.ethereum.crypto.ECKey; import org.ethereum.util.ByteUtil; import org.ethereum.vm.DataWord; @@ -72,13 +70,14 @@ public void logUpdateCollections(RskAddress sender) { @Override public void logAddSignature(FederationMember federationMember, BtcTransaction btcTx, byte[] rskTxHash) { - ECKey federatorPublicKey = getFederatorPublicKey(federationMember); - String federatorRskAddress = ByteUtil.toHexString(federatorPublicKey.getAddress()); + ECKey federatorRskPublicKey = getFederatorRskPublicKey(federationMember); + String federatorRskAddress = ByteUtil.toHexString(federatorRskPublicKey.getAddress()); + BtcECKey federatorBtcPublicKey = federationMember.getBtcPublicKey(); - logAddSignatureInSolidityFormat(rskTxHash, federatorRskAddress, federationMember.getBtcPublicKey()); + logAddSignatureInSolidityFormat(rskTxHash, federatorRskAddress, federatorBtcPublicKey); } - private ECKey getFederatorPublicKey(FederationMember federationMember) { + private ECKey getFederatorRskPublicKey(FederationMember federationMember) { if (!shouldUseRskPublicKey()) { return ECKey.fromPublicOnly(federationMember.getBtcPublicKey().getPubKey()); } diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java index 58995a0843d..8bce1e257d9 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java @@ -140,7 +140,7 @@ void addSignature_fedPubKey_belongs_to_active_federation() throws Exception { createHash3(1).getBytes() ); - verify(provider, times(1)).getPegoutsWaitingForSignatures(); + verify(provider, never()).getPegoutsWaitingForSignatures(); } @Test @@ -207,7 +207,7 @@ void addSignature_fedPubKey_belongs_to_retiring_federation() throws Exception { createHash3(1).getBytes() ); - verify(provider, times(1)).getPegoutsWaitingForSignatures(); + verify(provider, never()).getPegoutsWaitingForSignatures(); } @Test @@ -274,7 +274,7 @@ void addSignature_fedPubKey_no_belong_to_retiring_or_active_federation() throws createHash3(1).getBytes() ); - verify(provider, times(0)).getPegoutsWaitingForSignatures(); + verify(provider, never()).getPegoutsWaitingForSignatures(); } @Test @@ -296,7 +296,7 @@ void addSignature_fedPubKey_no_belong_to_active_federation_no_existing_retiring_ createHash3(1).getBytes() ); - verify(provider, times(0)).getPegoutsWaitingForSignatures(); + verify(provider, never()).getPegoutsWaitingForSignatures(); } @Test From 88bb819c6396ba87191aef3234aa24b41acff54e Mon Sep 17 00:00:00 2001 From: julia-zack Date: Tue, 8 Oct 2024 16:08:17 -0600 Subject: [PATCH 40/44] Use getScriptSigWithSignature instead of updateScriptWithSignature --- rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java | 3 ++- .../src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) 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 6da7074dd90..d0cbd4118e3 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1771,7 +1771,8 @@ private boolean sign( try { int sigIndex = inputScript.getSigInsertionIndex(sighash, federatorBtcPublicKey); - inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex, 1, 1); + Script outputScript = ScriptBuilder.createP2SHOutputScript(getRedeemScriptFromP2SHInputScript(inputScript)); + inputScript = outputScript.getScriptSigWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex); input.setScriptSig(inputScript); logger.debug("Tx input {} for tx {} signed.", i, rskTxHash); signed = true; diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index c789d2e8137..3657891be25 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -90,6 +90,13 @@ public static Script createBaseP2SHInputScriptThatSpendsFromRedeemScript(Script return outputScript.createEmptyInputScript(null, redeemScript); } + public static Script getRedeemScriptFromP2SHInputScript(Script inputScript) { + List inputScriptChunks = inputScript.getChunks(); + int redeemScriptIndex = inputScriptChunks.size() - 1; + ScriptChunk redeemScriptChunk = inputScriptChunks.get(redeemScriptIndex); + return new Script(redeemScriptChunk.data); + } + public static Optional searchForOutput(List transactionOutputs, Script outputScriptPubKey) { return transactionOutputs.stream() .filter(output -> output.getScriptPubKey().equals(outputScriptPubKey)) From 37316686b3ee1c5b9c9003bf2366720636910404 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Wed, 9 Oct 2024 13:40:38 -0600 Subject: [PATCH 41/44] Rename boolean method. Replace rskTxHashSerialized argument for the keccak rskTxHash. Remove logger.isDebugEnabled condition. Add tests for new utility methods. Rename method to be more specific Add test for when method should throw Add log when catching exception to avoid sonar warning Add logging error message when logging caught exception. Throw a dedicated exception in addSignature to better handling. Make deserializeRskTxHash method public to reuse it. Add tests. Remove unused import --- .../src/main/java/co/rsk/peg/Bridge.java | 13 ++-- .../co/rsk/peg/BridgeSerializationUtils.java | 12 ++-- .../main/java/co/rsk/peg/BridgeSupport.java | 38 +++++------ .../java/co/rsk/peg/bitcoin/BitcoinUtils.java | 2 +- .../peg/BridgeSupportAddSignatureTest.java | 32 +++++----- .../test/java/co/rsk/peg/BridgeUtilsTest.java | 27 +++++++- .../co/rsk/peg/bitcoin/BitcoinUtilsTest.java | 63 ++++++++++++++++++- 7 files changed, 133 insertions(+), 54 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/peg/Bridge.java b/rskj-core/src/main/java/co/rsk/peg/Bridge.java index 189efe4a724..5ede4ef422f 100644 --- a/rskj-core/src/main/java/co/rsk/peg/Bridge.java +++ b/rskj-core/src/main/java/co/rsk/peg/Bridge.java @@ -17,6 +17,7 @@ */ package co.rsk.peg; +import static co.rsk.peg.BridgeSerializationUtils.deserializeRskTxHash; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP417; import co.rsk.bitcoinj.core.*; @@ -617,15 +618,13 @@ public void addSignature(Object[] args) throws VMException { } signatures.add(signatureByteArray); } - byte[] rskTxHash = (byte[]) args[2]; - if (rskTxHash.length!=32) { - throw new BridgeIllegalArgumentException("Invalid rsk tx hash " + Bytes.of(rskTxHash)); - } + byte[] rskTxHashSerialized = (byte[]) args[2]; try { + Keccak256 rskTxHash = deserializeRskTxHash(rskTxHashSerialized); bridgeSupport.addSignature(federatorPublicKey, signatures, rskTxHash); - } catch (BridgeIllegalArgumentException e) { - throw e; - } catch (Exception e) { + } catch (IllegalArgumentException e) { + throw new BridgeIllegalArgumentException("Invalid rsk tx hash " + Bytes.of(rskTxHashSerialized)); + } catch (IOException e) { logger.warn("Exception in addSignature", e); throw new VMException("Exception in addSignature", e); } diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java b/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java index 5f3a8a10fd5..fac0b306815 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSerializationUtils.java @@ -65,8 +65,8 @@ private static byte[] serializeRskTxHash(Keccak256 rskTxHash) { return RLP.encodeElement(rskTxHash.getBytes()); } - private static Keccak256 deserializeRskTxHashFromHashData(byte[] hashBytes) { - return new Keccak256(hashBytes); + public static Keccak256 deserializeRskTxHash(byte[] rskTxHashSerialized) { + return new Keccak256(rskTxHashSerialized); } public static byte[] serializeBtcTransaction(BtcTransaction btcTransaction) { @@ -186,7 +186,7 @@ private static Map.Entry deserializeRskTxWaitingForSi RLPElement rskTxHashRLPElement = rlpList.get(index * 2); byte[] rskTxHashData = rskTxHashRLPElement.getRLPData(); - Keccak256 rskTxHash = deserializeRskTxHashFromHashData(rskTxHashData); + Keccak256 rskTxHash = deserializeRskTxHash(rskTxHashData); RLPElement btcTxRLPElement = rlpList.get(index * 2 + 1); byte[] btcRawTx = btcTxRLPElement.getRLPData(); @@ -667,7 +667,7 @@ private static List deserializeReleaseRequestQueueWit Address address = new Address(networkParameters, addressBytes); long amount = BigIntegers.fromUnsignedByteArray(rlpList.get(k * 3 + 1).getRLPData()).longValue(); - Keccak256 txHash = deserializeRskTxHashFromHashData(rlpList.get(k * 3 + 2).getRLPData()); + Keccak256 txHash = deserializeRskTxHash(rlpList.get(k * 3 + 2).getRLPData()); entries.add(new ReleaseRequestQueue.Entry(address, Coin.valueOf(amount), txHash)); } @@ -760,7 +760,7 @@ private static PegoutsWaitingForConfirmations deserializePegoutWaitingForConfirm BtcTransaction tx = new BtcTransaction(networkParameters, txPayload); long height = BigIntegers.fromUnsignedByteArray(rlpList.get(k * 3 + 1).getRLPData()).longValue(); - Keccak256 rskTxHash = deserializeRskTxHashFromHashData(rlpList.get(k * 3 + 2).getRLPData()); + Keccak256 rskTxHash = deserializeRskTxHash(rlpList.get(k * 3 + 2).getRLPData()); entries.add(new PegoutsWaitingForConfirmations.Entry(tx, height, rskTxHash)); } @@ -849,7 +849,7 @@ public static FlyoverFederationInformation deserializeFlyoverFederationInformati if (rlpList.size() != 2) { throw new RuntimeException(String.format("Invalid serialized Fast Bridge Federation: expected 2 value but got %d", rlpList.size())); } - Keccak256 derivationHash = deserializeRskTxHashFromHashData(rlpList.get(0).getRLPData()); + Keccak256 derivationHash = deserializeRskTxHash(rlpList.get(0).getRLPData()); byte[] federationP2SH = rlpList.get(1).getRLPData(); return new FlyoverFederationInformation(derivationHash, federationP2SH, flyoverScriptHash); 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 d0cbd4118e3..a0e09a5a53b 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1569,30 +1569,28 @@ private void adjustBalancesIfChangeOutputWasDust(BtcTransaction btcTx, Coin sent * * @param federatorBtcPublicKey Federator who is signing * @param signatures 1 signature per btc tx input - * @param rskTxHashSerialized The id of the rsk tx + * @param rskTxHash The hash of the rsk tx */ - public void addSignature(BtcECKey federatorBtcPublicKey, List signatures, byte[] rskTxHashSerialized) throws Exception { + public void addSignature(BtcECKey federatorBtcPublicKey, List signatures, Keccak256 rskTxHash) throws IOException { if (signatures == null || signatures.isEmpty()) { return; } Context.propagate(btcContext); - Keccak256 rskTxHash = new Keccak256(rskTxHashSerialized); - BtcTransaction releaseTx = provider.getPegoutsWaitingForSignatures().get(rskTxHash); if (releaseTx == null) { logger.warn("No tx waiting for signature for hash {}. Probably fully signed already.", rskTxHash); return; } - if (!enoughSignatures(releaseTx, signatures)) { + if (!hasEnoughSignatures(releaseTx, signatures)) { return; } - addReleaseSignatures(federatorBtcPublicKey, signatures, rskTxHashSerialized, releaseTx); + addReleaseSignatures(federatorBtcPublicKey, signatures, rskTxHash, releaseTx); } - private boolean enoughSignatures(BtcTransaction releaseTx, List signatures) { + private boolean hasEnoughSignatures(BtcTransaction releaseTx, List signatures) { int inputsSize = releaseTx.getInputs().size(); int signaturesSize = signatures.size(); @@ -1606,7 +1604,7 @@ private boolean enoughSignatures(BtcTransaction releaseTx, List signatur private void addReleaseSignatures( BtcECKey federatorPublicKey, List signatures, - byte[] rskTxHashSerialized, + Keccak256 rskTxHash, BtcTransaction btcTx ) throws IOException { Optional optionalFederation = getFederationFromPublicKey(federatorPublicKey); @@ -1629,14 +1627,14 @@ private void addReleaseSignatures( } FederationMember signingFederationMember = federationMember.get(); + byte[] rskTxHashSerialized = rskTxHash.getBytes(); if (!activations.isActive(ConsensusRule.RSKIP326)) { eventLogger.logAddSignature(signingFederationMember, btcTx, rskTxHashSerialized); } - processSigning(signingFederationMember, signatures, rskTxHashSerialized, btcTx); + processSigning(signingFederationMember, signatures, rskTxHash, btcTx); - Keccak256 rskTxHash = new Keccak256(rskTxHashSerialized); - if (!BridgeUtils.hasEnoughSignatures(btcContext, btcTx) && logger.isDebugEnabled()) { + if (!BridgeUtils.hasEnoughSignatures(btcContext, btcTx)) { logMissingSignatures(btcTx, rskTxHash, federation); return; } @@ -1676,13 +1674,13 @@ private void logReleaseBtc(BtcTransaction btcTx, byte[] rskTxHashSerialized) { private void processSigning( FederationMember federatorMember, List signatures, - byte[] rskTxHashSerialized, + Keccak256 rskTxHash, BtcTransaction btcTx) { // Build input hashes for signatures List sigHashes = new ArrayList<>(); for (int i = 0; i < btcTx.getInputs().size(); i++) { - Sha256Hash sigHash = generateSigHashForP2SHInput(btcTx, i); + Sha256Hash sigHash = generateSigHashForP2SHTransactionInput(btcTx, i); sigHashes.add(sigHash); } @@ -1692,15 +1690,15 @@ private void processSigning( try { txSigs = getTransactionSignatures(federatorBtcPublicKey, sigHashes, signatures); } catch (SignatureException e) { + logger.error("[processSigning] Unable to proceed with signing as the transaction signatures are incorrect. {} ", e.getMessage()); return; } // All signatures are correct. Proceed to signing - Keccak256 rskTxHash = new Keccak256(rskTxHashSerialized); boolean signed = sign(federatorBtcPublicKey, txSigs, sigHashes, rskTxHash, btcTx); if (signed && activations.isActive(ConsensusRule.RSKIP326)) { - eventLogger.logAddSignature(federatorMember, btcTx, rskTxHashSerialized); + eventLogger.logAddSignature(federatorMember, btcTx, rskTxHash.getBytes()); } } @@ -1717,7 +1715,7 @@ private List getTransactionSignatures(BtcECKey federatorBt "Signature {} {} is not valid for hash {} and public key {}", i, Bytes.of(decodedSignature.encodeToDER()), - sigHashes, + sigHash, federatorBtcPublicKey ); throw new SignatureException(); @@ -1771,9 +1769,11 @@ private boolean sign( try { int sigIndex = inputScript.getSigInsertionIndex(sighash, federatorBtcPublicKey); - Script outputScript = ScriptBuilder.createP2SHOutputScript(getRedeemScriptFromP2SHInputScript(inputScript)); - inputScript = outputScript.getScriptSigWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex); - input.setScriptSig(inputScript); + Script redeemScript = getRedeemScriptFromP2SHInputScript(inputScript); + Script outputScript = ScriptBuilder.createP2SHOutputScript(redeemScript); + + Script inputScriptWithSignature = outputScript.getScriptSigWithSignature(inputScript, txSigs.get(i).encodeToBitcoin(), sigIndex); + input.setScriptSig(inputScriptWithSignature); logger.debug("Tx input {} for tx {} signed.", i, rskTxHash); signed = true; } catch (IllegalStateException e) { diff --git a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java index 3657891be25..c0eed020475 100644 --- a/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/bitcoin/BitcoinUtils.java @@ -103,7 +103,7 @@ public static Optional searchForOutput(List btcTx.hashForSignature(inputIndex, redeemScript, BtcTransaction.SigHash.ALL, false)) diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java index 8bce1e257d9..9dd6c622419 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportAddSignatureTest.java @@ -137,7 +137,7 @@ void addSignature_fedPubKey_belongs_to_active_federation() throws Exception { bridgeSupport.addSignature( BtcECKey.fromPrivate(Hex.decode("fa01")), null, - createHash3(1).getBytes() + createHash3(1) ); verify(provider, never()).getPegoutsWaitingForSignatures(); @@ -204,7 +204,7 @@ void addSignature_fedPubKey_belongs_to_retiring_federation() throws Exception { bridgeSupport.addSignature( BtcECKey.fromPrivate(Hex.decode("fa01")), null, - createHash3(1).getBytes() + createHash3(1) ); verify(provider, never()).getPegoutsWaitingForSignatures(); @@ -271,7 +271,7 @@ void addSignature_fedPubKey_no_belong_to_retiring_or_active_federation() throws bridgeSupport.addSignature( BtcECKey.fromPrivate(Hex.decode("fa05")), null, - createHash3(1).getBytes() + createHash3(1) ); verify(provider, never()).getPegoutsWaitingForSignatures(); @@ -293,7 +293,7 @@ void addSignature_fedPubKey_no_belong_to_active_federation_no_existing_retiring_ bridgeSupport.addSignature( BtcECKey.fromPrivate(Hex.decode("fa03")), null, - createHash3(1).getBytes() + createHash3(1) ); verify(provider, never()).getPegoutsWaitingForSignatures(); @@ -324,7 +324,7 @@ void addSignatureToMissingTransaction() throws Exception { bridgeSupport.addSignature( genesisFederation.getBtcPublicKeys().get(0), null, - BitcoinTestUtils.createHash(1).getBytes() + createHash3(1) ); bridgeSupport.save(); @@ -355,7 +355,7 @@ void addSignatureFromInvalidFederator() throws Exception { bridgeSupport.addSignature( new BtcECKey(), null, - BitcoinTestUtils.createHash(1).getBytes() + createHash3(1) ); bridgeSupport.save(); @@ -457,9 +457,9 @@ private void test_addSignature_EventEmitted(boolean rskip326Active, boolean useV BtcECKey federatorPubKey = REGTEST_FEDERATION_PUBLIC_KEYS.get(indexOfKeyToSignWith); FederationMember federationMember = FederationTestUtils.getFederationMemberWithKey(federatorPubKey); - bridgeSupport.addSignature(federatorPubKey, derEncodedSigs, rskTxHash.getBytes()); + bridgeSupport.addSignature(federatorPubKey, derEncodedSigs, rskTxHash); if (shouldSignTwice) { - bridgeSupport.addSignature(federatorPubKey, derEncodedSigs, rskTxHash.getBytes()); + bridgeSupport.addSignature(federatorPubKey, derEncodedSigs, rskTxHash); } verify(eventLogger, times(wantedNumberOfInvocations)).logAddSignature(federationMember, btcTx, rskTxHash.getBytes()); @@ -584,7 +584,7 @@ void addSignatureMultipleInputsPartiallyValid() throws Exception { } // Sign with two valid signatures and one invalid signature - bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfFirstFed), derEncodedSigsFirstFed, keccak256.getBytes()); + bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfFirstFed), derEncodedSigsFirstFed, keccak256); bridgeSupport.save(); // Sign with two valid signatures and one malformed signature @@ -593,16 +593,16 @@ void addSignatureMultipleInputsPartiallyValid() throws Exception { malformedSignature[i] = (byte) i; } derEncodedSigsFirstFed.set(2, malformedSignature); - bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfFirstFed), derEncodedSigsFirstFed, keccak256.getBytes()); + bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfFirstFed), derEncodedSigsFirstFed, keccak256); bridgeSupport.save(); // Sign with fully valid signatures for same federator derEncodedSigsFirstFed.set(2, lastSig.encodeToDER()); - bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfFirstFed), derEncodedSigsFirstFed, keccak256.getBytes()); + bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfFirstFed), derEncodedSigsFirstFed, keccak256); bridgeSupport.save(); // Sign with second federation - bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfSecondFed), derEncodedSigsSecondFed, keccak256.getBytes()); + bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeyOfSecondFed), derEncodedSigsSecondFed, keccak256); bridgeSupport.save(); provider = new BridgeStorageProvider(repository, bridgeAddress, btcRegTestParams, activationsBeforeForks); @@ -720,7 +720,7 @@ void addSignature(ActivationConfig.ForBlock activations, FederationMember federa List derEncodedSigs = Collections.singletonList(sig.encodeToDER()); // Act - bridgeSupport.addSignature(federationMemberToSignWith.getBtcPublicKey(), derEncodedSigs, rskTxHash.getBytes()); + bridgeSupport.addSignature(federationMemberToSignWith.getBtcPublicKey(), derEncodedSigs, rskTxHash); // Assert commonAssertLogs(eventLogs); @@ -821,7 +821,7 @@ private void addSignatureFromValidFederator(List privateKeysToSignWith for (int i = 0; i < numberOfInputsToSign; i++) { derEncodedSigs.add(derEncodedSig); } - bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeysToSignWith.get(0)), derEncodedSigs, keccak256.getBytes()); + bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeysToSignWith.get(0)), derEncodedSigs, keccak256); if (signTwice) { // Create another valid signature with the same private key ECDSASigner signer = new ECDSASigner(); @@ -833,7 +833,7 @@ private void addSignatureFromValidFederator(List privateKeysToSignWith BtcECKey.ECDSASignature sig2 = new BtcECKey.ECDSASignature(components[0], components[1]).toCanonicalised(); List list = new ArrayList<>(); list.add(sig2.encodeToDER()); - bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeysToSignWith.get(0)), list, keccak256.getBytes()); + bridgeSupport.addSignature(findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeysToSignWith.get(0)), list, keccak256); } if (privateKeysToSignWith.size() > 1) { BtcECKey.ECDSASignature sig2 = privateKeysToSignWith.get(1).sign(sighash); @@ -845,7 +845,7 @@ private void addSignatureFromValidFederator(List privateKeysToSignWith bridgeSupport.addSignature( findPublicKeySignedBy(genesisFederation.getBtcPublicKeys(), privateKeysToSignWith.get(1)), derEncodedSigs2, - keccak256.getBytes() + keccak256 ); } bridgeSupport.save(); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/BridgeUtilsTest.java index 95d14a7e0c4..e9e64d7eb5c 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeUtilsTest.java @@ -63,8 +63,8 @@ import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static co.rsk.peg.BridgeSerializationUtils.deserializeRskTxHash; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; class BridgeUtilsTest { @@ -2016,4 +2016,27 @@ void testGetUTXOsSentToAddresses_no_utxo_sent_to_given_address_after_RSKIP293() Assertions.assertTrue(foundUTXOs.isEmpty()); } + + @Test + void deserializeRskTxHash_() { + // arrange + Keccak256 expectedRskTxHash = RskTestUtils.createHash(1); + byte[] rskTxHashSerialized = expectedRskTxHash.getBytes(); + + // act + Keccak256 rskTxHash = deserializeRskTxHash(rskTxHashSerialized); + + // assert + assertEquals(expectedRskTxHash, rskTxHash); + } + + @Test + void deserializeRskTxHash_withInvalidLength_throwsIllegalArgumentException() { + // arrange + byte[] rskTxHashSerialized = new byte[31]; + + // act & assert + assertThrows(IllegalArgumentException.class, + () -> deserializeRskTxHash(rskTxHashSerialized)); + } } diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java index 670d5f20d06..2e651c911a8 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java @@ -1,8 +1,6 @@ package co.rsk.peg.bitcoin; -import static co.rsk.peg.bitcoin.BitcoinUtils.addInputFromMatchingOutputScript; -import static co.rsk.peg.bitcoin.BitcoinUtils.createBaseP2SHInputScriptThatSpendsFromRedeemScript; -import static co.rsk.peg.bitcoin.BitcoinUtils.searchForOutput; +import static co.rsk.peg.bitcoin.BitcoinUtils.*; import static org.junit.jupiter.api.Assertions.*; import co.rsk.bitcoinj.core.*; @@ -468,6 +466,65 @@ void createBaseP2SHInputScriptThatSpendsFromRedeemScript_shouldCreateExpectedScr } } + @Test + void getRedeemScriptFromP2SHInputScript_shouldGetExpectedRedeemScript() { + // arrange + Federation federation = P2shErpFederationBuilder.builder().build(); + Script expectedRedeemScript = federation.getRedeemScript(); + + Script p2shInputScript = createBaseP2SHInputScriptThatSpendsFromRedeemScript(expectedRedeemScript); + + // act + Script redeemScript = getRedeemScriptFromP2SHInputScript(p2shInputScript); + + // assert + assertEquals(expectedRedeemScript, redeemScript); + } + + @Test + void generateSigHashForP2SHInput_shouldGenerateExpectedSigHash() { + // arrange + Federation federation = P2shErpFederationBuilder.builder().build(); + Script redeemScript = federation.getRedeemScript(); + + BtcTransaction fundTransaction = new BtcTransaction(btcMainnetParams); + fundTransaction.addOutput(Coin.valueOf(1000), federation.getAddress()); + + BtcTransaction transaction = new BtcTransaction(btcMainnetParams); + TransactionOutput outpoint = fundTransaction.getOutput(0); + transaction.addInput(outpoint); + + int inputIndex = 0; + TransactionInput input = transaction.getInput(inputIndex); + Script p2shInputScript = createBaseP2SHInputScriptThatSpendsFromRedeemScript(redeemScript); + input.setScriptSig(p2shInputScript); + + // act + Sha256Hash sigHash = generateSigHashForP2SHTransactionInput(transaction, inputIndex); + + // assert + Sha256Hash expectedSigHash = transaction.hashForSignature(inputIndex, redeemScript, BtcTransaction.SigHash.ALL, false); + assertEquals(expectedSigHash, sigHash); + } + + @Test + void generateSigHashForP2SHInput_forEmptyInputScript_shouldThrowIllegalArgumentException() { + // arrange + Federation federation = P2shErpFederationBuilder.builder().build(); + + BtcTransaction fundTransaction = new BtcTransaction(btcMainnetParams); + fundTransaction.addOutput(Coin.valueOf(1000), federation.getAddress()); + + BtcTransaction transaction = new BtcTransaction(btcMainnetParams); + TransactionOutput outpoint = fundTransaction.getOutput(0); + transaction.addInput(outpoint); + int inputIndex = 0; + + // act & assert + assertThrows(IllegalArgumentException.class, + () -> generateSigHashForP2SHTransactionInput(transaction, inputIndex)); + } + @Test void searchForOutput_whenTheOutputIsThere_shouldReturnOutputSentToExpectedScript() { // arrange From 2961f83edbe55d672521b5ae13220e49d016f644 Mon Sep 17 00:00:00 2001 From: julia-zack Date: Fri, 11 Oct 2024 12:40:48 -0300 Subject: [PATCH 42/44] Use already existing method to extract redeem script. Remove unnecessary new one --- .../src/main/java/co/rsk/peg/BridgeSupport.java | 14 +++++++++----- .../java/co/rsk/peg/bitcoin/BitcoinUtils.java | 7 ------- .../java/co/rsk/peg/bitcoin/BitcoinUtilsTest.java | 15 --------------- 3 files changed, 9 insertions(+), 27 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 a0e09a5a53b..3258071da5d 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -1754,22 +1754,26 @@ private boolean sign( boolean signed = false; for (int i = 0; i < sigHashes.size(); i++) { - Sha256Hash sighash = sigHashes.get(i); + Sha256Hash sigHash = sigHashes.get(i); TransactionInput input = btcTx.getInput(i); Script inputScript = input.getScriptSig(); boolean alreadySignedByThisFederator = - BridgeUtils.isInputSignedByThisFederator(federatorBtcPublicKey, sighash, input); + BridgeUtils.isInputSignedByThisFederator(federatorBtcPublicKey, sigHash, input); - // Sign the input if it wasn't already if (alreadySignedByThisFederator) { logger.warn("Input {} of tx {} already signed by this federator.", i, rskTxHash); break; } + Optional