From dd86bc1b0dd2a8a4c176821ff822d840549944df Mon Sep 17 00:00:00 2001 From: Julian Len Date: Tue, 21 Jan 2025 16:08:04 -0300 Subject: [PATCH 1/2] feat: Added a test such that if a P2SHP2WSH transaction is sent to the federation, it should refund the funds correctly --- .../co/rsk/peg/RegisterBtcTransactionIT.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java b/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java index cbb09abc38..7e07bfeb8b 100644 --- a/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java +++ b/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java @@ -26,6 +26,7 @@ import co.rsk.peg.utils.BridgeEventLoggerImpl; import co.rsk.test.builders.BridgeSupportBuilder; import co.rsk.test.builders.FederationSupportBuilder; +import org.bouncycastle.util.encoders.Hex; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.core.*; @@ -399,6 +400,65 @@ void registerBtcTransaction_whenLegacyPeginBtcTransactionFromUnknwonAddress_shou assertRejectedPeginTransaction(btcTransaction, BridgeEvents.UNREFUNDABLE_PEGIN.getEvent(), LEGACY_PEGIN_UNDETERMINED_SENDER.getValue()); } + @Test + void registerBtcTransaction_WhenLegacyPeginBtcTransactionFromP2WSHAddress_shouldRefundTheFunds() throws Exception { + // Arrange + // TODO(juli): At the moment rskj does not supports P2SHP2WSH we need to change this test, + // The rawTx is obtained from the code on the bottom of this file when executed in bitcoinj-thin. + String rawTx = "020000000001010100000000000000000000000000000000000000000000000000000000000000000000002322002051b06d" + + "bcfc7138cb09fc31d5caa882dc3b9ad81f03a400474f81e7d8b130f4ebffffffff0120a107000000000017a914eba536f25415ec64caa23" + + "28897dffb101b48743e870400483045022100c547bc3a0af10bd7ea4834a48336687dd386567b44abc5bc76d24f0cdf14e20a022006b2162" + + "6a722c2ac9f5239755c37c79449187f3a790c614137e18dd63162cf75010025512102c8f031561c4758c9551cff47246f2c347189fe684c04" + + "da35cf88e813f810e3c251ae00000000"; + + BtcTransaction btcTransaction = new BtcTransaction(btcNetworkParams, Hex.decode(rawTx)); + + PartialMerkleTree pmtWithTransactions = createValidPmtForTransactions(List.of(btcTransaction.getHash(true)), btcNetworkParams); + int btcBlockWithPmtHeight = bridgeConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeConstants.getPegoutTxIndexGracePeriodInBtcBlocks(); + int chainHeight = btcBlockWithPmtHeight + bridgeConstants.getBtc2RskMinimumAcceptableConfirmations(); + recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, btcNetworkParams); + bridgeSupport.save(); + + // Act + bridgeSupport.registerBtcTransaction(rskTx, btcTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize()); + bridgeSupport.save(); + + co.rsk.core.Coin expectedReceiverBalance = repository.getBalance(rskReceiver); + List expectedFederationUTXOs = List.copyOf(federationSupport.getActiveFederationBtcUTXOs()); + + // Assert + assertItWasProcessed(btcTransaction); + + Set pegoutEntries = bridgeStorageProvider.getPegoutsWaitingForConfirmations().getEntries(); + int expectedPegoutsWaitingForConfirmations = 1; + assertEquals(expectedPegoutsWaitingForConfirmations, pegoutEntries.size()); + + Optional pegOutWaitingForConfirmationOptional = pegoutEntries.stream().findFirst(); + assertTrue(pegOutWaitingForConfirmationOptional.isPresent()); + + PegoutsWaitingForConfirmations.Entry pegOutWaitingForConfirmationEntry = pegOutWaitingForConfirmationOptional.get(); + assertEquals(rskTx.getHash(), pegOutWaitingForConfirmationEntry.getPegoutCreationRskTxHash()); + assertEquals(rskExecutionBlock.getNumber() ,pegOutWaitingForConfirmationEntry.getPegoutCreationRskBlockNumber()); + + // Pegout value + fee == Pegin value + BtcTransaction pegOut = pegOutWaitingForConfirmationEntry.getBtcTransaction(); + int newOutputIndex = 0; + TransactionOutput pegOutOutput = pegOut.getOutput(newOutputIndex); + Coin pegOutTotalValue = pegOutOutput.getValue().add(pegOut.getFee()); + assertEquals(minimumPeginValue, pegOutTotalValue); + + Address pegInTxSender = btcLockSenderProvider.tryGetBtcLockSender(btcTransaction).get().getBTCAddress(); + Address pegoutReceiver = pegOutOutput.getAddressFromP2SH(btcNetworkParams); + assertEquals(pegInTxSender, pegoutReceiver); + + assertRejectedPeginTransaction(btcTransaction, BridgeEvents.REJECTED_PEGIN.getEvent(), RejectedPeginReason.LEGACY_PEGIN_MULTISIG_SENDER.getValue()); + assertReleaseBtcRequested(rskTx.getHash().getBytes(), pegOut, minimumPeginValue); + assertPegoutTransactionCreated(pegOut.getHash(), UtxoUtils.extractOutpointValues(pegOut)); + + assertEquals(expectedFederationUTXOs, federationSupport.getActiveFederationBtcUTXOs()); + assertEquals(expectedReceiverBalance, repository.getBalance(rskReceiver)); + } + private static UTXO utxoOf(BtcTransaction btcTransaction, TransactionOutput output) { int height = 0; return new UTXO( @@ -489,3 +549,47 @@ private void assertPegoutTransactionCreated(Sha256Hash pegoutTransactionHash, Li assertEventWasEmittedWithExpectedData(encodedData, logs); } } + + +/* + public void obtainTheHexCodeForTheP2WSH() { + NetworkParameters btcNetworkParams = NetworkParameters.fromID(NetworkParameters.ID_MAINNET); + Address receiver = Address.fromP2SHHash(btcNetworkParams, HEX.decode("eba536f25415ec64caa2328897dffb101b48743e")); + Coin value = Coin.valueOf(500_000); + + BtcTransaction btcTx = new BtcTransaction(btcNetworkParams); + Sha256Hash spendTxHash = Sha256Hash.wrap("0000000000000000000000000000000000000000000000000000000000000001"); + btcTx.addInput(spendTxHash, 0, new Script(new byte[]{})); + btcTx.addOutput(value, receiver); + btcTx.setVersion(2); + + // Multisig 1-of-1 + // data comes from BitcoinTestUtils.getBtcEcKeyFromSeed("seed") in rskj + BtcECKey btcPublicKey = BtcECKey.fromPublicOnly(Hex.decode("03ebc18a8b308fdd3efeae515861cb4b553d854ff6a40ce7bf1c3ced2524dc80ad")); + Script redeemScript = ScriptBuilder.createRedeemScript(1, Collections.singletonList(btcPublicKey)); + + Sha256Hash sigHash = btcTx.hashForWitnessSignature( + 0, // Input index + redeemScript, + value, + BtcTransaction.SigHash.ALL, + false + ); + byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); + Script scriptSig = new ScriptBuilder().number(OP_0).data(redeemScriptHash).build(); + Script segwitScriptSig = new ScriptBuilder().data(scriptSig.getProgram()).build(); + btcTx.getInput(0).setScriptSig(segwitScriptSig); + + BtcECKey.ECDSASignature signature = btcPublicKey.sign(sigHash); + TransactionSignature txSignature = new TransactionSignature( + signature, + BtcTransaction.SigHash.ALL, + false + ); + List signatures = Collections.singletonList(txSignature); + TransactionWitness txWitness = TransactionWitness.createWitnessErpStandardScript(redeemScript, signatures); + btcTx.setWitness(0, txWitness); + + System.out.println(Hex.toHexString(btcTx.bitcoinSerialize())); + } + */ From 9e6b4d248afbe5fbf41c4ce1037b0b2436a35073 Mon Sep 17 00:00:00 2001 From: Julian Len Date: Tue, 21 Jan 2025 16:29:54 -0300 Subject: [PATCH 2/2] fix: improved the output assignation for the bitcoin transactions --- .../co/rsk/peg/RegisterBtcTransactionIT.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java b/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java index 7e07bfeb8b..7a6c198534 100644 --- a/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java +++ b/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java @@ -11,6 +11,7 @@ import co.rsk.net.utils.TransactionUtils; import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.bitcoin.UtxoUtils; +import co.rsk.peg.btcLockSender.BtcLockSender; import co.rsk.peg.btcLockSender.BtcLockSenderProvider; import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.constants.BridgeMainNetConstants; @@ -375,7 +376,7 @@ void registerBtcTransaction_whenLegacyPeginBtcTransactionFromUnknwonAddress_shou BtcTransaction btcTransaction = new BtcTransaction(btcNetworkParams); Script scriptForAnUnknownSender = new Script(new byte[]{}); btcTransaction.addInput(BitcoinTestUtils.createHash(spendTxHashSeed), outputIndex, scriptForAnUnknownSender); - btcTransaction.addOutput(new TransactionOutput(btcNetworkParams, btcTransaction, minimumPeginValue, federationSupport.getActiveFederation().getAddress())); + btcTransaction.addOutput(minimumPeginValue, federationSupport.getActiveFederation().getAddress()); PartialMerkleTree pmtWithTransactions = createValidPmtForTransactions(List.of(btcTransaction.getHash()), btcNetworkParams); int btcBlockWithPmtHeight = bridgeConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeConstants.getPegoutTxIndexGracePeriodInBtcBlocks(); @@ -403,14 +404,13 @@ void registerBtcTransaction_whenLegacyPeginBtcTransactionFromUnknwonAddress_shou @Test void registerBtcTransaction_WhenLegacyPeginBtcTransactionFromP2WSHAddress_shouldRefundTheFunds() throws Exception { // Arrange - // TODO(juli): At the moment rskj does not supports P2SHP2WSH we need to change this test, + // TODO(juli): At the moment rskj does not supports P2SHP2WSH we need to change this test. // The rawTx is obtained from the code on the bottom of this file when executed in bitcoinj-thin. String rawTx = "020000000001010100000000000000000000000000000000000000000000000000000000000000000000002322002051b06d" + "bcfc7138cb09fc31d5caa882dc3b9ad81f03a400474f81e7d8b130f4ebffffffff0120a107000000000017a914eba536f25415ec64caa23" + "28897dffb101b48743e870400483045022100c547bc3a0af10bd7ea4834a48336687dd386567b44abc5bc76d24f0cdf14e20a022006b2162" + "6a722c2ac9f5239755c37c79449187f3a790c614137e18dd63162cf75010025512102c8f031561c4758c9551cff47246f2c347189fe684c04" + "da35cf88e813f810e3c251ae00000000"; - BtcTransaction btcTransaction = new BtcTransaction(btcNetworkParams, Hex.decode(rawTx)); PartialMerkleTree pmtWithTransactions = createValidPmtForTransactions(List.of(btcTransaction.getHash(true)), btcNetworkParams); @@ -447,7 +447,10 @@ void registerBtcTransaction_WhenLegacyPeginBtcTransactionFromP2WSHAddress_should Coin pegOutTotalValue = pegOutOutput.getValue().add(pegOut.getFee()); assertEquals(minimumPeginValue, pegOutTotalValue); - Address pegInTxSender = btcLockSenderProvider.tryGetBtcLockSender(btcTransaction).get().getBTCAddress(); + Optional btcLockSender = btcLockSenderProvider.tryGetBtcLockSender(btcTransaction); + assertTrue(btcLockSender.isPresent()); + + Address pegInTxSender = btcLockSender.get().getBTCAddress(); Address pegoutReceiver = pegOutOutput.getAddressFromP2SH(btcNetworkParams); assertEquals(pegInTxSender, pegoutReceiver); @@ -474,7 +477,7 @@ private static UTXO utxoOf(BtcTransaction btcTransaction, TransactionOutput outp private BtcTransaction createPegInTransaction(Address federationAddress, Coin coin, BtcECKey pubKey, int spendTxHashSeed, int outputIndex) { BtcTransaction btcTx = new BtcTransaction(btcNetworkParams); btcTx.addInput(BitcoinTestUtils.createHash(spendTxHashSeed), outputIndex, ScriptBuilder.createInputScript(null, pubKey)); - btcTx.addOutput(new TransactionOutput(btcNetworkParams, btcTx, coin, federationAddress)); + btcTx.addOutput(coin, federationAddress); return btcTx; } @@ -483,7 +486,7 @@ private BtcTransaction createTransactionWithMultiplePegIns(Address federationAdd BtcTransaction btcTx = new BtcTransaction(btcNetworkParams); btcTx.addInput(BitcoinTestUtils.createHash(spendTxHashSeed), outputIndex, ScriptBuilder.createInputScript(null, pubKey)); for (int i = 0; i < numberOfOutputs; i++) { - btcTx.addOutput(new TransactionOutput(btcNetworkParams, btcTx, value, federationAddress)); + btcTx.addOutput(value, federationAddress); } return btcTx; } @@ -495,7 +498,7 @@ private BtcTransaction createMultiSigPegInTransaction(Address federationAddress, outputIndex, ScriptBuilder.createP2SHMultiSigInputScript(null, federationSupport.getActiveFederation().getRedeemScript()) ); - btcTx.addOutput(new TransactionOutput(btcNetworkParams, btcTx, coin, federationAddress)); + btcTx.addOutput(coin, federationAddress); return btcTx; }