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 38dbf100d2..9c5897cec5 100644 --- a/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java +++ b/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java @@ -5,9 +5,11 @@ import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.script.ScriptBuilder; +import co.rsk.bitcoinj.store.BlockStoreException; import co.rsk.core.RskAddress; import co.rsk.net.utils.TransactionUtils; import co.rsk.peg.bitcoin.BitcoinTestUtils; +import co.rsk.peg.bitcoin.UtxoUtils; import co.rsk.peg.btcLockSender.BtcLockSenderProvider; import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.constants.BridgeMainNetConstants; @@ -17,6 +19,7 @@ import co.rsk.peg.feeperkb.FeePerKbStorageProviderImpl; import co.rsk.peg.feeperkb.FeePerKbSupport; import co.rsk.peg.feeperkb.FeePerKbSupportImpl; +import co.rsk.peg.pegin.RejectedPeginReason; import co.rsk.peg.storage.BridgeStorageAccessorImpl; import co.rsk.peg.storage.StorageAccessor; import co.rsk.peg.utils.BridgeEventLoggerImpl; @@ -46,19 +49,21 @@ class RegisterBtcTransactionIT { private Repository repository; private FederationSupport federationSupport; private BridgeStorageProvider bridgeStorageProvider; - private BtcTransaction bitcoinTransaction; private PartialMerkleTree pmtWithTransactions; private int btcBlockWithPmtHeight; private RskAddress rskReceiver; private BridgeSupport bridgeSupport; private ArrayList logs; + private BtcBlockStoreWithCache btcBlockStoreWithCache; + private BtcECKey btcPublicKey; + private BtcLockSenderProvider btcLockSenderProvider; @BeforeEach void setUp() throws Exception{ repository = BridgeSupportTestUtil.createRepository().startTracking(); - BtcLockSenderProvider btcLockSenderProvider = new BtcLockSenderProvider(); + btcLockSenderProvider = new BtcLockSenderProvider(); StorageAccessor bridgeStorageAccessor = new BridgeStorageAccessorImpl(repository); FeePerKbStorageProvider feePerKbStorageProvider = new FeePerKbStorageProviderImpl(bridgeStorageAccessor); @@ -76,23 +81,15 @@ void setUp() throws Exception{ bridgeStorageProvider = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, btcNetworkParams, activations); BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(btcNetworkParams, 100, 100); - BtcBlockStoreWithCache btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(repository, bridgeConstants, bridgeStorageProvider, activations); + btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(repository, bridgeConstants, bridgeStorageProvider, activations); - BtcECKey btcPublicKey = BitcoinTestUtils.getBtcEcKeyFromSeed("seed"); + btcPublicKey = BitcoinTestUtils.getBtcEcKeyFromSeed("seed"); ECKey ecKey = ECKey.fromPublicOnly(btcPublicKey.getPubKey()); rskReceiver = new RskAddress(ecKey.getAddress()); - bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey); - - pmtWithTransactions = createValidPmtForTransactions(List.of(bitcoinTransaction.getHash()), btcNetworkParams); - btcBlockWithPmtHeight = bridgeConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeConstants.getPegoutTxIndexGracePeriodInBtcBlocks(); - int chainHeight = btcBlockWithPmtHeight + bridgeConstants.getBtc2RskMinimumAcceptableConfirmations(); logs = new ArrayList<>(); BridgeEventLoggerImpl bridgeEventLogger = new BridgeEventLoggerImpl(bridgeConstants, activations, logs); - recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, btcNetworkParams); - bridgeStorageProvider.save(); - bridgeSupport = bridgeSupportBuilder .withBridgeConstants(bridgeConstants) .withProvider(bridgeStorageProvider) @@ -105,11 +102,15 @@ void setUp() throws Exception{ .withRepository(repository) .withBtcLockSenderProvider(btcLockSenderProvider) .build(); + bridgeStorageProvider.save(); } @Test void registerBtcTransaction_forALegacyBtcTransaction_shouldRegisterTheNewUtxoAndTransferTheRbtcBalance() throws Exception { // Act + BtcTransaction bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey); + setupChainWithBtcTransaction(bitcoinTransaction); + bridgeStorageProvider.save(); bridgeSupport.registerBtcTransaction(rskTx, bitcoinTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize()); bridgeSupport.save(); @@ -126,12 +127,16 @@ void registerBtcTransaction_forALegacyBtcTransaction_shouldRegisterTheNewUtxoAnd co.rsk.core.Coin expectedReceiverBalance = co.rsk.core.Coin.fromBitcoin(output.getValue()); assertEquals(expectedReceiverBalance, repository.getBalance(rskReceiver)); - assertLogPegInBtc(); + assertLogPegInBtc(bitcoinTransaction); } @Test void registerBtcTransaction_forARepeatedLegacyBtcTransaction_shouldNotPerformAnyChange() throws Exception { // Arrange + BtcTransaction bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey); + setupChainWithBtcTransaction(bitcoinTransaction); + + bridgeStorageProvider.save(); bridgeSupport.registerBtcTransaction(rskTx, bitcoinTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize()); bridgeSupport.save(); @@ -150,6 +155,10 @@ void registerBtcTransaction_forARepeatedLegacyBtcTransaction_shouldNotPerformAny @Test void registerBtcTransaction_whenLegacyBtcTransactionWithNegativeHeight_shouldNotPerformAnyChange() throws Exception { // Arrange + BtcTransaction bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue, btcPublicKey); + setupChainWithBtcTransaction(bitcoinTransaction); + bridgeStorageProvider.save(); + co.rsk.core.Coin expectedReceiverBalance = repository.getBalance(rskReceiver); List expectedFederationUTXOs = List.copyOf(federationSupport.getActiveFederationBtcUTXOs()); @@ -163,6 +172,51 @@ void registerBtcTransaction_whenLegacyBtcTransactionWithNegativeHeight_shouldNot assertEquals(expectedReceiverBalance, repository.getBalance(rskReceiver)); } + @Test + void whenRegisterALegacyBtcTransactionFromAMultiSig_shouldRefundTheFunds() throws Exception { + // Arrange + BtcTransaction bitcoinTransaction = createMultiSigPegInTransaction(federationSupport.getActiveFederation().getAddress(), minimumPeginValue); + setupChainWithBtcTransaction(bitcoinTransaction); + bridgeSupport.save(); + + co.rsk.core.Coin expectedReceiverBalance = repository.getBalance(rskReceiver); + List expectedFederationUTXOs = List.copyOf(federationSupport.getActiveFederationBtcUTXOs()); + + // Act + bridgeSupport.registerBtcTransaction(rskTx, bitcoinTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize()); + bridgeSupport.save(); + + // Assert + 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 outputIndex = 0; + TransactionOutput pegOutOutput = pegOut.getOutput(outputIndex); + Coin pegOutTotalValue = pegOutOutput.getValue().add(pegOut.getFee()); + assertEquals(minimumPeginValue, pegOutTotalValue); + + Address pegInTxSender = btcLockSenderProvider.tryGetBtcLockSender(bitcoinTransaction).get().getBTCAddress(); + Address pegoutReceiver = pegOutOutput.getAddressFromP2SH(btcNetworkParams); + assertEquals(pegInTxSender, pegoutReceiver); + + assertRejectedPeginTransaction(bitcoinTransaction); + assertReleaseBtcRequested(rskTx.getHash().getBytes(), pegOut, minimumPeginValue); + assertPegoutTransactioCreated(pegOut.getHash(), UtxoUtils.extractOutpointValues(pegOut)); + + assertEquals(expectedFederationUTXOs, federationSupport.getActiveFederationBtcUTXOs()); + assertEquals(expectedReceiverBalance, repository.getBalance(rskReceiver)); + } + private static UTXO utxoOf(BtcTransaction bitcoinTransaction, TransactionOutput output) { int height = 0; return new UTXO( @@ -175,6 +229,13 @@ private static UTXO utxoOf(BtcTransaction bitcoinTransaction, TransactionOutput ); } + private void setupChainWithBtcTransaction(BtcTransaction bitcoinTransaction) throws BlockStoreException { + pmtWithTransactions = createValidPmtForTransactions(List.of(bitcoinTransaction.getHash()), btcNetworkParams); + btcBlockWithPmtHeight = bridgeConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeConstants.getPegoutTxIndexGracePeriodInBtcBlocks(); + int chainHeight = btcBlockWithPmtHeight + bridgeConstants.getBtc2RskMinimumAcceptableConfirmations(); + recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, btcNetworkParams); + } + private BtcTransaction createPegInTransaction(Address federationAddress, Coin coin, BtcECKey pubKey) { BtcTransaction btcTx = new BtcTransaction(btcNetworkParams); int outputIndex = 0; @@ -185,11 +246,57 @@ private BtcTransaction createPegInTransaction(Address federationAddress, Coin co return btcTx; } - private void assertLogPegInBtc() { + private BtcTransaction createMultiSigPegInTransaction(Address federationAddress, Coin coin) { + BtcTransaction btcTx = new BtcTransaction(btcNetworkParams); + btcTx.addInput( + BitcoinTestUtils.createHash(1), + 0, + ScriptBuilder.createP2SHMultiSigInputScript(null, federationSupport.getActiveFederation().getRedeemScript()) + ); + btcTx.addOutput(new TransactionOutput(btcNetworkParams, btcTx, coin, federationAddress)); + + return btcTx; + } + + private void assertLogPegInBtc(BtcTransaction bitcoinTransaction) { + CallTransaction.Function pegInBtcEvent = BridgeEvents.PEGIN_BTC.getEvent(); Sha256Hash peginTransactionHash = bitcoinTransaction.getHash(); - List encodedTopics = getEncodedTopics(BridgeEvents.PEGIN_BTC.getEvent(), rskReceiver.toString(), peginTransactionHash.getBytes()); + + List encodedTopics = getEncodedTopics(pegInBtcEvent, rskReceiver.toString(), peginTransactionHash.getBytes()); + int protocolVersion = 0; - byte[] encodedData = getEncodedData(BridgeEvents.PEGIN_BTC.getEvent(), minimumPeginValue.getValue(), protocolVersion); + byte[] encodedData = getEncodedData(pegInBtcEvent, minimumPeginValue.getValue(), protocolVersion); + + assertEventWasEmittedWithExpectedTopics(encodedTopics, logs); + assertEventWasEmittedWithExpectedData(encodedData, logs); + } + + private void assertRejectedPeginTransaction(BtcTransaction bitcoinTransaction) { + CallTransaction.Function rejectedPeginEvent = BridgeEvents.REJECTED_PEGIN.getEvent(); + Sha256Hash peginTransactionHash = bitcoinTransaction.getHash(); + List encodedTopics = getEncodedTopics(rejectedPeginEvent, peginTransactionHash.getBytes()); + byte[] encodedData = getEncodedData(rejectedPeginEvent, RejectedPeginReason.LEGACY_PEGIN_MULTISIG_SENDER.getValue()); + + assertEventWasEmittedWithExpectedTopics(encodedTopics, logs); + assertEventWasEmittedWithExpectedData(encodedData, logs); + } + + private void assertReleaseBtcRequested(byte[] rskTransactionHash, BtcTransaction pegoutTransaction, Coin amount) { + CallTransaction.Function rejectedPeginEvent = BridgeEvents.RELEASE_REQUESTED.getEvent(); + byte[] pegoutTransactionHash = pegoutTransaction.getHash().getBytes(); + List encodedTopics = getEncodedTopics(rejectedPeginEvent, rskTransactionHash, pegoutTransactionHash); + byte[] encodedData = getEncodedData(rejectedPeginEvent, amount.getValue()); + + assertEventWasEmittedWithExpectedTopics(encodedTopics, logs); + assertEventWasEmittedWithExpectedData(encodedData, logs); + } + + private void assertPegoutTransactioCreated(Sha256Hash pegoutTransactionHash, List outpointValues) { + CallTransaction.Function pegoutTransactionCreatedEvent = BridgeEvents.PEGOUT_TRANSACTION_CREATED.getEvent(); + + List encodedTopics = getEncodedTopics(pegoutTransactionCreatedEvent, pegoutTransactionHash.getBytes()); + byte[] serializedOutpointValues = UtxoUtils.encodeOutpointValues(outpointValues); + byte[] encodedData = getEncodedData(pegoutTransactionCreatedEvent, serializedOutpointValues); assertEventWasEmittedWithExpectedTopics(encodedTopics, logs); assertEventWasEmittedWithExpectedData(encodedData, logs);