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