diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index adc09c6f6ab..1686908c02d 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -54,12 +54,12 @@ - - - + + + - - + + diff --git a/rskj-core/build.gradle b/rskj-core/build.gradle index f7a8fb332ad..da25c5c451e 100644 --- a/rskj-core/build.gradle +++ b/rskj-core/build.gradle @@ -21,6 +21,7 @@ checkstyle { } spotless { + enforceCheck false java { googleJavaFormat() indentWithTabs(2) @@ -150,6 +151,7 @@ tasks.withType(Checkstyle).configureEach { Checkstyle task -> } } } + task.dependsOn processResources } configurations { @@ -271,7 +273,7 @@ ext { jaxwsRtVer : '2.3.5', picocliVer : '4.6.3', - bitcoinjThinVer: '0.14.4-rsk-16-SNAPSHOT', + bitcoinjThinVer: '0.14.4-rsk-17', rskjNativeVer: '1.3.0', ] @@ -565,9 +567,14 @@ static def amendPathIfNeeded(details) { } } -tasks.named('check').configure { - dependsOn 'checkstyleMain' - dependsOn 'checkstyleTest' - dependsOn 'checkstyleJmh' - dependsOn 'checkstyleIntegrationTest' -} \ No newline at end of file +task checkstyleAll { + group = 'Verification' + description = 'Run all Checkstyle checks' + dependsOn 'checkstyleMain', 'checkstyleTest', 'checkstyleJmh', 'checkstyleIntegrationTest' +} + +task spotlessAll { + group = 'Verification' + description = 'Run all Spotless checks' + dependsOn 'spotlessJavaCheck' +} diff --git a/rskj-core/src/jmh/resources/conf/testnet-3_860_000.conf b/rskj-core/src/jmh/resources/conf/testnet-3_860_000.conf index 46a249de586..5a5872f04a6 100644 --- a/rskj-core/src/jmh/resources/conf/testnet-3_860_000.conf +++ b/rskj-core/src/jmh/resources/conf/testnet-3_860_000.conf @@ -33,9 +33,9 @@ sendRawTransaction.tx=0xf868826d28840387ee40825208947986b3df570230288501eea3d890 ############################ # estimateGas ############################ -estimateGas.from=0x05EEEF8B42D583880A24d74853669ee4a36bB530 -estimateGas.to=0x51B631722323189ee8d3e4E558eC399d7aa40FcC -estimateGas.data=0xcbf83a0400000000000000000000000000000000000000000000000000000000000000034254435553440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000409c969dacf4b38000000000000000000000000000005eeef8b42d583880a24d74853669ee4a36bb53000000000000000000000000000000000000000000000000000000000003567dd0000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000038e9c434a2b164e2c07758f0b30af4cf4b5ad0dc98e8f13413b8beb36124574b82c84681feac9d982591f4701ba33e2949d1ae0df7363ac90d99d5c16f91a9f0c6c1131f84944ae1dd2bba3bbcf3e592a838286bbcf816247939548ee33ec23e200000000000000000000000000000000000000000000000000000000000000034821d173b4098826fb4d5a42dbfd4582de9ff98efabecb5596ac088ec387e9c765a5d167455ef2de2636857fde728c153e94e6406779063f6d95f76a85a07ef33a0ab20a151a912a49838ad9262524986595531bc0680a502ca04fe736414775 +estimateGas.from=0x824dB056754BE52602CF9a2Ea1b8082Dad4dc89C +estimateGas.to=0xB913c7494b918Ed637D4df1308E1B7FF820b75B6 +estimateGas.data=0x5a686699000000000000000000000000000000000000000000000000f438a7c30553000000000000000000000000000000000000000000000000000000000000645fa03b0000000000000000000000006951020041bfa2565877bf0eaf7f5df039b490dc ############################ # debug diff --git a/rskj-core/src/main/java/co/rsk/RskContext.java b/rskj-core/src/main/java/co/rsk/RskContext.java index b3f8a0ee4da..70d16a00351 100644 --- a/rskj-core/src/main/java/co/rsk/RskContext.java +++ b/rskj-core/src/main/java/co/rsk/RskContext.java @@ -63,6 +63,9 @@ import co.rsk.rpc.*; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; +import co.rsk.rpc.modules.debug.trace.call.CallTracer; import co.rsk.rpc.modules.eth.*; import co.rsk.rpc.modules.eth.subscribe.BlockHeaderNotificationEmitter; import co.rsk.rpc.modules.eth.subscribe.LogsNotificationEmitter; @@ -104,6 +107,7 @@ import org.ethereum.crypto.ECKey; import org.ethereum.crypto.signature.Secp256k1; import org.ethereum.datasource.*; +import org.ethereum.db.BlockStore; import org.ethereum.db.IndexedBlockStore; import org.ethereum.db.ReceiptStore; import org.ethereum.db.ReceiptStoreImplV2; @@ -812,15 +816,16 @@ public synchronized ConfigCapabilities getConfigCapabilities() { public synchronized DebugModule getDebugModule() { checkIfNotClosed(); + Web3InformationRetriever web3i = getWeb3InformationRetriever(); + BlockStore bs = getBlockStore(); + BlockExecutor be = getBlockExecutor(); + RskTracer rskTracer = new RskTracer(bs, getReceiptStore(), + be, web3i); + + CallTracer callTracer = new CallTracer(bs, be, web3i, getReceiptStore(), getBlockchain()); + TraceProvider traceProvider = new TraceProvider(Arrays.asList(callTracer, rskTracer)); if (debugModule == null) { - debugModule = new DebugModuleImpl( - getBlockStore(), - getReceiptStore(), - getNodeMessageHandler(), - getBlockExecutor(), - getTxQuotaChecker(), - getWeb3InformationRetriever() - ); + debugModule = new DebugModuleImpl(traceProvider, getNodeMessageHandler(), getTxQuotaChecker()); } return debugModule; 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 342d13cdd43..92c93eb4de9 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeSupport.java @@ -19,6 +19,7 @@ import static co.rsk.peg.BridgeUtils.getRegularPegoutTxSize; import static co.rsk.peg.ReleaseTransactionBuilder.BTC_TX_VERSION_2; +import static co.rsk.peg.bitcoin.BitcoinUtils.findWitnessCommitment; import static co.rsk.peg.bitcoin.UtxoUtils.extractOutpointValues; import static co.rsk.peg.pegin.RejectedPeginReason.INVALID_AMOUNT; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; @@ -52,6 +53,7 @@ import co.rsk.peg.whitelist.*; import co.rsk.rpc.modules.trace.CallType; import co.rsk.rpc.modules.trace.ProgramSubtrace; +import co.rsk.util.HexUtils; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.io.InputStream; @@ -60,6 +62,7 @@ import java.util.*; import java.util.stream.Collectors; import javax.annotation.Nullable; + import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; @@ -1765,7 +1768,7 @@ public Long getBtcTransactionConfirmationsGetCost(Object[] args) { Context.propagate(btcContext); try { this.ensureBtcBlockStore(); - final StoredBlock block = btcBlockStore.getFromCache(btcBlockHash); + final StoredBlock block = getBlockKeepingTestnetConsensus(btcBlockHash); // Block not found, default to basic cost if (block == null) { @@ -1803,7 +1806,7 @@ public Integer getBtcTransactionConfirmations(Sha256Hash btcTxHash, Sha256Hash b this.ensureBtcBlockChain(); // Get the block using the given block hash - StoredBlock block = btcBlockStore.getFromCache(btcBlockHash); + StoredBlock block = getBlockKeepingTestnetConsensus(btcBlockHash); if (block == null) { return BTC_TRANSACTION_CONFIRMATION_INEXISTENT_BLOCK_HASH_ERROR_CODE; } @@ -1839,6 +1842,31 @@ public Integer getBtcTransactionConfirmations(Sha256Hash btcTxHash, Sha256Hash b return bestChainHeight - block.getHeight() + 1; } + private StoredBlock getBlockKeepingTestnetConsensus(Sha256Hash btcBlockHash) throws BlockStoreException { + long rskBlockNumber = 5_148_285; + + boolean networkIsTestnet = bridgeConstants.getBtcParams().equals(NetworkParameters.fromID(NetworkParameters.ID_TESTNET)); + Sha256Hash blockHash = Sha256Hash.wrap("00000000e8e7b540df01a7067e020fd7e2026bf86289def2283a35120c1af379"); + + // DO NOT MODIFY. + // This check is needed since this block caused a misbehaviour + // for being stored in the cache but not in the storage + if (rskExecutionBlock.getNumber() == rskBlockNumber + && networkIsTestnet + && btcBlockHash.equals(blockHash) + ) { + byte[] rawBtcBlockHeader = HexUtils.stringHexToByteArray("000000203b5d178405c4e6e7dc07d63d6de5db1342044791721654760c00000000000000796cf6743a036300b43fb3abe6703d04a7999751b6d5744f20327d1175320bd37b954e66ffff001d56dc11ce"); + + BtcBlock btcBlockHeader = new BtcBlock(bridgeConstants.getBtcParams(), rawBtcBlockHeader); + BigInteger btcBlockChainWork = new BigInteger("000000000000000000000000000000000000000000000ddeb5fbcd969312a77c", 16); + int btcBlockNumber = 2_817_125; + + return new StoredBlock(btcBlockHeader, btcBlockChainWork, btcBlockNumber); + } + + return btcBlockStore.get(btcBlockHash); + } + private StoredBlock getPrevBlockAtHeight(StoredBlock cursor, int height) throws BlockStoreException { if (cursor.getHeight() == height) { return cursor; @@ -2144,49 +2172,88 @@ public boolean increaseLockingCap(Transaction tx, Coin newLockingCap) throws Loc return lockingCapSupport.increaseLockingCap(tx, newLockingCap); } - public void registerBtcCoinbaseTransaction(byte[] btcTxSerialized, Sha256Hash blockHash, byte[] pmtSerialized, Sha256Hash witnessMerkleRoot, byte[] witnessReservedValue) throws VMException { + public void registerBtcCoinbaseTransaction( + byte[] btcTxSerialized, + Sha256Hash blockHash, + byte[] pmtSerialized, + Sha256Hash witnessMerkleRoot, + byte[] witnessReservedValue + ) throws VMException { Context.propagate(btcContext); try{ this.ensureBtcBlockStore(); }catch (BlockStoreException | IOException e) { - logger.warn("Exception in registerBtcCoinbaseTransaction", e); - throw new VMException("Exception in registerBtcCoinbaseTransaction", e); + String message = String.format("Exception in registerBtcCoinbaseTransaction. %s", e.getMessage()); + logger.warn("[registerBtcCoinbaseTransaction] {}", message); + throw new VMException(message, e); } Sha256Hash btcTxHash = BtcTransactionFormatUtils.calculateBtcTxHash(btcTxSerialized); + logger.debug("[registerBtcCoinbaseTransaction] Going to register coinbase information for btcTx: {}", btcTxHash); if (witnessReservedValue.length != 32) { - logger.warn("[btcTx:{}] WitnessResevedValue length can't be different than 32 bytes", btcTxHash); - throw new BridgeIllegalArgumentException("WitnessResevedValue length can't be different than 32 bytes"); + String message = String.format( + "Witness reserved value length can't be different than 32 bytes. Value received: %s", + Bytes.of(witnessReservedValue) + ); + logger.warn("[registerBtcCoinbaseTransaction] {}", message); + throw new BridgeIllegalArgumentException(message); } + logger.trace("[registerBtcCoinbaseTransaction] Witness reserved value: {}", Bytes.of(witnessReservedValue)); if (!PartialMerkleTreeFormatUtils.hasExpectedSize(pmtSerialized)) { - logger.warn("[btcTx:{}] PartialMerkleTree doesn't have expected size", btcTxHash); - throw new BridgeIllegalArgumentException("PartialMerkleTree doesn't have expected size"); + String message = String.format( + "PartialMerkleTree doesn't have expected size. Value received: %s", + Bytes.of(pmtSerialized) + ); + logger.warn("[registerBtcCoinbaseTransaction] {}", message); + throw new BridgeIllegalArgumentException(message); } Sha256Hash merkleRoot; - try { PartialMerkleTree pmt = new PartialMerkleTree(networkParameters, pmtSerialized, 0); List hashesInPmt = new ArrayList<>(); merkleRoot = pmt.getTxnHashAndMerkleRoot(hashesInPmt); if (!hashesInPmt.contains(btcTxHash)) { - logger.warn("Supplied Btc Tx {} is not in the supplied partial merkle tree", btcTxHash); + logger.warn( + "[registerBtcCoinbaseTransaction] Supplied btc tx {} is not in the supplied partial merkle tree {}", + btcTxHash, + pmt + ); return; } } catch (VerificationException e) { - logger.warn("[btcTx:{}] PartialMerkleTree could not be parsed", btcTxHash); - throw new BridgeIllegalArgumentException(String.format("PartialMerkleTree could not be parsed %s", Bytes.of(pmtSerialized)), e); + String message = String.format("Partial merkle tree could not be parsed. %s", Bytes.of(pmtSerialized)); + logger.warn("[registerBtcCoinbaseTransaction] {}", message); + throw new BridgeIllegalArgumentException(message, e); } + logger.trace("[registerBtcCoinbaseTransaction] Merkle root: {}", merkleRoot); // Check merkle root equals btc block merkle root at the specified height in the btc best chain // Btc blockstore is available since we've already queried the best chain height - StoredBlock storedBlock = btcBlockStore.getFromCache(blockHash); + StoredBlock storedBlock = null; + try { + storedBlock = btcBlockStore.get(blockHash); + } catch (BlockStoreException e) { + logger.error( + "[registerBtcCoinbaseTransaction] Error gettin block {} from block store. {}", + blockHash, + e.getMessage() + ); + } + if (storedBlock == null) { - logger.warn("[btcTx:{}] Block not registered", btcTxHash); - throw new BridgeIllegalArgumentException(String.format("Block not registered %s", blockHash.toString())); + String message = String.format("Block %s not yet registered", blockHash); + logger.warn("[registerBtcCoinbaseTransaction] {}", message); + throw new BridgeIllegalArgumentException(message); } + logger.trace( + "[registerBtcCoinbaseTransaction] Found block with hash {} at height {}", + blockHash, + storedBlock.getHeight() + ); + BtcBlock blockHeader = storedBlock.getHeader(); if (!blockHeader.getMerkleRoot().equals(merkleRoot)) { String panicMessage = String.format( @@ -2195,7 +2262,7 @@ public void registerBtcCoinbaseTransaction(byte[] btcTxSerialized, Sha256Hash bl merkleRoot, blockHeader.getMerkleRoot() ); - logger.warn(panicMessage); + logger.warn("[registerBtcCoinbaseTransaction] {}", panicMessage); panicProcessor.panic("btclock", panicMessage); return; } @@ -2203,17 +2270,33 @@ public void registerBtcCoinbaseTransaction(byte[] btcTxSerialized, Sha256Hash bl BtcTransaction btcTx = new BtcTransaction(networkParameters, btcTxSerialized); btcTx.verify(); - Sha256Hash witnessCommitment = Sha256Hash.twiceOf(witnessMerkleRoot.getReversedBytes(), witnessReservedValue); - - if(!witnessCommitment.equals(btcTx.findWitnessCommitment())){ - logger.warn("[btcTx:{}] WitnessCommitment does not match", btcTxHash); - throw new BridgeIllegalArgumentException("WitnessCommitment does not match"); - } + validateWitnessInformation(btcTx, witnessMerkleRoot, witnessReservedValue); CoinbaseInformation coinbaseInformation = new CoinbaseInformation(witnessMerkleRoot); provider.setCoinbaseInformation(blockHeader.getHash(), coinbaseInformation); - logger.warn("[btcTx:{}] Registered coinbase information", btcTxHash); + logger.debug("[registerBtcCoinbaseTransaction] Registered coinbase information for btc tx {}", btcTxHash); + } + + private void validateWitnessInformation( + BtcTransaction coinbaseTransaction, + Sha256Hash witnessMerkleRoot, + byte[] witnessReservedValue + ) throws BridgeIllegalArgumentException { + Optional expectedWitnessCommitment = findWitnessCommitment(coinbaseTransaction); + Sha256Hash calculatedWitnessCommitment = Sha256Hash.twiceOf(witnessMerkleRoot.getReversedBytes(), witnessReservedValue); + + if (expectedWitnessCommitment.isEmpty() || !expectedWitnessCommitment.get().equals(calculatedWitnessCommitment)) { + String message = String.format( + "[btcTx: %s] Witness commitment does not match. Expected: %s, Calculated: %s", + coinbaseTransaction.getHash(), + expectedWitnessCommitment.orElse(null), + calculatedWitnessCommitment + ); + logger.warn("[validateWitnessInformation] {}", message); + throw new BridgeIllegalArgumentException(message); + } + logger.debug("[validateWitnessInformation] Witness commitment {} validated for btc tx {}", calculatedWitnessCommitment, coinbaseTransaction.getHash()); } public boolean hasBtcBlockCoinbaseTransactionInformation(Sha256Hash blockHash) { diff --git a/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java b/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java index 5487d8afb82..fb2e4af1b0b 100644 --- a/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java +++ b/rskj-core/src/main/java/co/rsk/peg/BridgeUtils.java @@ -19,9 +19,7 @@ import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.crypto.TransactionSignature; -import co.rsk.bitcoinj.script.RedeemScriptParserFactory; -import co.rsk.bitcoinj.script.Script; -import co.rsk.bitcoinj.script.ScriptChunk; +import co.rsk.bitcoinj.script.*; import co.rsk.bitcoinj.wallet.Wallet; import co.rsk.peg.constants.BridgeConstants; import co.rsk.core.RskAddress; @@ -48,10 +46,7 @@ import javax.annotation.Nonnull; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP284; diff --git a/rskj-core/src/main/java/co/rsk/peg/RepositoryBtcBlockStoreWithCache.java b/rskj-core/src/main/java/co/rsk/peg/RepositoryBtcBlockStoreWithCache.java index 04db27e4e70..cf783d9edb6 100644 --- a/rskj-core/src/main/java/co/rsk/peg/RepositoryBtcBlockStoreWithCache.java +++ b/rskj-core/src/main/java/co/rsk/peg/RepositoryBtcBlockStoreWithCache.java @@ -18,6 +18,9 @@ package co.rsk.peg; +import static co.rsk.bitcoinj.core.StoredBlock.deserializeCompactLegacy; +import static co.rsk.bitcoinj.core.StoredBlock.deserializeCompactV2; + import co.rsk.bitcoinj.core.BtcBlock; import co.rsk.bitcoinj.core.NetworkParameters; import co.rsk.bitcoinj.core.Sha256Hash; @@ -126,8 +129,7 @@ public synchronized StoredBlock get(Sha256Hash hash) { logger.trace("[get] Block with hash {} not found in storage", hash); return null; } - StoredBlock storedBlock = byteArrayToStoredBlock(ba); - return storedBlock; + return byteArrayToStoredBlock(ba); } @Override @@ -200,7 +202,7 @@ public StoredBlock getStoredBlockAtMainChainHeight(int height) throws BlockStore if (depth < 0) { String message = String.format( - "Height provided is higher than chain head. provided: %n. chain head: %n", + "Height provided is higher than chain head. provided: %s. chain head: %s", height, chainHead.getHeight() ); @@ -308,17 +310,36 @@ public StoredBlock getStoredBlockAtMainChainDepth(int depth) throws BlockStoreEx } private byte[] storedBlockToByteArray(StoredBlock block) { - ByteBuffer byteBuffer = ByteBuffer.allocate(128); - block.serializeCompact(byteBuffer); + ByteBuffer byteBuffer = serializeBlock(block); byte[] ba = new byte[byteBuffer.position()]; byteBuffer.flip(); byteBuffer.get(ba); return ba; } + private ByteBuffer serializeBlock(StoredBlock block) { + if (shouldUseLegacy12ByteChainworkFormat()) { + ByteBuffer byteBuffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE_LEGACY); + block.serializeCompactLegacy(byteBuffer); + return byteBuffer; + } + + ByteBuffer byteBuffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE_V2); + block.serializeCompactV2(byteBuffer); + return byteBuffer; + } + + private boolean shouldUseLegacy12ByteChainworkFormat() { + return !activations.isActive(ConsensusRule.RSKIP454); + } + private StoredBlock byteArrayToStoredBlock(byte[] ba) { ByteBuffer byteBuffer = ByteBuffer.wrap(ba); - return StoredBlock.deserializeCompact(btcNetworkParams, byteBuffer); + if (ba.length == StoredBlock.COMPACT_SERIALIZED_SIZE_LEGACY) { + return deserializeCompactLegacy(btcNetworkParams, byteBuffer); + } + + return deserializeCompactV2(btcNetworkParams, byteBuffer); } private void checkIfInitialized() { 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..cfcfb56e9dc 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,20 +1,20 @@ 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.script.Script; -import co.rsk.bitcoinj.script.ScriptBuilder; -import co.rsk.bitcoinj.script.ScriptChunk; +import static co.rsk.bitcoinj.script.ScriptOpCodes.OP_RETURN; + +import co.rsk.bitcoinj.core.*; +import co.rsk.bitcoinj.script.*; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.util.*; +import org.bouncycastle.util.encoders.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.Optional; - public class BitcoinUtils { - + protected static final byte[] WITNESS_COMMITMENT_HEADER = Hex.decode("aa21a9ed"); + protected static final int WITNESS_COMMITMENT_LENGTH = WITNESS_COMMITMENT_HEADER.length + Sha256Hash.LENGTH; + private static final int MINIMUM_WITNESS_COMMITMENT_SIZE = WITNESS_COMMITMENT_LENGTH + 2; // 1 extra by for OP_RETURN and another one for data length private static final Logger logger = LoggerFactory.getLogger(BitcoinUtils.class); private static final int FIRST_INPUT_INDEX = 0; @@ -81,4 +81,54 @@ public static void removeSignaturesFromTransactionWithP2shMultiSigInputs(BtcTran input.setScriptSig(emptyInputScript); } } + + public static Optional findWitnessCommitment(BtcTransaction tx) { + Preconditions.checkState(tx.isCoinBase()); + // If more than one witness commitment, take the last one as the valid one + List outputsReversed = Lists.reverse(tx.getOutputs()); + + for (TransactionOutput output : outputsReversed) { + Script scriptPubKey = output.getScriptPubKey(); + if (isWitnessCommitment(scriptPubKey)) { + Sha256Hash witnessCommitment = extractWitnessCommitmentHash(scriptPubKey); + return Optional.of(witnessCommitment); + } + } + + return Optional.empty(); + } + + private static boolean isWitnessCommitment(Script scriptPubKey) { + byte[] scriptPubKeyProgram = scriptPubKey.getProgram(); + + return scriptPubKeyProgram.length >= MINIMUM_WITNESS_COMMITMENT_SIZE + && hasCommitmentStructure(scriptPubKeyProgram); + } + + private static boolean hasCommitmentStructure(byte[] scriptPubKeyProgram) { + return scriptPubKeyProgram[0] == OP_RETURN + && scriptPubKeyProgram[1] == WITNESS_COMMITMENT_LENGTH + && hasWitnessCommitmentHeader(Arrays.copyOfRange(scriptPubKeyProgram, 2, 6)); + } + + private static boolean hasWitnessCommitmentHeader(byte[] header) { + return Arrays.equals(header, WITNESS_COMMITMENT_HEADER); + } + + /** + * Retrieves the hash from a segwit commitment (in an output of the coinbase transaction). + */ + private static Sha256Hash extractWitnessCommitmentHash(Script scriptPubKey) { + byte[] scriptPubKeyProgram = scriptPubKey.getProgram(); + Preconditions.checkState(scriptPubKeyProgram.length >= MINIMUM_WITNESS_COMMITMENT_SIZE); + + final int WITNESS_COMMITMENT_HASH_START = 6; // 4 bytes for header + OP_RETURN + data length + byte[] witnessCommitmentHash = Arrays.copyOfRange( + scriptPubKeyProgram, + WITNESS_COMMITMENT_HASH_START, + MINIMUM_WITNESS_COMMITMENT_SIZE + ); + + return Sha256Hash.wrap(witnessCommitmentHash); + } } diff --git a/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java b/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java index c59feddf07f..98074fc4ac8 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/Web3DebugModule.java @@ -21,8 +21,7 @@ import co.rsk.net.handler.quota.TxQuota; import co.rsk.rpc.modules.debug.DebugModule; import com.fasterxml.jackson.databind.JsonNode; - -import java.util.Map; +import org.ethereum.rpc.parameters.DebugTracerParam; @java.lang.SuppressWarnings("squid:S100") public interface Web3DebugModule { @@ -32,27 +31,28 @@ default String debug_wireProtocolQueueSize() { } default JsonNode debug_traceTransaction(String transactionHash) throws Exception { - return debug_traceTransaction(transactionHash, null); + return getDebugModule().traceTransaction(transactionHash); } - default JsonNode debug_traceTransaction(String transactionHash, Map traceOptions) throws Exception { - return getDebugModule().traceTransaction(transactionHash, traceOptions); + default JsonNode debug_traceTransaction(String transactionHash, DebugTracerParam traceParams) throws Exception { + return getDebugModule().traceTransaction(transactionHash, traceParams.getTraceOptions(), traceParams.getTracerType()); } default JsonNode debug_traceBlockByHash(String blockHash) throws Exception { - return debug_traceBlockByHash(blockHash, null); + return getDebugModule().traceBlockByHash(blockHash); } - default JsonNode debug_traceBlockByHash(String blockHash, Map traceOptions) throws Exception { - return getDebugModule().traceBlockByHash(blockHash, traceOptions); + default JsonNode debug_traceBlockByHash(String blockHash, DebugTracerParam debugTracerParam) throws Exception { + return getDebugModule().traceBlockByHash(blockHash, debugTracerParam.getTraceOptions(), debugTracerParam.getTracerType()); } default JsonNode debug_traceBlockByNumber(String bnOrId) throws Exception { - return debug_traceBlockByNumber(bnOrId, null); + + return debug_traceBlockByNumber(bnOrId, new DebugTracerParam()); } - default JsonNode debug_traceBlockByNumber(String bnOrId, Map traceOptions) throws Exception { - return getDebugModule().traceBlockByNumber(bnOrId, traceOptions); + default JsonNode debug_traceBlockByNumber(String bnOrId, DebugTracerParam debugTracerParam) throws Exception { + return getDebugModule().traceBlockByNumber(bnOrId, debugTracerParam.getTraceOptions(), debugTracerParam.getTracerType()); } default TxQuota debug_accountTransactionQuota(String address) { diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java index 4b7c3e2ce30..ead881a9b3e 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModule.java @@ -19,19 +19,21 @@ package co.rsk.rpc.modules.debug; import co.rsk.net.handler.quota.TxQuota; +import co.rsk.rpc.modules.debug.trace.TracerType; import com.fasterxml.jackson.databind.JsonNode; -import java.util.Map; - public interface DebugModule { String wireProtocolQueueSize(); - JsonNode traceTransaction(String transactionHash, Map traceOptions) throws Exception; + JsonNode traceTransaction(String transactionHash) throws Exception; + + JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) throws Exception; - JsonNode traceBlockByHash(String blockHash, Map traceOptions) throws Exception; + JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions, TracerType tracerType) throws Exception; + JsonNode traceBlockByHash(String blockHash) throws Exception; - JsonNode traceBlockByNumber(String bnOrId, Map traceOptions) throws Exception; + JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions, TracerType tracerType) throws Exception; TxQuota accountTransactionQuota(String address); } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java index 62be8e6e55c..ff99323790e 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DebugModuleImpl.java @@ -19,55 +19,33 @@ package co.rsk.rpc.modules.debug; import co.rsk.core.RskAddress; -import co.rsk.core.bc.BlockExecutor; -import co.rsk.crypto.Keccak256; import co.rsk.net.MessageHandler; import co.rsk.net.handler.quota.TxQuota; import co.rsk.net.handler.quota.TxQuotaChecker; -import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; +import co.rsk.rpc.modules.debug.trace.TracerType; import co.rsk.util.HexUtils; import co.rsk.util.StringUtils; import com.fasterxml.jackson.databind.JsonNode; -import org.ethereum.core.Block; -import org.ethereum.core.Transaction; -import org.ethereum.db.BlockStore; -import org.ethereum.db.ReceiptStore; -import org.ethereum.db.TransactionInfo; -import org.ethereum.vm.trace.ProgramTraceProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - public class DebugModuleImpl implements DebugModule { + //this could be configurable + public static final TracerType DEFAULT_TRACER_TYPE = TracerType.RSK_TRACER; private static final Logger logger = LoggerFactory.getLogger("web3"); - - private final BlockStore blockStore; - private final ReceiptStore receiptStore; - + private final TraceProvider traceProvider; private final MessageHandler messageHandler; - private final BlockExecutor blockExecutor; - private final TxQuotaChecker txQuotaChecker; - private final Web3InformationRetriever web3InformationRetriever; - - public DebugModuleImpl( - BlockStore blockStore, - ReceiptStore receiptStore, - MessageHandler messageHandler, - BlockExecutor blockExecutor, - TxQuotaChecker txQuotaChecker, - Web3InformationRetriever web3InformationRetriever) { - this.blockStore = blockStore; - this.receiptStore = receiptStore; + + public DebugModuleImpl(TraceProvider traceProvider, MessageHandler messageHandler, TxQuotaChecker txQuotaChecker) { + this.traceProvider = traceProvider; this.messageHandler = messageHandler; - this.blockExecutor = blockExecutor; this.txQuotaChecker = txQuotaChecker; - this.web3InformationRetriever = web3InformationRetriever; } + @Override public String wireProtocolQueueSize() { long n = messageHandler.getMessageQueueSize(); @@ -75,89 +53,71 @@ public String wireProtocolQueueSize() { } @Override - public JsonNode traceTransaction(String transactionHash, Map traceOptions) { - logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); - - TraceOptions options = toTraceOptions(traceOptions); - - byte[] hash = HexUtils.stringHexToByteArray(transactionHash); - TransactionInfo txInfo = receiptStore.getInMainChain(hash, blockStore).orElse(null); - - if (txInfo == null) { - logger.trace("No transaction info for txHash: {}", StringUtils.trim(transactionHash)); - return null; + public TxQuota accountTransactionQuota(String address) { + if (logger.isTraceEnabled()) { + logger.trace("debug_accountTransactionQuota({})", StringUtils.trim(address)); } - - Block block = blockStore.getBlockByHash(txInfo.getBlockHash()); - Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); - Transaction tx = block.getTransactionsList().get(txInfo.getIndex()); - txInfo.setTransaction(tx); - - ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); - blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); - - return programTraceProcessor.getProgramTraceAsJsonNode(tx.getHash()); + RskAddress rskAddress = new RskAddress(address); + return txQuotaChecker.getTxQuota(rskAddress); } @Override - public JsonNode traceBlockByHash(String blockHash, Map traceOptions) { - logger.trace("debug_traceBlockByHash for blockHash: {}", StringUtils.trim(blockHash)); + public JsonNode traceTransaction(String transactionHash) throws Exception { + return traceTransaction(transactionHash, new TraceOptions(), null); + } - TraceOptions options = toTraceOptions(traceOptions); + @Override + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions, TracerType tracerType) throws Exception { + TracerType type = getTracerTypeOrDefault(tracerType); - byte[] bHash = HexUtils.stringHexToByteArray(blockHash); - Block block = blockStore.getBlockByHash(bHash); - if (block == null) { - logger.trace("No block is found for blockHash: {}", StringUtils.trim(blockHash)); - return null; + if (traceOptions == null) { + traceOptions = new TraceOptions(); } - - return traceBlock(block, options); + DebugTracer tracer = traceProvider.getTracer(type); + if (logger.isTraceEnabled()) { + logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); + } + return tracer.traceTransaction(transactionHash, traceOptions); } @Override - public JsonNode traceBlockByNumber(String bnOrId, Map traceOptions) { - logger.trace("debug_traceBlockByNumber for bnOrId: {}", StringUtils.trim(bnOrId)); + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions, TracerType tracerType) { + TracerType type = getTracerTypeOrDefault(tracerType); - TraceOptions options = toTraceOptions(traceOptions); - - Block block = web3InformationRetriever.getBlock(bnOrId).orElse(null); - if (block == null) { - logger.trace("No block is found for bnOrId: {}", StringUtils.trim(bnOrId)); - return null; + if (traceOptions == null) { + traceOptions = new TraceOptions(); } - - return traceBlock(block, options); + if (logger.isTraceEnabled()) { + logger.trace("debug_traceBlockByHash for blockHash: {}", StringUtils.trim(blockHash)); + } + DebugTracer tracer = traceProvider.getTracer(type); + return tracer.traceBlockByHash(blockHash, traceOptions); } - private JsonNode traceBlock(Block block, TraceOptions options) { - Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); - - ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); - blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); - - List txHashes = block.getTransactionsList().stream() - .map(Transaction::getHash) - .collect(Collectors.toList()); - - return programTraceProcessor.getProgramTracesAsJsonNode(txHashes); + @Override + public JsonNode traceBlockByHash(String blockHash) throws Exception { + return traceBlockByHash(blockHash, new TraceOptions(), null); } - private TraceOptions toTraceOptions(Map traceOptions) { - TraceOptions options = new TraceOptions(traceOptions); - if (!options.getUnsupportedOptions().isEmpty()) { - // TODO: implement the logic that takes into account the remaining trace options. - logger.warn("Received {} unsupported trace options", options.getUnsupportedOptions().size()); + @Override + public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions, TracerType tracerType) throws Exception { + TracerType type = getTracerTypeOrDefault(tracerType); + if (traceOptions == null) { + traceOptions = new TraceOptions(); } - - return options; + if (logger.isTraceEnabled()) { + logger.trace("debug_traceBlockByNumber for bnOrId: {}", StringUtils.trim(bnOrId)); + } + DebugTracer tracer = traceProvider.getTracer(type); + return tracer.traceBlockByNumber(bnOrId, traceOptions); } - @Override - public TxQuota accountTransactionQuota(String address) { - logger.trace("debug_accountTransactionQuota({})", StringUtils.trim(address)); - RskAddress rskAddress = new RskAddress(address); - return this.txQuotaChecker.getTxQuota(rskAddress); + private TracerType getTracerTypeOrDefault(TracerType tracerType) { + //TODO review about this default tracer logic + if (tracerType == null) { + return DEFAULT_TRACER_TYPE; + } + return tracerType; } } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java index 6fac1c24916..94987d8bc5d 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/DisableOption.java @@ -31,4 +31,13 @@ public enum DisableOption { this.option = option; this.value = value; } + + public static DisableOption getDisableOption(String option) { + for (DisableOption disableOption : DisableOption.values()) { + if (disableOption.option.equalsIgnoreCase(option)) { + return disableOption; + } + } + return null; + } } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java index b5d3fa3cc40..b6814b1c051 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/TraceOptions.java @@ -19,42 +19,62 @@ package co.rsk.rpc.modules.debug; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; +import java.io.Serial; import java.util.*; -import java.util.stream.Collectors; +@JsonDeserialize(using = TraceOptions.Deserializer.class) public class TraceOptions { - private final List supportedOptions; private final Set disabledFields; private final Set unsupportedOptions; + private boolean onlyTopCall; + private boolean withLog; public TraceOptions() { - supportedOptions = Arrays.stream(DisableOption.values()).map(option -> option.option) - .collect(Collectors.toList()); - this.disabledFields = new HashSet<>(); this.unsupportedOptions = new HashSet<>(); } public TraceOptions(Map traceOptions) { this(); + if (traceOptions == null) { + return; + } + traceOptions.forEach(this::addOption); + } - if (traceOptions == null || traceOptions.isEmpty()) return; - - // Disabled Fields Parsing + public final void addOption(String key, String value) { + switch (key) { + case "onlyTopCall" -> onlyTopCall = Boolean.parseBoolean(value); + case "withLog" -> withLog = Boolean.parseBoolean(value); + default -> addDisableOption(key, value); + } + } - for (DisableOption disableOption : DisableOption.values()) { - if (Boolean.parseBoolean(traceOptions.get(disableOption.option))) { - this.disabledFields.add(disableOption.value); + private void addDisableOption(String key, String value) { + DisableOption disableOption = DisableOption.getDisableOption(key); + if (disableOption != null) { + if (Boolean.parseBoolean(value)) { + disabledFields.add(disableOption.value); } + } else { + unsupportedOptions.add(key); } + } - // Unsupported Options + public boolean isOnlyTopCall() { + return onlyTopCall; + } - traceOptions.keySet() - .stream() - .filter(key -> supportedOptions.stream().noneMatch(option -> option.equals(key))) - .forEach(unsupportedOptions::add); + public boolean isWithLog() { + return withLog; } public Set getDisabledFields() { @@ -65,4 +85,33 @@ public Set getUnsupportedOptions() { return Collections.unmodifiableSet(unsupportedOptions); } + public static class Deserializer extends StdDeserializer { + @Serial + private static final long serialVersionUID = 4222943114560623356L; + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public TraceOptions deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node = jp.getCodec().readTree(jp); + TraceOptions traceOptions = new TraceOptions(); + Iterator> fields = node.fields(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + if ("tracerConfig".equalsIgnoreCase(entry.getKey())) { + JsonNode tracerConfigNode = entry.getValue(); + tracerConfigNode.fields().forEachRemaining(tracerConfigEntry + -> traceOptions.addOption(tracerConfigEntry.getKey(), tracerConfigEntry.getValue().asText())); + } + traceOptions.addOption(entry.getKey(), entry.getValue().asText()); + } + return traceOptions; + } + } } diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java new file mode 100644 index 00000000000..02ea22aaff0 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/DebugTracer.java @@ -0,0 +1,31 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +import co.rsk.rpc.modules.debug.TraceOptions; +import com.fasterxml.jackson.databind.JsonNode; + +public interface DebugTracer { + JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) throws Exception; + + JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions); + + JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions); + + TracerType getTracerType(); +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/RskTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/RskTracer.java new file mode 100644 index 00000000000..0dd923e9bad --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/RskTracer.java @@ -0,0 +1,123 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +import co.rsk.core.bc.BlockExecutor; +import co.rsk.crypto.Keccak256; +import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.util.HexUtils; +import co.rsk.util.StringUtils; +import com.fasterxml.jackson.databind.JsonNode; +import org.ethereum.core.Block; +import org.ethereum.core.Transaction; +import org.ethereum.db.BlockStore; +import org.ethereum.db.ReceiptStore; +import org.ethereum.db.TransactionInfo; +import org.ethereum.vm.trace.ProgramTraceProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.stream.Collectors; + +public class RskTracer implements DebugTracer { + + private static final Logger logger = LoggerFactory.getLogger("web3"); + private final ReceiptStore receiptStore; + private final BlockStore blockStore; + private final BlockExecutor blockExecutor; + private final Web3InformationRetriever web3InformationRetriever; + + public RskTracer( + BlockStore blockStore, + ReceiptStore receiptStore, + BlockExecutor blockExecutor, + Web3InformationRetriever web3InformationRetriever) { + this.blockStore = blockStore; + this.receiptStore = receiptStore; + this.blockExecutor = blockExecutor; + this.web3InformationRetriever = web3InformationRetriever; + } + + @Override + public JsonNode traceTransaction(String transactionHash, TraceOptions traceOptions) { + logger.trace("debug_traceTransaction for txHash: {}", StringUtils.trim(transactionHash)); + + + byte[] hash = HexUtils.stringHexToByteArray(transactionHash); + TransactionInfo txInfo = receiptStore.getInMainChain(hash, blockStore).orElse(null); + + if (txInfo == null) { + logger.trace("No transaction info for txHash: {}", StringUtils.trim(transactionHash)); + return null; + } + + Block block = blockStore.getBlockByHash(txInfo.getBlockHash()); + Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); + Transaction tx = block.getTransactionsList().get(txInfo.getIndex()); + txInfo.setTransaction(tx); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(traceOptions); + blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); + + return programTraceProcessor.getProgramTraceAsJsonNode(tx.getHash()); + } + + @Override + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions) { + + byte[] bHash = HexUtils.stringHexToByteArray(blockHash); + Block block = blockStore.getBlockByHash(bHash); + if (block == null) { + logger.trace("No block is found for blockHash: {}", StringUtils.trim(blockHash)); + return null; + } + + return traceBlock(block, traceOptions); + } + + @Override + public JsonNode traceBlockByNumber(String bnOrId, TraceOptions traceOptions){ + Block block = web3InformationRetriever.getBlock(bnOrId).orElse(null); + if (block == null) { + logger.trace("No block is found for bnOrId: {}", StringUtils.trim(bnOrId)); + return null; + } + + return traceBlock(block, traceOptions); + } + + @Override + public TracerType getTracerType() { + return TracerType.RSK_TRACER; + } + + private JsonNode traceBlock(Block block, TraceOptions options) { + Block parent = blockStore.getBlockByHash(block.getParentHash().getBytes()); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(options); + blockExecutor.traceBlock(programTraceProcessor, 0, block, parent.getHeader(), false, false); + + List txHashes = block.getTransactionsList().stream() + .map(Transaction::getHash) + .collect(Collectors.toList()); + + return programTraceProcessor.getProgramTracesAsJsonNode(txHashes); + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TraceProvider.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TraceProvider.java new file mode 100644 index 00000000000..bd180c5726d --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TraceProvider.java @@ -0,0 +1,41 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class TraceProvider { + + + private final Map tracers; + + public TraceProvider(List tracers) { + this.tracers = tracers.stream().collect(Collectors.toMap(DebugTracer::getTracerType, Function.identity())); + } + + + public DebugTracer getTracer(TracerType tracerType) { + return Optional.ofNullable(tracers.get(tracerType)) + .orElseThrow(() -> new IllegalArgumentException("Requested Tracer is not available." + tracerType.getTracerName())); + } + +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerType.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerType.java new file mode 100644 index 00000000000..862ea970239 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/TracerType.java @@ -0,0 +1,43 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace; + +public enum TracerType { + CALL_TRACER("callTracer"), RSK_TRACER("rskTracer"); + + private final String tracerName; + TracerType(String tracerName) { + this.tracerName = tracerName; + } + + public static TracerType getTracerType(String tracerName) { + for (TracerType tracerType : TracerType.values()) { + if (tracerType.getTracerName().equalsIgnoreCase(tracerName)) { + return tracerType; + } + } + return null; + } + public String getTracerName() { + return tracerName; + } + + public TracerType getDefault() { + return RSK_TRACER; + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java new file mode 100644 index 00000000000..67fa1a6232c --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTraceTransformer.java @@ -0,0 +1,215 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import co.rsk.core.RskAddress; +import co.rsk.rpc.modules.eth.EthModule; +import co.rsk.rpc.modules.trace.CallType; +import co.rsk.rpc.modules.trace.CreationData; +import co.rsk.rpc.modules.trace.ProgramSubtrace; +import co.rsk.rpc.modules.trace.TraceType; +import co.rsk.util.HexUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.bouncycastle.util.encoders.Hex; +import org.ethereum.db.TransactionInfo; +import org.ethereum.vm.DataWord; +import org.ethereum.vm.LogInfo; +import org.ethereum.vm.program.ProgramResult; +import org.ethereum.vm.program.invoke.InvokeData; +import org.ethereum.vm.trace.SummarizedProgramTrace; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CallTraceTransformer { + + private CallTraceTransformer() { + } + + public static TransactionTrace toTrace(SummarizedProgramTrace trace, TransactionInfo txInfo, DataWord codeAddress, boolean onlyTopCall, boolean withLog) { + boolean isContractCreation = txInfo.getReceipt().getTransaction().isContractCreation(); + CallType callType = isContractCreation ? CallType.NONE : CallType.CALL; + + ProgramResult programResult = ProgramResult.empty(); + programResult.spendGas(new BigInteger(1, txInfo.getReceipt().getGasUsed()).longValue()); + + if (trace.getReverted()) { + programResult.setRevert(); + } + + if (withLog) { + programResult.addLogInfos(txInfo.getReceipt().getLogInfoList()); + } + + InvokeData invoke = trace.getInvokeData(); + + CreationData creationData = null; + + TraceType traceType = TraceType.CALL; + + if (isContractCreation) { + String outputText = trace.getResult(); + byte[] createdCode = outputText == null ? new byte[0] : Hex.decode(outputText); + RskAddress createdAddress = txInfo.getReceipt().getTransaction().getContractAddress(); + byte[] input = txInfo.getReceipt().getTransaction().getData(); + creationData = new CreationData(input, createdCode, createdAddress); + traceType = TraceType.CREATE; + } + + TxTraceResult traceOutput = toTrace(traceType, callType, invoke, codeAddress, programResult, creationData, withLog); + if (!onlyTopCall) { + for (ProgramSubtrace subtrace : trace.getSubtraces()) { + traceOutput.addCall(toTrace(subtrace, withLog)); + } + } + + return new TransactionTrace(txInfo.getReceipt().getTransaction().getHash().toHexString(), traceOutput); + } + + private static TxTraceResult toTrace(ProgramSubtrace programSubtrace, boolean withLog) { + InvokeData invokeData = programSubtrace.getInvokeData(); + TxTraceResult subTrace = toTrace(programSubtrace.getTraceType(), programSubtrace.getCallType(), invokeData, programSubtrace.getCodeAddress(), programSubtrace.getProgramResult(), programSubtrace.getCreationData(), withLog); + for (ProgramSubtrace call : programSubtrace.getSubtraces()) { + subTrace.addCall(toTrace(call, withLog)); + } + return subTrace; + } + + private static TxTraceResult toTrace(TraceType traceType, CallType callType, InvokeData invoke, DataWord codeAddress, ProgramResult programResult, CreationData creationData, boolean withLog) { + String type = traceType == TraceType.CREATE ? "CREATE" : callType.name(); + String from; + String to = null; + String gas = null; + String input = null; + String value = null; + String output = null; + String gasUsed = null; + String revertReason = null; + String error = null; + + + from = getFrom(callType, invoke); + + List logInfoResultList = null; + + DataWord callValue = invoke.getCallValue(); + + + if (traceType == TraceType.CREATE) { + if (creationData != null) { + input = HexUtils.toUnformattedJsonHex(creationData.getCreationInput()); + output = creationData.getCreatedAddress().toJsonString(); + } + value = HexUtils.toQuantityJsonHex(callValue.getData()); + gas = HexUtils.toQuantityJsonHex(invoke.getGas()); + } + + if (traceType == TraceType.CALL) { + input = HexUtils.toUnformattedJsonHex(invoke.getDataCopy(DataWord.ZERO, invoke.getDataSize())); + value = HexUtils.toQuantityJsonHex(callValue.getData()); + + if (callType == CallType.DELEGATECALL) { + // The code address should not be null in a DELEGATECALL case + // but handling the case here + if (codeAddress != null) { + to = new RskAddress(codeAddress.getLast20Bytes()).toJsonString(); + } + } else { + to = new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString(); + } + + gas = HexUtils.toQuantityJsonHex(invoke.getGas()); + } + + + if (programResult != null) { + gasUsed = HexUtils.toQuantityJsonHex(programResult.getGasUsed()); + + if (programResult.isRevert()) { + Pair programRevert = EthModule.decodeProgramRevert(programResult); + revertReason = programRevert.getLeft(); + output = HexUtils.toQuantityJsonHex(programRevert.getRight()); + error = "execution reverted"; + } else if (traceType != TraceType.CREATE) { + output = HexUtils.toQuantityJsonHex(programResult.getHReturn()); + } + + if (programResult.getException() != null) { + error = programResult.getException().toString(); + } + } + + if (withLog) { + logInfoResultList = getLogs(programResult); + } + + + return TxTraceResult.builder() + .type(type) + .from(from) + .to(to) + .value(value) + .gas(gas) + .input(input) + .gasUsed(gasUsed) + .output(output) + .revertReason(revertReason) + .error(error) + .logs(logInfoResultList) + .build(); + + } + + private static String getFrom(CallType callType, InvokeData invoke) { + if (callType == CallType.DELEGATECALL) { + return new RskAddress(invoke.getOwnerAddress().getLast20Bytes()).toJsonString(); + } else { + return new RskAddress(invoke.getCallerAddress().getLast20Bytes()).toJsonString(); + } + } + + private static List getLogs(ProgramResult programResult) { + if (programResult == null) { + return Collections.emptyList(); + } + List logInfoResultList = new ArrayList<>(); + List logInfoList = programResult.getLogInfoList(); + if (logInfoList != null) { + for (int i = 0; i < programResult.getLogInfoList().size(); i++) { + LogInfo logInfo = programResult.getLogInfoList().get(i); + LogInfoResult logInfoResult = fromLogInfo(logInfo, i); + logInfoResultList.add(logInfoResult); + } + } + return logInfoResultList; + } + + private static LogInfoResult fromLogInfo(LogInfo logInfo, int index) { + String address = HexUtils.toJsonHex(logInfo.getAddress()); + List topics = logInfo.getTopics().stream().map(DataWord::getData).map(HexUtils::toJsonHex).toList(); + String data = HexUtils.toJsonHex(logInfo.getData()); + return LogInfoResult.builder() + .index(index) + .address(address) + .topics(topics) + .data(data) + .build(); + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java new file mode 100644 index 00000000000..b769e2c31df --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/CallTracer.java @@ -0,0 +1,174 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import co.rsk.config.VmConfig; +import co.rsk.core.bc.BlockExecutor; +import co.rsk.rpc.Web3InformationRetriever; +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.TracerType; +import co.rsk.util.HexUtils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.db.BlockStore; +import org.ethereum.db.ReceiptStore; +import org.ethereum.db.TransactionInfo; +import org.ethereum.vm.trace.ProgramTraceProcessor; +import org.ethereum.vm.trace.Serializers; +import org.ethereum.vm.trace.SummarizedProgramTrace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CallTracer implements DebugTracer { + private static final Logger logger = LoggerFactory.getLogger("callTracer"); + private static final ObjectMapper OBJECT_MAPPER = Serializers.createMapper(true); + + private final Web3InformationRetriever web3InformationRetriever; + private final BlockStore blockStore; + private final BlockExecutor blockExecutor; + private final ReceiptStore receiptStore; + private final Blockchain blockchain; + + public CallTracer(BlockStore blockStore, BlockExecutor blockExecutor, Web3InformationRetriever web3InformationRetriever, ReceiptStore receiptStore, Blockchain blockchain) { + this.blockStore = blockStore; + this.blockExecutor = blockExecutor; + this.web3InformationRetriever = web3InformationRetriever; + this.receiptStore = receiptStore; + this.blockchain = blockchain; + } + + @Override + public JsonNode traceTransaction(@Nonnull String transactionHash, @Nonnull TraceOptions traceOptions) throws Exception { + logger.trace("trace_transaction({})", transactionHash); + + byte[] hash = HexUtils.stringHexToByteArray(transactionHash); + if(hash == null) { + logger.error("Invalid transaction hash: {}", transactionHash); + throw new IllegalArgumentException("Invalid transaction hash: " + transactionHash); + } + + TransactionInfo txInfo = this.receiptStore.getInMainChain(hash, this.blockStore).orElse(null); + + if (txInfo == null) { + logger.trace("No transaction info for {}", transactionHash); + throw new IllegalArgumentException("No transaction info for " + transactionHash); + } + + Block block = this.blockchain.getBlockByHash(txInfo.getBlockHash()); + Block parent = this.blockchain.getBlockByHash(block.getParentHash().getBytes()); + Transaction tx = block.getTransactionsList().get(txInfo.getIndex()); + txInfo.setTransaction(tx); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(); + this.blockExecutor.traceBlock(programTraceProcessor, VmConfig.LIGHT_TRACE, block, parent.getHeader(), false, false); + + SummarizedProgramTrace programTrace = (SummarizedProgramTrace) programTraceProcessor.getProgramTrace(tx.getHash()); + + if (programTrace == null) { + //TODO define and return proper exception + logger.error("No program trace could be obtained for transaction: {}", transactionHash); + return null; + } + + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, traceOptions.isOnlyTopCall(), traceOptions.isWithLog()); + return OBJECT_MAPPER.valueToTree(trace.getResult()); + } + + @Override + public JsonNode traceBlockByHash(String blockHash, TraceOptions traceOptions) { + byte[] bHash = HexUtils.stringHexToByteArray(blockHash); + Block block = blockStore.getBlockByHash(bHash); + if (block == null) { + if (logger.isTraceEnabled()) { + logger.trace("No block is found for blockHash: {}", StringUtils.trim(blockHash)); + } + return null; + } + return traceBlock(block, traceOptions); + } + + @Override + public JsonNode traceBlockByNumber(@Nonnull String bnOrId, @Nonnull TraceOptions traceOptions) { + Block block = web3InformationRetriever.getBlock(bnOrId).orElse(null); + if (block == null) { + if (logger.isTraceEnabled()) { + logger.trace("No block is found for bnOrId: {}", StringUtils.trim(bnOrId)); + } + return null; + } + + return traceBlock(block, traceOptions); + } + + @Override + public TracerType getTracerType() { + return TracerType.CALL_TRACER; + } + + private JsonNode traceBlock(Block block, @Nonnull TraceOptions traceOptions) { + List result = buildBlockTraces(block, traceOptions.isOnlyTopCall(), traceOptions.isWithLog()); + return OBJECT_MAPPER.valueToTree(result); + } + + private List buildBlockTraces(Block block, boolean onlyTopCall, boolean withLog) { + List blockTraces = new ArrayList<>(); + + if (block != null && block.getNumber() != 0) { + List txList = block.getTransactionsList(); + + ProgramTraceProcessor programTraceProcessor = new ProgramTraceProcessor(); + Block parent = this.blockchain.getBlockByHash(block.getParentHash().getBytes()); + this.blockExecutor.traceBlock(programTraceProcessor, VmConfig.LIGHT_TRACE, block, parent.getHeader(), false, false); + + + for (Transaction tx : txList) { + TransactionInfo txInfo = receiptStore.getInMainChain(tx.getHash().getBytes(), this.blockStore).orElse(null); + if (txInfo == null) { // for a pending block we have no receipt, so empty one is being provided + txInfo = new TransactionInfo(new TransactionReceipt(), block.getHash().getBytes(), block.getTransactionsList().indexOf(tx)); + } + txInfo.setTransaction(tx); + + SummarizedProgramTrace programTrace = (SummarizedProgramTrace) programTraceProcessor.getProgramTrace(tx.getHash()); + + if (programTrace == null) { + blockTraces.clear(); + return Collections.emptyList(); + } + + TransactionTrace trace = CallTraceTransformer.toTrace(programTrace, txInfo, null, onlyTopCall, withLog); + + blockTraces.add(trace); + } + } + + return blockTraces; + } + + +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java new file mode 100644 index 00000000000..6d2b0975793 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/LogInfoResult.java @@ -0,0 +1,61 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record LogInfoResult(@JsonProperty int index, @JsonProperty String address, @JsonProperty List topics, + @JsonProperty String data) { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private int index; + private String address; + private List topics; + private String data; + + public Builder index(int index) { + this.index = index; + return this; + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public Builder topics(List topics) { + this.topics = topics; + return this; + } + + public Builder data(String data) { + this.data = data; + return this; + } + + public LogInfoResult build() { + return new LogInfoResult(index, address, topics, data); + } + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TransactionTrace.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TransactionTrace.java new file mode 100644 index 00000000000..86cd3e5c271 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TransactionTrace.java @@ -0,0 +1,41 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TransactionTrace { + + private String txHash; + private TxTraceResult result; + + public TransactionTrace(String txHash, TxTraceResult result) { + this.txHash = txHash; + this.result = result; + } + + @JsonProperty("txHash") + public String getTxHash() { + return txHash; + } + + @JsonProperty("result") + public TxTraceResult getResult() { + return result; + } +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java new file mode 100644 index 00000000000..11a221484a2 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/debug/trace/call/TxTraceResult.java @@ -0,0 +1,223 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.rpc.modules.debug.trace.call; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.ArrayList; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TxTraceResult { + + private final String type; + private final String from; + private final String to; + private final String value; + private final String gas; + private final String gasUsed; + private final String input; + private final String output; + private final String error; + private final String revertReason; + private final List calls; + private final List logs; + + //Used by deserializer + public TxTraceResult(){ + this.type = null; + this.from = null; + this.to = null; + this.value = null; + this.gas = null; + this.gasUsed = null; + this.input = null; + this.output = null; + this.error = null; + this.revertReason = null; + this.calls = new ArrayList<>(); + this.logs = new ArrayList<>(); + } + + public TxTraceResult(String type, String from, String to, String value, String gas, String gasUsed, String input, String output, String error, String revertReason, List calls, List logs) { + this.type = type; + this.from = from; + this.to = to; + this.value = value; + this.gas = gas; + this.gasUsed = gasUsed; + this.input = input; + this.output = output; + this.error = error; + this.revertReason = revertReason; + this.calls = calls == null ? new ArrayList<>() : calls; + this.logs = logs == null ? new ArrayList<>() : logs; + } + + @JsonGetter("type") + public String getType() { + return type; + } + + @JsonGetter("from") + public String getFrom() { + return from; + } + + @JsonGetter("to") + public String getTo() { + return to; + } + + @JsonGetter("value") + public String getValue() { + return value; + } + + @JsonGetter("gas") + public String getGas() { + return gas; + } + + @JsonGetter("gasUsed") + public String getGasUsed() { + return gasUsed; + } + + @JsonGetter("input") + public String getInput() { + return input; + } + + @JsonGetter("output") + public String getOutput() { + return output; + } + + @JsonGetter("error") + public String getError() { + return error; + } + + @JsonGetter("revertReason") + public String getRevertReason() { + return revertReason; + } + + @JsonGetter("calls") + public List getCalls() { + return calls.isEmpty() ? null : calls; + } + + @JsonGetter("logs") + public List getLogs() { + return logs.isEmpty() ? null : logs; + } + + @JsonIgnore + public static Builder builder() { + return new Builder(); + } + + public void addCall(TxTraceResult call) { + calls.add(call); + } + + //Builder class + public static class Builder { + private String type; + private String from; + private String to; + private String value; + private String gas; + private String gasUsed; + private String input; + private String output; + private String error; + private String revertReason; + private List calls; + private List logs; + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder from(String from) { + this.from = from; + return this; + } + + public Builder to(String to) { + this.to = to; + return this; + } + + public Builder value(String value) { + this.value = value; + return this; + } + + public Builder gas(String gas) { + this.gas = gas; + return this; + } + + public Builder gasUsed(String gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public Builder input(String input) { + this.input = input; + return this; + } + + public Builder output(String output) { + this.output = output; + return this; + } + + public Builder error(String error) { + this.error = error; + return this; + } + + public Builder revertReason(String revertReason) { + this.revertReason = revertReason; + return this; + } + + public Builder calls(List calls) { + this.calls = calls; + return this; + } + + public Builder logs(List logs) { + this.logs = logs; + return this; + } + + public TxTraceResult build() { + return new TxTraceResult(type, from, to, value, gas, gasUsed, input, output, error, revertReason, calls, logs); + } + } + +} diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/eth/EthModule.java b/rskj-core/src/main/java/co/rsk/rpc/modules/eth/EthModule.java index b7857351509..c2dcbb2d2b2 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/eth/EthModule.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/eth/EthModule.java @@ -146,20 +146,7 @@ public String call(CallArgumentsParam argsParam, BlockIdentifierParam bnOrId) { } else { res = callConstant(args, block); } - if (res.isRevert()) { - Pair programRevert = decodeProgramRevert(res); - String revertReason = programRevert.getLeft(); - byte[] revertData = programRevert.getRight(); - if (revertData == null) { - throw RskJsonRpcRequestException.transactionRevertedExecutionError(); - } - - if (revertReason == null) { - throw RskJsonRpcRequestException.transactionRevertedExecutionError(revertData); - } - - throw RskJsonRpcRequestException.transactionRevertedExecutionError(revertReason, revertData); - } + handleTransactionRevertIfHappens(res); hReturn = HexUtils.toUnformattedJsonHex(res.getHReturn()); return hReturn; @@ -193,6 +180,9 @@ public String estimateGas(CallArgumentsParam args, @Nonnull BlockIdentifierParam snapshot ); + ProgramResult res = executor.getResult(); + handleTransactionRevertIfHappens(res); + estimation = internalEstimateGas(executor.getResult()); return estimation; @@ -357,4 +347,22 @@ private ProgramResult callConstantWithState(CallArguments args, Block executionB hexArgs.getFromAddress() ); } + + private void handleTransactionRevertIfHappens(ProgramResult res) { + if (res.isRevert()) { + Pair programRevert = decodeProgramRevert(res); + String revertReason = programRevert.getLeft(); + byte[] revertData = programRevert.getRight(); + + if (revertData == null) { + throw RskJsonRpcRequestException.transactionRevertedExecutionError(); + } + + if (revertReason == null) { + throw RskJsonRpcRequestException.transactionRevertedExecutionError(revertData); + } + + throw RskJsonRpcRequestException.transactionRevertedExecutionError(revertReason, revertData); + } + } } diff --git a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java index 014229ac9f4..b2a6ce9b0b6 100644 --- a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java +++ b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java @@ -98,7 +98,11 @@ public enum ConsensusRule { RSKIP427("rskip427"), RSKIP428("rskip428"), RSKIP434("rskip434"), - RSKIP438("rskip438") + RSKIP438("rskip438"), + RSKIP445("rskip445"), // From EIP-5656 MCOPY instruction + RSKIP446("rskip446") ,// Transient storage opcodes addition implementing EIP-1153 + RSKIP453("rskip453"), + RSKIP454("rskip454") ; private final String configKey; diff --git a/rskj-core/src/main/java/org/ethereum/core/Repository.java b/rskj-core/src/main/java/org/ethereum/core/Repository.java index 0cf1bfa183d..38b10af2f68 100644 --- a/rskj-core/src/main/java/org/ethereum/core/Repository.java +++ b/rskj-core/src/main/java/org/ethereum/core/Repository.java @@ -27,7 +27,7 @@ import java.math.BigInteger; -public interface Repository extends RepositorySnapshot { +public interface Repository extends RepositorySnapshot, TransientRepository { Trie getTrie(); /** diff --git a/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java index 79f89cfff83..bd026d1f10c 100644 --- a/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java +++ b/rskj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -176,7 +176,7 @@ private boolean init() { } long txGasLimit = GasCost.toGas(tx.getGasLimit()); - long gasLimit = activations.isActive(RSKIP144)? sublistGasLimit : GasCost.toGas(executionBlock.getGasLimit()); + long gasLimit = activations.isActive(RSKIP144) ? sublistGasLimit : GasCost.toGas(executionBlock.getGasLimit()); if (!gasIsValid(txGasLimit, gasLimit)) { return false; @@ -583,6 +583,8 @@ private void finalization() { // Traverse list of suicides result.getDeleteAccounts().forEach(address -> track.delete(new RskAddress(address))); + track.clearTransientStorage(); + logger.trace("tx listener done"); logger.trace("tx finalization done"); @@ -676,7 +678,7 @@ public void extractTrace(ProgramTraceProcessor programTraceProcessor) { programTraceProcessor.processProgramTrace(trace, tx.getHash()); } else { - TransferInvoke invoke = new TransferInvoke(DataWord.valueOf(tx.getSender(signatureCache).getBytes()), DataWord.valueOf(tx.getReceiveAddress().getBytes()), 0L, DataWord.valueOf(tx.getValue().getBytes())); + TransferInvoke invoke = new TransferInvoke(DataWord.valueOf(tx.getSender(signatureCache).getBytes()), DataWord.valueOf(tx.getReceiveAddress().getBytes()), 0L, DataWord.valueOf(tx.getValue().getBytes()), tx.getData()); SummarizedProgramTrace trace = new SummarizedProgramTrace(invoke); diff --git a/rskj-core/src/main/java/org/ethereum/core/TransientRepository.java b/rskj-core/src/main/java/org/ethereum/core/TransientRepository.java new file mode 100644 index 00000000000..eee1f29d4ab --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/core/TransientRepository.java @@ -0,0 +1,40 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * (derived from ethereumJ library, Copyright (c) 2016 ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.ethereum.core; + +import co.rsk.core.RskAddress; +import org.ethereum.vm.DataWord; + +import javax.annotation.Nullable; + +public interface TransientRepository { + + void addTransientStorageRow(RskAddress addr, DataWord key, DataWord value); + + void addTransientStorageBytes(RskAddress addr, DataWord key, byte[] value); + + void clearTransientStorage(); + + @Nullable + DataWord getTransientStorageValue(RskAddress addr, DataWord key); + + @Nullable + byte[] getTransientStorageBytes(RskAddress addr, DataWord key); +} \ No newline at end of file diff --git a/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java b/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java index 42dd7336b25..f92bf4a9eee 100644 --- a/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java +++ b/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java @@ -25,19 +25,29 @@ import co.rsk.crypto.Keccak256; import co.rsk.db.MutableTrieCache; import co.rsk.db.MutableTrieImpl; -import co.rsk.trie.*; +import co.rsk.trie.IterationElement; +import co.rsk.trie.MutableTrie; +import co.rsk.trie.Trie; +import co.rsk.trie.TrieKeySlice; +import co.rsk.trie.TrieStore; +import co.rsk.trie.TrieStoreImpl; import com.google.common.annotations.VisibleForTesting; import org.ethereum.core.AccountState; import org.ethereum.core.Repository; import org.ethereum.crypto.HashUtil; import org.ethereum.crypto.Keccak256Helper; +import org.ethereum.datasource.HashMapDB; import org.ethereum.vm.DataWord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.math.BigInteger; -import java.util.*; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; public class MutableRepository implements Repository { private static final Logger logger = LoggerFactory.getLogger("repository"); @@ -47,21 +57,36 @@ public class MutableRepository implements Repository { private final TrieKeyMapper trieKeyMapper; private final MutableTrie mutableTrie; + private MutableTrie transientTrie; private final IReadWrittenKeysTracker tracker; public MutableRepository(TrieStore trieStore, Trie trie) { - this(new MutableTrieImpl(trieStore, trie)); + this(new MutableTrieImpl(trieStore, trie), aInMemoryMutableTrie()); } public MutableRepository(MutableTrie mutableTrie) { + this(mutableTrie, aInMemoryMutableTrie()); + } + + public MutableRepository(MutableTrie mutableTrie, IReadWrittenKeysTracker tracker) { + this(mutableTrie, aInMemoryMutableTrie(), tracker); + } + + private static MutableTrieImpl aInMemoryMutableTrie() { + return new MutableTrieImpl(new TrieStoreImpl(new HashMapDB()), new Trie()); + } + + public MutableRepository(MutableTrie mutableTrie, MutableTrie transientTrie) { this.trieKeyMapper = new TrieKeyMapper(); this.mutableTrie = mutableTrie; + this.transientTrie = transientTrie; this.tracker = new DummyReadWrittenKeysTracker(); } - public MutableRepository(MutableTrie mutableTrie, IReadWrittenKeysTracker tracker) { + public MutableRepository(MutableTrie mutableTrie, MutableTrie transientTrie, IReadWrittenKeysTracker tracker) { this.trieKeyMapper = new TrieKeyMapper(); this.mutableTrie = mutableTrie; + this.transientTrie = transientTrie; this.tracker = tracker; } @@ -327,7 +352,7 @@ public synchronized Set getAccountsKeys() { // To start tracking, a new repository is created, with a MutableTrieCache in the middle @Override public synchronized Repository startTracking() { - return new MutableRepository(new MutableTrieCache(mutableTrie), tracker); + return new MutableRepository(new MutableTrieCache(mutableTrie), new MutableTrieCache(transientTrie), tracker); } @Override @@ -338,11 +363,13 @@ public void save() { @Override public synchronized void commit() { mutableTrie.commit(); + transientTrie.commit(); } @Override public synchronized void rollback() { mutableTrie.rollback(); + transientTrie.rollback(); } @Override @@ -406,4 +433,47 @@ private Optional internalGetValueHash(byte[] key) { tracker.addNewReadKey(new ByteArrayWrapper(key)); return mutableTrie.getValueHash(key); } + + @Override + public void addTransientStorageRow(RskAddress addr, DataWord key, DataWord value) { + addTransientStorageBytes(addr, key, value.getByteArrayForStorage()); + } + + @Override + public void addTransientStorageBytes(RskAddress addr, DataWord key, byte[] value) { + byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key); + + // Special case: if the value is an empty vector, we pass "null" which commands the trie to remove the item. + // Note that if the call comes from addTransientStorageRow(), this method will already have replaced 0 by null, so the + // conversion here only applies if this is called directly. If suppose this only occurs in tests, but it can + // also occur in precompiled contracts that store data directly using this method. + if (value == null || value.length == 0) { + transientTrie.put(triekey, null); + } else { + transientTrie.put(triekey, value); + } + } + + @Override + public void clearTransientStorage() { + this.transientTrie = aInMemoryMutableTrie(); + } + + @Nullable + @Override + public DataWord getTransientStorageValue(RskAddress addr, DataWord key) { + byte[] value = getTransientStorageBytes(addr, key); + if (value == null) { + return null; + } + + return DataWord.valueOf(value); + } + + @Nullable + @Override + public byte[] getTransientStorageBytes(RskAddress addr, DataWord key) { + byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key); + return transientTrie.get(triekey); + } } diff --git a/rskj-core/src/main/java/org/ethereum/rpc/parameters/CallArgumentsParam.java b/rskj-core/src/main/java/org/ethereum/rpc/parameters/CallArgumentsParam.java index 96134328f51..4982b25f0dc 100644 --- a/rskj-core/src/main/java/org/ethereum/rpc/parameters/CallArgumentsParam.java +++ b/rskj-core/src/main/java/org/ethereum/rpc/parameters/CallArgumentsParam.java @@ -24,7 +24,9 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.ethereum.rpc.CallArguments; +import javax.annotation.Nullable; import java.io.IOException; +import java.util.function.Function; @JsonDeserialize(using = CallArgumentsParam.Deserializer.class) public class CallArgumentsParam { @@ -137,18 +139,28 @@ public static class Deserializer extends StdDeserializer { public CallArgumentsParam deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JsonNode node = jp.getCodec().readTree(jp); - HexAddressParam from = node.has("from") ? new HexAddressParam(node.get("from").asText()) : null; - HexAddressParam to = node.has("to") ? new HexAddressParam(node.get("to").asText()) : null; - HexNumberParam gas = node.has("gas") ? new HexNumberParam(node.get("gas").asText()) : null; - HexNumberParam gasPrice = node.has("gasPrice") ? new HexNumberParam(node.get("gasPrice").asText()) : null; - HexNumberParam gasLimit = node.has("gasLimit") ? new HexNumberParam(node.get("gasLimit").asText()) : null; - HexNumberParam nonce = node.has("nonce") ? new HexNumberParam(node.get("nonce").asText()) : null; - HexNumberParam chainId = node.has("chainId") ? new HexNumberParam(node.get("chainId").asText()) : null; - HexNumberParam value = node.has("value") ? new HexNumberParam(node.get("value").asText()) : null; - HexDataParam data = node.has("data") ? new HexDataParam(node.get("data").asText()) : null; - HexDataParam input = node.has("input") ? new HexDataParam(node.get("input").asText()) : null; + HexAddressParam from = paramOrNull(node, "from", HexAddressParam::new); + HexAddressParam to = paramOrNull(node, "to", HexAddressParam::new); + HexNumberParam gas = paramOrNull(node, "gas", HexNumberParam::new); + HexNumberParam gasPrice = paramOrNull(node, "gasPrice", HexNumberParam::new); + HexNumberParam gasLimit = paramOrNull(node, "gasLimit", HexNumberParam::new); + HexNumberParam nonce = paramOrNull(node, "nonce", HexNumberParam::new); + HexNumberParam chainId = paramOrNull(node, "chainId", HexNumberParam::new); + HexNumberParam value = paramOrNull(node, "value", HexNumberParam::new); + HexDataParam data = paramOrNull(node, "data", HexDataParam::new); + HexDataParam input = paramOrNull(node, "input", HexDataParam::new); return new CallArgumentsParam(from, to, gas, gasPrice, gasLimit, nonce, chainId, value, data, input); } + + @Nullable + private static T paramOrNull(JsonNode node, String fieldName, Function paramFactory) { + JsonNode fieldNode = node.get(fieldName); + if (fieldNode == null || fieldNode.isNull()) { + return null; + } + + return paramFactory.apply(fieldNode.asText()); + } } } diff --git a/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java new file mode 100644 index 00000000000..752a90817bb --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/rpc/parameters/DebugTracerParam.java @@ -0,0 +1,100 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package org.ethereum.rpc.parameters; + +import co.rsk.rpc.modules.debug.TraceOptions; +import co.rsk.rpc.modules.debug.trace.TracerType; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; +import java.io.Serial; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; + +@JsonDeserialize(using = DebugTracerParam.Deserializer.class) +public class DebugTracerParam { + private final TracerType tracerType; + private final TraceOptions traceOptions; + + public DebugTracerParam() { + this(null, null); + } + + public DebugTracerParam(TracerType tracerType, TraceOptions traceOptions) { + this.tracerType = tracerType; + this.traceOptions = traceOptions; + } + + public TracerType getTracerType() { + return tracerType; + } + + public TraceOptions getTraceOptions() { + return traceOptions; + } + + public static class Deserializer extends StdDeserializer { + @Serial + private static final long serialVersionUID = 4222943114560623356L; + + public Deserializer() { + this(null); + } + + public Deserializer(Class vc) { + super(vc); + } + + @Override + public DebugTracerParam deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node; + try { + node = jp.getCodec().readTree(jp); + } catch (Exception e) { + throw new IllegalArgumentException("Can not deserialize parameters", e); + } + TracerType tracerType = null; + TraceOptions traceOptions = new TraceOptions(); + Iterator> fields = node.fields(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + if ("tracerConfig".equalsIgnoreCase(entry.getKey())) { + JsonNode tracerConfigNode = entry.getValue(); + tracerConfigNode.fields().forEachRemaining(tracerConfigEntry + -> traceOptions.addOption(tracerConfigEntry.getKey(), tracerConfigEntry.getValue().asText())); + } else if ("tracer".equalsIgnoreCase(entry.getKey())) { + tracerType = getTracerType(entry.getValue().asText()); + } else { + traceOptions.addOption(entry.getKey(), entry.getValue().asText()); + } + } + return new DebugTracerParam(tracerType, traceOptions); + } + + //TODO check how this exeption is handled + private TracerType getTracerType(String tracerType) { + return Optional.ofNullable(TracerType.getTracerType(tracerType)).orElseThrow(() + -> new IllegalArgumentException("Invalid tracer type: " + tracerType)); + } + } +} diff --git a/rskj-core/src/main/java/org/ethereum/vm/GasCost.java b/rskj-core/src/main/java/org/ethereum/vm/GasCost.java index c9da2ebc095..c9c786841ff 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/GasCost.java +++ b/rskj-core/src/main/java/org/ethereum/vm/GasCost.java @@ -61,6 +61,9 @@ public class GasCost { public static final long REFUND_SSTORE = 15000; public static final long CREATE = 32000; + public static final long TLOAD = 100; + public static final long TSTORE = 100; + public static final long JUMPDEST = 1; public static final long CREATE_DATA_BYTE = 5; public static final long CALL = 700; diff --git a/rskj-core/src/main/java/org/ethereum/vm/OpCode.java b/rskj-core/src/main/java/org/ethereum/vm/OpCode.java index f9de1515e87..0204af6e2ec 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/OpCode.java +++ b/rskj-core/src/main/java/org/ethereum/vm/OpCode.java @@ -328,7 +328,20 @@ public enum OpCode { * (0x5b) */ JUMPDEST(0x5b, 0, 0, SPECIAL_TIER), + /** + * (0x5e) Memory copying instruction + */ + MCOPY(0x5e, 3, 0, VERY_LOW_TIER), + /** + * (0x5c) Load word from transient storage at address + */ + TLOAD(0x5c, 1, 1, SPECIAL_TIER), // Will adjust the correct inputs and outputs later + + /** + * (0x5c) Store word from transient storage at address + */ + TSTORE(0x5d, 2, 0, SPECIAL_TIER), // Will adjust the correct inputs and outputs later /* Push Operations */ /** diff --git a/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java b/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java index 7b483642fb7..16f84d945b3 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java +++ b/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java @@ -325,6 +325,18 @@ private OpCodes() { * (0x5b) */ static final byte OP_JUMPDEST =0x5b ; + /** + * (0x5e) + */ + public static final byte OP_MCOPY = 0x5e; + /** + * (0x5c) + */ + public static final byte OP_TLOAD =0x5c ; + /** + * (0x5d) + */ + public static final byte OP_TSTORE =0x5d ; /* Push Operations */ /** diff --git a/rskj-core/src/main/java/org/ethereum/vm/VM.java b/rskj-core/src/main/java/org/ethereum/vm/VM.java index 47246e2bbc2..1c9537ccd6f 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/VM.java +++ b/rskj-core/src/main/java/org/ethereum/vm/VM.java @@ -42,7 +42,21 @@ import java.util.Iterator; import java.util.List; -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP103; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP120; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP125; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP140; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP150; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP151; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP152; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP169; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP191; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP398; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP412; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP445; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP446; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP90; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP91; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; import static org.ethereum.vm.OpCode.CALL; @@ -50,37 +64,37 @@ /** * The Ethereum Virtual Machine (EVM) is responsible for initialization * and executing a transaction on a contract. - * + * It is a quasi-Turing-complete machine; the quasi qualification * comes from the fact that the computation is intrinsically bounded * through a parameter, gas, which limits the total amount of computation done. - * + * The EVM is a simple stack-based architecture. The word size of the machine * (and thus size of stack item) is 256-bit. This was chosen to facilitate * the SHA3-256 hash scheme and elliptic-curve computations. The memory model * is a simple word-addressed byte array. The stack has an unlimited size. * The machine also has an independent storage model; this is similar in concept * to the memory but rather than a byte array, it is a word-addressable word array. - * + * Unlike memory, which is volatile, storage is non volatile and is * maintained as part of the system state. All locations in both storage * and memory are well-defined initially as zero. - * + * The machine does not follow the standard von Neumann architecture. * Rather than storing program code in generally-accessible memory or storage, * it is stored separately in a virtual ROM interactable only though * a specialised instruction. - * + * The machine can have exceptional execution for several reasons, * including stack underflows and invalid instructions. These unambiguously * and validly result in immediate halting of the machine with all state changes * left intact. The one piece of exceptional execution that does not leave * state changes intact is the out-of-gas (OOG) exception. - * + * Here, the machine halts immediately and reports the issue to * the execution agent (either the transaction processor or, recursively, * the spawning execution environment) and which will deal with it separately. - * + * @author Roman Mandeleil * @since 01.06.2014 */ @@ -767,6 +781,25 @@ private long computeDataCopyGas() { return calcMemGas(oldMemSize, newMemSize, copySize); } + private long computeMemoryCopyGas() { + DataWord length = stack.get(stack.size() - 3); + DataWord src = stack.get(stack.size() - 2); + DataWord dst = stack.peek(); + + long copySize = Program.limitToMaxLong(length); + checkSizeArgument(copySize); + + DataWord offset = dst; + if (src.value().compareTo(dst.value()) > 0) { + offset = src; + } + + long newMemSize = memNeeded(offset, copySize); + + // Note: 3 additional units are added outside because of the "Very Low Tier" configuration + return calcMemGas(oldMemSize, newMemSize, copySize); + } + protected void doCODESIZE() { if (computeGas) { if (op == OpCode.EXTCODESIZE) { @@ -1326,6 +1359,48 @@ else if (oldValue != null && newValue.isZero()) { program.step(); } + protected void doTLOAD(){ + if (computeGas) { + gasCost = GasCost.TLOAD; + spendOpCodeGas(); + } + + DataWord key = program.stackPop(); + if (isLogEnabled) { + logger.info("Executing TLOAD with parameters: key = {}", key); + } + DataWord val = program.transientStorageLoad(key); + + if (val == null) { + val = DataWord.ZERO; + } + + program.stackPush(val); + // key could be returned to the pool, but transientStorageLoad semantics should be checked + // to make sure storageLoad always gets a copy, not a reference. + program.step(); + } + + protected void doTSTORE(){ + if (computeGas) { + gasCost = GasCost.TSTORE; + spendOpCodeGas(); + } + + if (program.isStaticCall()) { + throw Program.ExceptionHelper.modificationException(program); + } + + DataWord key = program.stackPop(); + DataWord value = program.stackPop(); + + if (isLogEnabled) { + logger.info("Executing TSTORE with parameters: address={} | value = {}", key, value); + } + program.transientStorageSave(key, value); + program.step(); + } + protected void doJUMP(){ spendOpCodeGas(); // EXECUTION PHASE @@ -1436,6 +1511,25 @@ protected void doJUMPDEST() program.step(); } + protected void doMCOPY() { + if (computeGas) { + gasCost = GasCost.add(gasCost, computeMemoryCopyGas()); + spendOpCodeGas(); + } + + // EXECUTION PHASE + DataWord dst = program.stackPop(); + DataWord src = program.stackPop(); + DataWord length = program.stackPop(); + + if (isLogEnabled) { + hint = "dst: " + dst + " src: " + src + " length: " + length; + } + + program.memoryCopy(dst.intValueSafe(), src.intValueSafe(), length.intValueSafe()); + program.step(); + } + protected void doCREATE(){ if (program.isStaticCall() && program.getActivations().isActive(RSKIP91)) { throw Program.ExceptionHelper.modificationException(program); @@ -1815,14 +1909,15 @@ protected void executeOpcode() { break; case OpCodes.OP_CALLDATACOPY: doCALLDATACOPY(); break; - case OpCodes.OP_CODESIZE: - case OpCodes.OP_EXTCODESIZE: doCODESIZE(); + case OpCodes.OP_CODESIZE, + OpCodes.OP_EXTCODESIZE: + doCODESIZE(); break; - case OpCodes.OP_CODECOPY: - case OpCodes.OP_EXTCODECOPY: doCODECOPY(); + case OpCodes.OP_CODECOPY, + OpCodes.OP_EXTCODECOPY: + doCODECOPY(); break; - case OpCodes.OP_EXTCODEHASH: if (!activations.isActive(RSKIP140)) { throw Program.ExceptionHelper.invalidOpCode(program); @@ -1880,39 +1975,41 @@ protected void executeOpcode() { case OpCodes.OP_POP: doPOP(); break; - case OpCodes.OP_DUP_1: - case OpCodes.OP_DUP_2: - case OpCodes.OP_DUP_3: - case OpCodes.OP_DUP_4: - case OpCodes.OP_DUP_5: - case OpCodes.OP_DUP_6: - case OpCodes.OP_DUP_7: - case OpCodes.OP_DUP_8: - case OpCodes.OP_DUP_9: - case OpCodes.OP_DUP_10: - case OpCodes.OP_DUP_11: - case OpCodes.OP_DUP_12: - case OpCodes.OP_DUP_13: - case OpCodes.OP_DUP_14: - case OpCodes.OP_DUP_15: - case OpCodes.OP_DUP_16: doDUP(); - break; - case OpCodes.OP_SWAP_1: - case OpCodes.OP_SWAP_2: - case OpCodes.OP_SWAP_3: - case OpCodes.OP_SWAP_4: - case OpCodes.OP_SWAP_5: - case OpCodes.OP_SWAP_6: - case OpCodes.OP_SWAP_7: - case OpCodes.OP_SWAP_8: - case OpCodes.OP_SWAP_9: - case OpCodes.OP_SWAP_10: - case OpCodes.OP_SWAP_11: - case OpCodes.OP_SWAP_12: - case OpCodes.OP_SWAP_13: - case OpCodes.OP_SWAP_14: - case OpCodes.OP_SWAP_15: - case OpCodes.OP_SWAP_16: doSWAP(); + case OpCodes.OP_DUP_1 , + OpCodes.OP_DUP_2 , + OpCodes.OP_DUP_3 , + OpCodes.OP_DUP_4 , + OpCodes.OP_DUP_5 , + OpCodes.OP_DUP_6 , + OpCodes.OP_DUP_7 , + OpCodes.OP_DUP_8 , + OpCodes.OP_DUP_9 , + OpCodes.OP_DUP_10 , + OpCodes.OP_DUP_11 , + OpCodes.OP_DUP_12 , + OpCodes.OP_DUP_13 , + OpCodes.OP_DUP_14 , + OpCodes.OP_DUP_15 , + OpCodes.OP_DUP_16: + doDUP(); + break; + case OpCodes.OP_SWAP_1, + OpCodes.OP_SWAP_2, + OpCodes.OP_SWAP_3, + OpCodes.OP_SWAP_4, + OpCodes.OP_SWAP_5, + OpCodes.OP_SWAP_6, + OpCodes.OP_SWAP_7, + OpCodes.OP_SWAP_8, + OpCodes.OP_SWAP_9, + OpCodes.OP_SWAP_10, + OpCodes.OP_SWAP_11, + OpCodes.OP_SWAP_12, + OpCodes.OP_SWAP_13, + OpCodes.OP_SWAP_14, + OpCodes.OP_SWAP_15, + OpCodes.OP_SWAP_16: + doSWAP(); break; case OpCodes.OP_SWAPN: if (activations.isActive(RSKIP191)) { @@ -1923,11 +2020,12 @@ protected void executeOpcode() { break; - case OpCodes.OP_LOG_0: - case OpCodes.OP_LOG_1: - case OpCodes.OP_LOG_2: - case OpCodes.OP_LOG_3: - case OpCodes.OP_LOG_4: doLOG(); + case OpCodes.OP_LOG_0, + OpCodes.OP_LOG_1, + OpCodes.OP_LOG_2, + OpCodes.OP_LOG_3, + OpCodes.OP_LOG_4: + doLOG(); break; case OpCodes.OP_MLOAD: doMLOAD(); break; @@ -1938,7 +2036,19 @@ protected void executeOpcode() { case OpCodes.OP_SLOAD: doSLOAD(); break; case OpCodes.OP_SSTORE: doSSTORE(); - break; + break; + case OpCodes.OP_TLOAD: + if (!activations.isActive(RSKIP446)) { + throw Program.ExceptionHelper.invalidOpCode(program); + } + doTLOAD(); + break; + case OpCodes.OP_TSTORE: + if (!activations.isActive(RSKIP446)) { + throw Program.ExceptionHelper.invalidOpCode(program); + } + doTSTORE(); + break; case OpCodes.OP_JUMP: doJUMP(); break; case OpCodes.OP_JUMPI: doJUMPI(); @@ -1957,41 +2067,48 @@ protected void executeOpcode() { doPUSH0(); break; - case OpCodes.OP_PUSH_1: - case OpCodes.OP_PUSH_2: - case OpCodes.OP_PUSH_3: - case OpCodes.OP_PUSH_4: - case OpCodes.OP_PUSH_5: - case OpCodes.OP_PUSH_6: - case OpCodes.OP_PUSH_7: - case OpCodes.OP_PUSH_8: - case OpCodes.OP_PUSH_9: - case OpCodes.OP_PUSH_10: - case OpCodes.OP_PUSH_11: - case OpCodes.OP_PUSH_12: - case OpCodes.OP_PUSH_13: - case OpCodes.OP_PUSH_14: - case OpCodes.OP_PUSH_15: - case OpCodes.OP_PUSH_16: - case OpCodes.OP_PUSH_17: - case OpCodes.OP_PUSH_18: - case OpCodes.OP_PUSH_19: - case OpCodes.OP_PUSH_20: - case OpCodes.OP_PUSH_21: - case OpCodes.OP_PUSH_22: - case OpCodes.OP_PUSH_23: - case OpCodes.OP_PUSH_24: - case OpCodes.OP_PUSH_25: - case OpCodes.OP_PUSH_26: - case OpCodes.OP_PUSH_27: - case OpCodes.OP_PUSH_28: - case OpCodes.OP_PUSH_29: - case OpCodes.OP_PUSH_30: - case OpCodes.OP_PUSH_31: - case OpCodes.OP_PUSH_32: doPUSH(); + case OpCodes.OP_PUSH_1, + OpCodes.OP_PUSH_2, + OpCodes.OP_PUSH_3, + OpCodes.OP_PUSH_4, + OpCodes.OP_PUSH_5, + OpCodes.OP_PUSH_6, + OpCodes.OP_PUSH_7, + OpCodes.OP_PUSH_8, + OpCodes.OP_PUSH_9, + OpCodes.OP_PUSH_10, + OpCodes.OP_PUSH_11, + OpCodes.OP_PUSH_12, + OpCodes.OP_PUSH_13, + OpCodes.OP_PUSH_14, + OpCodes.OP_PUSH_15, + OpCodes.OP_PUSH_16, + OpCodes.OP_PUSH_17, + OpCodes.OP_PUSH_18, + OpCodes.OP_PUSH_19, + OpCodes.OP_PUSH_20, + OpCodes.OP_PUSH_21, + OpCodes.OP_PUSH_22, + OpCodes.OP_PUSH_23, + OpCodes.OP_PUSH_24, + OpCodes.OP_PUSH_25, + OpCodes.OP_PUSH_26, + OpCodes.OP_PUSH_27, + OpCodes.OP_PUSH_28, + OpCodes.OP_PUSH_29, + OpCodes.OP_PUSH_30, + OpCodes.OP_PUSH_31, + OpCodes.OP_PUSH_32: + doPUSH(); break; case OpCodes.OP_JUMPDEST: doJUMPDEST(); break; + case OpCodes.OP_MCOPY: + if (!activations.isActive(RSKIP445)) { + throw Program.ExceptionHelper.invalidOpCode(program); + } + doMCOPY(); + break; case OpCodes.OP_CREATE: doCREATE(); break; case OpCodes.OP_CREATE2: @@ -2000,10 +2117,10 @@ protected void executeOpcode() { } doCREATE2(); break; - case OpCodes.OP_CALL: - case OpCodes.OP_CALLCODE: - case OpCodes.OP_DELEGATECALL: - doCALL(); + case OpCodes.OP_CALL, + OpCodes.OP_CALLCODE, + OpCodes.OP_DELEGATECALL: + doCALL(); break; case OpCodes.OP_STATICCALL: if (!activations.isActive(RSKIP91)) { @@ -2170,9 +2287,7 @@ private void dumpLine(OpCode op, long gasBefore, long gasCost, long memWords, Pr RskAddress ownerAddress = new RskAddress(program.getOwnerAddress()); if ("standard+".equals(vmConfig.dumpStyle())) { switch (op) { - case STOP: - case RETURN: - case SUICIDE: + case STOP, RETURN, SUICIDE: Iterator keysIterator = storage.getStorageKeys(ownerAddress); while (keysIterator.hasNext()) { DataWord key = keysIterator.next(); diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java index e326c26a5cf..1d28804eb75 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -95,7 +95,6 @@ public class Program { private static final Logger logger = LoggerFactory.getLogger("VM"); private static final Logger gasLogger = LoggerFactory.getLogger("gas"); - public static final long MAX_MEMORY = (1<<30); //Max size for stack checks @@ -390,6 +389,10 @@ public byte[] memoryChunk(int offset, int size) { return memory.read(offset, size); } + public void memoryCopy(int dst, int src, int length) { + memorySave(dst, memoryChunk(src, length)); + } + /** * Allocates extra memory in the program for * a specified size, calculated from a given offset @@ -684,12 +687,24 @@ private ProgramResult getProgramResult(RskAddress senderAddress, byte[] nonce, D this, "No gas to return just created contract", storageCost)); + + if (activations.isActive(ConsensusRule.RSKIP453)) { + track.rollback(); + stackPushZero(); + return null; + } } else if (codeLength > Constants.getMaxContractSize()) { programResult.setException( ExceptionHelper.tooLargeContractSize( this, Constants.getMaxContractSize(), codeLength)); + + if (activations.isActive(ConsensusRule.RSKIP453)) { + track.rollback(); + stackPushZero(); + return null; + } } else { programResult.spendGas(storageCost); track.saveCode(contractAddress, code); @@ -812,7 +827,6 @@ public void callToAddress(MessageCall msg) { DataWord callerAddress = DataWord.valueOf(senderAddress.getBytes()); DataWord ownerAddress = DataWord.valueOf(contextAddress.getBytes()); DataWord transferValue = DataWord.valueOf(endowment.getBytes()); - TransferInvoke invoke = new TransferInvoke(callerAddress, ownerAddress, msg.getGas().longValue(), transferValue); ProgramResult result = new ProgramResult(); @@ -985,6 +999,10 @@ private void storageSave(byte[] key, byte[] val) { getStorage().addStorageRow(getOwnerRskAddress(), keyWord, valWord); } + public void transientStorageSave(DataWord key, DataWord value) { + getStorage().addTransientStorageRow(getOwnerRskAddress(), key, value); + } + private RskAddress getOwnerRskAddress() { if (rskOwnerAddress == null) { rskOwnerAddress = new RskAddress(getOwnerAddress()); @@ -1095,6 +1113,10 @@ public DataWord storageLoad(DataWord key) { return getStorage().getStorageValue(getOwnerRskAddress(), key); } + public DataWord transientStorageLoad(DataWord key) { + return getStorage().getTransientStorageValue(getOwnerRskAddress(), key); + } + public DataWord getPrevHash() { return invoke.getPrevHash(); } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java b/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java index 836e4d4122a..99802802b9b 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java @@ -30,6 +30,7 @@ import org.ethereum.vm.program.listener.ProgramListener; import org.ethereum.vm.program.listener.ProgramListenerAware; +import javax.annotation.Nullable; import java.math.BigInteger; import java.util.Iterator; import java.util.Set; @@ -223,4 +224,31 @@ public byte[] getRoot() { public void updateAccountState(RskAddress addr, AccountState accountState) { throw new UnsupportedOperationException(); } + + @Override + public void addTransientStorageRow(RskAddress addr, DataWord key, DataWord value) { + repository.addTransientStorageRow(addr, key, value); + } + + @Override + public void addTransientStorageBytes(RskAddress addr, DataWord key, byte[] value) { + repository.addTransientStorageBytes(addr, key, value); + } + + @Override + public void clearTransientStorage() { + repository.clearTransientStorage(); + } + + @Nullable + @Override + public DataWord getTransientStorageValue(RskAddress addr, DataWord key) { + return repository.getTransientStorageValue(addr, key); + } + + @Nullable + @Override + public byte[] getTransientStorageBytes(RskAddress addr, DataWord key) { + return repository.getTransientStorageBytes(addr, key); + } } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java index 7d23a50a97f..cb373bf3a01 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/TransferInvoke.java @@ -22,17 +22,27 @@ import org.ethereum.util.ByteUtil; import org.ethereum.vm.DataWord; +import java.math.BigInteger; + public class TransferInvoke implements InvokeData { + private static BigInteger maxMsgData = BigInteger.valueOf(Integer.MAX_VALUE); + private final DataWord ownerAddress; private final DataWord callerAddress; private final long gas; private final DataWord callValue; + private final byte[] msgData; public TransferInvoke(DataWord callerAddress, DataWord ownerAddress, long gas, DataWord callValue) { + this(callerAddress, ownerAddress, gas, callValue, ByteUtil.EMPTY_BYTE_ARRAY); + } + + public TransferInvoke(DataWord callerAddress, DataWord ownerAddress, long gas, DataWord callValue, byte[] msgData) { this.callerAddress = callerAddress; this.ownerAddress = ownerAddress; this.gas = gas; this.callValue = callValue; + this.msgData = msgData; } @Override @@ -57,16 +67,55 @@ public DataWord getCallValue() { @Override public DataWord getDataSize() { - return DataWord.ZERO; + if (msgData == null || msgData.length == 0) { + return DataWord.ZERO; + } + int size = msgData.length; + return DataWord.valueOf(size); } @Override public DataWord getDataValue(DataWord indexData) { - return DataWord.ZERO; + if (msgData == null || msgData.length == 0) { + return DataWord.ZERO; + } + BigInteger tempIndex = indexData.value(); + int index = tempIndex.intValue(); // possible overflow is caught below + int size = 32; // maximum datavalue size + + if (index >= msgData.length + || tempIndex.compareTo(maxMsgData) > 0) { + return DataWord.ZERO; + } + if (index + size > msgData.length) { + size = msgData.length - index; + } + + byte[] data = new byte[32]; + System.arraycopy(msgData, index, data, 0, size); + return DataWord.valueOf(data); } @Override public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { - return ByteUtil.EMPTY_BYTE_ARRAY; + int offset = offsetData.intValueSafe(); + int length = lengthData.intValueSafe(); + + byte[] data = new byte[length]; + + if (msgData == null) { + return data; + } + + if (offset > msgData.length) { + return data; + } + if (offset + length > msgData.length) { + length = msgData.length - offset; + } + + System.arraycopy(msgData, offset, data, 0, length); + + return data; } } diff --git a/rskj-core/src/main/resources/expected.conf b/rskj-core/src/main/resources/expected.conf index 6dc5b16c5c5..21d2159e71c 100644 --- a/rskj-core/src/main/resources/expected.conf +++ b/rskj-core/src/main/resources/expected.conf @@ -20,86 +20,90 @@ blockchain = { lovell700 = } consensusRules = { - areBridgeTxsPaid = - rskip85 = - rskip87 = - rskip88 = - rskip89 = - rskip90 = - rskip91 = - rskip92 = - rskip97 = - rskip98 = - rskip103 = - rskip106 = - rskip110 = - rskip119 = - rskip120 = - rskip122 = - rskip123 = - rskip124 = - rskip125 = - rskip126 = - rskip132 = - rskip134 = - rskip136 = - rskip137 = - rskip140 = - rskip143 = - rskip146 = - rskip150 = - rskip151 = - rskip152 = - rskip156 = - rskipUMM = - rskip153 = - rskip169 = - rskip170 = - rskip171 = - rskip174 = - rskip176 = - rskip179 = - rskip180 = - rskip181 = - rskip185 = - rskip186 = - rskip191 = - rskip197 = - rskip199 = - rskip200 = - rskip201 = - rskip203 = - rskip218 = - rskip219 = - rskip220 = - rskip252 = - rskip271 = - rskip284 = - rskip290 = - rskip293 = - rskip294 = - rskip297 = - rskip351 = - rskip144 = - rskip326 = - rskip353 = - rskip357 = - rskip374 = - rskip375 = - rskip376 = - rskip377 = - rskip379 = - rskip383 = - rskip385 = - rskip398 = - rskip400 = - rskip412 = - rskip415 = - rskip417 = - rskip427 = - rskip428 = - rskip434 = - rskip438 = + areBridgeTxsPaid = + rskip85 = + rskip87 = + rskip88 = + rskip89 = + rskip90 = + rskip91 = + rskip92 = + rskip97 = + rskip98 = + rskip103 = + rskip106 = + rskip110 = + rskip119 = + rskip120 = + rskip122 = + rskip123 = + rskip124 = + rskip125 = + rskip126 = + rskip132 = + rskip134 = + rskip136 = + rskip137 = + rskip140 = + rskip143 = + rskip146 = + rskip150 = + rskip151 = + rskip152 = + rskip156 = + rskipUMM = + rskip153 = + rskip169 = + rskip170 = + rskip171 = + rskip174 = + rskip176 = + rskip179 = + rskip180 = + rskip181 = + rskip185 = + rskip186 = + rskip191 = + rskip197 = + rskip199 = + rskip200 = + rskip201 = + rskip203 = + rskip218 = + rskip219 = + rskip220 = + rskip252 = + rskip271 = + rskip284 = + rskip290 = + rskip293 = + rskip294 = + rskip297 = + rskip351 = + rskip144 = + rskip326 = + rskip353 = + rskip357 = + rskip374 = + rskip375 = + rskip376 = + rskip377 = + rskip379 = + rskip383 = + rskip385 = + rskip398 = + rskip400 = + rskip412 = + rskip415 = + rskip417 = + rskip427 = + rskip428 = + rskip434 = + rskip438 = + rskip445 = + rskip446 = + rskip453 = + rskip454 = } } gc = { diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index a7d71a2b4db..d35f0988b72 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -85,6 +85,10 @@ blockchain = { rskip428 = lovell700 rskip434 = arrowhead631 rskip438 = lovell700 + rskip445 = lovell700 + rskip446 = lovell700 + rskip453 = lovell700 + rskip454 = lovell700 } } gc = { diff --git a/rskj-core/src/main/resources/version.properties b/rskj-core/src/main/resources/version.properties index 8a6347af516..eaf705e6203 100644 --- a/rskj-core/src/main/resources/version.properties +++ b/rskj-core/src/main/resources/version.properties @@ -1,2 +1,2 @@ -versionNumber='6.5.0' +versionNumber='6.6.0' modifier="SNAPSHOT" diff --git a/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java b/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java index f158eb2bcd2..5f5d1b1400c 100644 --- a/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java +++ b/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java @@ -35,6 +35,9 @@ import co.rsk.rpc.Web3RskImpl; import co.rsk.rpc.modules.debug.DebugModule; import co.rsk.rpc.modules.debug.DebugModuleImpl; +import co.rsk.rpc.modules.debug.trace.DebugTracer; +import co.rsk.rpc.modules.debug.trace.RskTracer; +import co.rsk.rpc.modules.debug.trace.TraceProvider; import co.rsk.rpc.modules.eth.*; import co.rsk.rpc.modules.personal.PersonalModuleWalletEnabled; import co.rsk.rpc.modules.txpool.TxPoolModule; @@ -82,6 +85,7 @@ import java.math.BigInteger; import java.time.Clock; +import java.util.List; import static org.mockito.Mockito.mock; @@ -650,7 +654,9 @@ repositoryLocator, new EthModuleWalletEnabled(wallet, transactionPool, signature config.getCallGasCap() ); TxPoolModule txPoolModule = new TxPoolModuleImpl(transactionPool, new ReceivedTxSignatureCache()); - DebugModule debugModule = new DebugModuleImpl(null, null, Web3Mocks.getMockMessageHandler(), null, null, null); + DebugTracer debugTracer = new RskTracer(null, null, null, null); + TraceProvider traceProvider = new TraceProvider(List.of(debugTracer)); + DebugModule debugModule = new DebugModuleImpl(traceProvider, Web3Mocks.getMockMessageHandler(), null); ChannelManager channelManager = new SimpleChannelManager(); return new Web3RskImpl( 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..89911bc95e0 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportIT.java @@ -60,6 +60,10 @@ import java.io.*; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.security.*; import java.time.Instant; import java.util.*; @@ -70,6 +74,7 @@ import org.ethereum.TestUtils; import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfig.ForBlock; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; import org.ethereum.core.*; import org.ethereum.crypto.ECKey; @@ -82,6 +87,8 @@ import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -118,7 +125,8 @@ public class BridgeSupportIT { private final BridgeSupportBuilder bridgeSupportBuilder = BridgeSupportBuilder.builder(); private final FederationSupportBuilder federationSupportBuilder = FederationSupportBuilder.builder(); - private BridgeConstants bridgeConstants; + private static final BridgeConstants bridgeMainNetConstants = BridgeMainNetConstants.getInstance(); + private static final BridgeConstants bridgeRegTestConstants = new BridgeRegTestConstants(); private FederationConstants federationConstants; private NetworkParameters btcParams; private ActivationConfig.ForBlock activationsBeforeForks; @@ -129,12 +137,14 @@ public class BridgeSupportIT { private WhitelistSupport whitelistSupport; private WhitelistStorageProvider whitelistStorageProvider; private LockingCapSupport lockingCapSupport; + private Repository track; + + private BridgeSupport bridgeSupport; @BeforeEach void setUpOnEachTest() { - bridgeConstants = new BridgeRegTestConstants(); - federationConstants = bridgeConstants.getFederationConstants(); - btcParams = bridgeConstants.getBtcParams(); + federationConstants = bridgeRegTestConstants.getFederationConstants(); + btcParams = bridgeRegTestConstants.getBtcParams(); activationsBeforeForks = ActivationConfigsForTest.genesis().forBlock(0); activations = mock(ActivationConfig.ForBlock.class); signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache()); @@ -155,71 +165,214 @@ void setUpOnEachTest() { LockingCapMainNetConstants.getInstance(), signatureCache ); + + track = createRepository().startTracking(); + + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + btcParams); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, + btcParams, + activations); + + FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); + FederationSupport federationSupport = federationSupportBuilder + .withFederationConstants(bridgeRegTestConstants.getFederationConstants()) + .withFederationStorageProvider(federationStorageProvider) + .withActivations(activations) + .build(); + + this.bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeRegTestConstants) + .withProvider(provider) + .withRepository(track) + .withBtcBlockStoreFactory(btcBlockStoreFactory) + .withFederationSupport(federationSupport) + .withActivations(activations) + .build(); } @Test - void testInitialChainHeadWithoutBtcCheckpoints() throws Exception { - Repository repository = createRepository(); - Repository track = repository.startTracking(); + void initialChainHeadWithoutBtcCheckpoints() throws Exception { + // Act + // Force instantiation of blockstore + bridgeSupport.getBtcBlockchainBestChainHeight(); + + // Assert + StoredBlock actualChainHead = bridgeSupport.getBtcBlockStore().getChainHead(); + int expectedChainHead = 0; + assertEquals(expectedChainHead, actualChainHead.getHeight()); + BtcBlock expectedHeader = btcParams.getGenesisBlock(); + assertEquals(expectedHeader, actualChainHead.getHeader()); + } + + @Test + void initialChainHeadWithBtcCheckpoints() throws Exception { + // arrange + NetworkParameters btcNetworkParams = bridgeMainNetConstants.getBtcParams(); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, + btcNetworkParams, + activations); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + btcNetworkParams); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); FederationSupport federationSupport = federationSupportBuilder - .withFederationConstants(federationConstants) + .withFederationConstants(bridgeMainNetConstants.getFederationConstants()) .withFederationStorageProvider(federationStorageProvider) - .withActivations(activationsBeforeForks) + .withActivations(activations) .build(); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); - - BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + this.bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeMainNetConstants) + .withBtcBlockStoreFactory(btcBlockStoreFactory) .withProvider(provider) + .withFederationSupport(federationSupport) .withRepository(track) + .withActivations(activations) + .build(); + + // act + // Force instantiation of blockstore + bridgeSupport.getBtcBlockchainBestChainHeight(); + + // assert + assertGenesisFedCreationTimeIsCheckpointBestChainHeight(bridgeMainNetConstants); + } + + @ParameterizedTest + @CsvSource( + { + "12-byte-chainwork.production.checkpoints", + "12-byte-chainwork-mix-format.production.checkpoints" + } + ) + void initialChainHeadWithBtcCheckpoints_whenCheckpointsWith12BytesChainWork_before_RSKIP454_ok( + String checkpointFileName) throws Exception { + // arrange + arrangeCheckpointsSource(checkpointFileName, bridgeMainNetConstants); + + activations = ActivationConfigsForTest.arrowhead631().forBlock(0); + + NetworkParameters btcNetworkParams = bridgeMainNetConstants.getBtcParams(); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + btcNetworkParams); + + FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); + FederationSupport federationSupport = federationSupportBuilder + .withFederationConstants(bridgeMainNetConstants.getFederationConstants()) + .withFederationStorageProvider(federationStorageProvider) + .withActivations(activations) + .build(); + + this.bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeMainNetConstants) .withBtcBlockStoreFactory(btcBlockStoreFactory) .withFederationSupport(federationSupport) + .withRepository(track) + .withActivations(activations) .build(); + // act // Force instantiation of blockstore bridgeSupport.getBtcBlockchainBestChainHeight(); + assertGenesisFedCreationTimeIsCheckpointBestChainHeight(bridgeMainNetConstants); + } - StoredBlock chainHead = bridgeSupport.getBtcBlockStore().getChainHead(); - assertEquals(0, chainHead.getHeight()); - assertEquals(btcParams.getGenesisBlock(), chainHead.getHeader()); + private void arrangeCheckpointsSource(String checkpointFileName, + BridgeConstants bridgeConstants) throws IOException { + String checkpointToCreate = "/rskbitcoincheckpoints/" + bridgeConstants.getBtcParams().getId() + ".checkpoints"; + + Path target = Paths.get(getClass().getResource(checkpointToCreate).getPath()); + + Path source = Paths.get(getClass().getResource("/checkpoints/" + checkpointFileName).getPath()); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); } @Test - void testInitialChainHeadWithBtcCheckpoints() throws Exception { - BridgeConstants bridgeTestNetConstants = BridgeTestNetConstants.getInstance(); - - Repository repository = createRepository(); - Repository track = repository.startTracking(); + void initialChainHeadWithBtcCheckpoints_whenCheckpointsWith32BytesChainWork_before_RSKIP454_shouldFail() throws Exception { + // arrange + arrangeCheckpointsSource("32-byte-chainwork.production.checkpoints", bridgeMainNetConstants); + activations = ActivationConfigsForTest.arrowhead631().forBlock(0); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeTestNetConstants.getBtcParams()); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeTestNetConstants.getBtcParams(), activationsBeforeForks); + NetworkParameters btcNetworkParams = bridgeMainNetConstants.getBtcParams(); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + btcNetworkParams); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); FederationSupport federationSupport = federationSupportBuilder - .withFederationConstants(federationConstants) + .withFederationConstants(bridgeMainNetConstants.getFederationConstants()) .withFederationStorageProvider(federationStorageProvider) - .withActivations(activationsBeforeForks) + .withActivations(activations) .build(); - BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeTestNetConstants) - .withProvider(provider) + this.bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeMainNetConstants) + .withBtcBlockStoreFactory(btcBlockStoreFactory) + .withFederationSupport(federationSupport) .withRepository(track) + .withActivations(activations) + .build(); + + // act and assert + Assertions.assertThrows(IllegalArgumentException.class, bridgeSupport::getBtcBlockchainBestChainHeight, "The given number does not fit in 12"); + } + + @ParameterizedTest + @CsvSource( + { + "12-byte-chainwork.production.checkpoints", + "12-byte-chainwork.production.checkpoints", + "32-byte-chainwork.production.checkpoints", + "12-byte-chainwork-mix-format.production.checkpoints" + } + ) + void initialChainHeadWithBtcCheckpoints_whenCheckpointsWith32BytesChainWork_after_RSKIP454_ok( + String checkpointFileName) throws Exception { + // arrange + arrangeCheckpointsSource(checkpointFileName, bridgeMainNetConstants); + activations = ActivationConfigsForTest.lovell700().forBlock(0); + + NetworkParameters btcNetworkParams = bridgeMainNetConstants.getBtcParams(); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + btcNetworkParams); + + FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); + FederationSupport federationSupport = federationSupportBuilder + .withFederationConstants(bridgeMainNetConstants.getFederationConstants()) + .withFederationStorageProvider(federationStorageProvider) + .withActivations(activations) + .build(); + + this.bridgeSupport = bridgeSupportBuilder + .withBridgeConstants(bridgeMainNetConstants) .withBtcBlockStoreFactory(btcBlockStoreFactory) .withFederationSupport(federationSupport) + .withRepository(track) + .withActivations(activations) .build(); + // act // Force instantiation of blockstore bridgeSupport.getBtcBlockchainBestChainHeight(); + // assert + assertGenesisFedCreationTimeIsCheckpointBestChainHeight(bridgeMainNetConstants); + } + + private void assertGenesisFedCreationTimeIsCheckpointBestChainHeight(BridgeConstants bridgeConstants) + throws BlockStoreException, IOException { InputStream checkpointsStream = bridgeSupport.getCheckPoints(); - CheckpointManager manager = new CheckpointManager(bridgeTestNetConstants.getBtcParams(), checkpointsStream); - long time = bridgeSupport.getActiveFederation().getCreationTime().toEpochMilli() - 604800L; // The magic number is a substraction CheckpointManager does when getting the checkpoints. - StoredBlock checkpoint = manager.getCheckpointBefore(time); + CheckpointManager manager = new CheckpointManager(bridgeConstants.getBtcParams(), + checkpointsStream); + + /** + * Time to use in CheckpointManager adjust checkpoint backwards by a week to account for possible clock drift in the block headers. + * For more detail please see {@link CheckpointManager#checkpoint(NetworkParameters, InputStream, co.rsk.bitcoinj.store.BtcBlockStore, long)} + */ + final long dayInSeconds = 24 * 60 * 60; + final long weekInSeconds = 7 * dayInSeconds; + long genesisFedCreationTimeAdjusted = bridgeSupport.getActiveFederation().getCreationTime().toEpochMilli() - weekInSeconds; + StoredBlock checkpoint = manager.getCheckpointBefore(genesisFedCreationTimeAdjusted); assertEquals(checkpoint.getHeight(), bridgeSupport.getBtcBlockchainBestChainHeight()); } @@ -229,14 +382,14 @@ void feePerKbFromStorageProvider() { Repository repository = createRepository(); Repository track = repository.startTracking(); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); Coin expected = Coin.MILLICOIN; when(feePerKbSupport.getFeePerKb()).thenReturn(expected); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withFeePerKbSupport(feePerKbSupport) @@ -247,30 +400,11 @@ void feePerKbFromStorageProvider() { @Test void testGetBtcBlockchainBlockLocatorWithoutBtcCheckpoints() throws Exception { - Repository repository = createRepository(); - Repository track = repository.startTracking(); - - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); - - FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); - FederationSupport federationSupport = federationSupportBuilder - .withFederationConstants(federationConstants) - .withFederationStorageProvider(federationStorageProvider) - .withActivations(activationsBeforeForks) - .build(); - - BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) - .withProvider(provider) - .withRepository(track) - .withBtcBlockStoreFactory(btcBlockStoreFactory) - .withFederationSupport(federationSupport) - .build(); - + // act // Force instantiation of blockstore bridgeSupport.getBtcBlockchainBestChainHeight(); + // assert StoredBlock chainHead = bridgeSupport.getBtcBlockStore().getChainHead(); assertEquals(0, chainHead.getHeight()); assertEquals(btcParams.getGenesisBlock(), chainHead.getHeader()); @@ -293,11 +427,10 @@ void testGetBtcBlockchainBlockLocatorWithoutBtcCheckpoints() throws Exception { @Test void testGetBtcBlockchainBlockLocatorWithBtcCheckpoints() throws Exception { - Repository repository = createRepository(); - Repository track = repository.startTracking(); - - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + // arrange + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); List checkpoints = createBtcBlocks(btcParams, btcParams.getGenesisBlock(), 10); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); @@ -308,7 +441,7 @@ void testGetBtcBlockchainBlockLocatorWithBtcCheckpoints() throws Exception { .build(); BridgeSupport bridgeSupport = new BridgeSupport( - bridgeConstants, + bridgeRegTestConstants, provider, mock(BridgeEventLogger.class), mock(BtcLockSenderProvider.class), @@ -330,9 +463,11 @@ InputStream getCheckPoints() { } }; + // act // Force instantiation of blockstore bridgeSupport.getBtcBlockchainBestChainHeight(); + // assert StoredBlock chainHead = bridgeSupport.getBtcBlockStore().getChainHead(); assertEquals(10, chainHead.getHeight()); assertEquals(checkpoints.get(9), chainHead.getHeader()); @@ -386,10 +521,10 @@ private InputStream getCheckpoints(NetworkParameters networkParameters, List blocks = blockGenerator.getSimpleBlockChain(blockGenerator.getGenesisBlock(), 10); @@ -999,7 +1134,7 @@ void callUpdateCollectionsWithTransactionsWaitingForConfirmationWithEnoughConfir .build(); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withExecutionBlock(rskCurrentBlock) @@ -1011,7 +1146,7 @@ void callUpdateCollectionsWithTransactionsWaitingForConfirmationWithEnoughConfir track.commit(); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); assertEquals(0, provider2.getReleaseRequestQueue().getEntries().size()); assertEquals(2, provider2.getPegoutsWaitingForConfirmations().getEntries().size()); @@ -1027,20 +1162,22 @@ void sendOrphanBlockHeader() throws IOException, BlockStoreException { BridgeStorageProvider provider = new BridgeStorageProvider( track, BRIDGE_ADDRESS, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); - BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, bridgeConstants, provider, activations); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); + BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, + bridgeRegTestConstants, provider, activations); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); FederationSupport federationSupport = federationSupportBuilder .withFederationConstants(federationConstants) .build(); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -1078,20 +1215,22 @@ void addBlockHeaderToBlockchain() throws IOException, BlockStoreException { BridgeStorageProvider provider = new BridgeStorageProvider( track, BRIDGE_ADDRESS, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); - BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, bridgeConstants, provider, activations); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); + BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, + bridgeRegTestConstants, provider, activations); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); FederationSupport federationSupport = federationSupportBuilder .withFederationConstants(federationConstants) .build(); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -1159,9 +1298,9 @@ void releaseBtcWithDustOutput() throws AddressFormatException, IOException { tx.sign(new org.ethereum.crypto.ECKey().getPrivKeyBytes()); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .build(); @@ -1171,7 +1310,7 @@ void releaseBtcWithDustOutput() throws AddressFormatException, IOException { track.commit(); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); assertEquals(0, provider.getReleaseRequestQueue().getEntries().size()); assertEquals(0, provider2.getReleaseRequestQueue().getEntries().size()); @@ -1196,9 +1335,9 @@ void releaseBtc() throws AddressFormatException, IOException { tx.sign(new org.ethereum.crypto.ECKey().getPrivKeyBytes()); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .build(); @@ -1208,7 +1347,7 @@ void releaseBtc() throws AddressFormatException, IOException { track.commit(); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); assertEquals(1, provider.getReleaseRequestQueue().getEntries().size()); assertEquals(1, provider2.getReleaseRequestQueue().getEntries().size()); @@ -1236,9 +1375,9 @@ void releaseBtcFromContract() throws AddressFormatException, IOException { ); track.saveCode(tx.getSender(), new byte[]{0x1}); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .build(); @@ -1257,12 +1396,12 @@ void registerBtcTransactionOfAlreadyProcessedTransaction() throws BlockStoreExce Repository track = repository.startTracking(); BtcTransaction tx = createTransaction(); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); provider.setHeightBtcTxhashAlreadyProcessed(tx.getHash(), 1L); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .build(); @@ -1272,7 +1411,7 @@ void registerBtcTransactionOfAlreadyProcessedTransaction() throws BlockStoreExce track.commit(); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); assertTrue(federationStorageProvider.getNewFederationBtcUTXOs(btcParams, activationsBeforeForks).isEmpty()); @@ -1287,12 +1426,13 @@ void registerBtcTransactionOfTransactionNotInMerkleTree() throws BlockStoreExcep Repository repository = createRepository(); Repository track = repository.startTracking(); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); BtcTransaction tx = createTransaction(); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(btcBlockStoreFactory) @@ -1310,7 +1450,7 @@ void registerBtcTransactionOfTransactionNotInMerkleTree() throws BlockStoreExcep track.commit(); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); assertTrue(federationStorageProvider.getNewFederationBtcUTXOs(btcParams, activationsBeforeForks).isEmpty()); @@ -1325,12 +1465,13 @@ void registerBtcTransactionOfTransactionInMerkleTreeWithNegativeHeight() throws Repository repository = createRepository(); Repository track = repository.startTracking(); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); BtcTransaction tx = createTransaction(); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(btcBlockStoreFactory) @@ -1348,7 +1489,7 @@ void registerBtcTransactionOfTransactionInMerkleTreeWithNegativeHeight() throws track.commit(); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); assertTrue(federationStorageProvider.getNewFederationBtcUTXOs(btcParams, activationsBeforeForks).isEmpty()); @@ -1363,12 +1504,13 @@ void registerBtcTransactionOfTransactionInMerkleTreeWithNotEnoughtHeight() throw Repository repository = createRepository(); Repository track = repository.startTracking(); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); BtcTransaction tx = createTransaction(); - BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider = new BridgeStorageProvider(track, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(btcBlockStoreFactory) @@ -1386,7 +1528,7 @@ void registerBtcTransactionOfTransactionInMerkleTreeWithNotEnoughtHeight() throw track.commit(); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); assertTrue(federationStorageProvider.getNewFederationBtcUTXOs(btcParams, activationsBeforeForks).isEmpty()); @@ -1448,17 +1590,19 @@ void registerBtcTransactionTxNotLockNorReleaseTx() throws BlockStoreException, A BridgeStorageProvider provider = new BridgeStorageProvider( track, BRIDGE_ADDRESS, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); - BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, bridgeConstants, provider, activations); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); + BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, + bridgeRegTestConstants, provider, activations); BtcBlockChain btcBlockChain = new SimpleBlockChain(btcContext, btcBlockStore); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -1495,7 +1639,7 @@ void registerBtcTransactionTxNotLockNorReleaseTx() throws BlockStoreException, A FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); assertEquals(0, federationStorageProvider.getNewFederationBtcUTXOs(btcParams, activationsBeforeForks).size()); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); assertEquals(0, provider2.getReleaseRequestQueue().getEntries().size()); assertEquals(0, provider2.getPegoutsWaitingForConfirmations().getEntries().size()); assertTrue(provider2.getPegoutsWaitingForSignatures().isEmpty()); @@ -1545,13 +1689,13 @@ void registerBtcTransactionReleaseTx() throws BlockStoreException, AddressFormat BridgeStorageProvider provider = new BridgeStorageProvider( track, BRIDGE_ADDRESS, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); FederationSupport federationSupport = federationSupportBuilder .withFederationConstants(federationConstants) @@ -1559,7 +1703,7 @@ void registerBtcTransactionReleaseTx() throws BlockStoreException, AddressFormat .build(); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -1602,7 +1746,7 @@ void registerBtcTransactionReleaseTx() throws BlockStoreException, AddressFormat assertEquals(1, federationStorageProvider.getNewFederationBtcUTXOs(btcParams, activationsBeforeForks).size()); assertEquals(Coin.COIN, federationStorageProvider.getNewFederationBtcUTXOs(btcParams, activationsBeforeForks).get(0).getValue()); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); assertEquals(0, provider2.getReleaseRequestQueue().getEntries().size()); assertEquals(0, provider2.getPegoutsWaitingForConfirmations().getEntries().size()); assertTrue(provider2.getPegoutsWaitingForSignatures().isEmpty()); @@ -1611,7 +1755,7 @@ void registerBtcTransactionReleaseTx() throws BlockStoreException, AddressFormat @Test void registerBtcTransactionMigrationTx() throws BlockStoreException, AddressFormatException, IOException, BridgeIllegalArgumentException { - NetworkParameters parameters = bridgeConstants.getBtcParams(); + NetworkParameters parameters = bridgeRegTestConstants.getBtcParams(); List activeFederationKeys = Stream.of( BtcECKey.fromPrivate(Hex.decode("fa01")), @@ -1679,7 +1823,7 @@ void registerBtcTransactionMigrationTx() throws BlockStoreException, AddressForm BridgeStorageProvider provider = new BridgeStorageProvider( track, BRIDGE_ADDRESS, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); FederationStorageProvider federationStorageProvider = createFederationStorageProvider(track); @@ -1688,7 +1832,7 @@ void registerBtcTransactionMigrationTx() throws BlockStoreException, AddressForm BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); FederationSupport federationSupport = federationSupportBuilder .withFederationConstants(federationConstants) @@ -1697,7 +1841,7 @@ void registerBtcTransactionMigrationTx() throws BlockStoreException, AddressForm .build(); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -1742,7 +1886,7 @@ void registerBtcTransactionMigrationTx() throws BlockStoreException, AddressForm @Test void registerBtcTransactionWithCrossFederationsChange() throws Exception { - NetworkParameters params = bridgeConstants.getBtcParams(); + NetworkParameters params = bridgeRegTestConstants.getBtcParams(); List activeFederationKeys = Stream.of("fa01", "fa02") .map(Hex::decode) @@ -1767,7 +1911,7 @@ void registerBtcTransactionWithCrossFederationsChange() throws Exception { BtcECKey.fromPrivate(Hex.decode("e1b17fcd0ef1942465eee61b20561b16750191143d365e71de08b33dd84a9788")) ); - Federation retiringFederation = createFederation(bridgeConstants, retiringFedPrivateKeys); + Federation retiringFederation = createFederation(bridgeRegTestConstants, retiringFedPrivateKeys); List activeFederationUtxos = new ArrayList<>(); List retiringFederationUtxos = new ArrayList<>(); @@ -1831,7 +1975,7 @@ void registerBtcTransactionWithCrossFederationsChange() throws Exception { .build(); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withBtcBlockStoreFactory(mockFactory) .withExecutionBlock(rskCurrentBlock) .withFederationSupport(federationSupport) @@ -1925,7 +2069,7 @@ void registerBtcTransactionLockTxWhitelisted() throws Exception { BridgeStorageProvider provider = new BridgeStorageProvider( track, BRIDGE_ADDRESS, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); // Whitelist the addresses @@ -1938,10 +2082,10 @@ void registerBtcTransactionLockTxWhitelisted() throws Exception { whitelist.put(address3, new OneOffWhiteListEntry(address3, Coin.COIN.multiply(2).add(Coin.COIN.multiply(3)))); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withBtcLockSenderProvider(new BtcLockSenderProvider()) .withRepository(track) @@ -2010,7 +2154,7 @@ void registerBtcTransactionLockTxWhitelisted() throws Exception { assertEquals(Coin.COIN.multiply(10), federationStorageProvider.getOldFederationBtcUTXOs().get(0).getValue()); assertEquals(Coin.COIN.multiply(3), federationStorageProvider.getOldFederationBtcUTXOs().get(1).getValue()); - BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeConstants.getBtcParams(), activationsBeforeForks); + BridgeStorageProvider provider2 = new BridgeStorageProvider(repository, PrecompiledContracts.BRIDGE_ADDR, bridgeRegTestConstants.getBtcParams(), activationsBeforeForks); assertEquals(0, provider2.getReleaseRequestQueue().getEntries().size()); assertEquals(0, provider2.getPegoutsWaitingForConfirmations().getEntries().size()); assertTrue(provider2.getPegoutsWaitingForSignatures().isEmpty()); @@ -2022,7 +2166,7 @@ void registerBtcTransactionLockTxWhitelisted() throws Exception { @Test void isBtcTxHashAlreadyProcessed() throws IOException { BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(getBridgeStorageProviderMockWithProcessedHashes()) .withRepository(null) .build(); @@ -2036,7 +2180,7 @@ void isBtcTxHashAlreadyProcessed() throws IOException { @Test void getBtcTxHashProcessedHeight() throws IOException { BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(getBridgeStorageProviderMockWithProcessedHashes()) .withRepository(null) .build(); @@ -3480,9 +3624,10 @@ void getRetiringFederationWallet_nonEmpty() { @Test void getBtcBlockchainInitialBlockHeight() throws IOException { Repository repository = createRepository(); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(null) .withRepository(repository) .withBtcBlockStoreFactory(btcBlockStoreFactory) @@ -3514,11 +3659,11 @@ void getBtcTransactionConfirmations_inexistentBlockHash() throws BlockStoreExcep BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3543,7 +3688,7 @@ void getBtcTransactionConfirmations_blockNotInBestChain() throws BlockStoreExcep StoredBlock block = new StoredBlock(blockHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(blockHash)).thenReturn(block); + when(btcBlockStore.get(blockHash)).thenReturn(block); StoredBlock chainHead = new StoredBlock(blockHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -3552,17 +3697,17 @@ void getBtcTransactionConfirmations_blockNotInBestChain() throws BlockStoreExcep BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); Sha256Hash btcTransactionHash = Sha256Hash.of(Hex.decode("112233")); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3587,26 +3732,26 @@ void getBtcTransactionConfirmations_blockNotInBestChainBlockWithHeightNotFound() StoredBlock block = new StoredBlock(blockHeader, new BigInteger("0"), 50); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(blockHash)).thenReturn(block); + when(btcBlockStore.get(blockHash)).thenReturn(block); StoredBlock chainHead = new StoredBlock(blockHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); - when(btcBlockStore.getFromCache(blockHash)).thenReturn(block); + when(btcBlockStore.get(blockHash)).thenReturn(block); BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); Sha256Hash btcTransactionHash = Sha256Hash.of(Hex.decode("112233")); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3632,7 +3777,7 @@ void getBtcTransactionConfirmations_blockTooOld() throws BlockStoreException, IO StoredBlock block = new StoredBlock(blockHeader, new BigInteger("0"), BLOCK_HEIGHT); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(blockHash)).thenReturn(block); + when(btcBlockStore.get(blockHash)).thenReturn(block); StoredBlock chainHead = new StoredBlock(blockHeader, new BigInteger("0"), BLOCK_HEIGHT + 4320 + 1); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -3640,17 +3785,17 @@ void getBtcTransactionConfirmations_blockTooOld() throws BlockStoreException, IO BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); Sha256Hash btcTransactionHash = Sha256Hash.of(Hex.decode("112233")); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3675,7 +3820,7 @@ void getBtcTransactionConfirmations_heightInconsistency() throws BlockStoreExcep StoredBlock block = new StoredBlock(blockHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(blockHash)).thenReturn(block); + when(btcBlockStore.get(blockHash)).thenReturn(block); StoredBlock chainHead = new StoredBlock(blockHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -3684,17 +3829,17 @@ void getBtcTransactionConfirmations_heightInconsistency() throws BlockStoreExcep BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); Sha256Hash btcTransactionHash = Sha256Hash.of(Hex.decode("112233")); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3721,7 +3866,7 @@ void getBtcTransactionConfirmations_merkleBranchDoesNotProve() throws BlockStore StoredBlock block = new StoredBlock(blockHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(blockHash)).thenReturn(block); + when(btcBlockStore.get(blockHash)).thenReturn(block); StoredBlock chainHead = new StoredBlock(blockHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -3730,16 +3875,16 @@ void getBtcTransactionConfirmations_merkleBranchDoesNotProve() throws BlockStore BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); Sha256Hash btcTransactionHash = Sha256Hash.of(Hex.decode("112233")); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3762,7 +3907,7 @@ void getBtcTransactionConfirmationsGetCost_ok() throws BlockStoreException { StoredBlock block = new StoredBlock(null, new BigInteger("0"), 50); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(blockHash)).thenReturn(block); + when(btcBlockStore.get(blockHash)).thenReturn(block); BtcBlock btcBlock = mock(BtcBlock.class); doReturn(Sha256Hash.of(Hex.decode("aa"))).when(btcBlock).getHash(); @@ -3773,16 +3918,16 @@ void getBtcTransactionConfirmationsGetCost_ok() throws BlockStoreException { BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3807,14 +3952,14 @@ void getBtcTransactionConfirmationsGetCost_blockDoesNotExist() { BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3837,14 +3982,14 @@ void getBtcTransactionConfirmationsGetCost_getBestChainHeightError() { BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3868,14 +4013,14 @@ void getBtcTransactionConfirmationsGetCost_blockTooDeep() { BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); BtcBlockStoreWithCache.Factory mockFactory = mock(BtcBlockStoreWithCache.Factory.class); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) @@ -3896,21 +4041,23 @@ void getBtcBlockchainBlockHashAtDepth() throws Exception { BridgeStorageProvider provider = new BridgeStorageProvider( track, PrecompiledContracts.BRIDGE_ADDR, - bridgeConstants.getBtcParams(), + bridgeRegTestConstants.getBtcParams(), activationsBeforeForks ); - BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeConstants.getBtcParams()); - BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, bridgeConstants, provider, activations); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory( + bridgeRegTestConstants.getBtcParams()); + BtcBlockStoreWithCache btcBlockStore = btcBlockStoreFactory.newInstance(track, + bridgeRegTestConstants, provider, activations); BtcBlockStoreWithCache.Factory mockFactory = mock(RepositoryBtcBlockStoreWithCache.Factory.class); - when(mockFactory.newInstance(track, bridgeConstants, provider, activations)).thenReturn(btcBlockStore); + when(mockFactory.newInstance(track, bridgeRegTestConstants, provider, activations)).thenReturn(btcBlockStore); FederationSupport federationSupport = federationSupportBuilder .withFederationConstants(federationConstants) .build(); BridgeSupport bridgeSupport = bridgeSupportBuilder - .withBridgeConstants(bridgeConstants) + .withBridgeConstants(bridgeRegTestConstants) .withProvider(provider) .withRepository(track) .withBtcBlockStoreFactory(mockFactory) 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..d4161e02247 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTest.java @@ -75,6 +75,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static co.rsk.peg.BridgeSupport.BTC_TRANSACTION_CONFIRMATION_INCONSISTENT_BLOCK_ERROR_CODE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.core.Is.is; @@ -5279,7 +5280,7 @@ void getBtcTransactionConfirmations_rejects_tx_with_witness_before_rskip_143() t StoredBlock block = new StoredBlock(registerHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(block); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(block); when(btcBlockStore.getStoredBlockAtMainChainHeight(block.getHeight())).thenReturn(block); StoredBlock chainHead = new StoredBlock(registerHeader, new BigInteger("0"), 132); @@ -5348,7 +5349,7 @@ void getBtcTransactionConfirmations_accepts_tx_with_witness_after_rskip_143() th StoredBlock block = new StoredBlock(registerHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(block); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(block); StoredBlock chainHead = new StoredBlock(registerHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -5430,7 +5431,7 @@ void getBtcTransactionConfirmations_unregistered_coinbase_after_rskip_143() thro StoredBlock block = new StoredBlock(registerHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(block); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(block); StoredBlock chainHead = new StoredBlock(registerHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -5509,7 +5510,7 @@ void getBtcTransactionConfirmations_registered_coinbase_unequal_witnessroot_afte StoredBlock block = new StoredBlock(registerHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(block); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(block); StoredBlock chainHead = new StoredBlock(registerHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -5567,7 +5568,7 @@ void getBtcTransactionConfirmations_tx_without_witness_unequal_roots_after_rskip StoredBlock block = new StoredBlock(registerHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(block); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(block); StoredBlock chainHead = new StoredBlock(registerHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -5638,7 +5639,7 @@ void getBtcTransactionConfirmations_accepts_tx_without_witness_after_rskip_143() StoredBlock block = new StoredBlock(registerHeader, new BigInteger("0"), height); BtcBlockStoreWithCache btcBlockStore = mock(BtcBlockStoreWithCache.class); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(block); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(block); StoredBlock chainHead = new StoredBlock(registerHeader, new BigInteger("0"), 132); when(btcBlockStore.getChainHead()).thenReturn(chainHead); @@ -5894,7 +5895,7 @@ void when_RegisterBtcCoinbaseTransaction_notVerify_noSent() throws BlockStoreExc int height = 5; mockChainOfStoredBlocks(btcBlockStore, registerHeader, 5, height); Sha256Hash hash = registerHeader.getHash(); - when(btcBlockStore.getFromCache(hash)).thenReturn(new StoredBlock(registerHeader, BigInteger.ZERO, 0)); + when(btcBlockStore.get(hash)).thenReturn(new StoredBlock(registerHeader, BigInteger.ZERO, 0)); byte[] btcTxSerialized = tx.bitcoinSerialize(); byte[] pmtSerialized = pmt.bitcoinSerialize(); @@ -5971,7 +5972,7 @@ void when_RegisterBtcCoinbaseTransaction_not_equal_merkle_root_noSent() throws B StoredBlock storedBlock = mock(StoredBlock.class); when(btcBlock.getMerkleRoot()).thenReturn(Sha256Hash.ZERO_HASH); when(storedBlock.getHeader()).thenReturn(btcBlock); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(storedBlock); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(storedBlock); bridgeSupport.registerBtcCoinbaseTransaction( txWithoutWitness.bitcoinSerialize(), @@ -6130,7 +6131,7 @@ void registerBtcCoinbaseTransaction() throws BlockStoreException, AddressFormatE //Leaving no confirmation blocks int height = 5; mockChainOfStoredBlocks(btcBlockStore, registerHeader, 5, height); - when(btcBlockStore.getFromCache(registerHeader.getHash())).thenReturn(new StoredBlock(registerHeader, BigInteger.ZERO, 0)); + when(btcBlockStore.get(registerHeader.getHash())).thenReturn(new StoredBlock(registerHeader, BigInteger.ZERO, 0)); bridgeSupport.registerBtcCoinbaseTransaction( txWithoutWitness.bitcoinSerialize(), registerHeader.getHash(), @@ -7058,6 +7059,97 @@ void receiveHeader_block_exist_in_storage() throws IOException, BlockStoreExcept assertEquals(-4, result); } + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @Tag("test the methods involved are keeping testnet consensus") + class BuildBlockThatKeepsTestnetConsensusTests { + BridgeConstants bridgeTestnetConstants = BridgeTestNetConstants.getInstance(); + BtcBlockStoreWithCache.Factory btcBlockStoreFactory; + BtcBlockStoreWithCache btcBlockStoreWithCache; + + BridgeSupport bridgeSupport; + FederationSupport federationSupport = mock(FederationSupport.class); + Block rskExecutionBlock = mock(Block.class); + Sha256Hash btcBlockHash = Sha256Hash.wrap("00000000e8e7b540df01a7067e020fd7e2026bf86289def2283a35120c1af379"); + + @BeforeEach + void setUp() { + long rskBlockNumber = 5_148_285; + when(rskExecutionBlock.getNumber()).thenReturn(rskBlockNumber); + + Repository repository = createRepository(); + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider( + repository, + PrecompiledContracts.BRIDGE_ADDR, + bridgeTestnetConstants.getBtcParams(), + activationsAfterForks + ); + + btcBlockStoreFactory = new RepositoryBtcBlockStoreWithCache.Factory(bridgeTestnetConstants.getBtcParams(), 100, 100); + btcBlockStoreWithCache = btcBlockStoreFactory.newInstance(repository, bridgeTestnetConstants, bridgeStorageProvider, activationsAfterForks); + + Federation activeFederation = P2shErpFederationBuilder.builder().build(); + when(federationSupport.getActiveFederation()).thenReturn(activeFederation); + + bridgeSupport = BridgeSupportBuilder.builder() + .withBridgeConstants(bridgeTestnetConstants) + .withProvider(bridgeStorageProvider) + .withRepository(repository) + .withBtcBlockStoreFactory(btcBlockStoreFactory) + .withExecutionBlock(rskExecutionBlock) + .withActivations(activationsAfterForks) + .withFederationSupport(federationSupport) + .build(); + } + + @Test + void getBtcTransactionConfirmations() throws BlockStoreException, IOException { + // act + int result = bridgeSupport.getBtcTransactionConfirmations(mock(Sha256Hash.class), btcBlockHash, mock(MerkleBranch.class)); // we dont reach the use of btcTxHash and merkleBranch in this situation + + // assert + // checking the block is not in the storage + assertNull(btcBlockStoreWithCache.get(btcBlockHash)); + // checking the method returns the expected code from the error thrown + // when block from cache and block from storage dont match + assertEquals(BTC_TRANSACTION_CONFIRMATION_INCONSISTENT_BLOCK_ERROR_CODE, result); + } + + @Test + void getBtcTransactionConfirmationsGetCost() throws BlockStoreException { + // arrange + Object[] args = new Object[4]; + args[1] = btcBlockHash.getBytes(); + args[3] = new Object[]{}; + + // recreating the chainHead as real btc block 2_817_200 so blockDepth is not 0 + byte[] chainHeadRawHeader = HexUtils.stringHexToByteArray("0000642d6b8df2c8ae4a20e20a10ae3b34e485701b8ae7e80ede0828040000000000000063a17c3e5063da2f8f952e496beee729932d35f62c0765baaf12f32931f01c9b49354f66ecd410190f25f402"); + BtcBlock chainHeadHeader = new BtcBlock(bridgeTestnetConstants.getBtcParams(), chainHeadRawHeader); + int chainHeadBlockNumber = 2_817_200; + BigInteger chainHeadChainWork = BigInteger.ONE; + StoredBlock chainHead = new StoredBlock(chainHeadHeader, chainHeadChainWork, chainHeadBlockNumber); + btcBlockStoreWithCache.setChainHead(chainHead); + + // act + Long result = bridgeSupport.getBtcTransactionConfirmationsGetCost(args); + + // assert + long BASIC_COST = 27_000; + long STEP_COST = 315; + long DOUBLE_HASH_COST = 144; + int blockDepth = chainHeadBlockNumber - 2_817_125; + int branchHashesSize = 0; // since branchHashesSize = args[3].length + Long expectedResult = BASIC_COST + blockDepth * STEP_COST + branchHashesSize * DOUBLE_HASH_COST; + + // checking the block is not in the storage + assertNull(btcBlockStoreWithCache.get(btcBlockHash)); + // check the result is different from BASIC_COST (result = BASIC_COST would break consensus) + assertTrue(result > BASIC_COST); + assertEquals(expectedResult, result); + } + } + + @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tag("test chain work before and after rskip 434") 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 e2b5062a7a4..c90c25063d1 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTestUtil.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeSupportTestUtil.java @@ -1,22 +1,17 @@ package co.rsk.peg; -import co.rsk.bitcoinj.core.BtcBlock; -import co.rsk.bitcoinj.core.Sha256Hash; -import co.rsk.bitcoinj.core.StoredBlock; +import static org.mockito.Mockito.*; + +import co.rsk.bitcoinj.core.*; import co.rsk.bitcoinj.store.BlockStoreException; import co.rsk.db.MutableTrieCache; import co.rsk.db.MutableTrieImpl; import co.rsk.trie.Trie; +import java.math.BigInteger; import org.bouncycastle.util.encoders.Hex; import org.ethereum.core.Repository; import org.ethereum.db.MutableRepository; -import java.math.BigInteger; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public final class BridgeSupportTestUtil { public static Repository createRepository() { return new MutableRepository(new MutableTrieCache(new MutableTrieImpl(null, new Trie()))); @@ -35,5 +30,4 @@ public static void mockChainOfStoredBlocks(BtcBlockStoreWithCache btcBlockStore, when(btcBlockStore.getChainHead()).thenReturn(currentStored); when(currentStored.getHeight()).thenReturn(headHeight); } - } diff --git a/rskj-core/src/test/java/co/rsk/peg/RepositoryBtcBlockStoreWithCacheChainWorkTest.java b/rskj-core/src/test/java/co/rsk/peg/RepositoryBtcBlockStoreWithCacheChainWorkTest.java new file mode 100644 index 00000000000..8beac06fa83 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/peg/RepositoryBtcBlockStoreWithCacheChainWorkTest.java @@ -0,0 +1,434 @@ +package co.rsk.peg; + +import static co.rsk.bitcoinj.core.StoredBlock.COMPACT_SERIALIZED_SIZE_LEGACY; +import static co.rsk.bitcoinj.core.StoredBlock.COMPACT_SERIALIZED_SIZE_V2; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import co.rsk.bitcoinj.core.*; +import co.rsk.core.RskAddress; +import co.rsk.db.MutableTrieCache; +import co.rsk.db.MutableTrieImpl; +import co.rsk.peg.constants.BridgeConstants; +import co.rsk.peg.constants.BridgeMainNetConstants; +import co.rsk.trie.Trie; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; +import org.ethereum.core.Repository; +import org.ethereum.db.MutableRepository; +import org.ethereum.vm.DataWord; +import org.ethereum.vm.PrecompiledContracts; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.spongycastle.util.encoders.Hex; + +class RepositoryBtcBlockStoreWithCacheChainWorkTest { + + private static final String BLOCK_STORE_CHAIN_HEAD_KEY = "blockStoreChainHead"; + // Max chain work to fit in 12 bytes + private static final BigInteger MAX_WORK_V1 = new BigInteger(/* 12 bytes */ "ffffffffffffffffffffffff", 16); + // Chain work too large to fit in 12 bytes + private static final BigInteger TOO_LARGE_WORK_V1 = new BigInteger(/* 13 bytes */ "ffffffffffffffffffffffffff", 16); + // Max chain work to fit in 32 bytes + private static final BigInteger MAX_WORK_V2 = new BigInteger(/* 32 bytes */ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + // Chain work too large to fit in 32 bytes + private static final BigInteger TOO_LARGE_WORK_V2 = new BigInteger(/* 33 bytes */ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + + private static final ActivationConfig.ForBlock arrowHeadActivations = ActivationConfigsForTest.arrowhead631().forBlock(0); + private static final ActivationConfig.ForBlock lovellActivations = ActivationConfigsForTest.lovell700().forBlock(0); + + private static final BridgeConstants bridgeMainnetConstants = BridgeMainNetConstants.getInstance(); + private static final NetworkParameters mainneNetworkParameters = bridgeMainnetConstants.getBtcParams(); + private static final RskAddress BRIDGE_ADDR = PrecompiledContracts.BRIDGE_ADDR; + + private Repository repository; + private RepositoryBtcBlockStoreWithCache repositoryBtcBlockStoreWithCache; + + // Just an arbitrary block + private static final String BLOCK_HEADER = "00e00820925b77c9ff4d0036aa29f3238cde12e9af9d55c34ed30200000000000000000032a9fa3e12ef87a2327b55db6a16a1227bb381db8b269d90aa3a6e38cf39665f91b47766255d0317c1b1575f"; + private static final int BLOCK_HEIGHT = 849137; + private static final BtcBlock BLOCK = new BtcBlock(mainneNetworkParameters, Hex.decode(BLOCK_HEADER)); + + @BeforeEach + void setUp() { + repository = spy(new MutableRepository( + new MutableTrieCache(new MutableTrieImpl(null, new Trie())) + )); + } + + void arrange(ActivationConfig.ForBlock activations) { + Map cacheBlocks = new HashMap<>(); + BridgeStorageProvider bridgeStorageProvider = new BridgeStorageProvider(repository, + BRIDGE_ADDR, mainneNetworkParameters, activations); + + repositoryBtcBlockStoreWithCache = new RepositoryBtcBlockStoreWithCache( + mainneNetworkParameters, + repository, + cacheBlocks, + BRIDGE_ADDR, + bridgeMainnetConstants, + bridgeStorageProvider, + activations + ); + } + + @ParameterizedTest() + @MethodSource("invalidChainWorkForV1") + void put_preRskip_whenInvalidChainWorkForV1_shouldFail(BigInteger chainWork) { + arrange(arrowHeadActivations); + StoredBlock storedBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + // act + IllegalArgumentException actualException = assertThrows( + IllegalArgumentException.class, () -> repositoryBtcBlockStoreWithCache.put(storedBlock)); + + String expectedMessage = "The given number does not fit in 12"; + String actualMessage = actualException.getMessage(); + Assertions.assertEquals(expectedMessage, actualMessage); + } + + private static Stream invalidChainWorkForV1() { + return Stream.of( + Arguments.of(TOO_LARGE_WORK_V1), + Arguments.of(MAX_WORK_V2), + Arguments.of(TOO_LARGE_WORK_V2) + ); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV1") + void put_preRskip_whenValidChainWorkForV1_shouldStoreBlock(BigInteger chainWork) { + arrange(arrowHeadActivations); + StoredBlock storedBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + // act + repositoryBtcBlockStoreWithCache.put(storedBlock); + + // assert + Sha256Hash expectedHash = storedBlock.getHeader().getHash(); + + int expectedCompactSerializedSize = COMPACT_SERIALIZED_SIZE_LEGACY; + ByteBuffer byteBufferForExpectedBlock = ByteBuffer.allocate(expectedCompactSerializedSize); + storedBlock.serializeCompactLegacy(byteBufferForExpectedBlock); + + byte[] expectedSerializedBlock = byteBufferForExpectedBlock.array(); + verify(repository, times(1)).addStorageBytes( + BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString()), + expectedSerializedBlock + ); + byte[] actualSerializedBlock = repository.getStorageBytes(BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString())); + Assertions.assertNotNull(actualSerializedBlock); + Assertions.assertEquals(expectedCompactSerializedSize, actualSerializedBlock.length); + + Assertions.assertArrayEquals(expectedSerializedBlock, actualSerializedBlock); + } + + private static Stream validChainWorkForV1 () { + return Stream.of( + Arguments.of(BigInteger.ZERO), // no work + Arguments.of(BigInteger.ONE), // small work + Arguments.of(BigInteger.valueOf(Long.MAX_VALUE)), // a larg-ish work + Arguments.of(MAX_WORK_V1) + ); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV2") + void put_postRskip_whenChainWorkAnySizeUnder32Bytes_shouldStoreBlock(BigInteger chainWork) { + int expectedCompactSerializedSize = COMPACT_SERIALIZED_SIZE_V2; + + arrange(lovellActivations); + StoredBlock storedBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + // act + repositoryBtcBlockStoreWithCache.put(storedBlock); + + // assert + Sha256Hash expectedHash = storedBlock.getHeader().getHash(); + + ByteBuffer byteBufferForExpectedBlock = ByteBuffer.allocate(expectedCompactSerializedSize); + storedBlock.serializeCompactV2(byteBufferForExpectedBlock); + + byte[] expectedSerializedBlock = byteBufferForExpectedBlock.array(); + verify(repository, times(1)).addStorageBytes( + BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString()), + expectedSerializedBlock + ); + byte[] actualSerializedBlock = repository.getStorageBytes(BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString())); + Assertions.assertNotNull(actualSerializedBlock); + Assertions.assertEquals(expectedCompactSerializedSize, actualSerializedBlock.length); + + Assertions.assertArrayEquals(expectedSerializedBlock, actualSerializedBlock); + } + + private static Stream validChainWorkForV2() { + return Stream.of( + Arguments.of(BigInteger.ZERO), // no work + Arguments.of(BigInteger.ONE), // small work + Arguments.of(BigInteger.valueOf(Long.MAX_VALUE)), // a larg-ish work + Arguments.of(MAX_WORK_V1), + Arguments.of(TOO_LARGE_WORK_V1), + Arguments.of(MAX_WORK_V2) + ); + } + + @ParameterizedTest() + @MethodSource("invalidChainWorkForV2") + void put_postRskip_whenInvalidChainWorkForV2_shouldFail(BigInteger chainWork) { + arrange(lovellActivations); + StoredBlock storedBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + // act + IllegalArgumentException actualException = assertThrows( + IllegalArgumentException.class, () -> repositoryBtcBlockStoreWithCache.put(storedBlock) + ); + + String expectedMessage = "The given number does not fit in 32"; + String actualMessage = actualException.getMessage(); + Assertions.assertEquals(expectedMessage, actualMessage); + } + + private static Stream invalidChainWorkForV2() { + return Stream.of( + Arguments.of(TOO_LARGE_WORK_V2) + ); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV1") + void get_preRskip_whenValidChainWorkForV1_shouldGetStoredBlock(BigInteger chainWork) { + arrange(arrowHeadActivations); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + Sha256Hash expectedHash = expectedStoreBlock.getHeader().getHash(); + arrangeRepositoryWithExpectedStoredBlock(expectedStoreBlock); + + // act + StoredBlock actualStoreBlock = repositoryBtcBlockStoreWithCache.get(expectedHash); + + // assert + verify(repository, times(1)).getStorageBytes( + BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString()) + ); + + Assertions.assertEquals(expectedStoreBlock, actualStoreBlock); + } + + private void arrangeRepositoryWithExpectedStoredBlock(StoredBlock expectedStoreBlock) { + Sha256Hash expectedHash = expectedStoreBlock.getHeader().getHash(); + ByteBuffer byteBuffer = ByteBuffer.allocate(COMPACT_SERIALIZED_SIZE_LEGACY); + expectedStoreBlock.serializeCompactLegacy(byteBuffer); + when(repository.getStorageBytes( + BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString()) + )).thenReturn(byteBuffer.array()); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV1") + void get_postRskip_whenChainWorkForV1_shouldGetStoredBlock(BigInteger chainWork) { + arrange(lovellActivations); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + Sha256Hash expectedHash = expectedStoreBlock.getHeader().getHash(); + arrangeRepositoryWithExpectedStoredBlockV2(expectedStoreBlock); + + // act + StoredBlock actualStoreBlock = repositoryBtcBlockStoreWithCache.get(expectedHash); + + // assert + verify(repository, times(1)).getStorageBytes( + BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString()) + ); + + Assertions.assertEquals(expectedStoreBlock, actualStoreBlock); + } + + private void arrangeRepositoryWithExpectedStoredBlockV2(StoredBlock expectedStoreBlock) { + Sha256Hash expectedHash = expectedStoreBlock.getHeader().getHash(); + ByteBuffer byteBuffer = ByteBuffer.allocate(COMPACT_SERIALIZED_SIZE_V2); + expectedStoreBlock.serializeCompactV2(byteBuffer); + when(repository.getStorageBytes( + BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString()) + )).thenReturn(byteBuffer.array()); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV2") + void get_postRskip_whenStoredBLochHasChainWorkOver12Bytes_shouldGetStoredBlock(BigInteger chainWork) { + arrange(lovellActivations); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + Sha256Hash expectedHash = expectedStoreBlock.getHeader().getHash(); + arrangeRepositoryWithExpectedStoredBlockV2(expectedStoreBlock); + + // act + StoredBlock actualStoreBlock = repositoryBtcBlockStoreWithCache.get(expectedHash); + + // assert + verify(repository, times(1)).getStorageBytes( + BRIDGE_ADDR, + DataWord.valueFromHex(expectedHash.toString()) + ); + + Assertions.assertEquals(expectedStoreBlock, actualStoreBlock); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV1") + void getChainHead_preRskip_whenValidChainWorkForV1_shouldGetChainHead(BigInteger chainWork) { + arrange(arrowHeadActivations); + reset(repository); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + arrangeRepositoryWithExpectedChainHead(expectedStoreBlock); + + // act + StoredBlock actualStoreBlock = repositoryBtcBlockStoreWithCache.getChainHead(); + + // assert + verify(repository, times(1)).getStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY) + ); + + Assertions.assertEquals(expectedStoreBlock, actualStoreBlock); + } + + private void arrangeRepositoryWithExpectedChainHead(StoredBlock expectedStoreBlock) { + ByteBuffer byteBuffer = ByteBuffer.allocate(COMPACT_SERIALIZED_SIZE_LEGACY); + expectedStoreBlock.serializeCompactLegacy(byteBuffer); + when(repository.getStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY)) + ).thenReturn(byteBuffer.array()); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV1") + void getChainHead_postRskip_whenChainWorkForV1_shouldGetChainHead(BigInteger chainWork) { + arrange(lovellActivations); + reset(repository); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + arrangeRepositoryWithChainHeadV2(expectedStoreBlock); + + // act + StoredBlock actualStoreBlock = repositoryBtcBlockStoreWithCache.getChainHead(); + + // assert + verify(repository, times(1)).getStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY) + ); + + Assertions.assertEquals(expectedStoreBlock, actualStoreBlock); + } + + private void arrangeRepositoryWithChainHeadV2(StoredBlock expectedStoreBlock) { + ByteBuffer byteBuffer = ByteBuffer.allocate(COMPACT_SERIALIZED_SIZE_V2); + expectedStoreBlock.serializeCompactV2(byteBuffer); + when(repository.getStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY)) + ).thenReturn(byteBuffer.array()); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV2") + void getChainHead_postRskip_whenStoredBLochHasChainWorkOver12Bytes_shouldGetChainHead(BigInteger chainWork) { + arrange(lovellActivations); + reset(repository); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + arrangeRepositoryWithChainHeadV2(expectedStoreBlock); + + // act + StoredBlock actualStoreBlock = repositoryBtcBlockStoreWithCache.getChainHead(); + + // assert + verify(repository, times(1)).getStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY) + ); + + Assertions.assertEquals(expectedStoreBlock, actualStoreBlock); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV1") + void setChainHead_preRskip_whenValidChainWorkForV1_shouldStoreChainHead(BigInteger chainWork) { + arrange(arrowHeadActivations); + reset(repository); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + // act + repositoryBtcBlockStoreWithCache.setChainHead(expectedStoreBlock); + + // assert + ByteBuffer byteBuffer = ByteBuffer.allocate(COMPACT_SERIALIZED_SIZE_LEGACY); + expectedStoreBlock.serializeCompactLegacy(byteBuffer); + + verify(repository, times(1)).addStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY), + byteBuffer.array() + ); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV1") + void setChainHead_postRskip_whenChainWorkForV1_shouldStoreChainHead(BigInteger chainWork) { + arrange(lovellActivations); + reset(repository); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + // act + repositoryBtcBlockStoreWithCache.setChainHead(expectedStoreBlock); + + // assert + ByteBuffer byteBuffer = ByteBuffer.allocate(COMPACT_SERIALIZED_SIZE_V2); + expectedStoreBlock.serializeCompactV2(byteBuffer); + + verify(repository, times(1)).addStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY), + byteBuffer.array() + ); + } + + @ParameterizedTest() + @MethodSource("validChainWorkForV2") + void setChainHead_postRskip_whenStoredBLochHasChainWorkOver12Bytes1_shouldStoreChainHead(BigInteger chainWork) { + arrange(lovellActivations); + reset(repository); + StoredBlock expectedStoreBlock = new StoredBlock(BLOCK, chainWork, BLOCK_HEIGHT); + + // act + repositoryBtcBlockStoreWithCache.setChainHead(expectedStoreBlock); + + // assert + ByteBuffer byteBuffer = ByteBuffer.allocate(COMPACT_SERIALIZED_SIZE_V2); + expectedStoreBlock.serializeCompactV2(byteBuffer); + + verify(repository, times(1)).addStorageBytes( + BRIDGE_ADDR, + DataWord.fromString(BLOCK_STORE_CHAIN_HEAD_KEY), + byteBuffer.array() + ); + } +} \ No newline at end of file diff --git a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java index 146d389f73f..9439cd27f4f 100644 --- a/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/bitcoin/BitcoinTestUtils.java @@ -11,8 +11,10 @@ import java.util.stream.Collectors; import org.bouncycastle.util.encoders.Hex; import org.ethereum.crypto.HashUtil; +import org.ethereum.util.ByteUtil; public class BitcoinTestUtils { + public static final Sha256Hash WITNESS_RESERVED_VALUE = Sha256Hash.ZERO_HASH; public static BtcECKey getBtcEcKeyFromSeed(String seed) { byte[] serializedSeed = HashUtil.keccak256(seed.getBytes(StandardCharsets.UTF_8)); @@ -154,4 +156,84 @@ private static void signLegacyTransactionInputFromP2shMultiSig(BtcTransaction tr input.setScriptSig(inputScriptSig); } } + + public static BtcTransaction createCoinbaseTransaction(NetworkParameters networkParameters) { + Address rewardAddress = createP2PKHAddress(networkParameters, "miner"); + Script inputScript = new Script(new byte[]{ 1, 0 }); // Free-form, as long as it's has at least 2 bytes + + BtcTransaction coinbaseTx = new BtcTransaction(networkParameters); + coinbaseTx.addInput( + Sha256Hash.ZERO_HASH, + -1L, + inputScript + ); + coinbaseTx.addOutput(Coin.COIN, rewardAddress); + coinbaseTx.verify(); + + return coinbaseTx; + } + + public static BtcTransaction createCoinbaseTransactionWithWitnessCommitment( + NetworkParameters networkParameters, + Sha256Hash witnessCommitment + ) { + BtcTransaction coinbaseTx = createCoinbaseTxWithWitnessReservedValue(networkParameters); + + byte[] witnessCommitmentWithHeader = ByteUtil.merge( + BitcoinUtils.WITNESS_COMMITMENT_HEADER, + witnessCommitment.getBytes() + ); + coinbaseTx.addOutput(Coin.ZERO, ScriptBuilder.createOpReturnScript(witnessCommitmentWithHeader)); + coinbaseTx.verify(); + + return coinbaseTx; + } + + public static BtcTransaction createCoinbaseTransactionWithMultipleWitnessCommitments( + NetworkParameters networkParameters, + List witnessCommitments + ) { + BtcTransaction coinbaseTx = createCoinbaseTxWithWitnessReservedValue(networkParameters); + + for (Sha256Hash witnessCommitment : witnessCommitments) { + byte[] witnessCommitmentWithHeader = ByteUtil.merge( + BitcoinUtils.WITNESS_COMMITMENT_HEADER, + witnessCommitment.getBytes() + ); + coinbaseTx.addOutput(Coin.ZERO, ScriptBuilder.createOpReturnScript(witnessCommitmentWithHeader)); + } + coinbaseTx.verify(); + + return coinbaseTx; + } + + public static BtcTransaction createCoinbaseTransactionWithWrongWitnessCommitment( + NetworkParameters networkParameters, + Sha256Hash witnessCommitment + ) { + BtcTransaction coinbaseTx = createCoinbaseTxWithWitnessReservedValue(networkParameters); + + byte[] wrongWitnessCommitmentWithHeader = ByteUtil.merge( + new byte[]{ScriptOpCodes.OP_RETURN}, + new byte[]{ScriptOpCodes.OP_PUSHDATA1}, + new byte[]{(byte) BitcoinUtils.WITNESS_COMMITMENT_LENGTH}, + BitcoinUtils.WITNESS_COMMITMENT_HEADER, + witnessCommitment.getBytes() + ); + Script wrongWitnessCommitmentScript = new Script(wrongWitnessCommitmentWithHeader); + coinbaseTx.addOutput(Coin.ZERO, wrongWitnessCommitmentScript); + coinbaseTx.verify(); + + return coinbaseTx; + } + + private static BtcTransaction createCoinbaseTxWithWitnessReservedValue(NetworkParameters networkParameters) { + BtcTransaction coinbaseTx = createCoinbaseTransaction(networkParameters); + + TransactionWitness txWitness = new TransactionWitness(1); + txWitness.setPush(0, WITNESS_RESERVED_VALUE.getBytes()); + coinbaseTx.setWitness(0, txWitness); + + return coinbaseTx; + } } 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 c6cff78fc17..3b7337928e0 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,22 +1,22 @@ package co.rsk.peg.bitcoin; +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 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; -import org.junit.jupiter.api.Test; +import org.ethereum.util.ByteUtil; +import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; 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(); @@ -33,7 +33,7 @@ private boolean isSigHashValid(Sha256Hash sigHash, List pubKeys, List< LinkedList keys = new LinkedList<>(pubKeys); LinkedList sigs = new LinkedList<>(signatures); - while (sigs.size() > 0){ + while (!sigs.isEmpty()){ BtcECKey pubKey = keys.pollFirst(); BtcECKey.ECDSASignature signature = sigs.getFirst(); if(pubKey.verify(sigHash, signature)){ @@ -73,21 +73,21 @@ void test_getFirstInputSigHash_pegout_or_migration(String btcTxHex) { TransactionInput txInput = btcTx.getInput(FIRST_INPUT_INDEX); Optional