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 404350737a..78f82a7b4d 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java @@ -17,6 +17,8 @@ */ package co.rsk.peg; +import static co.rsk.peg.BridgeSupportTestUtil.createFederationStorageProvider; +import static co.rsk.peg.BridgeSupportTestUtil.createRepository; import static co.rsk.peg.PegTestUtils.createFederation; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; @@ -36,8 +38,6 @@ import co.rsk.core.BlockDifficulty; import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; -import co.rsk.db.MutableTrieCache; -import co.rsk.db.MutableTrieImpl; import co.rsk.peg.bitcoin.BitcoinTestUtils; import co.rsk.peg.bitcoin.MerkleBranch; import co.rsk.peg.btcLockSender.BtcLockSenderProvider; @@ -56,7 +56,6 @@ import co.rsk.peg.whitelist.*; import co.rsk.peg.whitelist.constants.WhitelistMainNetConstants; import co.rsk.test.builders.*; -import co.rsk.trie.Trie; import com.google.common.collect.Lists; import java.io.*; import java.math.BigInteger; @@ -80,7 +79,6 @@ import org.ethereum.core.*; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; -import org.ethereum.db.MutableRepository; import org.ethereum.vm.DataWord; import org.ethereum.vm.PrecompiledContracts; import org.ethereum.vm.program.InternalTransaction; @@ -4366,13 +4364,4 @@ private void mockChainOfStoredBlocks(BtcBlockStoreWithCache btcBlockStore, BtcBl when(btcBlockStore.getChainHead()).thenReturn(currentStored); when(currentStored.getHeight()).thenReturn(headHeight); } - - public static Repository createRepository() { - return new MutableRepository(new MutableTrieCache(new MutableTrieImpl(null, new Trie()))); - } - - private FederationStorageProvider createFederationStorageProvider(Repository repository) { - StorageAccessor bridgeStorageAccessor = new BridgeStorageAccessorImpl(repository); - return new FederationStorageProviderImpl(bridgeStorageAccessor); - } -} \ No newline at end of file +} diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTestUtil.java b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTestUtil.java index 16b24772d1..12f19b9f56 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTestUtil.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTestUtil.java @@ -7,10 +7,18 @@ import co.rsk.db.MutableTrieCache; import co.rsk.db.MutableTrieImpl; import co.rsk.peg.bitcoin.BitcoinTestUtils; +import co.rsk.peg.federation.FederationStorageProvider; +import co.rsk.peg.federation.FederationStorageProviderImpl; +import co.rsk.peg.storage.BridgeStorageAccessorImpl; +import co.rsk.peg.storage.StorageAccessor; import co.rsk.trie.Trie; import java.math.BigInteger; import java.util.*; import org.bouncycastle.util.encoders.Hex; +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.core.Repository; import org.ethereum.db.MutableRepository; @@ -52,7 +60,7 @@ public static void recreateChainFromPmt( btcBlockStoreWithCache.setChainHead(storedChainHeadBlock); } - private static BtcBlock createBtcBlockWithPmt(PartialMerkleTree pmt, NetworkParameters networkParameters) { + public static BtcBlock createBtcBlockWithPmt(PartialMerkleTree pmt, NetworkParameters networkParameters) { Sha256Hash prevBlockHash = BitcoinTestUtils.createHash(1); Sha256Hash merkleRoot = pmt.getTxnHashAndMerkleRoot(new ArrayList<>()); @@ -81,4 +89,19 @@ public static void mockChainOfStoredBlocks(BtcBlockStoreWithCache btcBlockStore, when(btcBlockStore.getChainHead()).thenReturn(currentStored); when(currentStored.getHeight()).thenReturn(headHeight); } + + public static FederationStorageProvider createFederationStorageProvider(Repository repository) { + StorageAccessor bridgeStorageAccessor = new BridgeStorageAccessorImpl(repository); + return new FederationStorageProviderImpl(bridgeStorageAccessor); + } + + public static Block getRskExecutionBlock() { + long rskExecutionBlockNumber = 1000L; + long rskExecutionBlockTimestamp = 10L; + 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/RegisterBtcTransactionIT.java b/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java new file mode 100644 index 0000000000..0bbf9231ec --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/peg/RegisterBtcTransactionIT.java @@ -0,0 +1,156 @@ +package co.rsk.peg; + +import static co.rsk.peg.BridgeSupportTestUtil.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import co.rsk.bitcoinj.core.*; +import co.rsk.bitcoinj.script.ScriptBuilder; +import co.rsk.core.RskAddress; +import co.rsk.net.utils.TransactionUtils; +import co.rsk.peg.bitcoin.BitcoinTestUtils; +import co.rsk.peg.btcLockSender.BtcLockSenderProvider; +import co.rsk.peg.constants.BridgeConstants; +import co.rsk.peg.constants.BridgeMainNetConstants; +import co.rsk.peg.federation.*; +import co.rsk.peg.federation.constants.FederationConstants; +import co.rsk.peg.feeperkb.FeePerKbStorageProvider; +import co.rsk.peg.feeperkb.FeePerKbStorageProviderImpl; +import co.rsk.peg.feeperkb.FeePerKbSupport; +import co.rsk.peg.feeperkb.FeePerKbSupportImpl; +import co.rsk.peg.storage.BridgeStorageAccessorImpl; +import co.rsk.peg.storage.StorageAccessor; +import co.rsk.peg.utils.BridgeEventLoggerImpl; +import co.rsk.test.builders.BridgeSupportBuilder; +import co.rsk.test.builders.FederationSupportBuilder; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; +import org.ethereum.core.*; +import org.ethereum.vm.PrecompiledContracts; +import org.junit.jupiter.api.Test; +import java.util.*; + +class RegisterBtcTransactionIT { + + private final BridgeConstants bridgeConstants = BridgeMainNetConstants.getInstance(); + private final NetworkParameters btcParams = bridgeConstants.getBtcParams(); + private final BridgeSupportBuilder bridgeSupportBuilder = BridgeSupportBuilder.builder(); + + @Test + void whenRegisterALegacyBtcTransaction_shouldRegisterTheNewUtxoAndTransferTheRbtcBalance() throws Exception { + // Arrange + ActivationConfig.ForBlock activations = ActivationConfigsForTest.all().forBlock(0); + Repository repository = BridgeSupportTestUtil.createRepository(); + Repository track = repository.startTracking(); + Block rskExecutionBlock = getRskExecutionBlock(); + + BtcLockSenderProvider btcLockSenderProvider = new BtcLockSenderProvider(); + FeePerKbSupport feePerKbSupport = getFeePerKbSupport(repository, bridgeConstants); + + Federation federation = P2shErpFederationBuilder.builder().build(); + FederationStorageProvider federationStorageProvider = getFederationStorageProvider(track, federation); + FederationSupport federationSupport = getFederationSupport(federationStorageProvider, activations, bridgeConstants.getFederationConstants()); + + BtcECKey btcPublicKey = BitcoinTestUtils.getBtcEcKeyFromSeed("seed"); + Coin btcTransferred = bridgeConstants.getMinimumPeginTxValue(activations); + BtcTransaction bitcoinTransaction = createPegInTransaction(federationSupport.getActiveFederation().getAddress(), btcTransferred, btcPublicKey); + TransactionOutput output = bitcoinTransaction.getOutput(0); + List expectedFederationUtxos = Collections.singletonList(utxoOf(bitcoinTransaction, output)); + + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activations); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams(), 100, 100); + BtcBlockStoreWithCache btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(track, bridgeConstants, bridgeStorageProvider, activations); + + PartialMerkleTree pmtWithTransactions = createValidPmtForTransactions(Collections.singletonList(bitcoinTransaction.getHash()), bridgeConstants.getBtcParams()); + int btcBlockWithPmtHeight = bridgeConstants.getBtcHeightWhenPegoutTxIndexActivates() + bridgeConstants.getPegoutTxIndexGracePeriodInBtcBlocks(); + int chainHeight = btcBlockWithPmtHeight + bridgeConstants.getBtc2RskMinimumAcceptableConfirmations(); + + BridgeEventLoggerImpl bridgeEventLogger = spy(new BridgeEventLoggerImpl(bridgeConstants, activations, new ArrayList<>())); + + recreateChainFromPmt(btcBlockStoreWithCache, chainHeight, pmtWithTransactions, btcBlockWithPmtHeight, bridgeConstants.getBtcParams()); + + bridgeStorageProvider.save(); + + BridgeSupport bridgeSupport = getBridgeSupport(bridgeEventLogger, bridgeStorageProvider, activations, federationSupport, feePerKbSupport, rskExecutionBlock, btcBlockStoreFactory, track, btcLockSenderProvider); + + Transaction rskTx = TransactionUtils.createTransaction(); + org.ethereum.crypto.ECKey key = org.ethereum.crypto.ECKey.fromPublicOnly(btcPublicKey.getPubKey()); + + RskAddress receiver = new RskAddress(key.getAddress()); + co.rsk.core.Coin receiverBalance = track.getBalance(receiver); + co.rsk.core.Coin expectedReceiverBalance = receiverBalance.add(co.rsk.core.Coin.fromBitcoin(btcTransferred)); + + // Act + bridgeSupport.registerBtcTransaction(rskTx, bitcoinTransaction.bitcoinSerialize(), btcBlockWithPmtHeight, pmtWithTransactions.bitcoinSerialize()); + + bridgeSupport.save(); + track.commit(); + + // Assert + Optional heightIfBtcTxHashIsAlreadyProcessed = bridgeStorageProvider.getHeightIfBtcTxhashIsAlreadyProcessed(bitcoinTransaction.getHash()); + + assertTrue(heightIfBtcTxHashIsAlreadyProcessed.isPresent()); + assertEquals(rskExecutionBlock.getNumber(), heightIfBtcTxHashIsAlreadyProcessed.get()); + assertEquals(expectedFederationUtxos, federationSupport.getActiveFederationBtcUTXOs()); + assertEquals(expectedReceiverBalance, repository.getBalance(receiver)); + + verify(bridgeEventLogger, times(1)).logPeginBtc(receiver, bitcoinTransaction, btcTransferred, 0); + } + + private static UTXO utxoOf(BtcTransaction bitcoinTransaction, TransactionOutput output) { + return new UTXO( + bitcoinTransaction.getHash(), + output.getIndex(), + output.getValue(), + 0, + bitcoinTransaction.isCoinBase(), + output.getScriptPubKey() + ); + } + + private static FederationSupport getFederationSupport(FederationStorageProvider federationStorageProvider, ActivationConfig.ForBlock activationConfig, FederationConstants federationConstants) { + return FederationSupportBuilder.builder() + .withFederationConstants(federationConstants) + .withFederationStorageProvider(federationStorageProvider) + .withActivations(activationConfig) + .build(); + } + + private FederationStorageProvider getFederationStorageProvider(Repository track, Federation federation) { + FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); + federationStorageProvider.setNewFederation(federation); + return federationStorageProvider; + } + + private static FeePerKbSupport getFeePerKbSupport(Repository repository, BridgeConstants bridgeConstants) { + StorageAccessor bridgeStorageAccessor = new BridgeStorageAccessorImpl(repository); + FeePerKbStorageProvider feePerKbStorageProvider = new FeePerKbStorageProviderImpl(bridgeStorageAccessor); + return new FeePerKbSupportImpl( + bridgeConstants.getFeePerKbConstants(), + feePerKbStorageProvider + ); + } + + private BridgeSupport getBridgeSupport(BridgeEventLoggerImpl bridgeEventLogger, BridgeStorageProvider bridgeStorageProvider, ActivationConfig.ForBlock activationsBeforeForks, FederationSupport federationSupport, FeePerKbSupport feePerKbSupport, Block rskExecutionBlock, BtcBlockStoreWithCache.Factory btcBlockStoreFactory, Repository repository, BtcLockSenderProvider btcLockSenderProvider) { + return bridgeSupportBuilder + .withBridgeConstants(bridgeConstants) + .withProvider(bridgeStorageProvider) + .withEventLogger(bridgeEventLogger) + .withActivations(activationsBeforeForks) + .withFederationSupport(federationSupport) + .withFeePerKbSupport(feePerKbSupport) + .withExecutionBlock(rskExecutionBlock) + .withBtcBlockStoreFactory(btcBlockStoreFactory) + .withRepository(repository) + .withBtcLockSenderProvider(btcLockSenderProvider) + .build(); + } + + private BtcTransaction createPegInTransaction(Address federationAddress, Coin coin, BtcECKey pubKey) { + BtcTransaction btcTx = new BtcTransaction(btcParams); + btcTx.addInput(BitcoinTestUtils.createHash(0), 0, ScriptBuilder.createInputScript(null, pubKey)); + btcTx.addOutput(new TransactionOutput(btcParams, btcTx, coin, federationAddress)); + + return btcTx; + } +}