diff --git a/rskj-core/src/integrationTest/java/co/rsk/cli/tools/CliToolsIntegrationTest.java b/rskj-core/src/integrationTest/java/co/rsk/cli/tools/CliToolsIntegrationTest.java index e7b939903b6..31fc9068c16 100644 --- a/rskj-core/src/integrationTest/java/co/rsk/cli/tools/CliToolsIntegrationTest.java +++ b/rskj-core/src/integrationTest/java/co/rsk/cli/tools/CliToolsIntegrationTest.java @@ -96,7 +96,8 @@ void setup() throws IOException { String.format("-Xrpc.providers.web.http.port=%s", port) }; strBaseArgs = String.join(" ", baseArgs); - baseJavaCmd = String.format("java %s %s", String.format("-Dlogback.configurationFile=%s", logbackXmlFile), String.format("-Drsk.conf.file=%s", rskConfFile)); + baseJavaCmd = String.format("java %s %s", String.format("-Dlogback.configurationFile=%s", logbackXmlFile), + String.format("-Drsk.conf.file=%s", rskConfFile)); } catch (Exception e) { // Ensure cleanup on setup failure cleanupTempFiles(); @@ -113,7 +114,8 @@ void whenExportBlocksRuns_shouldExportSpecifiedBlocks() throws Exception { blocksFile = createTempFile("blocks.txt"); - String cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.ExportBlocks --fromBlock 0 --toBlock %s --file %s %s", + String cmd = String.format( + "%s -cp %s/%s co.rsk.cli.tools.ExportBlocks --fromBlock 0 --toBlock %s --file %s %s", baseJavaCmd, buildLibsPath, jarName, blockNumber, blocksFile, strBaseArgs); executeCommand(cmd, 1, TimeUnit.MINUTES); @@ -126,7 +128,8 @@ void whenExportBlocksRuns_shouldExportSpecifiedBlocks() throws Exception { String[] exportedBlocksLineParts = exportedBlocksLine.split(","); Assertions.assertFalse(exportedBlocksLines.isEmpty()); - Assertions.assertTrue(blockInfo.transactionsNode.get(0).get("blockHash").asText().contains(exportedBlocksLineParts[1])); + Assertions.assertTrue( + blockInfo.transactionsNode.get(0).get("blockHash").asText().contains(exportedBlocksLineParts[1])); } finally { safeDeleteFile(blocksFile); } @@ -174,7 +177,8 @@ void whenShowStateInfoRuns_shouldShowSpecifiedState() throws Exception { List stateInfoLines = Arrays.asList(showStateInfoProc.getOutput().split("\\n")); Assertions.assertFalse(stateInfoLines.isEmpty()); - Assertions.assertTrue(stateInfoLines.stream().anyMatch(l -> l.contains(HexUtils.removeHexPrefix(blockInfo.blockHash)))); + Assertions.assertTrue( + stateInfoLines.stream().anyMatch(l -> l.contains(HexUtils.removeHexPrefix(blockInfo.blockHash)))); } @Test @@ -238,7 +242,8 @@ void whenImportBlocksRuns_shouldImportAllExportedBlocks() throws Exception { blocksFile = createTempFile("blocks.txt"); - String cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.ExportBlocks --fromBlock 0 --toBlock 20 --file %s %s", + String cmd = String.format( + "%s -cp %s/%s co.rsk.cli.tools.ExportBlocks --fromBlock 0 --toBlock 20 --file %s %s", baseJavaCmd, buildLibsPath, jarName, blocksFile, strBaseArgs); executeCommand(cmd, 1, TimeUnit.MINUTES); @@ -359,47 +364,58 @@ void whenRewindBlocksRuns_shouldRewindSpecifiedBlocks() throws Exception { @Test void whenDbMigrateRuns_shouldMigrateLevelDbToRocksDbAndShouldStartNodeWithPrevDbKind() throws Exception { - String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); - CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES, false); + String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, + strBaseArgs); + CommandLineFixture.CustomProcess startProc = executeCommand(cmd, 5, TimeUnit.MINUTES); + validateProcessOutput(startProc, "[minerserver]"); - cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.DbMigrate --targetDb rocksdb %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); - CommandLineFixture.CustomProcess dbMigrateProc = CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES); + cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.DbMigrate --targetDb rocksdb %s", baseJavaCmd, buildLibsPath, + jarName, strBaseArgs); + CommandLineFixture.CustomProcess dbMigrateProc = executeCommand(cmd, 5, TimeUnit.MINUTES); + validateProcessOutput(dbMigrateProc, "DbMigrate finished"); cmd = String.format("%s -cp %s/%s co.rsk.Start --regtest %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); - CommandLineFixture.CustomProcess proc = CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES, false); + CommandLineFixture.CustomProcess proc = CommandLineFixture.runCommand(cmd, 5, TimeUnit.MINUTES, false); + validateProcessOutput(proc, "[minerserver]"); List logLines = Arrays.asList(proc.getOutput().split("\\n")); - Assertions.assertTrue(dbMigrateProc.getOutput().contains("DbMigrate finished")); - Assertions.assertTrue(logLines.stream().anyMatch(l -> l.contains("[minerserver] [miner client] Mined block import result is IMPORTED_BEST"))); + Assertions.assertTrue(logLines.stream() + .anyMatch(l -> l.contains("[minerserver] [miner client] Mined block import result is IMPORTED_BEST"))); Assertions.assertTrue(logLines.stream().noneMatch(l -> l.contains("Exception:"))); } @Test void whenDbMigrateRuns_shouldMigrateLevelDbToRocksDbAndShouldStartNodeSuccessfully() throws Exception { - String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); - CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES); + String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, + strBaseArgs); + CommandLineFixture.CustomProcess startProc = executeCommand(cmd, 5, TimeUnit.MINUTES); + validateProcessOutput(startProc, "[minerserver]"); - cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.DbMigrate --targetDb rocksdb %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); - CommandLineFixture.CustomProcess dbMigrateProc = CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES); + cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.DbMigrate --targetDb rocksdb %s", baseJavaCmd, buildLibsPath, + jarName, strBaseArgs); + CommandLineFixture.CustomProcess dbMigrateProc = executeCommand(cmd, 5, TimeUnit.MINUTES); + validateProcessOutput(dbMigrateProc, "DbMigrate finished"); LinkedList args = Stream.of(baseArgs) .map(arg -> arg.equals("-Xkeyvalue.datasource=leveldb") ? "-Xkeyvalue.datasource=rocksdb" : arg) .collect(Collectors.toCollection(LinkedList::new)); - cmd = String.format("%s -cp %s/%s co.rsk.Start %s", baseJavaCmd, buildLibsPath, jarName, String.join(" ", args)); - CommandLineFixture.CustomProcess proc = CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES); + cmd = String.format("%s -cp %s/%s co.rsk.Start %s", baseJavaCmd, buildLibsPath, jarName, + String.join(" ", args)); + CommandLineFixture.CustomProcess proc = executeCommand(cmd, 5, TimeUnit.MINUTES); List logLines = Arrays.asList(proc.getOutput().split("\\n")); - Assertions.assertTrue(dbMigrateProc.getOutput().contains("DbMigrate finished")); - Assertions.assertTrue(logLines.stream().anyMatch(l -> l.contains("[minerserver] [miner client] Mined block import result is IMPORTED_BEST"))); + Assertions.assertTrue(logLines.stream() + .anyMatch(l -> l.contains("[minerserver] [miner client] Mined block import result is IMPORTED_BEST"))); Assertions.assertTrue(logLines.stream().noneMatch(l -> l.contains("Exception:"))); } @Test void whenStartBootstrapRuns_shouldRunSuccessfully() throws Exception { - String cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.StartBootstrap --reset %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); + String cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.StartBootstrap --reset %s", baseJavaCmd, + buildLibsPath, jarName, strBaseArgs); CommandLineFixture.CustomProcess proc = CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES); Assertions.assertTrue(proc.getOutput().contains("Identified public IP")); @@ -408,12 +424,14 @@ void whenStartBootstrapRuns_shouldRunSuccessfully() throws Exception { @Test void whenIndexBloomsRuns_shouldIndexBlockRangeSInBLoomsDbSuccessfully() throws Exception { - String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); + String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, + strBaseArgs); CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES); FileUtil.recursiveDelete(bloomsDbDir); - cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.IndexBlooms -fb %s -tb %s %s", baseJavaCmd, buildLibsPath, jarName, "earliest", "latest", strBaseArgs); + cmd = String.format("%s -cp %s/%s co.rsk.cli.tools.IndexBlooms -fb %s -tb %s %s", baseJavaCmd, buildLibsPath, + jarName, "earliest", "latest", strBaseArgs); CommandLineFixture.CustomProcess proc = CommandLineFixture.runCommand(cmd, 1, TimeUnit.MINUTES); Assertions.assertTrue(proc.getOutput().contains("[c.r.c.t.IndexBlooms] [main] Processed ")); @@ -444,7 +462,8 @@ private Path createFileWithRetry(Path filePath) throws IOException { } } catch (IOException e) { if (attempt == maxRetries - 1) { - throw new IOException("Failed to create file " + filePath + " after " + maxRetries + " attempts", e); + throw new IOException("Failed to create file " + filePath + " after " + maxRetries + " attempts", + e); } try { Thread.sleep(retryDelayMs * (attempt + 1)); @@ -459,7 +478,8 @@ private Path createFileWithRetry(Path filePath) throws IOException { private void writeToFile(Path filePath, String content) throws IOException { try { - Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); + Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE); } catch (IOException e) { throw new IOException("Failed to write to file " + filePath, e); } @@ -488,7 +508,8 @@ private void safeDeleteFile(Path filePath) { } catch (IOException e) { if (attempt == maxRetries - 1) { // Log warning but don't fail the test - System.err.println("Warning: Failed to delete file " + filePath + " after " + maxRetries + " attempts: " + e.getMessage()); + System.err.println("Warning: Failed to delete file " + filePath + " after " + maxRetries + + " attempts: " + e.getMessage()); } else { try { Thread.sleep(retryDelayMs * (attempt + 1)); @@ -566,7 +587,8 @@ private void closeRskContext(RskContext rskContext) { /** * Executes a command and waits for it to complete */ - private CommandLineFixture.CustomProcess executeCommand(String cmd, int timeout, TimeUnit timeUnit) throws InterruptedException, IOException { + private CommandLineFixture.CustomProcess executeCommand(String cmd, int timeout, TimeUnit timeUnit) + throws InterruptedException, IOException { return CommandLineFixture.runCommand(cmd, timeout, timeUnit); } @@ -582,7 +604,8 @@ private void executeCommandWithCallback(String cmd, int timeout, TimeUnit timeUn * Starts RSK node and waits for it to be ready */ private void startRskNode() throws Exception { - String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); + String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, + strBaseArgs); executeCommand(cmd, 1, TimeUnit.MINUTES); } @@ -590,7 +613,8 @@ private void startRskNode() throws Exception { * Starts RSK node and waits for it to be ready with callback */ private void startRskNodeWithCallback(java.util.function.Consumer callback) throws Exception { - String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, strBaseArgs); + String cmd = String.format("%s -cp %s/%s co.rsk.Start --reset %s", baseJavaCmd, buildLibsPath, jarName, + strBaseArgs); executeCommandWithCallback(cmd, 1, TimeUnit.MINUTES, callback); } diff --git a/rskj-core/src/main/java/co/rsk/core/RskAddress.java b/rskj-core/src/main/java/co/rsk/core/RskAddress.java index 90aec5e1e8d..ca4389ff010 100644 --- a/rskj-core/src/main/java/co/rsk/core/RskAddress.java +++ b/rskj-core/src/main/java/co/rsk/core/RskAddress.java @@ -19,10 +19,8 @@ package co.rsk.core; import co.rsk.core.exception.InvalidRskAddressException; -import com.google.common.primitives.UnsignedBytes; - import co.rsk.util.HexUtils; - +import com.google.common.primitives.UnsignedBytes; import org.ethereum.util.ByteUtil; import org.ethereum.vm.DataWord; @@ -41,18 +39,15 @@ public class RskAddress { * This is the size of an RSK address in bytes. */ public static final int LENGTH_IN_BYTES = 20; - - private static final RskAddress NULL_ADDRESS = new RskAddress(); // public static final RskAddress ZERO_ADDRESS = new RskAddress("0000000000000000000000000000000000000000"); - /** * This compares using the lexicographical order of the sender unsigned bytes. */ public static final Comparator LEXICOGRAPHICAL_COMPARATOR = Comparator.comparing( RskAddress::getBytes, UnsignedBytes.lexicographicalComparator()); - + private static final RskAddress NULL_ADDRESS = new RskAddress(); private final byte[] bytes; /** @@ -77,7 +72,7 @@ public RskAddress(byte[] bytes) { throw new InvalidRskAddressException(String.format("An RSK address must be %d bytes long", LENGTH_IN_BYTES)); } - this.bytes = bytes; + this.bytes = Arrays.copyOf(bytes, bytes.length); } /** @@ -95,7 +90,7 @@ public static RskAddress nullAddress() { } public byte[] getBytes() { - return bytes; + return Arrays.copyOf(bytes, bytes.length); } public String toHexString() { diff --git a/rskj-core/src/main/java/co/rsk/core/bc/BlockExecutor.java b/rskj-core/src/main/java/co/rsk/core/bc/BlockExecutor.java index 2c8b9a66e4a..2c4a4975312 100644 --- a/rskj-core/src/main/java/co/rsk/core/bc/BlockExecutor.java +++ b/rskj-core/src/main/java/co/rsk/core/bc/BlockExecutor.java @@ -15,7 +15,6 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package co.rsk.core.bc; import co.rsk.config.RskSystemProperties; @@ -29,12 +28,18 @@ import co.rsk.metrics.profilers.MetricKind; import co.rsk.metrics.profilers.Profiler; import co.rsk.metrics.profilers.ProfilerFactory; +import co.rsk.peg.union.UnionBridgeStorageIndexKey; import com.google.common.annotations.VisibleForTesting; import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; -import org.ethereum.core.*; -import org.ethereum.util.ByteUtil; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.Bloom; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionExecutor; +import org.ethereum.core.TransactionReceipt; import org.ethereum.vm.DataWord; import org.ethereum.vm.LogInfo; import org.ethereum.vm.PrecompiledContracts; @@ -45,12 +50,34 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP126; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP85; +import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.ethereum.util.ByteUtil.toHexString; /** * This is a stateless class with methods to execute blocks with its transactions. @@ -71,9 +98,6 @@ public class BlockExecutor { private final Set concurrentContractsDisallowed; private final Map transactionResults = new ConcurrentHashMap<>(); - private boolean registerProgramResults; - private long minSequentialSetGasLimit; - /** * An array of ExecutorService's of size `Constants.getTransactionExecutionThreads()`. Each parallel list uses an executor * at specific index of this array, so that threads chosen by thread pools cannot be "reused" for executing parallel @@ -81,6 +105,8 @@ public class BlockExecutor { * on some circumstances. */ private final ExecutorService[] execServices; + private final long minSequentialSetGasLimit; + private boolean registerProgramResults; public BlockExecutor( RepositoryLocator repositoryLocator, @@ -174,11 +200,33 @@ private void fill(Block block, BlockResult result) { header.setPaidFees(result.getPaidFees()); header.setLogsBloom(calculateLogsBloom(result.getTransactionReceipts())); header.setTxExecutionSublistsEdges(result.getTxEdges()); + setBaseEventIfRskip535IsActive(block, header); block.flushRLP(); profiler.stop(metric); } + private void setBaseEventIfRskip535IsActive(Block block, BlockHeader header) { + if (block != null && header != null && activationConfig.isActive(ConsensusRule.RSKIP535, block.getNumber())) { + try { + Repository repo = repositoryLocator.startTrackingAt(header); + if (repo != null) { + RskAddress address = PrecompiledContracts.BRIDGE_ADDR; + DataWord key = UnionBridgeStorageIndexKey.BASE_EVENT.getKey(); + byte[] baseEvent = repo.getStorageBytes(address, key); + if (baseEvent == null) { + baseEvent = EMPTY_BYTE_ARRAY; + } + header.setBaseEvent(baseEvent); + } + } catch (Exception e) { + // If repository access fails, just skip setting baseEvent + // This can happen in test environments or when repository is not available + logger.warn("Failed to set baseEvent in block header. {}", e.getMessage()); + } + } + } + /** * Execute and validate the final state of a block. * @@ -295,11 +343,11 @@ public BlockResult executeForMining(Block block, BlockHeader parent, boolean dis * Execute a block while saving the execution trace in the trace processor */ public BlockResult traceBlock(ProgramTraceProcessor programTraceProcessor, - int vmTraceOptions, - Block block, - BlockHeader parent, - boolean discardInvalidTxs, - boolean ignoreReadyToExecute) { + int vmTraceOptions, + Block block, + BlockHeader parent, + boolean discardInvalidTxs, + boolean ignoreReadyToExecute) { return execute(Objects.requireNonNull(programTraceProcessor), vmTraceOptions, block, parent, discardInvalidTxs, ignoreReadyToExecute, false); } @@ -523,7 +571,7 @@ public void execute(@Nonnull Runnable command) { // Review collision if (readWrittenKeysTracker.detectCollision()) { - logger.warn("block: [{}]/[{}] execution failed. Block data: [{}]", block.getNumber(), block.getHash(), ByteUtil.toHexString(block.getEncoded())); + logger.warn("block: [{}]/[{}] execution failed. Block data: [{}]", block.getNumber(), block.getHash(), toHexString(block.getEncoded())); profiler.stop(metric); return BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT; } @@ -823,7 +871,7 @@ private TransactionReceipt buildTransactionReceipt(Transaction tx, TransactionEx receipt.setTxStatus(txExecutor.getReceipt().isSuccessful()); receipt.setTransaction(tx); List logs = txExecutor.getVMLogs(); - if(logs!= null) { + if (logs != null) { for (int i = 0; i < logs.size(); i++) { LogInfo log = logs.get(i); log.setLogIndex(i + logIndexOffset); @@ -837,7 +885,7 @@ private TransactionReceipt buildTransactionReceipt(Transaction tx, TransactionEx private BlockResult getBlockResultAndLogExecutionInterrupted(Block block, Metric metric, Transaction tx) { logger.warn("block: [{}]/[{}] execution interrupted because of invalid tx: [{}]", - block.getNumber(), block.getHash(), tx.getHash()); + block.getNumber(), block.getHash(), tx.getHash()); profiler.stop(metric); return BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT; } @@ -881,7 +929,7 @@ public void setRegisterProgramResults(boolean value) { * its core pool size is zero and maximum pool size is unbounded, while each thead created has keep-alive time - 15 mins. */ private static final class ThreadPoolExecutorImpl extends ThreadPoolExecutor { - private static final long KEEP_ALIVE_TIME_IN_SECS = 15*60L; /* 15 minutes */ + private static final long KEEP_ALIVE_TIME_IN_SECS = 15 * 60L; /* 15 minutes */ public ThreadPoolExecutorImpl(int parallelListIndex) { super(0, Integer.MAX_VALUE, diff --git a/rskj-core/src/main/java/co/rsk/core/bc/TransactionPoolImpl.java b/rskj-core/src/main/java/co/rsk/core/bc/TransactionPoolImpl.java index f2cbb2f3ecf..7c48c843e09 100644 --- a/rskj-core/src/main/java/co/rsk/core/bc/TransactionPoolImpl.java +++ b/rskj-core/src/main/java/co/rsk/core/bc/TransactionPoolImpl.java @@ -457,7 +457,14 @@ private long getCurrentBestBlockNumber() { private Block createFakePendingBlock(Block best) { // creating fake lightweight calculated block with no hashes calculations - return blockFactory.newBlock(blockFactory.getBlockHeaderBuilder().setParentHash(best.getHash().getBytes()).setDifficulty(best.getDifficulty()).setNumber(best.getNumber() + 1).setGasLimit(ByteUtil.longToBytesNoLeadZeroes(Long.MAX_VALUE)).setTimestamp(best.getTimestamp() + 1).build(), Collections.emptyList(), Collections.emptyList()); + return blockFactory.newBlock(blockFactory.getBlockHeaderBuilder() + .setParentHash(best.getHash().getBytes()) + .setDifficulty(best.getDifficulty()) + .setNumber(best.getNumber() + 1) + .setGasLimit(ByteUtil.longToBytesNoLeadZeroes(Long.MAX_VALUE)) + .setTimestamp(best.getTimestamp() + 1) + .build(), + Collections.emptyList(), Collections.emptyList()); } private TransactionValidationResult shouldAcceptTx(Transaction tx, RepositorySnapshot currentRepository) { diff --git a/rskj-core/src/main/java/co/rsk/rpc/modules/eth/subscribe/BlockHeaderNotification.java b/rskj-core/src/main/java/co/rsk/rpc/modules/eth/subscribe/BlockHeaderNotification.java index 42d9df4ca0f..4d52b6ff2ea 100644 --- a/rskj-core/src/main/java/co/rsk/rpc/modules/eth/subscribe/BlockHeaderNotification.java +++ b/rskj-core/src/main/java/co/rsk/rpc/modules/eth/subscribe/BlockHeaderNotification.java @@ -40,6 +40,7 @@ public class BlockHeaderNotification implements EthSubscriptionNotificationDTO { private final String timestamp; private final String transactionsRoot; private final String hash; + private final String baseEvent; public BlockHeaderNotification(Block block) { difficulty = HexUtils.toQuantityJsonHex(block.getDifficulty().getBytes()); @@ -56,6 +57,7 @@ public BlockHeaderNotification(Block block) { timestamp = HexUtils.toQuantityJsonHex(block.getTimestamp()); transactionsRoot = HexUtils.toJsonHex(block.getTxTrieRoot()); hash = block.getHashJsonString(); + baseEvent = HexUtils.toJsonHex(block.getHeader().getBaseEvent()); } public String getDifficulty() { @@ -113,4 +115,8 @@ public String getTransactionsRoot() { public String getHash() { return hash; } + + public String getBaseEvent() { + return baseEvent; + } } diff --git a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java index 0874729cf4c..4ad5ab4804d 100644 --- a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java +++ b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ActivationConfig.java @@ -15,7 +15,6 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package org.ethereum.config.blockchain.upgrades; import com.google.common.annotations.VisibleForTesting; @@ -23,7 +22,12 @@ import com.typesafe.config.ConfigException.WrongType; import com.typesafe.config.ConfigValue; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; public class ActivationConfig { @@ -43,8 +47,8 @@ public ActivationConfig(Map activationHeights, Map missing = new ArrayList<>(Arrays.asList(ConsensusRule.values())); missing.removeAll(activationHeights.keySet()); throw new IllegalArgumentException(String.format( - "The configuration must contain all consensus rule values but is missing [%s]", - missing.stream().map(ConsensusRule::getConfigKey).collect(Collectors.joining(", ")) + "The configuration must contain all consensus rule values but is missing [%s]", + missing.stream().map(ConsensusRule::getConfigKey).collect(Collectors.joining(", ")) )); } @@ -52,37 +56,6 @@ public ActivationConfig(Map activationHeights, Map networkUpgrades = new EnumMap<>(NetworkUpgrade.class); Config networkUpgradesConfig = config.getConfig(PROPERTY_ACTIVATION_HEIGHTS); @@ -121,6 +94,37 @@ private static long parseActivationHeight( } } + public byte getHeaderVersion(long blockNumber) { + if (this.isActive(ConsensusRule.RSKIP351, blockNumber)) { + return (byte) (this.isActive(ConsensusRule.RSKIP535, blockNumber) ? 0x2 : 0x1); + } + + return 0x0; + } + + public boolean isActive(ConsensusRule consensusRule, long blockNumber) { + long activationHeight = activationHeights.get(consensusRule); + return 0 <= activationHeight && activationHeight <= blockNumber; + } + + public boolean containsNetworkUpgrade(NetworkUpgrade networkUpgrade) { + return networkUpgrades.containsKey(networkUpgrade); + } + + public boolean isActive(NetworkUpgrade networkUpgrade, long blockNumber) { + long activationHeight = networkUpgrades.get(networkUpgrade); + return 0 <= activationHeight && activationHeight <= blockNumber; + } + + private boolean isActivating(ConsensusRule consensusRule, long blockNumber) { + long activationHeight = activationHeights.get(consensusRule); + return activationHeight == blockNumber; + } + + public ForBlock forBlock(long blockNumber) { + return new ForBlock(blockNumber); + } + public class ForBlock { private final long blockNumber; 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 a1c7c3baab1..357628e250d 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 @@ -110,6 +110,7 @@ public enum ConsensusRule { RSKIP502("rskip502"), // PowPeg and Union Bridge integration RSKIP516("rskip516"), // Addition of precompiled contracts for add and mul operations on the secp256k1 curve RSKIP529("rskip529"), // New storage cells in Bridge native contract for base and super events info + RSKIP535("rskip535"), // BaseEvent for the union bridge RSKIP536("rskip536"), // Additional methods for BlockHeader precompiled contract ; diff --git a/rskj-core/src/main/java/org/ethereum/core/Block.java b/rskj-core/src/main/java/org/ethereum/core/Block.java index 627662d0519..191ddd4c3d1 100644 --- a/rskj-core/src/main/java/org/ethereum/core/Block.java +++ b/rskj-core/src/main/java/org/ethereum/core/Block.java @@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableList; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.BigIntegers; +import org.ethereum.core.exception.SealedBlockException; import org.ethereum.util.RLP; import javax.annotation.Nonnull; @@ -51,11 +52,11 @@ public class Block { private static final PanicProcessor panicProcessor = new PanicProcessor(); - private BlockHeader header; + private final BlockHeader header; private List transactionsList; - private List uncleList; + private final List uncleList; /* Private */ private byte[] rlpEncoded; @@ -75,9 +76,7 @@ private Block(BlockHeader header, List transactionsList, List. */ - package org.ethereum.core; import co.rsk.config.MiningConfig; @@ -39,15 +38,36 @@ import static org.ethereum.crypto.HashUtil.EMPTY_TRIE_HASH; public class BlockFactory { - private static final int RLP_HEADER_SIZE = 19; - private static final int RLP_HEADER_SIZE_WITH_MERGED_MINING = 22; - + public static final int NUMBER_OF_EXTRA_HEADER_FIELDS = 3; + private static final int RLP_HEADER_SIZE = 20; + private static final int RLP_HEADER_SIZE_WITH_MERGED_MINING = 23; private final ActivationConfig activationConfig; public BlockFactory(ActivationConfig activationConfig) { this.activationConfig = activationConfig; } + private static BigInteger parseBigInteger(byte[] bytes) { + return bytes == null ? BigInteger.ZERO : BigIntegers.fromUnsignedByteArray(bytes); + } + + private static List parseTxs(RLPList txTransactions) { + List parsedTxs = new ArrayList<>(); + + for (int i = 0; i < txTransactions.size(); i++) { + RLPElement transactionRaw = txTransactions.get(i); + Transaction tx = new ImmutableTransaction(transactionRaw.getRLPData()); + + if (tx.isRemascTransaction(i, txTransactions.size())) { + // It is the remasc transaction + tx = new RemascTransaction(transactionRaw.getRLPData()); + } + parsedTxs.add(tx); + } + + return Collections.unmodifiableList(parsedTxs); + } + public BlockHeaderBuilder getBlockHeaderBuilder() { return new BlockHeaderBuilder(activationConfig); } @@ -62,7 +82,7 @@ public Block decodeBlock(byte[] rawData) { private Block decodeBlock(byte[] rawData, boolean sealed) { RLPList block = RLP.decodeList(rawData); - if (block.size() != 3) { + if (block.size() != NUMBER_OF_EXTRA_HEADER_FIELDS) { throw new IllegalArgumentException("A block must have 3 exactly items"); } @@ -77,7 +97,7 @@ private Block decodeBlock(byte[] rawData, boolean sealed) { for (int k = 0; k < uncleHeadersRlp.size(); k++) { RLPElement element = uncleHeadersRlp.get(k); - BlockHeader uncleHeader = decodeHeader((RLPList)element, false, sealed); + BlockHeader uncleHeader = decodeHeader((RLPList) element, false, sealed); uncleList.add(uncleHeader); } @@ -102,7 +122,7 @@ private BlockHeader decodeHeader(RLPList rlpHeader, boolean compressed, boolean byte[] unclesHash = rlpHeader.get(1).getRLPData(); byte[] coinBaseBytes = rlpHeader.get(2).getRLPData(); RskAddress coinbase = RLP.parseRskAddress(coinBaseBytes); - byte[] stateRoot = rlpHeader.get(3).getRLPData(); + byte[] stateRoot = rlpHeader.get(NUMBER_OF_EXTRA_HEADER_FIELDS).getRLPData(); if (stateRoot == null) { stateRoot = EMPTY_TRIE_HASH; } @@ -163,10 +183,16 @@ private BlockHeader decodeHeader(RLPList rlpHeader, boolean compressed, boolean } short[] txExecutionSublistsEdges = null; + byte[] baseEvent = null; + + if (!activationConfig.isActive(ConsensusRule.RSKIP351, blockNumber) || !compressed) { + if (rlpHeader.size() > r && activationConfig.isActive(ConsensusRule.RSKIP144, blockNumber)) { + txExecutionSublistsEdges = ByteUtil.rlpToShorts(rlpHeader.get(r++).getRLPRawData()); + } - if ((!activationConfig.isActive(ConsensusRule.RSKIP351, blockNumber) || !compressed) && - (rlpHeader.size() > r && activationConfig.isActive(ConsensusRule.RSKIP144, blockNumber))) { - txExecutionSublistsEdges = ByteUtil.rlpToShorts(rlpHeader.get(r++).getRLPRawData()); + if (rlpHeader.size() > r && activationConfig.isActive(ConsensusRule.RSKIP535, blockNumber)) { + baseEvent = rlpHeader.get(r++).getRLPRawData(); + } } byte[] bitcoinMergedMiningHeader = null; @@ -175,7 +201,7 @@ private BlockHeader decodeHeader(RLPList rlpHeader, boolean compressed, boolean if (rlpHeader.size() > r) { bitcoinMergedMiningHeader = rlpHeader.get(r++).getRLPData(); bitcoinMergedMiningMerkleProof = rlpHeader.get(r++).getRLPRawData(); - bitcoinMergedMiningCoinbaseTransaction = rlpHeader.get(r++).getRLPData(); + bitcoinMergedMiningCoinbaseTransaction = rlpHeader.get(r).getRLPData(); } boolean useRskip92Encoding = activationConfig.isActive(ConsensusRule.RSKIP92, blockNumber); @@ -185,7 +211,7 @@ private BlockHeader decodeHeader(RLPList rlpHeader, boolean compressed, boolean return createBlockHeader(compressed, sealed, parentHash, unclesHash, coinBaseBytes, coinbase, stateRoot, txTrieRoot, receiptTrieRoot, extensionData, difficultyBytes, difficulty, glBytes, blockNumber, gasUsed, timestamp, extraData, - paidFees, minimumGasPriceBytes, minimumGasPrice, uncleCount, ummRoot, version, txExecutionSublistsEdges, + paidFees, minimumGasPriceBytes, minimumGasPrice, uncleCount, ummRoot, baseEvent, version, txExecutionSublistsEdges, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, bitcoinMergedMiningCoinbaseTransaction, useRskip92Encoding, includeForkDetectionData); } @@ -193,7 +219,7 @@ private BlockHeader decodeHeader(RLPList rlpHeader, boolean compressed, boolean private BlockHeader createBlockHeader(boolean compressed, boolean sealed, byte[] parentHash, byte[] unclesHash, byte[] coinBaseBytes, RskAddress coinbase, byte[] stateRoot, byte[] txTrieRoot, byte[] receiptTrieRoot, byte[] extensionData, byte[] difficultyBytes, BlockDifficulty difficulty, byte[] glBytes, long blockNumber, long gasUsed, long timestamp, byte[] extraData, - Coin paidFees, byte[] minimumGasPriceBytes, Coin minimumGasPrice, int uncleCount, byte[] ummRoot, byte version, short[] txExecutionSublistsEdges, + Coin paidFees, byte[] minimumGasPriceBytes, Coin minimumGasPrice, int uncleCount, byte[] ummRoot, byte[] baseEvent, byte version, short[] txExecutionSublistsEdges, byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, byte[] bitcoinMergedMiningCoinbaseTransaction, boolean useRskip92Encoding, boolean includeForkDetectionData) { if (blockNumber == Genesis.NUMBER) { @@ -216,6 +242,18 @@ private BlockHeader createBlockHeader(boolean compressed, boolean sealed, byte[] stateRoot); } + if (version == 2) { + return new BlockHeaderV2( + parentHash, unclesHash, coinbase, stateRoot, + txTrieRoot, receiptTrieRoot, extensionData, difficulty, + blockNumber, glBytes, gasUsed, timestamp, extraData, + paidFees, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, + bitcoinMergedMiningCoinbaseTransaction, new byte[0], + minimumGasPrice, uncleCount, sealed, useRskip92Encoding, includeForkDetectionData, + ummRoot, baseEvent, txExecutionSublistsEdges, compressed + ); + } + if (version == 1) { return new BlockHeaderV1( parentHash, unclesHash, coinbase, stateRoot, @@ -235,51 +273,32 @@ private BlockHeader createBlockHeader(boolean compressed, boolean sealed, byte[] paidFees, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, bitcoinMergedMiningCoinbaseTransaction, new byte[0], minimumGasPrice, uncleCount, sealed, useRskip92Encoding, includeForkDetectionData, - ummRoot, txExecutionSublistsEdges + ummRoot, baseEvent, txExecutionSublistsEdges ); } private boolean canBeDecoded(RLPList rlpHeader, long blockNumber, boolean compressed) { int preUmmHeaderSizeAdjustment = activationConfig.isActive(ConsensusRule.RSKIPUMM, blockNumber) ? 0 : 1; int preParallelSizeAdjustment = activationConfig.isActive(ConsensusRule.RSKIP144, blockNumber) ? 0 : 1; - int preRSKIP351SizeAdjustment = getRSKIP351SizeAdjustment(blockNumber, compressed, preParallelSizeAdjustment); + int prebaseEventSizeAdjustmentRSKIP535 = activationConfig.isActive(ConsensusRule.RSKIP535, blockNumber) ? 0 : 1; + int preRSKIP351SizeAdjustment = getRSKIP351SizeAdjustment(blockNumber, compressed, preParallelSizeAdjustment, prebaseEventSizeAdjustmentRSKIP535); - int expectedSize = RLP_HEADER_SIZE - preUmmHeaderSizeAdjustment - preParallelSizeAdjustment - preRSKIP351SizeAdjustment; - int expectedSizeMM = RLP_HEADER_SIZE_WITH_MERGED_MINING - preUmmHeaderSizeAdjustment - preParallelSizeAdjustment - preRSKIP351SizeAdjustment; + int expectedSize = RLP_HEADER_SIZE - preUmmHeaderSizeAdjustment - preParallelSizeAdjustment - preRSKIP351SizeAdjustment - prebaseEventSizeAdjustmentRSKIP535; + int expectedSizeMM = RLP_HEADER_SIZE_WITH_MERGED_MINING - preUmmHeaderSizeAdjustment - preParallelSizeAdjustment - preRSKIP351SizeAdjustment - prebaseEventSizeAdjustmentRSKIP535; return rlpHeader.size() == expectedSize || rlpHeader.size() == expectedSizeMM; } - private int getRSKIP351SizeAdjustment(long blockNumber, boolean compressed, int preParallelSizeAdjustment) { + private int getRSKIP351SizeAdjustment(long blockNumber, boolean compressed, int preParallelSizeAdjustment, + int preBaseEventSizeAdjustmentRSKIP535) { if (!activationConfig.isActive(ConsensusRule.RSKIP351, blockNumber)) { return 1; // remove version } if (compressed) { - return 2 - preParallelSizeAdjustment; // remove version and edges if existent + return NUMBER_OF_EXTRA_HEADER_FIELDS - preParallelSizeAdjustment - preBaseEventSizeAdjustmentRSKIP535; // remove version, edges and baseEvent size if existent } return 0; } - - private static BigInteger parseBigInteger(byte[] bytes) { - return bytes == null ? BigInteger.ZERO : BigIntegers.fromUnsignedByteArray(bytes); - } - - private static List parseTxs(RLPList txTransactions) { - List parsedTxs = new ArrayList<>(); - - for (int i = 0; i < txTransactions.size(); i++) { - RLPElement transactionRaw = txTransactions.get(i); - Transaction tx = new ImmutableTransaction(transactionRaw.getRLPData()); - - if (tx.isRemascTransaction(i, txTransactions.size())) { - // It is the remasc transaction - tx = new RemascTransaction(transactionRaw.getRLPData()); - } - parsedTxs.add(tx); - } - - return Collections.unmodifiableList(parsedTxs); - } } diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeader.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeader.java index dd3b4c69361..b014bdc40bd 100644 --- a/rskj-core/src/main/java/org/ethereum/core/BlockHeader.java +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeader.java @@ -26,6 +26,7 @@ import co.rsk.util.ListArrayUtil; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; +import org.ethereum.core.exception.SealedBlockHeaderException; import org.ethereum.crypto.HashUtil; import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; @@ -54,6 +55,8 @@ public abstract class BlockHeader { public abstract void setLogsBloom(byte[] logsBloom); public abstract short[] getTxExecutionSublistsEdges(); // Edges of the transaction execution lists public abstract void setTxExecutionSublistsEdges(short[] edges); + public abstract byte[] getBaseEvent(); + public abstract void setBaseEvent(byte[] baseEvent); // called after encoding the header, used to add elements at the end public abstract void addExtraFieldsToEncodedHeader(boolean usingCompressedEncoding, List fieldsToEncode); @@ -394,13 +397,20 @@ public byte[] getEncoded(boolean withMergedMiningFields, boolean withMerkleProof return RLP.encodeList(fieldToEncodeList.toArray(new byte[][]{})); } - public void addTxExecutionSublistsEdgesIfAny(List fieldsToEncode) { + protected void addTxExecutionSublistsEdgesIfAny(List fieldsToEncode) { short[] txExecutionSublistsEdges = this.getTxExecutionSublistsEdges(); if (txExecutionSublistsEdges != null) { fieldsToEncode.add(ByteUtil.shortsToRLP(txExecutionSublistsEdges)); } } + protected void addBaseEvent(List fieldsToEncode) { + byte[] baseEvent = this.getBaseEvent(); + if (baseEvent != null) { + fieldsToEncode.add(RLP.encodeElement(baseEvent)); + } + } + /** * This is here to override specific non-minimal instances such as the mainnet Genesis */ @@ -465,6 +475,7 @@ private String toStringWithSuffix(final String suffix) { toStringBuff.append(" extraData=").append(toHexStringOrEmpty(extraData)).append(suffix); toStringBuff.append(" minGasPrice=").append(minimumGasPrice).append(suffix); toStringBuff.append(" txExecutionSublistsEdges=").append(Arrays.toString(this.getTxExecutionSublistsEdges())).append(suffix); + toStringBuff.append(" baseEvent=").append(toHexStringOrEmpty(this.getBaseEvent())).append(suffix); return toStringBuff.toString(); } diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderBuilder.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderBuilder.java index 61aeb871d05..5029a1d5bb8 100644 --- a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderBuilder.java +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderBuilder.java @@ -15,7 +15,6 @@ * 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.BlockDifficulty; @@ -32,11 +31,12 @@ import java.util.Arrays; import static org.ethereum.crypto.HashUtil.EMPTY_TRIE_HASH; +import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; public class BlockHeaderBuilder { private static final byte[] EMPTY_LIST_HASH = HashUtil.keccak256(RLP.encodeList()); - + private final ActivationConfig activationConfig; private byte[] parentHash; private byte[] unclesHash; private RskAddress coinbase; @@ -50,23 +50,18 @@ public class BlockHeaderBuilder { private byte[] gasLimit; private long gasUsed; private Coin paidFees; - private byte[] extraData; private byte[] bitcoinMergedMiningHeader; private byte[] bitcoinMergedMiningMerkleProof; private byte[] bitcoinMergedMiningCoinbaseTransaction; private byte[] mergedMiningForkDetectionData; private byte[] ummRoot; + private byte[] baseEvent; private short[] txExecutionSublistsEdges; - private Coin minimumGasPrice; private int uncleCount; - private boolean useRskip92Encoding; private boolean includeForkDetectionData; - - private final ActivationConfig activationConfig; - private boolean createConsensusCompliantHeader; private boolean createUmmCompliantHeader; private boolean createParallelCompliantHeader; @@ -264,6 +259,11 @@ public BlockHeaderBuilder setUmmRoot(byte[] ummRoot) { return this; } + public BlockHeaderBuilder setBaseEvent(byte[] baseEvent) { + this.baseEvent = copy(baseEvent, null); + return this; + } + public BlockHeaderBuilder setTxExecutionSublistsEdges(short[] edges) { if (edges != null) { this.txExecutionSublistsEdges = new short[edges.length]; @@ -326,19 +326,26 @@ public BlockHeader build() { } if (createUmmCompliantHeader) { - if (activationConfig.isActive(ConsensusRule.RSKIPUMM, number)) { - if (ummRoot == null) { - ummRoot = new byte[0]; - } + if (activationConfig.isActive(ConsensusRule.RSKIPUMM, number) && ummRoot == null) { + ummRoot = EMPTY_BYTE_ARRAY; } } + if (activationConfig.isActive(ConsensusRule.RSKIP144, number) && createParallelCompliantHeader && txExecutionSublistsEdges == null) { txExecutionSublistsEdges = new short[0]; } - if (activationConfig.getHeaderVersion(number) == 0x1) { - return new BlockHeaderV1( + if (activationConfig.isActive(ConsensusRule.RSKIP535, number)) { + if (baseEvent == null) { + baseEvent = EMPTY_BYTE_ARRAY; + } + } + + byte version = activationConfig.getHeaderVersion(number); + + return switch (version) { + case 0x2 -> new BlockHeaderV2( parentHash, unclesHash, coinbase, stateRoot, txTrieRoot, receiptTrieRoot, logsBloom, difficulty, number, @@ -349,22 +356,33 @@ public BlockHeader build() { mergedMiningForkDetectionData, minimumGasPrice, uncleCount, false, useRskip92Encoding, - includeForkDetectionData, ummRoot, txExecutionSublistsEdges, false - ); - } + includeForkDetectionData, ummRoot, baseEvent, txExecutionSublistsEdges, false); + + case 0x1 -> new BlockHeaderV1( + parentHash, unclesHash, coinbase, + stateRoot, txTrieRoot, receiptTrieRoot, + logsBloom, difficulty, number, + gasLimit, gasUsed, timestamp, extraData, paidFees, + bitcoinMergedMiningHeader, + bitcoinMergedMiningMerkleProof, + bitcoinMergedMiningCoinbaseTransaction, + mergedMiningForkDetectionData, + minimumGasPrice, uncleCount, + false, useRskip92Encoding, + includeForkDetectionData, ummRoot, txExecutionSublistsEdges, false); - return new BlockHeaderV0( - parentHash, unclesHash, coinbase, - stateRoot, txTrieRoot, receiptTrieRoot, - logsBloom, difficulty, number, - gasLimit, gasUsed, timestamp, extraData, paidFees, - bitcoinMergedMiningHeader, - bitcoinMergedMiningMerkleProof, - bitcoinMergedMiningCoinbaseTransaction, - mergedMiningForkDetectionData, - minimumGasPrice, uncleCount, - false, useRskip92Encoding, - includeForkDetectionData, ummRoot, txExecutionSublistsEdges - ); - } -} \ No newline at end of file + default -> new BlockHeaderV0( + parentHash, unclesHash, coinbase, + stateRoot, txTrieRoot, receiptTrieRoot, + logsBloom, difficulty, number, + gasLimit, gasUsed, timestamp, extraData, paidFees, + bitcoinMergedMiningHeader, + bitcoinMergedMiningMerkleProof, + bitcoinMergedMiningCoinbaseTransaction, + mergedMiningForkDetectionData, + minimumGasPrice, uncleCount, + false, useRskip92Encoding, + includeForkDetectionData, ummRoot, baseEvent, txExecutionSublistsEdges); + }; + } +} diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtension.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtension.java index b087e4f4afb..9f94cb5c5b3 100644 --- a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtension.java +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtension.java @@ -1,3 +1,21 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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 org.ethereum.util.RLP; @@ -6,13 +24,16 @@ import java.util.Objects; public interface BlockHeaderExtension { - byte[] getEncoded(); - byte[] getHash(); - static byte[] toEncoded(BlockHeaderExtension extension) { if (!(Objects.requireNonNull(extension) instanceof BlockHeaderExtensionV1)) { throw new IllegalArgumentException("Unknown extension"); } + if (extension instanceof BlockHeaderExtensionV2) { + return RLP.encodeList( + RLP.encodeByte((byte) 0x2), + RLP.encodeElement(extension.getEncoded()) + ); + } return RLP.encodeList( RLP.encodeByte((byte) 0x1), RLP.encodeElement(extension.getEncoded()) @@ -21,11 +42,23 @@ static byte[] toEncoded(BlockHeaderExtension extension) { static BlockHeaderExtension fromEncoded(byte[] encoded) { RLPList rlpList = RLP.decodeList(encoded); + if (rlpList.size() != 2) { + throw new IllegalArgumentException("Invalid extension encoding"); + } byte[] versionData = rlpList.get(0).getRLPData(); byte version = versionData == null || versionData.length == 0 ? 0 : versionData[0]; + if (version == 0x2) { + return BlockHeaderExtensionV2.fromEncoded(rlpList.get(1).getRLPData()); + } if (version == 0x1) { return BlockHeaderExtensionV1.fromEncoded(rlpList.get(1).getRLPData()); } throw new IllegalArgumentException("Unknown extension with version: " + version); } + + byte[] getEncoded(); + + byte[] getHash(); + + byte getVersion(); } diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtensionV1.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtensionV1.java index 6f0a0ce2ad2..f0cab34aa11 100644 --- a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtensionV1.java +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtensionV1.java @@ -1,3 +1,21 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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 com.google.common.collect.Lists; @@ -18,6 +36,19 @@ public BlockHeaderExtensionV1(byte[] logsBloom, short[] edges) { this.txExecutionSublistsEdges = edges != null ? Arrays.copyOf(edges, edges.length) : null; } + public static BlockHeaderExtensionV1 fromEncoded(byte[] encoded) { + RLPList rlpExtension = RLP.decodeList(encoded); + return new BlockHeaderExtensionV1( + rlpExtension.get(0).getRLPData(), + rlpExtension.size() == 2 ? ByteUtil.rlpToShorts(rlpExtension.get(1).getRLPRawData()) : null + ); + } + + @Override + public byte getVersion() { + return 0x1; + } + @Override public byte[] getHash() { return HashUtil.keccak256(this.getEncodedForHash()); @@ -26,24 +57,27 @@ public byte[] getHash() { @Override public byte[] getEncoded() { List fieldToEncodeList = Lists.newArrayList(RLP.encodeElement(this.getLogsBloom())); - this.addEdgesEncoded(fieldToEncodeList); + this.addElementsEncoded(fieldToEncodeList); return RLP.encodeList(fieldToEncodeList.toArray(new byte[][]{})); } - public byte[] getLogsBloom() { return this.logsBloom; } - public void setLogsBloom(byte[] logsBloom) { this.logsBloom = logsBloom; } - public short[] getTxExecutionSublistsEdges() { return this.txExecutionSublistsEdges != null ? Arrays.copyOf(this.txExecutionSublistsEdges, this.txExecutionSublistsEdges.length) : null; } - public void setTxExecutionSublistsEdges(short[] edges) { this.txExecutionSublistsEdges = edges != null? Arrays.copyOf(edges, edges.length) : null; } + public byte[] getLogsBloom() { + return this.logsBloom; + } - public static BlockHeaderExtensionV1 fromEncoded(byte[] encoded) { - RLPList rlpExtension = RLP.decodeList(encoded); - return new BlockHeaderExtensionV1( - rlpExtension.get(0).getRLPData(), - rlpExtension.size() == 2 ? ByteUtil.rlpToShorts(rlpExtension.get(1).getRLPRawData()): null - ); + public void setLogsBloom(byte[] logsBloom) { + this.logsBloom = Arrays.copyOf(logsBloom, logsBloom.length); + } + + public short[] getTxExecutionSublistsEdges() { + return this.txExecutionSublistsEdges != null ? Arrays.copyOf(this.txExecutionSublistsEdges, this.txExecutionSublistsEdges.length) : null; + } + + public void setTxExecutionSublistsEdges(short[] edges) { + this.txExecutionSublistsEdges = edges != null ? Arrays.copyOf(edges, edges.length) : null; } - private void addEdgesEncoded(List fieldToEncodeList) { + protected void addElementsEncoded(List fieldToEncodeList) { short[] internalExecutionSublistsEdges = this.getTxExecutionSublistsEdges(); if (internalExecutionSublistsEdges != null) { fieldToEncodeList.add(ByteUtil.shortsToRLP(internalExecutionSublistsEdges)); @@ -52,7 +86,7 @@ private void addEdgesEncoded(List fieldToEncodeList) { private byte[] getEncodedForHash() { List fieldToEncodeList = Lists.newArrayList(RLP.encodeElement(HashUtil.keccak256(this.getLogsBloom()))); - this.addEdgesEncoded(fieldToEncodeList); + this.addElementsEncoded(fieldToEncodeList); return RLP.encodeList(fieldToEncodeList.toArray(new byte[][]{})); } } diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtensionV2.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtensionV2.java new file mode 100644 index 00000000000..6a0fad9ce37 --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderExtensionV2.java @@ -0,0 +1,78 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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 org.ethereum.util.ByteUtil; +import org.ethereum.util.RLP; +import org.ethereum.util.RLPList; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; + +public class BlockHeaderExtensionV2 extends BlockHeaderExtensionV1 { + + private byte[] baseEvent; + + public BlockHeaderExtensionV2(byte[] logsBloom, short[] edges, byte[] baseEvent) { + super(logsBloom, edges); + this.baseEvent = baseEvent; + } + + public static BlockHeaderExtensionV2 fromEncoded(byte[] encoded) { + RLPList rlpExtension = RLP.decodeList(encoded); + byte[] logsBloom = rlpExtension.get(0).getRLPData(); + byte[] baseEvent = rlpExtension.get(1).getRLPData(); + + return new BlockHeaderExtensionV2( + logsBloom, + rlpExtension.size() == 3 ? toEdges(rlpExtension.get(2).getRLPRawData()) : null, + baseEvent + ); + } + + private static short[] toEdges(byte[] rlpData) { + if (rlpData == null) { + return null; + } + return ByteUtil.rlpToShorts(rlpData); + } + + @Override + public byte getVersion() { + return 0x2; + } + + public byte[] getBaseEvent() { + return baseEvent != null ? Arrays.copyOf(baseEvent, baseEvent.length) : null; + } + + public void setBaseEvent(byte[] baseEvent) { + this.baseEvent = baseEvent != null ? Arrays.copyOf(baseEvent, baseEvent.length) : null; + } + + @Override + protected void addElementsEncoded(List fieldToEncodeList) { + byte[] internalBridgeEvent = this.getBaseEvent(); + fieldToEncodeList.add(RLP.encodeElement(Objects.requireNonNullElseGet(internalBridgeEvent, () -> EMPTY_BYTE_ARRAY))); + super.addElementsEncoded(fieldToEncodeList); + } +} diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV0.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV0.java index ea80deb6ee1..f3a02ef55da 100644 --- a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV0.java +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV0.java @@ -21,31 +21,23 @@ import co.rsk.core.BlockDifficulty; import co.rsk.core.Coin; import co.rsk.core.RskAddress; +import org.ethereum.core.exception.SealedBlockHeaderException; import java.util.Arrays; import java.util.List; public class BlockHeaderV0 extends BlockHeader { - // block header for blocks before rskip351 - @Override - public byte getVersion() { return 0x0; } - @Override - public BlockHeaderExtension getExtension() { return null; } // block header v0 has no extension - @Override - public void setExtension(BlockHeaderExtension extension) { - // block header v0 has no extension - } - + private final byte[] baseEvent; private short[] txExecutionSublistsEdges; public BlockHeaderV0(byte[] parentHash, byte[] unclesHash, RskAddress coinbase, byte[] stateRoot, - byte[] txTrieRoot, byte[] receiptTrieRoot, byte[] logsBloom, BlockDifficulty difficulty, - long number, byte[] gasLimit, long gasUsed, long timestamp, byte[] extraData, - Coin paidFees, byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, - byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] mergedMiningForkDetectionData, - Coin minimumGasPrice, int uncleCount, boolean sealed, - boolean useRskip92Encoding, boolean includeForkDetectionData, byte[] ummRoot, short[] txExecutionSublistsEdges) { - super(parentHash,unclesHash, coinbase, stateRoot, + byte[] txTrieRoot, byte[] receiptTrieRoot, byte[] logsBloom, BlockDifficulty difficulty, + long number, byte[] gasLimit, long gasUsed, long timestamp, byte[] extraData, + Coin paidFees, byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, + byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] mergedMiningForkDetectionData, + Coin minimumGasPrice, int uncleCount, boolean sealed, + boolean useRskip92Encoding, boolean includeForkDetectionData, byte[] ummRoot, byte[] baseEvent, short[] txExecutionSublistsEdges) { + super(parentHash, unclesHash, coinbase, stateRoot, txTrieRoot, receiptTrieRoot, logsBloom, difficulty, number, gasLimit, gasUsed, timestamp, extraData, paidFees, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, @@ -53,11 +45,31 @@ public BlockHeaderV0(byte[] parentHash, byte[] unclesHash, RskAddress coinbase, minimumGasPrice, uncleCount, sealed, useRskip92Encoding, includeForkDetectionData, ummRoot); this.txExecutionSublistsEdges = txExecutionSublistsEdges != null ? Arrays.copyOf(txExecutionSublistsEdges, txExecutionSublistsEdges.length) : null; + this.baseEvent = baseEvent != null ? Arrays.copyOf(baseEvent, baseEvent.length) : null; + } + + // block header for blocks before rskip351 + @Override + public byte getVersion() { + return 0x0; + } + + @Override + public BlockHeaderExtension getExtension() { + return null; + } // block header v0 has no extension + + @Override + public void setExtension(BlockHeaderExtension extension) { + // block header v0 has no extension } // logs bloom is stored in the extension data @Override - public byte[] getLogsBloom() { return extensionData; } + public byte[] getLogsBloom() { + return extensionData; + } + @Override public void setLogsBloom(byte[] logsBloom) { if (this.sealed) { @@ -69,11 +81,30 @@ public void setLogsBloom(byte[] logsBloom) { } @Override - public short[] getTxExecutionSublistsEdges() { return this.txExecutionSublistsEdges != null ? Arrays.copyOf(this.txExecutionSublistsEdges, this.txExecutionSublistsEdges.length) : null; } + public short[] getTxExecutionSublistsEdges() { + return this.txExecutionSublistsEdges != null ? Arrays.copyOf(this.txExecutionSublistsEdges, this.txExecutionSublistsEdges.length) : null; + } @Override public void setTxExecutionSublistsEdges(short[] edges) { - this.txExecutionSublistsEdges = edges != null? Arrays.copyOf(edges, edges.length) : null; + this.txExecutionSublistsEdges = edges != null ? Arrays.copyOf(edges, edges.length) : null; + } + + @Override + public byte[] getBaseEvent() { + return baseEvent != null ? Arrays.copyOf(baseEvent, baseEvent.length) : null; + } + + @Override + public void setBaseEvent(byte[] baseEvent) { + /* A sealed block header is immutable, cannot be changed */ + if (this.sealed) { + throw new SealedBlockHeaderException("trying to alter baseEvent"); + } + + if (baseEvent != null) { + throw new UnsupportedOperationException("Block header v0 does not support base event"); + } } @Override @@ -84,5 +115,6 @@ public void addExtraFieldsToEncodedHeader(boolean usingCompressedEncoding, List< // 2. keep compressed encoding the same as uncompressed // since this difference should not exist on v0 this.addTxExecutionSublistsEdgesIfAny(fieldsToEncode); + this.addBaseEvent(fieldsToEncode); } } diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV1.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV1.java index 5ad22d5435d..101e8234e3e 100644 --- a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV1.java +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV1.java @@ -1,9 +1,28 @@ +/*Add a comment on lines R1 to R18Add diff commentMarkdown input: edit mode selected.WritePreviewAdd a suggestionHeadingBoldItalicQuoteCodeLinkUnordered listNumbered listTask listMentionReferenceSaved repliesAdd FilesPaste, drop, or click to add filesCancelCommentStart a reviewReturn to code + * This file is part of RskJ + * Copyright (C) 2025 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.BlockDifficulty; import co.rsk.core.Coin; import co.rsk.core.RskAddress; import com.google.common.annotations.VisibleForTesting; +import org.ethereum.core.exception.SealedBlockHeaderException; import org.ethereum.util.RLP; import java.util.Arrays; @@ -19,43 +38,77 @@ public BlockHeaderV1(byte[] parentHash, byte[] unclesHash, RskAddress coinbase, byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] mergedMiningForkDetectionData, Coin minimumGasPrice, int uncleCount, boolean sealed, boolean useRskip92Encoding, boolean includeForkDetectionData, byte[] ummRoot, short[] txExecutionSublistsEdges, boolean compressed) { - super(parentHash,unclesHash, coinbase, stateRoot, + this(parentHash, unclesHash, coinbase, stateRoot, + txTrieRoot, receiptTrieRoot, compressed ? extensionData : null, difficulty, + number, gasLimit, gasUsed, timestamp, extraData, + paidFees, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, + bitcoinMergedMiningCoinbaseTransaction, mergedMiningForkDetectionData, + minimumGasPrice, uncleCount, sealed, + useRskip92Encoding, includeForkDetectionData, ummRoot, + makeExtension(compressed, extensionData, txExecutionSublistsEdges), compressed); + } + + public BlockHeaderV1(byte[] parentHash, byte[] unclesHash, RskAddress coinbase, byte[] stateRoot, + byte[] txTrieRoot, byte[] receiptTrieRoot, byte[] extensionData, BlockDifficulty difficulty, + long number, byte[] gasLimit, long gasUsed, long timestamp, byte[] extraData, + Coin paidFees, byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, + byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] mergedMiningForkDetectionData, + Coin minimumGasPrice, int uncleCount, boolean sealed, + boolean useRskip92Encoding, boolean includeForkDetectionData, byte[] ummRoot, + BlockHeaderExtensionV1 extension, boolean compressed) { + super(parentHash, unclesHash, coinbase, stateRoot, txTrieRoot, receiptTrieRoot, compressed ? extensionData : null, difficulty, number, gasLimit, gasUsed, timestamp, extraData, paidFees, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, bitcoinMergedMiningCoinbaseTransaction, mergedMiningForkDetectionData, minimumGasPrice, uncleCount, sealed, useRskip92Encoding, includeForkDetectionData, ummRoot); - this.extension = compressed - ? new BlockHeaderExtensionV1(null, null) - : new BlockHeaderExtensionV1(extensionData, txExecutionSublistsEdges); - if(!compressed) { - this.updateExtensionData(); // update after calculating + this.extension = extension; + if (!compressed) { + // update after calculating + this.extensionData = createExtensionData(extension); } + } + @VisibleForTesting + public static byte[] createExtensionData(BlockHeaderExtension extension) { + return RLP.encodeList( + RLP.encodeByte(extension.getVersion()), + RLP.encodeElement(extension.getHash()) + ); + } + + private static BlockHeaderExtensionV1 makeExtension(boolean compressed, + byte[] extensionData, + short[] txExecutionSublistsEdges) { + return compressed + ? new BlockHeaderExtensionV1(null, null) + : new BlockHeaderExtensionV1(extensionData, txExecutionSublistsEdges); } - @Override - public byte getVersion() { return 0x1; } @Override - public BlockHeaderExtensionV1 getExtension() { return this.extension; } + public byte getVersion() { + return 0x1; + } + @Override - public void setExtension(BlockHeaderExtension extension) { this.extension = (BlockHeaderExtensionV1) extension; } + public BlockHeaderExtensionV1 getExtension() { + return this.extension; + } - @VisibleForTesting - public static byte[] createExtensionData(byte[] extensionHash) { - return RLP.encodeList( - RLP.encodeByte((byte) 0x1), - RLP.encodeElement(extensionHash) - ); + @Override + public void setExtension(BlockHeaderExtension extension) { + this.extension = (BlockHeaderExtensionV1) extension; } - private void updateExtensionData() { - this.extensionData = BlockHeaderV1.createExtensionData(this.extension.getHash()); + protected void updateExtensionData() { + this.extensionData = createExtensionData(this.extension); } @Override - public byte[] getLogsBloom() { return this.extension.getLogsBloom(); } + public byte[] getLogsBloom() { + return this.extension.getLogsBloom(); + } @Override public void setLogsBloom(byte[] logsBloom) { @@ -69,7 +122,9 @@ public void setLogsBloom(byte[] logsBloom) { } @Override - public short[] getTxExecutionSublistsEdges() { return this.extension.getTxExecutionSublistsEdges(); } + public short[] getTxExecutionSublistsEdges() { + return this.extension.getTxExecutionSublistsEdges(); + } @Override public void setTxExecutionSublistsEdges(short[] edges) { @@ -82,6 +137,18 @@ public void setTxExecutionSublistsEdges(short[] edges) { this.updateExtensionData(); } + @Override + public byte[] getBaseEvent() { + return null; + } + + @Override + public void setBaseEvent(byte[] baseEvent) { + if (baseEvent != null) { + throw new UnsupportedOperationException("Block header v1 does not support base event"); + } + } + @Override public void addExtraFieldsToEncodedHeader(boolean usingCompressedEncoding, List fieldsToEncode) { if (!usingCompressedEncoding) { diff --git a/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV2.java b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV2.java new file mode 100644 index 00000000000..3299ca2caa7 --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/core/BlockHeaderV2.java @@ -0,0 +1,118 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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.BlockDifficulty; +import co.rsk.core.Coin; +import co.rsk.core.RskAddress; +import com.google.common.annotations.VisibleForTesting; +import org.ethereum.core.exception.FieldMaxSizeBlockHeaderException; +import org.ethereum.core.exception.SealedBlockHeaderException; +import org.ethereum.util.RLP; + +import java.util.List; + +public class BlockHeaderV2 extends BlockHeaderV1 { + + public static final int BASE_EVENT_MAX_SIZE = 128; + + public BlockHeaderV2(byte[] parentHash, byte[] unclesHash, RskAddress coinbase, byte[] stateRoot, byte[] txTrieRoot, + byte[] receiptTrieRoot, byte[] extensionData, BlockDifficulty difficulty, long number, + byte[] gasLimit, long gasUsed, long timestamp, byte[] extraData, Coin paidFees, + byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, + byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] mergedMiningForkDetectionData, + Coin minimumGasPrice, int uncleCount, boolean sealed, boolean useRskip92Encoding, + boolean includeForkDetectionData, byte[] ummRoot, byte[] baseEvent, + short[] txExecutionSublistsEdges, boolean compressed) { + super(parentHash, unclesHash, coinbase, stateRoot, + txTrieRoot, receiptTrieRoot, compressed ? extensionData : null, difficulty, + number, gasLimit, gasUsed, timestamp, extraData, + paidFees, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, + bitcoinMergedMiningCoinbaseTransaction, mergedMiningForkDetectionData, + minimumGasPrice, uncleCount, sealed, + useRskip92Encoding, includeForkDetectionData, ummRoot, + makeExtension(compressed, extensionData, txExecutionSublistsEdges, baseEvent), compressed); + } + + private static BlockHeaderExtensionV1 makeExtension(boolean compressed, + byte[] extensionData, + short[] txExecutionSublistsEdges, + byte[] baseEvent) { + return compressed + ? new BlockHeaderExtensionV2(null, null, null) + : new BlockHeaderExtensionV2(extensionData, txExecutionSublistsEdges, baseEvent); + } + + @VisibleForTesting + public static byte[] createExtensionData(byte[] extensionHash) { + return RLP.encodeList( + RLP.encodeByte((byte) 0x2), + RLP.encodeElement(extensionHash) + ); + } + + @Override + public byte getVersion() { + return 0x2; + } + + @Override + public byte[] getBaseEvent() { + return this.getExtension().getBaseEvent(); + } + + @Override + public void setBaseEvent(byte[] baseEvent) { + /* A sealed block header is immutable, cannot be changed */ + if (this.sealed) { + throw new SealedBlockHeaderException("trying to alter baseEvent data of a sealed block"); + } + + if (baseEvent.length > BASE_EVENT_MAX_SIZE) { + throw new FieldMaxSizeBlockHeaderException("baseEvent length cannot exceed 128 bytes"); + } + this.hash = null; + + this.getExtension().setBaseEvent(baseEvent); + this.updateExtensionData(); + } + + @Override + public BlockHeaderExtensionV2 getExtension() { + return (BlockHeaderExtensionV2) super.getExtension(); + } + + @Override + public void setExtension(BlockHeaderExtension extension) { + super.setExtension(extension); + } + + @Override + protected void updateExtensionData() { + this.extensionData = BlockHeaderV2.createExtensionData(this.getExtension().getHash()); + } + + @Override + public void addExtraFieldsToEncodedHeader(boolean usingCompressedEncoding, List fieldsToEncode) { + super.addExtraFieldsToEncodedHeader(usingCompressedEncoding, fieldsToEncode); + if (!usingCompressedEncoding) { + addBaseEvent(fieldsToEncode); + } + } +} diff --git a/rskj-core/src/main/java/org/ethereum/core/GenesisHeader.java b/rskj-core/src/main/java/org/ethereum/core/GenesisHeader.java index 2c280e5080b..5afeab5a54d 100644 --- a/rskj-core/src/main/java/org/ethereum/core/GenesisHeader.java +++ b/rskj-core/src/main/java/org/ethereum/core/GenesisHeader.java @@ -53,6 +53,7 @@ public GenesisHeader(byte[] parentHash, useRskip92Encoding, false, null, + null, null); this.difficulty = ByteUtils.clone(difficulty); } @@ -97,6 +98,7 @@ public GenesisHeader(byte[] parentHash, useRskip92Encoding, false, null, + null, null); this.difficulty = ByteUtils.clone(difficulty); } diff --git a/rskj-core/src/main/java/org/ethereum/core/SealedBlockException.java b/rskj-core/src/main/java/org/ethereum/core/SealedBlockException.java deleted file mode 100644 index 04f30f20ee1..00000000000 --- a/rskj-core/src/main/java/org/ethereum/core/SealedBlockException.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.ethereum.core; - -/** - * Created by ajlopez on 14/08/2017. - */ - -public class SealedBlockException extends RuntimeException { - public SealedBlockException(String message) { - super("Sealed block: " + message); - } -} diff --git a/rskj-core/src/main/java/org/ethereum/core/SealedBlockHeaderException.java b/rskj-core/src/main/java/org/ethereum/core/SealedBlockHeaderException.java deleted file mode 100644 index 298dd628803..00000000000 --- a/rskj-core/src/main/java/org/ethereum/core/SealedBlockHeaderException.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.ethereum.core; - -/** - * Created by ajlopez on 14/08/2017. - */ - -public class SealedBlockHeaderException extends RuntimeException { - public SealedBlockHeaderException(String message) { - super("Sealed block header: " + message); - } -} diff --git a/rskj-core/src/main/java/org/ethereum/core/Transaction.java b/rskj-core/src/main/java/org/ethereum/core/Transaction.java index 2fd70875394..d9626c61b1d 100644 --- a/rskj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/rskj-core/src/main/java/org/ethereum/core/Transaction.java @@ -31,6 +31,7 @@ import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; +import org.ethereum.core.exception.TransactionException; import org.ethereum.cost.InitcodeCostCalculator; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.ECKey.MissingPrivateKeyException; diff --git a/rskj-core/src/main/java/org/ethereum/core/exception/FieldMaxSizeBlockHeaderException.java b/rskj-core/src/main/java/org/ethereum/core/exception/FieldMaxSizeBlockHeaderException.java new file mode 100644 index 00000000000..b1953b5cf9a --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/core/exception/FieldMaxSizeBlockHeaderException.java @@ -0,0 +1,26 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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.exception; + +public class FieldMaxSizeBlockHeaderException extends RuntimeException { + public FieldMaxSizeBlockHeaderException(String message) { + super("Max size exceeded for the block header field: " + message); + } +} diff --git a/rskj-core/src/main/java/org/ethereum/core/exception/SealedBlockException.java b/rskj-core/src/main/java/org/ethereum/core/exception/SealedBlockException.java new file mode 100644 index 00000000000..75a4896a5b4 --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/core/exception/SealedBlockException.java @@ -0,0 +1,29 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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.exception; + +/** + * Created by ajlopez on 14/08/2017. + */ + +public class SealedBlockException extends RuntimeException { + public SealedBlockException(String message) { + super("Sealed block: " + message); + } +} diff --git a/rskj-core/src/main/java/org/ethereum/core/exception/SealedBlockHeaderException.java b/rskj-core/src/main/java/org/ethereum/core/exception/SealedBlockHeaderException.java new file mode 100644 index 00000000000..301c6c22ac1 --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/core/exception/SealedBlockHeaderException.java @@ -0,0 +1,29 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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.exception; + +/** + * Created by ajlopez on 14/08/2017. + */ + +public class SealedBlockHeaderException extends RuntimeException { + public SealedBlockHeaderException(String message) { + super("Sealed block header: " + message); + } +} diff --git a/rskj-core/src/main/java/org/ethereum/core/TransactionException.java b/rskj-core/src/main/java/org/ethereum/core/exception/TransactionException.java similarity index 92% rename from rskj-core/src/main/java/org/ethereum/core/TransactionException.java rename to rskj-core/src/main/java/org/ethereum/core/exception/TransactionException.java index 5aadb0e2064..a2e81e595a6 100644 --- a/rskj-core/src/main/java/org/ethereum/core/TransactionException.java +++ b/rskj-core/src/main/java/org/ethereum/core/exception/TransactionException.java @@ -1,6 +1,6 @@ /* * This file is part of RskJ - * Copyright (C) 2017 RSK Labs Ltd. + * Copyright (C) 2025 RSK Labs Ltd. * (derived from ethereumJ library, Copyright (c) 2016 ) * * This program is free software: you can redistribute it and/or modify @@ -16,8 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - -package org.ethereum.core; +package org.ethereum.core.exception; /** * Created by ajlopez on 09/03/2017. diff --git a/rskj-core/src/main/java/org/ethereum/rpc/dto/BlockResultDTO.java b/rskj-core/src/main/java/org/ethereum/rpc/dto/BlockResultDTO.java index 1273995e56b..7d2f3fe73f4 100644 --- a/rskj-core/src/main/java/org/ethereum/rpc/dto/BlockResultDTO.java +++ b/rskj-core/src/main/java/org/ethereum/rpc/dto/BlockResultDTO.java @@ -29,7 +29,11 @@ import org.ethereum.db.BlockStore; import javax.annotation.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; import java.util.stream.IntStream; import static org.ethereum.crypto.HashUtil.EMPTY_TRIE_HASH; @@ -63,6 +67,7 @@ public class BlockResultDTO { private final String paidFees; private final String cumulativeDifficulty; private final short[] rskPteEdges; + private final String baseEvent; private BlockResultDTO( Long number, @@ -91,7 +96,8 @@ private BlockResultDTO( byte[] bitcoinMergedMiningMerkleProof, byte[] hashForMergedMining, Coin paidFees, - short[] rskPteEdges) { + short[] rskPteEdges, + byte[] baseEvent) { this.number = number != null ? HexUtils.toQuantityJsonHex(number) : null; this.hash = hash != null ? hash.toJsonString() : null; this.parentHash = parentHash.toJsonString(); @@ -125,6 +131,7 @@ private BlockResultDTO( this.paidFees = paidFees != null ? HexUtils.toQuantityJsonHex(paidFees.getBytes()) : null; this.rskPteEdges = copyOfArrayOrNull(rskPteEdges); + this.baseEvent = baseEvent != null && baseEvent.length > 0 ? HexUtils.toUnformattedJsonHex(baseEvent) : null; } public static BlockResultDTO fromBlock(Block b, boolean fullTx, BlockStore blockStore, boolean skipRemasc, boolean zeroSignatureIfRemasc, SignatureCache signatureCache) { @@ -153,9 +160,9 @@ public static BlockResultDTO fromBlock(Block b, boolean fullTx, BlockStore block // Useful for geth integration byte[] transactionsRoot = skipRemasc && b.getTransactionsList().size() == 1 && - b.getTransactionsList().get(0).isRemascTransaction(0,1) ? - EMPTY_TRIE_HASH : - b.getTxTrieRoot(); + b.getTransactionsList().get(0).isRemascTransaction(0, 1) ? + EMPTY_TRIE_HASH : + b.getTxTrieRoot(); return new BlockResultDTO( isPending ? null : b.getNumber(), @@ -184,24 +191,30 @@ public static BlockResultDTO fromBlock(Block b, boolean fullTx, BlockStore block b.getBitcoinMergedMiningMerkleProof(), b.getHashForMergedMining(), b.getFeesPaidToMiner(), - b.getHeader().getTxExecutionSublistsEdges() + b.getHeader().getTxExecutionSublistsEdges(), + b.getHeader().getBaseEvent() ); } private static Object toTransactionResult(int transactionIndex, Block block, boolean fullTx, boolean skipRemasc, boolean zeroSignatureIfRemasc, SignatureCache signatureCache) { Transaction transaction = block.getTransactionsList().get(transactionIndex); - if(skipRemasc && transaction.isRemascTransaction(transactionIndex, block.getTransactionsList().size())) { + if (skipRemasc && transaction.isRemascTransaction(transactionIndex, block.getTransactionsList().size())) { return null; } - - if(fullTx) { + + if (fullTx) { return new TransactionResultDTO(block, transactionIndex, transaction, zeroSignatureIfRemasc, signatureCache); } return transaction.getHash().toJsonString(); } + @Nullable + private static short[] copyOfArrayOrNull(short[] array) { + return array != null ? Arrays.copyOf(array, array.length) : null; + } + public String getNumber() { return number; } @@ -254,7 +267,9 @@ public String getTotalDifficulty() { return totalDifficulty; } - public String getCumulativeDifficulty() { return cumulativeDifficulty; } + public String getCumulativeDifficulty() { + return cumulativeDifficulty; + } public String getExtraData() { return extraData; @@ -280,7 +295,9 @@ public List getTransactions() { return Collections.unmodifiableList(transactions); } - public List getUncles() { return Collections.unmodifiableList(uncles); } + public List getUncles() { + return Collections.unmodifiableList(uncles); + } public String getMinimumGasPrice() { return minimumGasPrice; @@ -310,8 +327,7 @@ public short[] getRskPteEdges() { return copyOfArrayOrNull(rskPteEdges); } - @Nullable - private static short[] copyOfArrayOrNull(short[] array) { - return array != null ? Arrays.copyOf(array, array.length) : null; + public String getBaseEvent() { + return baseEvent; } } diff --git a/rskj-core/src/main/resources/config/regtest.conf b/rskj-core/src/main/resources/config/regtest.conf index e1da552c15f..8357d1035c2 100644 --- a/rskj-core/src/main/resources/config/regtest.conf +++ b/rskj-core/src/main/resources/config/regtest.conf @@ -24,6 +24,7 @@ blockchain.config { rskipUMM = 1 rskip144 = 1 rskip351 = 1 + rskip535 = 1 } } diff --git a/rskj-core/src/main/resources/expected.conf b/rskj-core/src/main/resources/expected.conf index be3ed0a5edf..8707bd10d36 100644 --- a/rskj-core/src/main/resources/expected.conf +++ b/rskj-core/src/main/resources/expected.conf @@ -114,6 +114,7 @@ blockchain = { rskip502 = rskip516 = rskip529 = + rskip535 = rskip536 = } } diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index 84c94564222..ec3058fc710 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -1,3 +1,4 @@ +# @formatter:off blockchain = { config = { # Flag that forces to verify whether a node is started with unknown configuration property names. @@ -96,6 +97,7 @@ blockchain = { rskip502 = reed810 rskip516 = reed800 rskip529 = reed810 + rskip535 = vetiver900 rskip536 = reed810 } } @@ -642,4 +644,4 @@ blooms { blocks = 16 service = false confirmations = 400 -} +} \ No newline at end of file diff --git a/rskj-core/src/test/java/co/rsk/blockchain/utils/BlockGenerator.java b/rskj-core/src/test/java/co/rsk/blockchain/utils/BlockGenerator.java index 2d8780c5300..6d406b32eb6 100644 --- a/rskj-core/src/test/java/co/rsk/blockchain/utils/BlockGenerator.java +++ b/rskj-core/src/test/java/co/rsk/blockchain/utils/BlockGenerator.java @@ -188,8 +188,6 @@ public Block createChildBlock(Block parent, List txs, byte[] stateR } public Block createChildBlock(Block parent, List txs, byte[] stateRoot, byte[] coinbase) { - Bloom logBloom = new Bloom(); - boolean isRskip126Enabled = activationConfig.isActive(ConsensusRule.RSKIP126, 0); long blockNumber = parent.getNumber() + 1; @@ -297,6 +295,7 @@ public Block createChildBlockUsingCoinbase( long blockNumber = parent.getNumber() + 1; byte[] ummRoot = activationConfig.isActive(ConsensusRule.RSKIPUMM, blockNumber) ? new byte[0] : null; + byte[] baseEvent = activationConfig.isActive(ConsensusRule.RSKIP535, blockNumber) ? new byte[0] : null; Coin coinMinGasPrice = (minGasPrice != null) ? new Coin(minGasPrice) : null; BlockHeader newHeader = blockFactory.getBlockHeaderBuilder() @@ -313,6 +312,7 @@ public Block createChildBlockUsingCoinbase( .setMinimumGasPrice(coinMinGasPrice) .setUncleCount(uncles.size()) .setUmmRoot(ummRoot) + .setBaseEvent(baseEvent) .setTxExecutionSublistsEdges(edges) .setCreateParallelCompliantHeader(activationConfig.isActive(ConsensusRule.RSKIP144, blockNumber)) .build(); diff --git a/rskj-core/src/test/java/co/rsk/core/BlockFactoryTest.java b/rskj-core/src/test/java/co/rsk/core/BlockFactoryTest.java index ca0000fe990..342670dcab6 100644 --- a/rskj-core/src/test/java/co/rsk/core/BlockFactoryTest.java +++ b/rskj-core/src/test/java/co/rsk/core/BlockFactoryTest.java @@ -31,16 +31,22 @@ import org.ethereum.crypto.HashUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPList; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.math.BigInteger; import java.util.Arrays; -import static org.ethereum.config.blockchain.upgrades.ConsensusRule.*; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP110; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP144; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP351; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP92; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIPUMM; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.AdditionalMatchers.geq; import static org.mockito.AdditionalMatchers.lt; import static org.mockito.ArgumentMatchers.eq; @@ -240,7 +246,7 @@ void decodeBlockRskip110OffRskipUMMOnAndNoMergedMiningFieldsNullUMMRoot() { RLPList headerRLP = RLP.decodeList(encodedHeader); assertThat(headerRLP.size(), is(16)); - Assertions.assertThrows(IllegalArgumentException.class, () -> factory.decodeHeader(encodedHeader, false)); + assertThrows(IllegalArgumentException.class, () -> factory.decodeHeader(encodedHeader, false)); } @Test @@ -291,12 +297,12 @@ void decodeBlockRskip110OffRskipUMMOnAndMergedMiningFieldsNullUmmRoot() { RLPList headerRLP = RLP.decodeList(encodedHeader); assertThat(headerRLP.size(), is(19)); - Assertions.assertThrows(IllegalArgumentException.class, () -> factory.decodeHeader(encodedHeader, false)); + assertThrows(IllegalArgumentException.class, () -> factory.decodeHeader(encodedHeader, false)); } @Test void genesisHasVersion0() { - Assertions.assertEquals((byte) 0x0, factory.decodeBlock(genesisRaw()).getHeader().getVersion()); + assertEquals((byte) 0x0, factory.decodeBlock(genesisRaw()).getHeader().getVersion()); } @Test @@ -304,7 +310,7 @@ void headerIsVersion0Before351Activation () { long number = 20L; enableRskip351At(number); BlockHeader header = factory.getBlockHeaderBuilder().setNumber(number - 1).build(); - Assertions.assertEquals(0, header.getVersion()); + assertEquals(0, header.getVersion()); } @Test @@ -312,7 +318,7 @@ void headerIsVersion1After351Activation () { long number = 20L; enableRskip351At(number); BlockHeader header = factory.getBlockHeaderBuilder().setNumber(number).build(); - Assertions.assertEquals(1, header.getVersion()); + assertEquals(1, header.getVersion()); } private BlockHeader testRSKIP351FullHeaderEncoding(byte[] encoded, byte expectedVersion, byte[] expectedLogsBloom, short[] expectedEdges) { @@ -326,9 +332,9 @@ private BlockHeader testRSKIP351CompressedHeaderEncoding(byte[] encoded, byte ex private BlockHeader testRSKIP351CompressedHeaderEncoding(byte[] encoded, byte expectedVersion, byte[] expectedLogsBloom, short[] expectedEdges, boolean compressed) { BlockHeader decodedHeader = factory.decodeHeader(encoded, compressed); - Assertions.assertEquals(expectedVersion, decodedHeader.getVersion()); - Assertions.assertArrayEquals(expectedLogsBloom, decodedHeader.getLogsBloom()); - Assertions.assertArrayEquals(expectedEdges, decodedHeader.getTxExecutionSublistsEdges()); + assertEquals(expectedVersion, decodedHeader.getVersion()); + assertArrayEquals(expectedLogsBloom, decodedHeader.getLogsBloom()); + assertArrayEquals(expectedEdges, decodedHeader.getTxExecutionSublistsEdges()); return decodedHeader; } @@ -353,7 +359,7 @@ void decodeCompressedBefore351() { byte[] encoded = header.getEncodedCompressed(); BlockHeader decodedHeader = testRSKIP351CompressedHeaderEncoding(encoded, (byte) 0, logsBloom, null); - Assertions.assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); + assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); } @Test @@ -380,7 +386,7 @@ void decodeCompressedBefore351WithEdges() { byte[] encoded = header.getEncodedCompressed(); BlockHeader decodedHeader = testRSKIP351CompressedHeaderEncoding(encoded, (byte) 0, logsBloom, edges); - Assertions.assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); + assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); } @Test @@ -401,7 +407,7 @@ void decodeCompressedWithNoEdgesAndMergedMiningFields() { BlockHeader decodedHeader = testRSKIP351CompressedHeaderEncoding(encoded, (byte) 1, null, null); - Assertions.assertArrayEquals(header.getExtensionData(), decodedHeader.getExtensionData()); + assertArrayEquals(header.getExtensionData(), decodedHeader.getExtensionData()); assertThat(header.getHash(), is(decodedHeader.getHash())); assertThat(header.getUmmRoot(), is(decodedHeader.getUmmRoot())); } @@ -435,7 +441,7 @@ void decodeCompressedOfExtendedBefore351() { byte[] encoded = header.getFullEncoded(); // used before hf BlockHeader decodedHeader = testRSKIP351CompressedHeaderEncoding(encoded, (byte) 0, logsBloom, null); - Assertions.assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); + assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); } @Test @@ -462,7 +468,7 @@ void decodeCompressedOfExtendedBefore351WithEdges() { byte[] encoded = header.getFullEncoded(); // used before hf BlockHeader decodedHeader = testRSKIP351CompressedHeaderEncoding(encoded, (byte) 0, logsBloom, edges); - Assertions.assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); + assertArrayEquals(logsBloom, decodedHeader.getExtensionData()); } @Test @@ -488,7 +494,7 @@ void decodeCompressedAfter351WithEdges() { byte[] encoded = header.getEncodedCompressed(); BlockHeader decodedHeader = testRSKIP351CompressedHeaderEncoding(encoded, (byte) 1, null, null); - Assertions.assertArrayEquals(BlockHeaderV1.createExtensionData(header.getExtension().getHash()), decodedHeader.getExtensionData()); + assertArrayEquals(BlockHeaderV1.createExtensionData(header.getExtension()), decodedHeader.getExtensionData()); } @Test @@ -512,7 +518,7 @@ void decodeFullBefore351And144() { byte[] encoded = header.getFullEncoded(); BlockHeader decodedHeader = testRSKIP351FullHeaderEncoding(encoded, (byte) 0, logsBloom, null); - Assertions.assertArrayEquals(header.getLogsBloom(), decodedHeader.getExtensionData()); + assertArrayEquals(header.getLogsBloom(), decodedHeader.getExtensionData()); } @Test @@ -539,7 +545,7 @@ void decodeFullBefore351WithEdges() { byte[] encoded = header.getFullEncoded(); BlockHeader decodedHeader = testRSKIP351FullHeaderEncoding(encoded, (byte) 0, logsBloom, edges); - Assertions.assertArrayEquals(header.getLogsBloom(), decodedHeader.getExtensionData()); + assertArrayEquals(header.getLogsBloom(), decodedHeader.getExtensionData()); } @Test @@ -565,7 +571,7 @@ void decodeFullAfter351WithEdges() { byte[] encoded = header.getFullEncoded(); BlockHeader decodedHeader = testRSKIP351FullHeaderEncoding(encoded, (byte) 1, logsBloom, edges); - Assertions.assertArrayEquals(BlockHeaderV1.createExtensionData(header.getExtension().getHash()), decodedHeader.getExtensionData()); + assertArrayEquals(BlockHeaderV1.createExtensionData(header.getExtension()), decodedHeader.getExtensionData()); } @Test diff --git a/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java b/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java index 2a478392b42..be9fffc2f20 100644 --- a/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java +++ b/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java @@ -23,7 +23,10 @@ import co.rsk.peg.PegTestUtils; import com.google.common.primitives.Bytes; import org.ethereum.TestUtils; -import org.ethereum.core.*; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.BlockHeaderV0; +import org.ethereum.core.BlockHeaderV1; +import org.ethereum.core.Bloom; import org.ethereum.crypto.HashUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPList; @@ -50,13 +53,24 @@ @ExtendWith(MockitoExtension.class) class BlockHeaderTest { + private static final byte[] logsBloom = new byte[Bloom.BLOOM_BYTES]; + private static final short[] edges = new short[]{1, 2, 3, 4}; + + @BeforeAll + static void setupLogsBloom() { + logsBloom[0] = 0x01; + logsBloom[1] = 0x02; + logsBloom[2] = 0x03; + logsBloom[3] = 0x04; + } + @Test void getHashForMergedMiningWithForkDetectionDataAndIncludedOnAndMergedMiningFields() { BlockHeader header = createBlockHeaderWithMergedMiningFields(new byte[0], true, new byte[0], new short[0]); byte[] encodedBlock = header.getEncoded(false, false, false); byte[] hashForMergedMiningPrefix = Arrays.copyOfRange(HashUtil.keccak256(encodedBlock), 0, 20); - byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData",12); + byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData", 12); byte[] hashForMergedMining = concatenate(hashForMergedMiningPrefix, forkDetectionData); byte[] coinbase = concatenate(RskMiningConstants.RSK_TAG, hashForMergedMining); header.setBitcoinMergedMiningCoinbaseTransaction(coinbase); @@ -79,7 +93,7 @@ void getHashForMergedMiningWithNoForkDetectionDataAndIncludedOffAndMergedMiningF @Test void getHashForMergedMiningWithForkDetectionDataAndIncludedOn() { - byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData",12); + byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData", 12); BlockHeader header = createBlockHeaderWithNoMergedMiningFields(forkDetectionData, true, new byte[0], new short[0]); byte[] hash = header.getHash().getBytes(); @@ -93,7 +107,7 @@ void getHashForMergedMiningWithForkDetectionDataAndIncludedOn() { @Test void getHashForMergedMiningWithForkDetectionDataAndIncludedOff() { - byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData",12); + byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData", 12); BlockHeader header = createBlockHeaderWithNoMergedMiningFields(forkDetectionData, false, new byte[0], new short[0]); byte[] hash = header.getHash().getBytes(); @@ -102,10 +116,9 @@ void getHashForMergedMiningWithForkDetectionDataAndIncludedOff() { MatcherAssert.assertThat(hash, is(hashForMergedMining)); } - @Test void getEncodedWithUmmRootWithMergedMiningFields() { - byte[] ummRoot = TestUtils.generateBytes("ummRoot",20); + byte[] ummRoot = TestUtils.generateBytes("ummRoot", 20); BlockHeader header = createBlockHeaderWithMergedMiningFields(new byte[0], false, ummRoot, new short[0]); byte[] headerEncoded = header.getFullEncoded(); @@ -116,7 +129,7 @@ void getEncodedWithUmmRootWithMergedMiningFields() { @Test void getEncodedWithUmmRootWithoutMergedMiningFields() { - byte[] ummRoot = TestUtils.generateBytes("ummRoot",20); + byte[] ummRoot = TestUtils.generateBytes("ummRoot", 20); BlockHeader header = createBlockHeaderWithNoMergedMiningFields(new byte[0], false, ummRoot, new short[0]); byte[] headerEncoded = header.getFullEncoded(); @@ -231,14 +244,13 @@ void getEncodedWithEdgesAndUmmRootWithoutMergedMiningFields() { MatcherAssert.assertThat(headerRLP.size(), is(18)); } - @Test void getMiningForkDetectionData() { BlockHeader header = createBlockHeaderWithMergedMiningFields(new byte[0], true, new byte[0], new short[0]); byte[] encodedBlock = header.getEncoded(false, false, false); byte[] hashForMergedMining = Arrays.copyOfRange(HashUtil.keccak256(encodedBlock), 0, 20); - byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData",12); + byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData", 12); byte[] coinbase = concatenate(hashForMergedMining, forkDetectionData); coinbase = concatenate(RskMiningConstants.RSK_TAG, coinbase); header.setBitcoinMergedMiningCoinbaseTransaction(coinbase); @@ -249,7 +261,7 @@ void getMiningForkDetectionData() { @Test void getUmmRoot() { - byte[] ummRoot = TestUtils.generateBytes("ummRoot",20); + byte[] ummRoot = TestUtils.generateBytes("ummRoot", 20); BlockHeader header = createBlockHeaderWithUmmRoot(ummRoot); MatcherAssert.assertThat(header.getUmmRoot(), is(ummRoot)); @@ -257,7 +269,7 @@ void getUmmRoot() { @Test void isUMMBlockWhenUmmRoot() { - byte[] ummRoot = TestUtils.generateBytes("ummRoot",20); + byte[] ummRoot = TestUtils.generateBytes("ummRoot", 20); BlockHeader header = createBlockHeaderWithUmmRoot(ummRoot); MatcherAssert.assertThat(header.isUMMBlock(), is(true)); @@ -281,8 +293,8 @@ void isUMMBlockWhenEmptyUmmRoot() { @Test void getHashForMergedMiningWhenUmmRoot() { - byte[] ummRoot = TestUtils.generateBytes("ummRoot",20); - byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData",12); + byte[] ummRoot = TestUtils.generateBytes("ummRoot", 20); + byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData", 12); BlockHeader header = createBlockHeaderWithUmmRoot(ummRoot, forkDetectionData); byte[] encodedBlock = header.getEncoded(false, false, false); @@ -311,7 +323,7 @@ void getHashForMergedMiningWhenEmptyUmmRoot() { @Test void getHashForMergedMiningWhenUmmRootWithLengthUnder20() { - byte[] ummRoot = TestUtils.generateBytes("ummRoot",19); + byte[] ummRoot = TestUtils.generateBytes("ummRoot", 19); BlockHeader header = createBlockHeaderWithUmmRoot(ummRoot); Assertions.assertThrows(IllegalStateException.class, header::getHashForMergedMining); @@ -319,7 +331,7 @@ void getHashForMergedMiningWhenUmmRootWithLengthUnder20() { @Test void getHashForMergedMiningWhenUmmRootWithLengthOver20() { - byte[] ummRoot = TestUtils.generateBytes("ummRoot",21); + byte[] ummRoot = TestUtils.generateBytes("ummRoot", 21); BlockHeader header = createBlockHeaderWithUmmRoot(ummRoot); Assertions.assertThrows(IllegalStateException.class, header::getHashForMergedMining); @@ -332,7 +344,7 @@ void getHashForMergedMiningWhenUmmRootWithLengthOver20() { void getMiningForkDetectionDataNoDataCanBeFound() { BlockHeader header = createBlockHeaderWithMergedMiningFields(new byte[0], true, new byte[0], new short[0]); - byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData",12); + byte[] forkDetectionData = TestUtils.generateBytes("forkDetectionData", 12); byte[] coinbase = concatenate(RskMiningConstants.RSK_TAG, forkDetectionData); header.setBitcoinMergedMiningCoinbaseTransaction(coinbase); header.seal(); @@ -366,7 +378,7 @@ void getHashShouldReuseCalculatedValue() { @Test void verifyRecalculatedHashForAmendedBlocks() { BlockHeader header = createBlockHeader(new byte[80], new byte[32], new byte[128], new byte[0], - false, new byte[0], new short[0], false, false); + false, new byte[0], new byte[0], new short[0], false, false); assertArrayEquals(HashUtil.keccak256(header.getEncodedForHash()), header.getHash().getBytes()); @@ -398,24 +410,15 @@ private void testHeaderVersion(byte version) { } @Test - void getVersion0() { this.testHeaderVersion((byte) 0x0); } + void getVersion0() { + this.testHeaderVersion((byte) 0x0); + } @Test void getVersion1() { this.testHeaderVersion((byte) 0x1); } - private static byte[] logsBloom = new byte[Bloom.BLOOM_BYTES]; - private static short[] edges = new short[]{ 1, 2, 3, 4 }; - - @BeforeAll - static void setupLogsBloom() { - logsBloom[0] = 0x01; - logsBloom[1] = 0x02; - logsBloom[2] = 0x03; - logsBloom[3] = 0x04; - } - @Test void encodeForLogsBloomField() { BlockHeaderV1 header = (BlockHeaderV1) createBlockHeaderWithVersion((byte) 0x1); @@ -467,7 +470,7 @@ private void testEncodingButVersion(BlockHeaderV0 headerV0, BlockHeaderV1 header } @Test - void encodedV0IsTheSameForV0andV1 () { + void encodedV0IsTheSameForV0andV1() { BlockHeaderV0 headerV0 = (BlockHeaderV0) createBlockHeaderWithVersion((byte) 0x0); headerV0.setLogsBloom(logsBloom); @@ -477,7 +480,7 @@ void encodedV0IsTheSameForV0andV1 () { } @Test - void fullEncodedV0IsTheSameForV0andV1 () { + void fullEncodedV0IsTheSameForV0andV1() { BlockHeaderV0 headerV0 = (BlockHeaderV0) createBlockHeaderWithVersion((byte) 0x0); headerV0.setLogsBloom(logsBloom); @@ -487,7 +490,7 @@ void fullEncodedV0IsTheSameForV0andV1 () { } @Test - void fullEncodedV0IsTheSameAsEncodedForHeaderMessage () { + void fullEncodedV0IsTheSameAsEncodedForHeaderMessage() { BlockHeaderV0 headerV0 = (BlockHeaderV0) createBlockHeaderWithVersion((byte) 0x0); headerV0.setLogsBloom(logsBloom); @@ -495,7 +498,7 @@ void fullEncodedV0IsTheSameAsEncodedForHeaderMessage () { } @Test - void fullEncodedV1IsTheSameAsCompressedButLogsBloomEdgesAndVersion () { + void fullEncodedV1IsTheSameAsCompressedButLogsBloomEdgesAndVersion() { // this test is added to assert that there were no changes in the rest of the elements BlockHeaderV1 headerV1 = (BlockHeaderV1) createBlockHeaderWithVersion((byte) 0x1); headerV1.setLogsBloom(logsBloom); @@ -519,7 +522,7 @@ void fullEncodedV1IsTheSameAsCompressedButLogsBloomEdgesAndVersion () { } @Test - void compressedEncodingV1HasSameRLPSizeAsFullEncodedV0WithoutEdges () { + void compressedEncodingV1HasSameRLPSizeAsFullEncodedV0WithoutEdges() { // this test is added to assert that the rlp header size does not change // in the hard fork, assuming both RSKIP 351 and RSKIP 144 are activated // together @@ -562,7 +565,7 @@ void hashOfV1IncludesEdges() { byte[] hash = headerV1.getHash().getBytes(); - short[] otherEdges = new short[]{ 1, 2, 3, 5}; + short[] otherEdges = new short[]{1, 2, 3, 5}; headerV1.setTxExecutionSublistsEdges(otherEdges); Assertions.assertFalse(Arrays.equals(hash, headerV1.getHash().getBytes())); @@ -570,53 +573,53 @@ void hashOfV1IncludesEdges() { private BlockHeader createBlockHeaderWithMergedMiningFields( byte[] forkDetectionData, - boolean includeForkDetectionData, byte[] ummRoot, short[] edges){ + boolean includeForkDetectionData, byte[] ummRoot, short[] edges) { return createBlockHeader(new byte[80], new byte[32], new byte[128], - forkDetectionData, includeForkDetectionData, ummRoot, edges, false); + forkDetectionData, includeForkDetectionData, ummRoot, null, edges, false); } private BlockHeader createBlockHeaderWithNoMergedMiningFields( byte[] forkDetectionData, boolean includeForkDetectionData, byte[] ummRoot, short[] edges) { return createBlockHeader(null, null, null, - forkDetectionData, includeForkDetectionData, ummRoot, edges, true); + forkDetectionData, includeForkDetectionData, ummRoot, null, edges, true); } private BlockHeader createBlockHeaderWithUmmRoot(byte[] ummRoot) { return createBlockHeader(null, null, null, - new byte[0], false, ummRoot, new short[0], true); + new byte[0], false, ummRoot, null, new short[0], true); } private BlockHeader createBlockHeaderWithUmmRoot(byte[] ummRoot, byte[] forkDetectionData) { return createBlockHeader(null, null, null, - forkDetectionData, true, ummRoot, new short[0], true); + forkDetectionData, true, ummRoot, null, new short[0], true); } private BlockHeader createBlockHeader(byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] forkDetectionData, - boolean includeForkDetectionData, byte[] ummRoot, short[] edges, boolean sealed) { + boolean includeForkDetectionData, byte[] ummRoot, byte[] baseEvent, short[] edges, boolean sealed) { return createBlockHeader(bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, bitcoinMergedMiningCoinbaseTransaction, - forkDetectionData, includeForkDetectionData, ummRoot, edges, true, sealed); + forkDetectionData, includeForkDetectionData, ummRoot, baseEvent, edges, true, sealed); } private BlockHeader createBlockHeader(byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] forkDetectionData, - boolean includeForkDetectionData, byte[] ummRoot, short[] edges, + boolean includeForkDetectionData, byte[] ummRoot, byte[] baseEvent, short[] edges, boolean useRskip92Encoding, boolean sealed) { return createBlockHeader((byte) 0x0, bitcoinMergedMiningHeader, bitcoinMergedMiningMerkleProof, bitcoinMergedMiningCoinbaseTransaction, - forkDetectionData, includeForkDetectionData, ummRoot, edges, useRskip92Encoding, sealed); + forkDetectionData, includeForkDetectionData, ummRoot, baseEvent, edges, useRskip92Encoding, sealed); } private BlockHeader createBlockHeaderWithVersion(byte version) { return createBlockHeader(version, new byte[80], new byte[32], new byte[128], - new byte[0], false, null, new short[0], false, false); + new byte[0], false, null, null, new short[0], false, false); } private BlockHeader createBlockHeader(byte version, byte[] bitcoinMergedMiningHeader, byte[] bitcoinMergedMiningMerkleProof, byte[] bitcoinMergedMiningCoinbaseTransaction, byte[] forkDetectionData, - boolean includeForkDetectionData, byte[] ummRoot, short[] edges, boolean useRskip92Encoding, + boolean includeForkDetectionData, byte[] ummRoot, byte[] baseEvent, short[] edges, boolean useRskip92Encoding, boolean sealed) { BlockDifficulty difficulty = new BlockDifficulty(BigInteger.ONE); long number = 1; @@ -676,6 +679,7 @@ private BlockHeader createBlockHeader(byte version, useRskip92Encoding, includeForkDetectionData, ummRoot, + baseEvent, edges); } diff --git a/rskj-core/src/test/java/co/rsk/core/BlockHeaderV0Test.java b/rskj-core/src/test/java/co/rsk/core/BlockHeaderV0Test.java index 7b60ee5199e..8829c10a327 100644 --- a/rskj-core/src/test/java/co/rsk/core/BlockHeaderV0Test.java +++ b/rskj-core/src/test/java/co/rsk/core/BlockHeaderV0Test.java @@ -39,6 +39,7 @@ private BlockHeaderV0 createBlockHeader(byte[] logsBloom, short[] edges) { false, false, null, + null, edges ); } diff --git a/rskj-core/src/test/java/co/rsk/core/BlockHeaderV1Test.java b/rskj-core/src/test/java/co/rsk/core/BlockHeaderV1Test.java index c9b47f3c9fc..05e5e04f69d 100644 --- a/rskj-core/src/test/java/co/rsk/core/BlockHeaderV1Test.java +++ b/rskj-core/src/test/java/co/rsk/core/BlockHeaderV1Test.java @@ -56,7 +56,7 @@ void createsAnExtensionWithGivenData() { @Test void setsExtension() { byte[] bloom = TestUtils.generateBytes("bloom", 256); - short[] edges = new short[]{ 1, 2, 3, 4 }; + short[] edges = new short[]{1, 2, 3, 4}; BlockHeaderV1 header = createBlockHeader(bloom); BlockHeaderExtensionV1 extension = new BlockHeaderExtensionV1(bloom, edges); header.setExtension(extension); diff --git a/rskj-core/src/test/java/co/rsk/core/BlockHeaderV2Test.java b/rskj-core/src/test/java/co/rsk/core/BlockHeaderV2Test.java new file mode 100644 index 00000000000..7f529892a81 --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/core/BlockHeaderV2Test.java @@ -0,0 +1,324 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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 co.rsk.core; + +import org.ethereum.TestUtils; +import org.ethereum.core.BlockHeaderExtensionV2; +import org.ethereum.core.BlockHeaderV2; +import org.ethereum.core.exception.FieldMaxSizeBlockHeaderException; +import org.ethereum.core.exception.SealedBlockHeaderException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BlockHeaderV2Test { + + @Test + void testVersionIs2() { + // given + BlockHeaderV2 header = createHeaderV2(); + // when + int version = header.getVersion(); + // then + assertEquals(0x2, version); + } + + @Test + void testGetAndSetBaseEvent() { + // given + BlockHeaderV2 header = createHeaderV2(); + byte[] hash = new byte[]{1, 2, 3, 4}; + // when + header.setBaseEvent(hash); + // then + assertArrayEquals(hash, header.getBaseEvent()); + } + + @Test + void testSetBaseEventThrowsIfSealed() { + // given + BlockHeaderV2 header = createHeaderV2(); + header.seal(); + // when / then + assertThrows(SealedBlockHeaderException.class, () -> header.setBaseEvent(new byte[]{1})); + } + + @Test + void testExtensionIsBlockHeaderExtensionV2() { + // given + BlockHeaderV2 header = createHeaderV2(); + // when + Object ext = header.getExtension(); + // then + assertInstanceOf(BlockHeaderExtensionV2.class, ext); + } + + @Test + void testSetExtension() { + // given + BlockHeaderV2 header = createHeaderV2(); + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(new byte[]{1}, new short[]{2}, new byte[]{3}); + // when + header.setExtension(ext); + // then + assertSame(ext, header.getExtension()); + } + + @Test + void testCreateExtensionData() { + // given + byte[] hash = new byte[]{9, 8, 7}; + // when + byte[] extData = BlockHeaderV2.createExtensionData(hash); + // then + assertNotNull(extData); + assertTrue(extData.length > 0); + } + + @Test + void testAddExtraFieldsToEncodedHeader() { + // given + BlockHeaderV2 header = createHeaderV2(); + java.util.List fields = new java.util.ArrayList<>(); + // when + header.addExtraFieldsToEncodedHeader(false, fields); + // then + assertFalse(fields.isEmpty()); + } + + @Test + void createsAnExtensionWithGivenData() { + // given + byte[] bloom = TestUtils.generateBytes("bloom", 256); + BlockHeaderV2 header = createHeaderV2(bloom); + // when + byte[] logsBloom = header.getExtension().getLogsBloom(); + // then + assertArrayEquals(bloom, logsBloom); + } + + @Test + void setsExtension() { + // given + byte[] bloom = TestUtils.generateBytes("bloom", 256); + short[] edges = new short[]{1, 2, 3, 4}; + BlockHeaderV2 header = createHeaderV2(bloom); + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(bloom, edges, null); + // when + header.setExtension(extension); + byte[] result = header.getExtension().getEncoded(); + // then + assertArrayEquals(extension.getEncoded(), result); + } + + private BlockHeaderV2 createHeaderV2() { + return createHeaderV2(new byte[0]); + } + + // Helper to create a minimal valid BlockHeaderV2 + private BlockHeaderV2 createHeaderV2(byte[] extensionData) { + return new BlockHeaderV2( + new byte[32], // parentHash + new byte[32], // unclesHash + new RskAddress(new byte[20]), // coinbase + new byte[32], // stateRoot + new byte[32], // txTrieRoot + new byte[32], // receiptTrieRoot + extensionData, // extensionData + BlockDifficulty.ONE, // difficulty + 1L, // number + new byte[8], // gasLimit + 0L, // gasUsed + 123456789L, // timestamp + new byte[0], // extraData + Coin.ZERO, // paidFees + new byte[80], // bitcoinMergedMiningHeader + new byte[0], // bitcoinMergedMiningMerkleProof + new byte[0], // bitcoinMergedMiningCoinbaseTransaction + new byte[0], // mergedMiningForkDetectionData + Coin.ZERO, // minimumGasPrice + 0, // uncleCount + false, // sealed + false, // useRskip92Encoding + false, // includeForkDetectionData + new byte[0], // ummRoot + new byte[32], // baseEvent + new short[0], // txExecutionSublistsEdges + false // compressed + ); + } + + @Test + void testSetBaseEventWithEmptyArray() { + // given + BlockHeaderV2 header = createHeaderV2(); + byte[] emptyArray = new byte[0]; + // when + header.setBaseEvent(emptyArray); + // then + assertArrayEquals(emptyArray, header.getBaseEvent()); + } + + @Test + void testSetBaseEventWithMaxSizeForTheEvent() { + // given + BlockHeaderV2 header = createHeaderV2(); + byte[] largeBaseEvent = new byte[128]; + for (int i = 0; i < 128; i++) { + largeBaseEvent[i] = (byte) i; + } + // when + header.setBaseEvent(largeBaseEvent); + // then + assertArrayEquals(largeBaseEvent, header.getBaseEvent()); + } + + @Test + void testSetBaseEventWithVeryLargeValueThrowsException() { + // given + BlockHeaderV2 header = createHeaderV2(); + byte[] veryLargeBaseEvent = new byte[1024]; + for (int i = 0; i < 1024; i++) { + veryLargeBaseEvent[i] = (byte) (i % 256); + } + // when / then + assertThrows(FieldMaxSizeBlockHeaderException.class, () -> { + header.setBaseEvent(veryLargeBaseEvent); + }); + } + + @Test + void testSetBaseEventWithSpecialBytes() { + // given + BlockHeaderV2 header = createHeaderV2(); + // Test with special byte values + byte[] specialBytes = new byte[]{0x00, (byte) 0xFF, (byte) 0x80, (byte) 0x7F, (byte) 0x01, (byte) 0xFE}; + // when + header.setBaseEvent(specialBytes); + // then + assertArrayEquals(specialBytes, header.getBaseEvent()); + } + + @Test + void testSetBaseEventWithZeroBytes() { + // given + BlockHeaderV2 header = createHeaderV2(); + byte[] zeroBytes = new byte[16]; + // when + header.setBaseEvent(zeroBytes); + // then + assertArrayEquals(zeroBytes, header.getBaseEvent()); + } + + @Test + void testSetBaseEventWithAllOnesBytes() { + // given + BlockHeaderV2 header = createHeaderV2(); + // Test with all ones bytes + byte[] onesBytes = new byte[16]; + for (int i = 0; i < 16; i++) { + onesBytes[i] = (byte) 0xFF; + } + // when + header.setBaseEvent(onesBytes); + // then + assertArrayEquals(onesBytes, header.getBaseEvent()); + } + + @Test + void testSetBaseEventMultipleTimes() { + // given + BlockHeaderV2 header = createHeaderV2(); + + // when + // Set baseEvent multiple times + byte[] firstBaseEvent = new byte[]{1, 2, 3}; + header.setBaseEvent(firstBaseEvent); + // then + assertArrayEquals(firstBaseEvent, header.getBaseEvent()); + + // when + byte[] secondBaseEvent = new byte[]{4, 5, 6, 7}; + header.setBaseEvent(secondBaseEvent); + // then + assertArrayEquals(secondBaseEvent, header.getBaseEvent()); + + // when + byte[] thirdBaseEvent = new byte[]{8, 9}; + header.setBaseEvent(thirdBaseEvent); + // then + assertArrayEquals(thirdBaseEvent, header.getBaseEvent()); + } + + @Test + void testSetBaseEventAfterSealing() { + // given + BlockHeaderV2 header = createHeaderV2(); + header.seal(); + + // when / then + assertThrows(SealedBlockHeaderException.class, () -> { + header.setBaseEvent(new byte[]{1, 2, 3}); + }); + } + + @Test + void testGetBaseEventReturnsCorrectValue() { + // given + BlockHeaderV2 header = createHeaderV2(); + byte[] expectedBaseEvent = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05}; + + // when + header.getExtension().setBaseEvent(expectedBaseEvent); + byte[] result = header.getBaseEvent(); + + // then + assertArrayEquals(expectedBaseEvent, result); + } + + @Test + void testBaseEventPersistenceAfterExtensionUpdate() { + // given + BlockHeaderV2 header = createHeaderV2(); + byte[] originalBaseEvent = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC}; + header.setBaseEvent(originalBaseEvent); + + // and a new extension with different baseEvent + BlockHeaderExtensionV2 newExtension = new BlockHeaderExtensionV2( + new byte[256], // logsBloom + new short[]{1, 2, 3}, // txExecutionSublistsEdges + new byte[]{(byte) 0xDD, (byte) 0xEE, (byte) 0xFF} // different baseEvent + ); + + // when + header.setExtension(newExtension); + byte[] result = header.getBaseEvent(); + + // then + assertArrayEquals(new byte[]{(byte) 0xDD, (byte) 0xEE, (byte) 0xFF}, result); + } + +} diff --git a/rskj-core/src/test/java/co/rsk/core/BlockTest.java b/rskj-core/src/test/java/co/rsk/core/BlockTest.java index bc2f9921d51..3b3a2bfe915 100644 --- a/rskj-core/src/test/java/co/rsk/core/BlockTest.java +++ b/rskj-core/src/test/java/co/rsk/core/BlockTest.java @@ -15,17 +15,21 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package co.rsk.core; - import co.rsk.blockchain.utils.BlockGenerator; import co.rsk.core.bc.BlockHashesHelper; import co.rsk.peg.PegTestUtils; import co.rsk.remasc.RemascTransaction; import org.ethereum.TestUtils; import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.BlockFactory; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.ImmutableTransaction; +import org.ethereum.core.Transaction; +import org.ethereum.core.exception.SealedBlockException; +import org.ethereum.core.exception.SealedBlockHeaderException; import org.ethereum.crypto.ECKey; import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; @@ -74,13 +78,13 @@ void testParseRemascTransaction() { .setEmptyUnclesHash() .setCoinbase(TestUtils.generateAddress("newHeaderAddress")) .setEmptyStateRoot() - .setTxTrieRoot( BlockHashesHelper.getTxTrieRoot(txs, true)) + .setTxTrieRoot(BlockHashesHelper.getTxTrieRoot(txs, true)) .setEmptyLogsBloom() .setEmptyReceiptTrieRoot() .setDifficultyFromBytes(BigInteger.ONE.toByteArray()) .setNumber(1) .setGasLimit(BigInteger.valueOf(4000000).toByteArray()) - .setGasUsed( 3000000L) + .setGasUsed(3000000L) .setTimestamp(100) .setEmptyExtraData() .setEmptyMergedMiningForkDetectionData() @@ -119,8 +123,7 @@ void sealedBlockSetStateRoot() { try { block.setStateRoot(new byte[32]); Assertions.fail(); - } - catch (SealedBlockException ex) { + } catch (SealedBlockException ex) { Assertions.assertEquals("Sealed block: trying to alter state root", ex.getMessage()); } } diff --git a/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java b/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java index 8e43e61b7b3..8748875c519 100644 --- a/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java +++ b/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java @@ -25,10 +25,15 @@ import co.rsk.core.RskAddress; import co.rsk.core.TransactionExecutorFactory; import co.rsk.core.types.bytes.Bytes; -import co.rsk.db.*; +import co.rsk.db.MutableTrieImpl; +import co.rsk.db.RepositoryLocator; +import co.rsk.db.RepositorySnapshot; +import co.rsk.db.StateRootHandler; +import co.rsk.db.StateRootsStoreImpl; import co.rsk.peg.BridgeSupportFactory; import co.rsk.peg.BtcBlockStoreWithCache.Factory; import co.rsk.peg.RepositoryBtcBlockStoreWithCache; +import co.rsk.peg.union.UnionBridgeStorageIndexKey; import co.rsk.remasc.RemascTransaction; import co.rsk.test.World; import co.rsk.test.dsl.DslParser; @@ -41,7 +46,19 @@ import org.ethereum.config.Constants; import org.ethereum.config.blockchain.upgrades.ActivationConfig; import org.ethereum.config.blockchain.upgrades.ConsensusRule; -import org.ethereum.core.*; +import org.ethereum.core.Account; +import org.ethereum.core.AccountState; +import org.ethereum.core.Block; +import org.ethereum.core.BlockFactory; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.BlockHeaderV2; +import org.ethereum.core.BlockTxSignatureCache; +import org.ethereum.core.Blockchain; +import org.ethereum.core.ReceivedTxSignatureCache; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionPool; +import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; import org.ethereum.crypto.cryptohash.Keccak256; @@ -55,25 +72,33 @@ import org.ethereum.net.server.Channel; import org.ethereum.util.RLP; import org.ethereum.util.RskTestFactory; +import org.ethereum.vm.DataWord; import org.ethereum.vm.GasCost; import org.ethereum.vm.PrecompiledContracts; import org.ethereum.vm.program.invoke.ProgramInvokeFactoryImpl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.junit.jupiter.api.io.TempDir; import java.math.BigInteger; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP126; import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP144; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; /** * Created by ajlopez on 29/07/2016. @@ -94,6 +119,57 @@ public class BlockExecutorTest { private TrieStore trieStore; private RepositorySnapshot repository; + public static Account createAccount(String seed, Repository repository, Coin balance) { + Account account = createAccount(seed); + repository.createAccount(account.getAddress()); + repository.addBalance(account.getAddress(), balance); + return account; + } + + public static Account createAccount(String seed) { + byte[] privateKeyBytes = HashUtil.keccak256(seed.getBytes()); + ECKey key = ECKey.fromPrivate(privateKeyBytes); + return new Account(key); + } + + private static Transaction createStrangeTransaction( + Account sender, Account receiver, + BigInteger value, BigInteger nonce, int strangeTransactionType) { + byte[] privateKeyBytes = sender.getEcKey().getPrivKeyBytes(); + byte[] to = receiver.getAddress().getBytes(); + byte[] gasLimitData = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(21000)); + byte[] valueData = BigIntegers.asUnsignedByteArray(value); + + if (strangeTransactionType == 0) { + to = new byte[1]; // one zero + to[0] = 127; + } else if (strangeTransactionType == 1) { + to = new byte[1024]; + java.util.Arrays.fill(to, (byte) -1); // fill with 0xff + } else { + // Bad encoding for value + byte[] newValueData = new byte[1024]; + System.arraycopy(valueData, 0, newValueData, 1024 - valueData.length, valueData.length); + valueData = newValueData; + } + + Transaction tx = Transaction.builder() + .nonce(nonce) + .gasPrice(BigInteger.ONE) + .gasLimit(gasLimitData) + .destination(to) + .value(valueData) + .build(); // no data + tx.sign(privateKeyBytes); + return tx; + } + + private static byte[] sha3(byte[] input) { + Keccak256 digest = new Keccak256(); + digest.update(Bytes.of(input)); + return digest.digest(); + } + @BeforeEach public void setUp() { RskTestFactory objects = new RskTestFactory(tempDir, config); @@ -342,7 +418,6 @@ void executeAndFillBlockWithOneTransaction(boolean activeRskip144) { Assertions.assertEquals(3000000, new BigInteger(1, block.getGasLimit()).longValue()); } - private Block createBlockWithExcludedTransaction(boolean withRemasc, boolean activeRskip144) { TrieStore trieStore = new TrieStoreImpl(new HashMapDB()); Repository repository = new MutableRepository(new MutableTrieImpl(trieStore, new Trie(trieStore))); @@ -509,7 +584,7 @@ void executeSequentiallyATransactionAndGasShouldBeSubtractedCorrectly(boolean ac Assertions.assertArrayEquals(new short[]{1}, blockResult.getTxEdges()); List transactionReceipts = blockResult.getTransactionReceipts(); - for (TransactionReceipt receipt: transactionReceipts) { + for (TransactionReceipt receipt : transactionReceipts) { Assertions.assertEquals(expectedAccumulatedGas, GasCost.toGas(receipt.getCumulativeGas())); } } @@ -540,7 +615,7 @@ void executeSequentiallyTenIndependentTxsAndThemShouldGoInBothSublists(boolean a long accumulatedGasUsed = 0L; short i = 0; short edgeIndex = 0; - for (TransactionReceipt receipt: transactionReceipts) { + for (TransactionReceipt receipt : transactionReceipts) { boolean isFromADifferentSublist = (edgeIndex < expectedEdges.length) && (i == expectedEdges[edgeIndex]); if (isFromADifferentSublist) { edgeIndex++; @@ -585,7 +660,7 @@ void whenParallelSublistsAreFullTheLastTxShouldGoToSequential(boolean activeRski Block parent = blockchain.getBestBlock(); int gasLimit = 21000; int numberOfTransactions = (int) (BlockUtils.getSublistGasLimit(parent, false, MIN_SEQUENTIAL_SET_GAS_LIMIT) / gasLimit); - short[] expectedEdges = new short[]{(short) numberOfTransactions, (short) (numberOfTransactions*2)}; + short[] expectedEdges = new short[]{(short) numberOfTransactions, (short) (numberOfTransactions * 2)}; int transactionsInSequential = 1; Block block = getBlockWithNIndependentTransactions(numberOfTransactions * Constants.getTransactionExecutionThreads() + transactionsInSequential, BigInteger.valueOf(gasLimit), false); List transactionsList = block.getTransactionsList(); @@ -599,7 +674,7 @@ void whenParallelSublistsAreFullTheLastTxShouldGoToSequential(boolean activeRski long accumulatedGasUsed = 0L; short i = 0; short edgeIndex = 0; - for (TransactionReceipt receipt: transactionReceipts) { + for (TransactionReceipt receipt : transactionReceipts) { accumulatedGasUsed += gasLimit; if ((edgeIndex < expectedEdges.length) && (i == expectedEdges[edgeIndex])) { @@ -629,7 +704,7 @@ void executeATxInSequentialAndBlockResultShouldTrackTheGasUsedInTheBlock(boolean Block block = getBlockWithNIndependentTransactions(totalTxsNumber, BigInteger.valueOf(gasLimit), false); BlockResult blockResult = executor.executeAndFill(block, parent.getHeader()); - Assertions.assertEquals(gasLimit * totalTxsNumber, blockResult.getGasUsed()); + Assertions.assertEquals((long) gasLimit * totalTxsNumber, blockResult.getGasUsed()); } @ParameterizedTest @@ -672,7 +747,6 @@ void withSequentialSublistFullRemascTxShouldFit(boolean activeRskip144) { Assertions.assertEquals(expectedNumberOfTx, blockResult.getExecutedTransactions().size()); } - @ParameterizedTest @ValueSource(booleans = {true, false}) void executeParallelBlocksWithDifferentSubsets(boolean activeRskip144) { @@ -727,6 +801,7 @@ void executeInvalidParallelBlockDueToCollision(boolean activeRskip144) { BlockResult result = executor.execute(null, 0, pBlock, parent.getHeader(), true, false, true); Assertions.assertEquals(BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT, result); } + @ParameterizedTest @ValueSource(booleans = {true, false}) void whenExecuteATxWithGasLimitExceedingSublistGasLimitShouldNotBeInlcuded(boolean activeRskip144) { @@ -813,13 +888,13 @@ void executeParallelBlockTwice(boolean activeRskip144) { Assertions.assertArrayEquals(block1.getHash().getBytes(), block2.getHash().getBytes()); } - private void testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition (int txAmount, short [] expectedSublistsEdges, Boolean activeRskip144) { + private void testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(int txAmount, short[] expectedSublistsEdges, Boolean activeRskip144) { Block block = getBlockWithNIndependentTransactions(txAmount, BigInteger.valueOf(21000), true); assertBlockResultHasTxEdgesAndRemascAtLastPosition(block, txAmount, expectedSublistsEdges, activeRskip144); } - private void assertBlockResultHasTxEdgesAndRemascAtLastPosition (Block block, int txAmount, short [] expectedSublistsEdges, Boolean activeRskip144) { + private void assertBlockResultHasTxEdgesAndRemascAtLastPosition(Block block, int txAmount, short[] expectedSublistsEdges, Boolean activeRskip144) { Block parent = blockchain.getBestBlock(); BlockExecutor executor = buildBlockExecutor(trieStore, activeRskip144, RSKIP_126_IS_ACTIVE); BlockResult blockResult = executor.executeAndFill(block, parent.getHeader()); @@ -833,35 +908,35 @@ private void assertBlockResultHasTxEdgesAndRemascAtLastPosition (Block block, in @ParameterizedTest @ValueSource(booleans = {true, false}) - void blockWithOnlyRemascShouldGoToSequentialSublist (boolean activeRskip144) { + void blockWithOnlyRemascShouldGoToSequentialSublist(boolean activeRskip144) { if (!activeRskip144) return; testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(0, new short[]{}, activeRskip144); } @ParameterizedTest @ValueSource(booleans = {true, false}) - void blockWithOneTxRemascShouldGoToSequentialSublist (boolean activeRskip144) { + void blockWithOneTxRemascShouldGoToSequentialSublist(boolean activeRskip144) { if (!activeRskip144) return; - testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(1, new short[]{ 1 }, activeRskip144); + testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(1, new short[]{1}, activeRskip144); } @ParameterizedTest @ValueSource(booleans = {true, false}) - void blockWithManyTxsRemascShouldGoToSequentialSublist (boolean activeRskip144) { + void blockWithManyTxsRemascShouldGoToSequentialSublist(boolean activeRskip144) { if (!activeRskip144) return; - testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(2, new short[]{ 1, 2 }, activeRskip144); + testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(2, new short[]{1, 2}, activeRskip144); } @ParameterizedTest @ValueSource(booleans = {true, false}) - void blockWithMoreThanThreadsTxsRemascShouldGoToSequentialSublist (boolean activeRskip144) { + void blockWithMoreThanThreadsTxsRemascShouldGoToSequentialSublist(boolean activeRskip144) { if (!activeRskip144) return; - testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(3, new short[]{ 2, 3 }, activeRskip144); + testBlockWithTxTxEdgesMatchAndRemascTxIsAtLastPosition(3, new short[]{2, 3}, activeRskip144); } @ParameterizedTest @ValueSource(booleans = {true, false}) - void blockWithExcludedTransactionHasRemascInSequentialSublist (boolean activeRskip144) { + void blockWithExcludedTransactionHasRemascInSequentialSublist(boolean activeRskip144) { if (!activeRskip144) return; Block block = createBlockWithExcludedTransaction(true, activeRskip144); assertBlockResultHasTxEdgesAndRemascAtLastPosition(block, 0, new short[]{}, activeRskip144); @@ -896,7 +971,7 @@ void validateStateRootWithRskip126DisabledAndInvalidStateRoot(boolean activeRski Trie trie = new Trie(trieStore); Block block = new BlockGenerator(Constants.regtest(), activationConfig).getBlock(1); - block.setStateRoot(new byte[] { 1, 2, 3, 4 }); + block.setStateRoot(new byte[]{1, 2, 3, 4}); BlockResult blockResult = new BlockResult(block, Collections.emptyList(), Collections.emptyList(), new short[0], 0, Coin.ZERO, trie); @@ -1178,6 +1253,8 @@ private Block getBlockWithTwoDependentTransactions(short[] edges) { ); } + /// /////////////////////////////////////////// + // Testing strange Txs private Block getBlockWithTenTransactions(short[] edges) { int nTxs = 10; int nAccounts = nTxs * 2; @@ -1266,23 +1343,7 @@ private Block getBlockWithNIndependentTransactions(int numberOfTxs, BigInteger t ); } - public static Account createAccount(String seed, Repository repository, Coin balance) { - Account account = createAccount(seed); - repository.createAccount(account.getAddress()); - repository.addBalance(account.getAddress(), balance); - return account; - } - - public static Account createAccount(String seed) { - byte[] privateKeyBytes = HashUtil.keccak256(seed.getBytes()); - ECKey key = ECKey.fromPrivate(privateKeyBytes); - Account account = new Account(key); - return account; - } - - ////////////////////////////////////////////// - // Testing strange Txs - ///////////////////////////////////////////// + /// ////////////////////////////////////////// @ParameterizedTest @ValueSource(booleans = {true, false}) void executeBlocksWithOneStrangeTransactions1(Boolean activeRskip144) { @@ -1413,44 +1474,6 @@ private byte[] calculateTxTrieRoot(List transactions, long blockNum ); } - private static Transaction createStrangeTransaction( - Account sender, Account receiver, - BigInteger value, BigInteger nonce, int strangeTransactionType) { - byte[] privateKeyBytes = sender.getEcKey().getPrivKeyBytes(); - byte[] to = receiver.getAddress().getBytes(); - byte[] gasLimitData = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(21000)); - byte[] valueData = BigIntegers.asUnsignedByteArray(value); - - if (strangeTransactionType == 0) { - to = new byte[1]; // one zero - to[0] = 127; - } else if (strangeTransactionType == 1) { - to = new byte[1024]; - java.util.Arrays.fill(to, (byte) -1); // fill with 0xff - } else { - // Bad encoding for value - byte[] newValueData = new byte[1024]; - System.arraycopy(valueData, 0, newValueData, 1024 - valueData.length, valueData.length); - valueData = newValueData; - } - - Transaction tx = Transaction.builder() - .nonce(nonce) - .gasPrice(BigInteger.ONE) - .gasLimit(gasLimitData) - .destination(to) - .value(valueData) - .build(); // no data - tx.sign(privateKeyBytes); - return tx; - } - - private static byte[] sha3(byte[] input) { - Keccak256 digest = new Keccak256(); - digest.update(Bytes.of(input)); - return digest.digest(); - } - private BlockExecutor buildBlockExecutor(TrieStore store, Boolean activeRskip144, boolean rskip126IsActive) { return buildBlockExecutor(store, config, activeRskip144, rskip126IsActive); } @@ -1486,6 +1509,230 @@ private BlockExecutor buildBlockExecutor(TrieStore store, RskSystemProperties co cfg); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void executeBlockWithContractEmittingLogsUsingDsl(boolean isRSKIP144Activated) throws Exception { + TestSystemProperties config = new TestSystemProperties(rawConfig -> + rawConfig.withValue("blockchain.config.consensusRules.rskip144", ConfigValueFactory.fromAnyRef(isRSKIP144Activated ? 1 : -1)) + ); + + // Use DSL to set up the blockchain with log-emitting contract + DslParser parser = DslParser.fromResource("dsl/log_indexes_pte.txt"); + World world = new World(config); + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + // Get the blocks created by DSL + Block deployBlock = world.getBlockByName("b01"); + Block callBlock = world.getBlockByName("b02"); // Now contains all three transactions + + Assertions.assertNotNull(deployBlock); + Assertions.assertNotNull(callBlock); + + // Verify the deployment transaction was successful + TransactionReceipt deployReceipt = world.getTransactionReceiptByName("txDeploy"); + Assertions.assertNotNull(deployReceipt); + Assertions.assertTrue(deployReceipt.isSuccessful()); + + // Verify all three transactions are in the same block + Assertions.assertEquals(3, callBlock.getTransactionsList().size(), "Block b02 should contain 3 transactions"); + + // Verify the first function call transaction and its logs + TransactionReceipt callReceipt = world.getTransactionReceiptByName("txCallEmit"); + Assertions.assertNotNull(callReceipt); + Assertions.assertTrue(callReceipt.isSuccessful()); + + // Most importantly: verify that logs were emitted + Assertions.assertNotNull(callReceipt.getLogInfoList()); + Assertions.assertFalse(callReceipt.getLogInfoList().isEmpty()); + Assertions.assertEquals(2, callReceipt.getLogInfoList().size(), "Should emit exactly 2 logs"); + + // Verify the second function call transaction and its logs + TransactionReceipt callReceipt2 = world.getTransactionReceiptByName("txCallEmit2"); + Assertions.assertNotNull(callReceipt2); + Assertions.assertTrue(callReceipt2.isSuccessful()); + Assertions.assertNotNull(callReceipt2.getLogInfoList()); + Assertions.assertFalse(callReceipt2.getLogInfoList().isEmpty()); + Assertions.assertEquals(2, callReceipt2.getLogInfoList().size(), "Second call should emit exactly 2 logs"); + + // Verify the third function call transaction and its logs + TransactionReceipt callReceipt3 = world.getTransactionReceiptByName("txCallEmit3"); + Assertions.assertNotNull(callReceipt3); + Assertions.assertTrue(callReceipt3.isSuccessful()); + Assertions.assertNotNull(callReceipt3.getLogInfoList()); + Assertions.assertFalse(callReceipt3.getLogInfoList().isEmpty()); + Assertions.assertEquals(2, callReceipt3.getLogInfoList().size(), "Third call should emit exactly 2 logs"); + + // Verify logs from first call contain the expected contract address + org.ethereum.vm.LogInfo logInfo1 = callReceipt.getLogInfoList().get(0); + org.ethereum.vm.LogInfo logInfo2 = callReceipt.getLogInfoList().get(1); + + // Verify logs from second call contain the expected contract address + org.ethereum.vm.LogInfo logInfo3 = callReceipt2.getLogInfoList().get(0); + org.ethereum.vm.LogInfo logInfo4 = callReceipt2.getLogInfoList().get(1); + + // Verify logs from third call contain the expected contract address + org.ethereum.vm.LogInfo logInfo5 = callReceipt3.getLogInfoList().get(0); + org.ethereum.vm.LogInfo logInfo6 = callReceipt3.getLogInfoList().get(1); + + // Verify that all logs from the same block have correct sequential log indexes + // Since all transactions are in the same block, log indexes should be sequential across all transactions: + // Transaction 1 (txCallEmit) should have logs at indexes 0, 1 + // Transaction 2 (txCallEmit2) should have logs at indexes 2, 3 + // Transaction 3 (txCallEmit3) should have logs at indexes 4, 5 + Assertions.assertEquals(0, logInfo1.getLogIndex(), "First log should have index 0"); + Assertions.assertEquals(1, logInfo2.getLogIndex(), "Second log should have index 1"); + Assertions.assertEquals(2, logInfo3.getLogIndex(), "Third log should have index 2"); + Assertions.assertEquals(3, logInfo4.getLogIndex(), "Fourth log should have index 3"); + Assertions.assertEquals(4, logInfo5.getLogIndex(), "Fifth log should have index 4"); + Assertions.assertEquals(5, logInfo6.getLogIndex(), "Sixth log should have index 5"); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void executeBlockWithLogEmittingContractAndVerifyLogIndexes(boolean isRSKIP144Activated) throws Exception { + TestSystemProperties config = new TestSystemProperties(rawConfig -> + rawConfig.withValue("blockchain.config.consensusRules.rskip144", ConfigValueFactory.fromAnyRef(isRSKIP144Activated ? 1 : -1)) + ); + + // Now use DSL to create a block with log-emitting contract for log index verification + DslParser parser = DslParser.fromResource("dsl/log_indexes_pte.txt"); + World world = new World(config); + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + // Get the blocks created by DSL + Block deployBlock = world.getBlockByName("b01"); + Block callBlock1 = world.getBlockByName("b02"); + Block callBlock2 = world.getBlockByName("b03"); + + Assertions.assertNotNull(deployBlock); + Assertions.assertNotNull(callBlock1); + Assertions.assertNotNull(callBlock2); + + BlockResult result = world.getBlockExecutor().execute(null, 0, callBlock2, callBlock1.getHeader(), false, false, false); + + List transactionReceipts = result.getTransactionReceipts(); + + Assertions.assertNotNull(transactionReceipts); + + Assertions.assertEquals(0, transactionReceipts.get(0).getLogInfoList().get(0).getLogIndex(), "First log should have index 0"); + Assertions.assertEquals(1, transactionReceipts.get(0).getLogInfoList().get(1).getLogIndex(), "Second log should have index 1"); + Assertions.assertEquals(2, transactionReceipts.get(1).getLogInfoList().get(0).getLogIndex(), "Third log should have index 2"); + Assertions.assertEquals(3, transactionReceipts.get(1).getLogInfoList().get(1).getLogIndex(), "Fourth log should have index 3"); + Assertions.assertEquals(4, transactionReceipts.get(2).getLogInfoList().get(0).getLogIndex(), "Fifth log should have index 4"); + Assertions.assertEquals(5, transactionReceipts.get(2).getLogInfoList().get(1).getLogIndex(), "Sixth log should have index 5"); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void executeBlockWithBaseEventFromRepository(boolean activeRskip535) { + //given + doReturn(activeRskip535).when(activationConfig).isActive(eq(ConsensusRule.RSKIP535), anyLong()); + + Block parent = blockchain.getBestBlock(); + Block block = new BlockGenerator(Constants.regtest(), activationConfig).createChildBlock(parent); + + // Only test BlockHeaderV2 when RSKIP535 is active, as that's when it's created + if (activeRskip535) { + // Ensure we have a BlockHeaderV2 + Assertions.assertInstanceOf(BlockHeaderV2.class, block.getHeader(), "Block should have BlockHeaderV2 header when RSKIP535 is active"); + + // Create a real repository with the expected baseEvent value + byte[] expectedBaseEvent = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05}; + Repository repository = new MutableRepository(trieStore, new Trie(trieStore)); + repository.startTracking(); + + // Set the baseEvent in the repository at the bridge address + RskAddress bridgeAddress = PrecompiledContracts.BRIDGE_ADDR; + DataWord baseEventKey = UnionBridgeStorageIndexKey.BASE_EVENT.getKey(); + repository.addStorageBytes(bridgeAddress, baseEventKey, expectedBaseEvent); + repository.commit(); + + // Update the parent block's state root to include our repository changes + parent.setStateRoot(repository.getRoot()); + + // when + BlockExecutor executor = buildBlockExecutor(trieStore, true, RSKIP_126_IS_ACTIVE); + + // then + BlockResult result = executor.executeAndFill(block, parent.getHeader()); + Assertions.assertNotNull(result); + Assertions.assertNotSame(BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT, result); + Assertions.assertArrayEquals(expectedBaseEvent, result.getBlock().getBaseEvent(), + "BaseEvent should be set from repository when RSKIP535 is active"); + } else { + // when RSKIP535 is not active, we should have a different header type + Assertions.assertFalse(block.getHeader() instanceof BlockHeaderV2, + "Block should not have BlockHeaderV2 header when RSKIP535 is not active"); + + BlockExecutor executor = buildBlockExecutor(trieStore, true, RSKIP_126_IS_ACTIVE); + BlockResult result = executor.executeAndFill(block, parent.getHeader()); + + // then + Assertions.assertNotNull(result); + Assertions.assertNotSame(BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT, result); + Assertions.assertNull(result.getBlock().getBaseEvent()); + } + } + + @Test + void executeBlockWithBaseEventFromRepository_WhenRepositoryIsNull() { + // given + // Create a block with BlockHeaderV2 + Block parent = blockchain.getBestBlock(); + Block block = new BlockGenerator(Constants.regtest(), activationConfig).createChildBlock(parent); + + // Create a real repository but don't set any baseEvent + Repository repository = new MutableRepository(trieStore, new Trie(trieStore)); + repository.startTracking(); + repository.commit(); + parent.setStateRoot(repository.getRoot()); + + BlockExecutor executor = buildBlockExecutor(trieStore, true, RSKIP_126_IS_ACTIVE); + + // when + BlockResult result = executor.executeAndFill(block, parent.getHeader()); + + // then + Assertions.assertNotNull(result); + Assertions.assertNotSame(BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT, result); + Assertions.assertArrayEquals(EMPTY_BYTE_ARRAY, result.getBlock().getBaseEvent(), + "BaseEvent should be set to empty array when repository has no baseEvent"); + } + + @Test + void executeBlockWithBaseEventFromRepository_WhenRepositoryThrowsException() { + // given + doReturn(true).when(activationConfig).isActive(eq(ConsensusRule.RSKIP535), anyLong()); + + Block parent = blockchain.getBestBlock(); + Block block = new BlockGenerator(Constants.regtest(), activationConfig).createChildBlock(parent); + + // Create a real repository with baseEvent data + Repository repository = new MutableRepository(trieStore, new Trie(trieStore)); + repository.startTracking(); + RskAddress bridgeAddress = PrecompiledContracts.BRIDGE_ADDR; + DataWord baseEventKey = UnionBridgeStorageIndexKey.BASE_EVENT.getKey(); + byte[] expectedBaseEvent = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05}; + repository.addStorageBytes(bridgeAddress, baseEventKey, expectedBaseEvent); + repository.commit(); + parent.setStateRoot(repository.getRoot()); + + // Create BlockExecutor with real repository + BlockExecutor executor = buildBlockExecutor(trieStore, true, RSKIP_126_IS_ACTIVE); + + // when + BlockResult result = executor.executeAndFill(block, parent.getHeader()); + + // then + Assertions.assertNotNull(result); + Assertions.assertNotSame(BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT, result); + // When repository has baseEvent, it should be retrieved correctly + Assertions.assertArrayEquals(expectedBaseEvent, result.getBlock().getBaseEvent(), + "BaseEvent should be retrieved from repository when available"); + } + public static class TestObjects { private final TrieStore trieStore; private final Block block; @@ -1619,119 +1866,4 @@ public void onLongSyncStarted() { } } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void executeBlockWithContractEmittingLogsUsingDsl(boolean isRSKIP144Activated) throws Exception { - TestSystemProperties config = new TestSystemProperties(rawConfig -> - rawConfig.withValue("blockchain.config.consensusRules.rskip144", ConfigValueFactory.fromAnyRef(isRSKIP144Activated ? 1 : -1)) - ); - - // Use DSL to set up the blockchain with log-emitting contract - DslParser parser = DslParser.fromResource("dsl/log_indexes_pte.txt"); - World world = new World(config); - WorldDslProcessor processor = new WorldDslProcessor(world); - processor.processCommands(parser); - - // Get the blocks created by DSL - Block deployBlock = world.getBlockByName("b01"); - Block callBlock = world.getBlockByName("b02"); // Now contains all three transactions - - Assertions.assertNotNull(deployBlock); - Assertions.assertNotNull(callBlock); - - // Verify the deployment transaction was successful - TransactionReceipt deployReceipt = world.getTransactionReceiptByName("txDeploy"); - Assertions.assertNotNull(deployReceipt); - Assertions.assertTrue(deployReceipt.isSuccessful()); - - // Verify all three transactions are in the same block - Assertions.assertEquals(3, callBlock.getTransactionsList().size(), "Block b02 should contain 3 transactions"); - - // Verify the first function call transaction and its logs - TransactionReceipt callReceipt = world.getTransactionReceiptByName("txCallEmit"); - Assertions.assertNotNull(callReceipt); - Assertions.assertTrue(callReceipt.isSuccessful()); - - // Most importantly: verify that logs were emitted - Assertions.assertNotNull(callReceipt.getLogInfoList()); - Assertions.assertFalse(callReceipt.getLogInfoList().isEmpty()); - Assertions.assertEquals(2, callReceipt.getLogInfoList().size(), "Should emit exactly 2 logs"); - - // Verify the second function call transaction and its logs - TransactionReceipt callReceipt2 = world.getTransactionReceiptByName("txCallEmit2"); - Assertions.assertNotNull(callReceipt2); - Assertions.assertTrue(callReceipt2.isSuccessful()); - Assertions.assertNotNull(callReceipt2.getLogInfoList()); - Assertions.assertFalse(callReceipt2.getLogInfoList().isEmpty()); - Assertions.assertEquals(2, callReceipt2.getLogInfoList().size(), "Second call should emit exactly 2 logs"); - - // Verify the third function call transaction and its logs - TransactionReceipt callReceipt3 = world.getTransactionReceiptByName("txCallEmit3"); - Assertions.assertNotNull(callReceipt3); - Assertions.assertTrue(callReceipt3.isSuccessful()); - Assertions.assertNotNull(callReceipt3.getLogInfoList()); - Assertions.assertFalse(callReceipt3.getLogInfoList().isEmpty()); - Assertions.assertEquals(2, callReceipt3.getLogInfoList().size(), "Third call should emit exactly 2 logs"); - - // Verify logs from first call contain the expected contract address - org.ethereum.vm.LogInfo logInfo1 = callReceipt.getLogInfoList().get(0); - org.ethereum.vm.LogInfo logInfo2 = callReceipt.getLogInfoList().get(1); - - // Verify logs from second call contain the expected contract address - org.ethereum.vm.LogInfo logInfo3 = callReceipt2.getLogInfoList().get(0); - org.ethereum.vm.LogInfo logInfo4 = callReceipt2.getLogInfoList().get(1); - - // Verify logs from third call contain the expected contract address - org.ethereum.vm.LogInfo logInfo5 = callReceipt3.getLogInfoList().get(0); - org.ethereum.vm.LogInfo logInfo6 = callReceipt3.getLogInfoList().get(1); - - // Verify that all logs from the same block have correct sequential log indexes - // Since all transactions are in the same block, log indexes should be sequential across all transactions: - // Transaction 1 (txCallEmit) should have logs at indexes 0, 1 - // Transaction 2 (txCallEmit2) should have logs at indexes 2, 3 - // Transaction 3 (txCallEmit3) should have logs at indexes 4, 5 - Assertions.assertEquals(0, logInfo1.getLogIndex(), "First log should have index 0"); - Assertions.assertEquals(1, logInfo2.getLogIndex(), "Second log should have index 1"); - Assertions.assertEquals(2, logInfo3.getLogIndex(), "Third log should have index 2"); - Assertions.assertEquals(3, logInfo4.getLogIndex(), "Fourth log should have index 3"); - Assertions.assertEquals(4, logInfo5.getLogIndex(), "Fifth log should have index 4"); - Assertions.assertEquals(5, logInfo6.getLogIndex(), "Sixth log should have index 5"); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void executeBlockWithLogEmittingContractAndVerifyLogIndexes(boolean isRSKIP144Activated) throws Exception { - TestSystemProperties config = new TestSystemProperties(rawConfig -> - rawConfig.withValue("blockchain.config.consensusRules.rskip144", ConfigValueFactory.fromAnyRef(isRSKIP144Activated ? 1 : -1)) - ); - - // Now use DSL to create a block with log-emitting contract for log index verification - DslParser parser = DslParser.fromResource("dsl/log_indexes_pte.txt"); - World world = new World(config); - WorldDslProcessor processor = new WorldDslProcessor(world); - processor.processCommands(parser); - - // Get the blocks created by DSL - Block deployBlock = world.getBlockByName("b01"); - Block callBlock1 = world.getBlockByName("b02"); - Block callBlock2 = world.getBlockByName("b03"); - - Assertions.assertNotNull(deployBlock); - Assertions.assertNotNull(callBlock1); - Assertions.assertNotNull(callBlock2); - - BlockResult result = world.getBlockExecutor().execute(null, 0, callBlock2, callBlock1.getHeader(), false, false, false); - - List transactionReceipts = result.getTransactionReceipts(); - - Assertions.assertNotNull(transactionReceipts); - - Assertions.assertEquals(0, transactionReceipts.get(0).getLogInfoList().get(0).getLogIndex(), "First log should have index 0"); - Assertions.assertEquals(1, transactionReceipts.get(0).getLogInfoList().get(1).getLogIndex(), "Second log should have index 1"); - Assertions.assertEquals(2, transactionReceipts.get(1).getLogInfoList().get(0).getLogIndex(), "Third log should have index 2"); - Assertions.assertEquals(3, transactionReceipts.get(1).getLogInfoList().get(1).getLogIndex(), "Fourth log should have index 3"); - Assertions.assertEquals(4, transactionReceipts.get(2).getLogInfoList().get(0).getLogIndex(), "Fifth log should have index 4"); - Assertions.assertEquals(5, transactionReceipts.get(2).getLogInfoList().get(1).getLogIndex(), "Sixth log should have index 5"); - } } diff --git a/rskj-core/src/test/java/co/rsk/mine/BlockToMineBuilderTest.java b/rskj-core/src/test/java/co/rsk/mine/BlockToMineBuilderTest.java index 1e57d56cdab..3a221107439 100644 --- a/rskj-core/src/test/java/co/rsk/mine/BlockToMineBuilderTest.java +++ b/rskj-core/src/test/java/co/rsk/mine/BlockToMineBuilderTest.java @@ -233,7 +233,7 @@ private BlockHeader createBlockHeader() { EMPTY_BYTE_ARRAY, 0L, 0L, EMPTY_BYTE_ARRAY, Coin.ZERO, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, Coin.ZERO, 0, false, true, false, - new byte[0], null + new byte[0], new byte[0],null ); } } diff --git a/rskj-core/src/test/java/co/rsk/mine/gas/provider/HttpGetMinGasPriceProviderTest.java b/rskj-core/src/test/java/co/rsk/mine/gas/provider/HttpGetMinGasPriceProviderTest.java index 9255bbfd61b..8ed7b260c3d 100644 --- a/rskj-core/src/test/java/co/rsk/mine/gas/provider/HttpGetMinGasPriceProviderTest.java +++ b/rskj-core/src/test/java/co/rsk/mine/gas/provider/HttpGetMinGasPriceProviderTest.java @@ -21,6 +21,7 @@ import co.rsk.config.mining.StableMinGasPriceSystemConfig; import co.rsk.net.http.HttpException; import co.rsk.net.http.SimpleHttpClient; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -28,21 +29,37 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class HttpGetMinGasPriceProviderTest { + private final MinGasPriceProvider fallbackProvider = mock(MinGasPriceProvider.class); + private final SimpleHttpClient httpClient = mock(SimpleHttpClient.class); + private StableMinGasPriceSystemConfig config; + + @BeforeEach + void setUp() { + reset(fallbackProvider, httpClient); + config = createStableMinGasPriceSystemConfig(); + } + @Test void returnsMappedPriceFromWebClient() throws HttpException { - MinGasPriceProvider fallbackProvider = mock(MinGasPriceProvider.class); + // given when(fallbackProvider.getType()).thenReturn(MinGasPriceProviderType.FIXED); - - SimpleHttpClient httpClient = mock(SimpleHttpClient.class); when(httpClient.doGet(anyString())).thenReturn("{\"bitcoin\":{\"usd\":10000}}"); - StableMinGasPriceSystemConfig config = createStableMinGasPriceSystemConfig(); + + // when HttpGetMinGasPriceProvider provider = new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient); + // then Optional result = provider.getBtcExchangeRate(); verify(fallbackProvider, times(0)).getMinGasPrice(); assertTrue(result.isPresent()); @@ -51,69 +68,66 @@ void returnsMappedPriceFromWebClient() throws HttpException { @Test void whenRequestingTheValueTwiceCachedValueIsUsed() throws HttpException { - MinGasPriceProvider fallbackProvider = mock(MinGasPriceProvider.class); + // given when(fallbackProvider.getType()).thenReturn(MinGasPriceProviderType.FIXED); - - SimpleHttpClient httpClient = mock(SimpleHttpClient.class); when(httpClient.doGet(anyString())).thenReturn("{\"bitcoin\":{\"usd\":10000}}"); - - StableMinGasPriceSystemConfig config = createStableMinGasPriceSystemConfig(); HttpGetMinGasPriceProvider provider = spy(new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient)); + // when // First call should fetch and cache the value - provider.getMinGasPrice(); - // Second call should use the cached value provider.getMinGasPrice(true); + // Second call should use the cached value + provider.getMinGasPrice(); + // then verify(httpClient, times(1)).doGet(anyString()); verify(provider, times(2)).getMinGasPrice(anyBoolean()); } @Test void whenEmptyResponseReturnsFallbackProvider() throws HttpException { - MinGasPriceProvider fallbackProvider = mock(MinGasPriceProvider.class); + // given when(fallbackProvider.getMinGasPrice()).thenReturn(10L); when(fallbackProvider.getType()).thenReturn(MinGasPriceProviderType.FIXED); - - SimpleHttpClient httpClient = mock(SimpleHttpClient.class); when(httpClient.doGet(anyString())).thenReturn(""); - StableMinGasPriceSystemConfig config = createStableMinGasPriceSystemConfig(); - HttpGetMinGasPriceProvider provider = new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient); + // when + HttpGetMinGasPriceProvider provider = new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient); Long result = provider.getMinGasPrice(true); + + // then verify(fallbackProvider, times(1)).getMinGasPrice(); assertEquals(10L, result); } @Test void whenErrorReturnsFallbackProvider() throws HttpException { - MinGasPriceProvider fallbackProvider = mock(MinGasPriceProvider.class); + // given when(fallbackProvider.getMinGasPrice()).thenReturn(10L); when(fallbackProvider.getType()).thenReturn(MinGasPriceProviderType.FIXED); - - SimpleHttpClient httpClient = mock(SimpleHttpClient.class); when(httpClient.doGet(anyString())).thenThrow(new HttpException("Error")); - StableMinGasPriceSystemConfig config = createStableMinGasPriceSystemConfig(); - HttpGetMinGasPriceProvider provider = new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient); + // when + HttpGetMinGasPriceProvider provider = new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient); Long result = provider.getMinGasPrice(true); + + // then verify(fallbackProvider, times(1)).getMinGasPrice(); assertEquals(10L, result); } @Test void whenPathIsWrongReturnsFallBack() throws HttpException { - MinGasPriceProvider fallbackProvider = mock(MinGasPriceProvider.class); + // given when(fallbackProvider.getMinGasPrice()).thenReturn(10L); when(fallbackProvider.getType()).thenReturn(MinGasPriceProviderType.FIXED); - - SimpleHttpClient httpClient = mock(SimpleHttpClient.class); when(httpClient.doGet(anyString())).thenReturn("{\"btc\":{\"usd\":10000}}"); - StableMinGasPriceSystemConfig config = createStableMinGasPriceSystemConfig(); - HttpGetMinGasPriceProvider provider = new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient); + // when + HttpGetMinGasPriceProvider provider = new HttpGetMinGasPriceProvider(config, fallbackProvider, httpClient); Long result = provider.getMinGasPrice(true); + // then verify(fallbackProvider, times(1)).getMinGasPrice(); assertEquals(10L, result); } @@ -124,6 +138,8 @@ private StableMinGasPriceSystemConfig createStableMinGasPriceSystemConfig() { when(config.getHttpGetConfig()).thenReturn(webConfig); when(config.getMinStableGasPrice()).thenReturn(4265280000000L); when(config.getRefreshRate()).thenReturn(Duration.ofSeconds(30)); + when(config.getMinValidPrice()).thenReturn(1000L); + when(config.getMaxValidPrice()).thenReturn(100000L); return config; } diff --git a/rskj-core/src/test/java/co/rsk/net/messages/MessageTest.java b/rskj-core/src/test/java/co/rsk/net/messages/MessageTest.java index b067aabf211..53e3396f65c 100644 --- a/rskj-core/src/test/java/co/rsk/net/messages/MessageTest.java +++ b/rskj-core/src/test/java/co/rsk/net/messages/MessageTest.java @@ -220,8 +220,8 @@ private BlockHeadersResponseMessage testBlockHeadersResponseMessage(BlockFactory } @Test - void encodeDecodeBlockHeadersResponseMessageWithoutRSKIP351() { - ActivationConfig activationConfig = ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351); + void encodeDecodeBlockHeadersResponseMessageWithoutRSKIP351AndRSKIP535() { + ActivationConfig activationConfig = ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351, ConsensusRule.RSKIP535); BlockFactory anotherBlockFactory = new BlockFactory(activationConfig); BlockMiner blockMiner = new BlockMiner(activationConfig); diff --git a/rskj-core/src/test/java/co/rsk/peg/BridgeEventsTestUtils.java b/rskj-core/src/test/java/co/rsk/peg/BridgeEventsTestUtils.java index 6852e7985ea..c704a0f43da 100644 --- a/rskj-core/src/test/java/co/rsk/peg/BridgeEventsTestUtils.java +++ b/rskj-core/src/test/java/co/rsk/peg/BridgeEventsTestUtils.java @@ -10,13 +10,13 @@ public class BridgeEventsTestUtils { - public static List getEncodedTopics(CallTransaction.Function bridgeEvent, Object... args) { - byte[][] encodedTopicsInBytes = bridgeEvent.encodeEventTopics(args); + public static List getEncodedTopics(CallTransaction.Function baseEvent, Object... args) { + byte[][] encodedTopicsInBytes = baseEvent.encodeEventTopics(args); return LogInfo.byteArrayToList(encodedTopicsInBytes); } - public static byte[] getEncodedData(CallTransaction.Function bridgeEvent, Object... args) { - return bridgeEvent.encodeEventData(args); + public static byte[] getEncodedData(CallTransaction.Function baseEvent, Object... args) { + return baseEvent.encodeEventData(args); } public static Optional getLogsTopics(List logs, List expectedTopics) { diff --git a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java index 57b3cd06f18..ef1a06494d3 100644 --- a/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java +++ b/rskj-core/src/test/java/co/rsk/peg/utils/BridgeEventLoggerImplTest.java @@ -17,42 +17,68 @@ */ package co.rsk.peg.utils; -import static co.rsk.RskTestUtils.createRskBlock; -import static co.rsk.peg.bitcoin.BitcoinTestUtils.coinListOf; -import static co.rsk.peg.bitcoin.BitcoinTestUtils.flatKeysAsByteArray; -import static org.junit.jupiter.api.Assertions.*; - import co.rsk.RskTestUtils; -import co.rsk.bitcoinj.core.*; +import co.rsk.bitcoinj.core.Address; +import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.BtcTransaction; +import co.rsk.bitcoinj.core.Coin; +import co.rsk.bitcoinj.core.NetworkParameters; +import co.rsk.bitcoinj.core.Sha256Hash; +import co.rsk.bitcoinj.core.TransactionWitness; import co.rsk.bitcoinj.script.ScriptBuilder; import co.rsk.core.RskAddress; import co.rsk.crypto.Keccak256; import co.rsk.peg.BridgeEvents; -import co.rsk.peg.bitcoin.*; +import co.rsk.peg.bitcoin.BitcoinTestUtils; +import co.rsk.peg.bitcoin.InvalidOutpointValueException; +import co.rsk.peg.bitcoin.UtxoUtils; import co.rsk.peg.constants.BridgeConstants; import co.rsk.peg.constants.BridgeMainNetConstants; -import co.rsk.peg.federation.*; +import co.rsk.peg.federation.Federation; +import co.rsk.peg.federation.FederationArgs; +import co.rsk.peg.federation.FederationFactory; +import co.rsk.peg.federation.FederationMember; +import co.rsk.peg.federation.FederationTestUtils; +import co.rsk.peg.federation.P2shErpFederationBuilder; import co.rsk.peg.federation.constants.FederationConstants; import co.rsk.peg.pegin.RejectedPeginReason; import co.rsk.peg.union.constants.UnionBridgeConstants; import co.rsk.peg.union.constants.UnionBridgeMainNetConstants; import co.rsk.peg.union.constants.UnionBridgeRegTestConstants; import co.rsk.peg.union.constants.UnionBridgeTestNetConstants; -import java.math.BigInteger; -import java.time.Instant; -import java.util.*; -import java.util.stream.Stream; import org.bouncycastle.util.encoders.Hex; -import org.ethereum.config.blockchain.upgrades.*; -import org.ethereum.core.*; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest; +import org.ethereum.core.Block; +import org.ethereum.core.CallTransaction; import org.ethereum.core.CallTransaction.Function; import org.ethereum.crypto.ECKey; import org.ethereum.vm.DataWord; import org.ethereum.vm.LogInfo; import org.ethereum.vm.PrecompiledContracts; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.*; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.math.BigInteger; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; + +import static co.rsk.RskTestUtils.createRskBlock; +import static co.rsk.peg.bitcoin.BitcoinTestUtils.coinListOf; +import static co.rsk.peg.bitcoin.BitcoinTestUtils.flatKeysAsByteArray; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; class BridgeEventLoggerImplTest { private static final RskAddress BRIDGE_ADDRESS = PrecompiledContracts.BRIDGE_ADDR; diff --git a/rskj-core/src/test/java/co/rsk/remasc/RemascTestRunner.java b/rskj-core/src/test/java/co/rsk/remasc/RemascTestRunner.java index be2ace22948..f4ef11a0ae5 100644 --- a/rskj-core/src/test/java/co/rsk/remasc/RemascTestRunner.java +++ b/rskj-core/src/test/java/co/rsk/remasc/RemascTestRunner.java @@ -294,7 +294,7 @@ public HardcodedHashBlockHeader( HashUtil.EMPTY_TRIE_HASH, new Bloom().getData(), finalDifficulty, parentBlock.getNumber() + 1, parentBlock.getGasLimit(), parentBlock.getGasUsed(), parentBlock.getTimestamp(), new byte[0], paidFees, null, null, null, new byte[0], - Coin.valueOf(10), uncles.size(), false, true, false, new byte[0], null + Coin.valueOf(10), uncles.size(), false, true, false, new byte[0], new byte[0],null ); this.blockHash = blockHash; } diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/eth/subscribe/EthSubscriptionNotificationTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/eth/subscribe/EthSubscriptionNotificationTest.java index 970144db69f..5280ab63285 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/eth/subscribe/EthSubscriptionNotificationTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/eth/subscribe/EthSubscriptionNotificationTest.java @@ -33,9 +33,9 @@ class EthSubscriptionNotificationTest { private static final Block TEST_BLOCK = new BlockGenerator(Constants.regtest(), ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351)).createBlock(12, 0); - private static final String TEST_BLOCK_RESULT_JSON = "{\"difficulty\":\"0x20000\",\"extraData\":\"0x\",\"gasLimit\":\"0x2fefd8\",\"gasUsed\":\"0x0\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0xe94aef644e428941ee0a3741f28d80255fddba7f\",\"number\":\"0xc\",\"parentHash\":\"0xbe5de0c9c661653c979ec457f610444dcd0048007e683b2d04ce05729af56280\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"timestamp\":\"0x1\",\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"hash\":\"0xae2699897284d434f3139376fbc606aabf2eade4b2cd1e551d37395f0b095dca\"}"; + private static final String TEST_BLOCK_RESULT_JSON = "{\"difficulty\":\"0x20000\",\"extraData\":\"0x\",\"gasLimit\":\"0x2fefd8\",\"gasUsed\":\"0x0\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0xe94aef644e428941ee0a3741f28d80255fddba7f\",\"number\":\"0xc\",\"parentHash\":\"0xbe5de0c9c661653c979ec457f610444dcd0048007e683b2d04ce05729af56280\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"timestamp\":\"0x1\",\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"hash\":\"0x79352db9469b5ec1c26410dfd16503f3da87e90b41d5c16a4153fec60e2c1e45\",\"baseEvent\":\"0x00\"}"; private static final Block TEST_BLOCK_2 = new BlockGenerator(Constants.regtest(), ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351)).createBlock(12, 0, 1000000L); - private static final String TEST_BLOCK_RESULT_JSON_2 = "{\"difficulty\":\"0x20000\",\"extraData\":\"0x\",\"gasLimit\":\"0xf4240\",\"gasUsed\":\"0x0\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0xe94aef644e428941ee0a3741f28d80255fddba7f\",\"number\":\"0xc\",\"parentHash\":\"0x82d804adc43b6382427216a764963f77c612694065f19b3b97c804338c6ceeec\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"timestamp\":\"0x1\",\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"hash\":\"0x644f8371116ee86c675cc1eadaddfd97c49617b01dd0deb47bc978fcdef86dfc\"}"; + private static final String TEST_BLOCK_RESULT_JSON_2 = "{\"difficulty\":\"0x20000\",\"extraData\":\"0x\",\"gasLimit\":\"0xf4240\",\"gasUsed\":\"0x0\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0xe94aef644e428941ee0a3741f28d80255fddba7f\",\"number\":\"0xc\",\"parentHash\":\"0x82d804adc43b6382427216a764963f77c612694065f19b3b97c804338c6ceeec\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"timestamp\":\"0x1\",\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"hash\":\"0x84e713871bd26e0f183d2756725d85befec7dbc1885b6086ed159a9bcef1b842\",\"baseEvent\":\"0x00\"}"; private static final String TEST_SYNC_RESULT_JSON = "{\"syncing\":true,\"status\":{\"startingBlock\":0,\"currentBlock\":1,\"highestBlock\":1000}}"; diff --git a/rskj-core/src/test/java/co/rsk/rpc/netty/Web3HttpServerTest.java b/rskj-core/src/test/java/co/rsk/rpc/netty/Web3HttpServerTest.java index d78b918f6df..efc7d11ea9f 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/netty/Web3HttpServerTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/netty/Web3HttpServerTest.java @@ -1,6 +1,5 @@ package co.rsk.rpc.netty; -import co.rsk.config.TestSystemProperties; import co.rsk.jsonrpc.JsonRpcError; import co.rsk.rpc.CorsConfiguration; import co.rsk.rpc.ModuleDescription; @@ -9,7 +8,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.googlecode.jsonrpc4j.ErrorResolver; -import com.squareup.okhttp.*; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; import com.typesafe.config.Config; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigValueFactory; @@ -20,13 +23,24 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import javax.net.ssl.*; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.InetAddress; +import java.net.ServerSocket; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Function; import static org.ethereum.TestUtils.waitFor; @@ -40,192 +54,197 @@ class Web3HttpServerTest { public static final String APPLICATION_JSON = "application/json"; - private static JsonNodeFactory JSON_NODE_FACTORY = JsonNodeFactory.instance; - private static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static final String APPLICATION_JSON_RPC = "application/json-rpc"; + public static final String LOCALHOST = "127.0.0.1"; + public static final String WEB3_SHA3_METHOD = "web3_sha3"; + public static final String ETH_GET_BLOCK_BY_NUMBER_METHOD = "eth_getBlockByNumber"; + public static final String LATEST_PARAM = "latest"; + public static final String MOCK_RESULT = "output"; + public static final int DEFAULT_MAX_BATCH_SIZE = 1; + public static final int MAX_RESPONSE_SIZE = 52428800; + + private static final JsonNodeFactory JSON_NODE_FACTORY = JsonNodeFactory.instance; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static OkHttpClient getUnsafeOkHttpClient() { + try { + // Create a trust manager that does not validate certificate chains + final TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, + String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, + String authType) throws CertificateException { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + }; + // Install the all-trusting trust manager + final SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + // Create an ssl socket factory with our all-trusting manager + final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + return new OkHttpClient() + .setSslSocketFactory(sslSocketFactory) + .setHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } @Test void smokeTestUsingJsonContentType() throws Exception { - smokeTest(APPLICATION_JSON); + new SmokeTestBuilder() + .contentType(APPLICATION_JSON) + .execute(); } @Test @Disabled("fix okhttp problem with charset/gzip") void smokeTestUsingJsonWithCharsetContentType() throws Exception { - smokeTest("application/json; charset: utf-8"); + new SmokeTestBuilder() + .contentType("application/json; charset: utf-8") + .execute(); } @Test @Disabled("fix okhttp problem with charset/gzip") void smokeTestUsingJsonRpcWithCharsetContentType() throws Exception { - smokeTest("application/json-rpc; charset: utf-8"); + new SmokeTestBuilder() + .contentType("application/json-rpc; charset: utf-8") + .execute(); } @Test void testMaxBatchRequest() throws Exception { - Web3 web3Mock = Mockito.mock(Web3.class); - String mockResult = "output"; - Mockito.when(web3Mock.web3_sha3(anyString())).thenReturn(mockResult); - CorsConfiguration mockCorsConfiguration = Mockito.mock(CorsConfiguration.class); - Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); - Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); + TestServerBuilder serverBuilder = new TestServerBuilder() + .maxBatchSize(DEFAULT_MAX_BATCH_SIZE); - int randomPort = 9000; - - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); - JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), new ArrayList<>()); - JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder().maxBatchRequestsSize(1).rpcModules(filteredModules).build(); - JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); - server.start(); - - String content = "[{\n" + - " \"method\": \"eth_getBlockByNumber\",\n" + - " \"params\": [\n" + - " \"latest\",\n" + - " true\n" + - " ],\n" + - " \"id\": 1,\n" + - " \"jsonrpc\": \"2.0\"\n" + - "},{\n" + - " \"method\": \"eth_getBlockByNumber\",\n" + - " \"params\": [\n" + - " \"latest\",\n" + - " true\n" + - " ],\n" + - " \"id\": 1,\n" + - " \"jsonrpc\": \"2.0\"\n" + - "}]"; - - Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", content); - String responseBody = response.body().string(); - JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); - - server.stop(); + String content = new BatchRequestBuilder() + .addRequest(ETH_GET_BLOCK_BY_NUMBER_METHOD, LATEST_PARAM) + .addRequest(ETH_GET_BLOCK_BY_NUMBER_METHOD, LATEST_PARAM) + .build(); - assertThat(response.code(), is(HttpResponseStatus.BAD_REQUEST.code())); - Assertions.assertEquals(jsonRpcResponse.get("error").get("code").asInt(), ErrorResolver.JsonError.INVALID_REQUEST.code); - Assertions.assertEquals("Cannot dispatch batch requests. 1 is the max number of supported batch requests", jsonRpcResponse.get("error").get("message").asText()); + runServerTest(serverBuilder, content, response -> { + try { + assertBadRequest(response, ErrorResolver.JsonError.INVALID_REQUEST.code, + "Cannot dispatch batch requests. 1 is the max number of supported batch requests"); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @Test void testMaxBatchRequestWithNestedLevels() throws Exception { - Web3 web3Mock = Mockito.mock(Web3.class); - String mockResult = "output"; - Mockito.when(web3Mock.web3_sha3(anyString())).thenReturn(mockResult); - CorsConfiguration mockCorsConfiguration = Mockito.mock(CorsConfiguration.class); - Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); - Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); - - int randomPort = 9000; - - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); - JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), new ArrayList<>()); - JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder().maxBatchRequestsSize(1).rpcModules(filteredModules).build(); - JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); - server.start(); - - String content = "[[[{\n" + - " \"method\": \"eth_getBlockByNumber\",\n" + - " \"params\": [\n" + - " \"latest\",\n" + - " true\n" + - " ],\n" + - " \"id\": 1,\n" + - " \"jsonrpc\": \"2.0\"\n" + - "},{\n" + - " \"method\": \"eth_getBlockByNumber\",\n" + - " \"params\": [\n" + - " \"latest\",\n" + - " true\n" + - " ],\n" + - " \"id\": 1,\n" + - " \"jsonrpc\": \"2.0\"\n" + - "}]]]"; - - Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", content); - String responseBody = response.body().string(); - JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); + TestServerBuilder serverBuilder = new TestServerBuilder() + .maxBatchSize(DEFAULT_MAX_BATCH_SIZE); - server.stop(); - - assertThat(response.code(), is(HttpResponseStatus.BAD_REQUEST.code())); - Assertions.assertEquals(jsonRpcResponse.get("error").get("code").asInt(), ErrorResolver.JsonError.INVALID_REQUEST.code); - Assertions.assertEquals("Cannot dispatch batch requests. 1 is the max number of supported batch requests", jsonRpcResponse.get("error").get("message").asText()); + String singleRequest = new JsonRpcRequestBuilder() + .method(ETH_GET_BLOCK_BY_NUMBER_METHOD) + .params(LATEST_PARAM, "true") + .build(); + String content = "[[" + singleRequest + "," + singleRequest + "]]"; + + runServerTest(serverBuilder, content, response -> { + try { + assertBadRequest(response, ErrorResolver.JsonError.INVALID_REQUEST.code, + "Cannot dispatch batch requests. 1 is the max number of supported batch requests"); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @Test void testStackOverflowErrorInRequest() throws Exception { - Web3 web3Mock = Mockito.mock(Web3.class); - String mockResult = "output"; - Mockito.when(web3Mock.web3_sha3(anyString())).thenReturn(mockResult); - CorsConfiguration mockCorsConfiguration = Mockito.mock(CorsConfiguration.class); - Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); - Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); + TestServerBuilder serverBuilder = new TestServerBuilder() + .maxBatchSize(DEFAULT_MAX_BATCH_SIZE); - int randomPort = 9900; + String baseRequest = new JsonRpcRequestBuilder() + .method(WEB3_SHA3_METHOD) + .params(LATEST_PARAM) + .build(); - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); - JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), new ArrayList<>()); - JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder().maxBatchRequestsSize(1).rpcModules(filteredModules).build(); - JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); - server.start(); + // Wrap the request in an array first, then add 998 levels of nesting + String arrayRequest = "[" + baseRequest + "]"; + String content = createNestedRequest(arrayRequest, 998); - String content = "[{\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"latest\"" + - " ],\n" + - " \"id\": 1,\n" + - " \"jsonrpc\": \"2.0\"\n" + - "}]"; - - int depth = 998; - StringBuilder sb = new StringBuilder(content.length() + 2*depth); - for (long i = 0; i < depth; i++) { + runServerTest(serverBuilder, content, response -> { + try { + assertBadRequest(response, ErrorResolver.JsonError.INVALID_REQUEST.code, "Invalid request"); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private String createNestedRequest(String baseRequest, int depth) { + StringBuilder sb = new StringBuilder(baseRequest.length() + 2 * depth); + for (int i = 0; i < depth; i++) { sb.append("["); } - sb.append(content); - for (long i = 0; i < depth; i++) { + sb.append(baseRequest); + for (int i = 0; i < depth; i++) { sb.append("]"); } - - Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", sb.toString()); - String responseBody = response.body().string(); - JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); - - assertThat(response.code(), is(HttpResponseStatus.BAD_REQUEST.code())); - Assertions.assertEquals(jsonRpcResponse.get("error").get("code").asInt(), ErrorResolver.JsonError.INVALID_REQUEST.code); - Assertions.assertEquals("Invalid request", jsonRpcResponse.get("error").get("message").asText()); + return sb.toString(); } @Test void smokeTestUsingJsonRpcContentType() throws Exception { - smokeTest("application/json-rpc"); + new SmokeTestBuilder() + .contentType("application/json-rpc") + .execute(); } @Test void smokeTestUsingInvalidContentType() { - Assertions.assertThrows(IOException.class, () -> smokeTest("text/plain")); + Assertions.assertThrows(IOException.class, () -> new SmokeTestBuilder() + .contentType("text/plain") + .execute()); } @Test void smokeTestUsingValidHost() throws Exception { - smokeTest(APPLICATION_JSON, "localhost"); + new SmokeTestBuilder() + .contentType(APPLICATION_JSON) + .host("localhost") + .execute(); } @Test void smokeTestUsingInvalidHost() { - Assertions.assertThrows(IOException.class, () -> smokeTest(APPLICATION_JSON, "evil.com")); + Assertions.assertThrows(IOException.class, () -> new SmokeTestBuilder() + .contentType(APPLICATION_JSON) + .host("evil.com") + .execute()); } @Test void smokeTestUsingValidHostAndHostName() throws Exception { String domain = "www.google.com"; - List rpcHost = new ArrayList<>(); - rpcHost.add(domain); - smokeTest(APPLICATION_JSON, domain, InetAddress.getByName(domain), rpcHost); + List rpcHosts = new ArrayList<>(); + rpcHosts.add(domain); + new SmokeTestBuilder() + .contentType(APPLICATION_JSON) + .host(domain) + .rpcAddress(InetAddress.getByName(domain)) + .rpcHosts(rpcHosts) + .execute(); } @Test @@ -239,7 +258,8 @@ void smokeTestUsingWildcardHostAndHostName() throws Exception { @Test void smokeTestUsingInvalidHostAndHostName() throws Exception { InetAddress google = InetAddress.getByName("www.google.com"); - Assertions.assertThrows(IOException.class, () -> smokeTest(APPLICATION_JSON, "this is a wrong host", google, new ArrayList<>())); + Assertions.assertThrows(IOException.class, + () -> smokeTest(APPLICATION_JSON, "this is a wrong host", google, new ArrayList<>())); } @Test @@ -250,7 +270,8 @@ void smokeTestUsingValidHostIpAndHostName() throws Exception { @Test void smokeTestProducesTimeout() throws Exception { - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 1, new HashMap<>())); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 1, new HashMap<>())); Function decorator = rawConfig -> { List list = rawConfig.getObjectList("rpc.modules"); @@ -271,7 +292,8 @@ void smokeTestProducesTimeout() throws Exception { @Test void smokeTestProducesTimeoutDueToMethodTimeout() throws Exception { - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 1, new HashMap<>())); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 1, new HashMap<>())); Function decorator = rawConfig -> { List list = rawConfig.getObjectList("rpc.modules"); @@ -298,7 +320,8 @@ void smokeTestProducesTimeoutDueToMethodTimeout() throws Exception { @Test void smokeTestProducesMethodNotFoundException() throws Exception { - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); String mockResult = "{\"jsonrpc\":\"2.0\",\"id\":13,\"error\":{\"code\":-32601,\"message\":\"method not found\"}}"; smokeTest(APPLICATION_JSON, "localhost", filteredModules, null, mockResult, ""); @@ -306,7 +329,8 @@ void smokeTestProducesMethodNotFoundException() throws Exception { @Test void smokeTestProducesMethodInvalidException() throws Exception { - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); String mockResult = "{\"jsonrpc\":\"2.0\",\"id\":13,\"error\":{\"code\":-32601,\"message\":\"method not found\"}}"; smokeTest(APPLICATION_JSON, "localhost", filteredModules, null, mockResult, "web3sha3"); @@ -317,55 +341,59 @@ void testMaxResponseSize() throws Exception { Web3 web3Mock = Mockito.mock(Web3.class); String mockResult = getMediumJsonResponse(); JsonNode jsonMockResult = OBJECT_MAPPER.readTree(mockResult); - int responseSize = jsonMockResult.toString().getBytes().length * 2 - 1; - + int responseSize = jsonMockResult.toString().getBytes().length * 2 - 1; Mockito.when(web3Mock.web3_sha3(anyString())).thenReturn(mockResult); CorsConfiguration mockCorsConfiguration = Mockito.mock(CorsConfiguration.class); Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); - int randomPort = 9110; + int randomPort = getAvailablePort(); - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); - JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), new ArrayList<>()); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); + JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), + new ArrayList<>()); JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder() .rpcModules(filteredModules) .rpcMaxResponseSize(responseSize) .build(); JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); + Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, + mockCorsConfiguration, filterHandler, serverHandler, 52428800); server.start(); + try { + String requestContent = """ + [ + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + }, + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + } + ]"""; + + Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", requestContent); - String requestContent = "[\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " },\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " }\n" + - "]"; - - Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", requestContent); - server.stop(); - - - String responseBody = response.body().string(); - JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); + String responseBody = response.body().string(); + JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); - Assertions.assertEquals(JsonRpcError.RPC_LIMIT_ERROR, jsonRpcResponse.get("error").get("code").asInt()); - verify(web3Mock, times(2)).web3_sha3(anyString()); + Assertions.assertEquals(JsonRpcError.RPC_LIMIT_ERROR, jsonRpcResponse.get("error").get("code").asInt()); + verify(web3Mock, times(2)).web3_sha3(anyString()); + } finally { + server.stop(); + } } @Test @@ -374,48 +402,50 @@ void testMaxResponseSize_stopBatch() throws Exception { String mockResult = getMediumJsonResponse(); int responseSize = mockResult.getBytes().length - 1; - Mockito.when(web3Mock.web3_sha3(anyString())).thenReturn(mockResult); CorsConfiguration mockCorsConfiguration = Mockito.mock(CorsConfiguration.class); Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); - int randomPort = 9110; + int randomPort = getAvailablePort(); - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); - JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), new ArrayList<>()); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); + JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), + new ArrayList<>()); JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder() .rpcModules(filteredModules) .rpcMaxResponseSize(responseSize) .build(); JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); + Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, + mockCorsConfiguration, filterHandler, serverHandler, 52428800); server.start(); - String requestContent = "[\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " },\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " }\n" + - "]"; + String requestContent = """ + [ + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + }, + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + } + ]"""; Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", requestContent); server.stop(); - String responseBody = response.body().string(); JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); @@ -436,47 +466,52 @@ void testResponseTimeoutException() throws Exception { Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); - int randomPort = 9111; + int randomPort = getAvailablePort(); - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); - JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), new ArrayList<>()); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); + JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), + new ArrayList<>()); JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder() .rpcModules(filteredModules) .rpcTimeout(600) .build(); JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); + Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, + mockCorsConfiguration, filterHandler, serverHandler, 52428800); server.start(); + try { + String requestContent = """ + [ + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + }, + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + } + ]"""; + + Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", requestContent); - String requestContent = "[\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " },\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " }\n" + - "]"; - - Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", requestContent); - server.stop(); - - - String responseBody = response.body().string(); - JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); + String responseBody = response.body().string(); + JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); - Assertions.assertEquals(JsonRpcError.RPC_LIMIT_ERROR, jsonRpcResponse.get("error").get("code").asInt()); - verify(web3Mock, times(2)).web3_sha3(anyString()); + Assertions.assertEquals(JsonRpcError.RPC_LIMIT_ERROR, jsonRpcResponse.get("error").get("code").asInt()); + verify(web3Mock, times(2)).web3_sha3(anyString()); + } finally { + server.stop(); + } } @Test @@ -493,82 +528,92 @@ void testResponseTimeoutException_stopsBatch() throws Exception { Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); - int randomPort = 9111; + int randomPort = getAvailablePort(); - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); - JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), new ArrayList<>()); + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); + JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", InetAddress.getLoopbackAddress(), + new ArrayList<>()); JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder() .rpcModules(filteredModules) .rpcTimeout(200) .build(); JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); + Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, + mockCorsConfiguration, filterHandler, serverHandler, 52428800); server.start(); + try { + String requestContent = """ + [ + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + }, + { + "jsonrpc": "2.0", + "method": "web3_sha3", + "params": [ + "0x68656c6c6f20776f726c64" + ], + "id": 64 + } + ]"""; + + Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", requestContent); - String requestContent = "[\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " },\n" + - " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"method\": \"web3_sha3\",\n" + - " \"params\": [\n" + - " \"0x68656c6c6f20776f726c64\"\n" + - " ],\n" + - " \"id\": 64\n" + - " }\n" + - "]"; - - Response response = sendHugeJsonRpcMessage(randomPort, "application/json-rpc", "127.0.0.1", requestContent); - server.stop(); - - - String responseBody = response.body().string(); - JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); - - Assertions.assertEquals(JsonRpcError.RPC_LIMIT_ERROR, jsonRpcResponse.get("error").get("code").asInt()); - verify(web3Mock, times(1)).web3_sha3(anyString()); - } + String responseBody = response.body().string(); + JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); - private void smokeTest(String contentType, String host) throws Exception { - smokeTest(contentType, host, InetAddress.getLoopbackAddress(), new ArrayList<>()); + Assertions.assertEquals(JsonRpcError.RPC_LIMIT_ERROR, jsonRpcResponse.get("error").get("code").asInt()); + verify(web3Mock, times(1)).web3_sha3(anyString()); + } finally { + server.stop(); + } } - private void smokeTest(String contentType, String host, List filteredModules, Function decorator, String mockResult, String method) throws Exception { - smokeTest(contentType, host, InetAddress.getLoopbackAddress(), new ArrayList<>(), filteredModules, decorator, mockResult, method); + private void smokeTest(String contentType, String host, List filteredModules, + Function decorator, String mockResult, String method) throws Exception { + smokeTest(contentType, host, InetAddress.getLoopbackAddress(), new ArrayList<>(), filteredModules, decorator, + mockResult, method); } - private void smokeTest(String contentType, String host, List filteredModules, Function decorator, String mockResult) throws Exception { - smokeTest(contentType, host, InetAddress.getLoopbackAddress(), new ArrayList<>(), filteredModules, decorator, mockResult, "web3_sha3"); + private void smokeTest(String contentType, String host, List filteredModules, + Function decorator, String mockResult) throws Exception { + smokeTest(contentType, host, InetAddress.getLoopbackAddress(), new ArrayList<>(), filteredModules, decorator, + mockResult, "web3_sha3"); } - private void smokeTest(String contentType, String host, InetAddress rpcAddress, List rpcHost) throws Exception { - List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); + private void smokeTest(String contentType, String host, InetAddress rpcAddress, List rpcHost) + throws Exception { + List filteredModules = Collections.singletonList(new ModuleDescription("web3", "1.0", true, + Collections.emptyList(), Collections.emptyList(), 0, new HashMap<>())); String mockResult = "output"; smokeTest(contentType, host, rpcAddress, rpcHost, filteredModules, null, mockResult, "web3_sha3"); } - private void smokeTest(String contentType, String host, InetAddress rpcAddress, List rpcHost, List filteredModules, Function decorator, String mockResult, String method) throws Exception { + private void smokeTest(String contentType, String host, InetAddress rpcAddress, List rpcHost, + List filteredModules, Function decorator, String mockResult, + String method) throws Exception { Web3 web3Mock = Mockito.mock(Web3.class); Mockito.when(web3Mock.web3_sha3(anyString())).thenReturn(mockResult); CorsConfiguration mockCorsConfiguration = Mockito.mock(CorsConfiguration.class); Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); - int randomPort = 9999;//new ServerSocket(0).getLocalPort(); + int randomPort = getAvailablePort(); - TestSystemProperties testSystemProperties = decorator == null ? new TestSystemProperties() : new TestSystemProperties(decorator); JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", rpcAddress, rpcHost); - JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder().maxBatchRequestsSize(5).rpcModules(filteredModules).build(); + JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder().maxBatchRequestsSize(5) + .rpcModules(filteredModules).build(); JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); - Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, mockCorsConfiguration, filterHandler, serverHandler, 52428800); + Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, + mockCorsConfiguration, filterHandler, serverHandler, 52428800); server.start(); try { Response response = sendJsonRpcMessage(randomPort, contentType, host, method); @@ -592,11 +637,8 @@ private void smokeTest(String contentType, String host, InetAddress rpcAddress, } } - private void smokeTest(String contentType) throws Exception { - smokeTest(contentType, "127.0.0.1"); - } - - private Response sendHugeJsonRpcMessage(int port, String contentType, String host, String content) throws IOException { + private Response sendHugeJsonRpcMessage(int port, String contentType, String host, String content) + throws IOException { Map jsonRpcRequestProperties = new HashMap<>(); jsonRpcRequestProperties.put("jsonrpc", JSON_NODE_FACTORY.textNode("2.0")); jsonRpcRequestProperties.put("id", JSON_NODE_FACTORY.numberNode(13)); @@ -631,105 +673,276 @@ private Response sendJsonRpcMessage(int port, String contentType, String host, S return getUnsafeOkHttpClient().newCall(request).execute(); } - private static OkHttpClient getUnsafeOkHttpClient() { - try { - // Create a trust manager that does not validate certificate chains - final TrustManager[] trustAllCerts = new TrustManager[]{ - new X509TrustManager() { - @Override - public void checkClientTrusted(java.security.cert.X509Certificate[] chain, - String authType) throws CertificateException { - } + private String getMediumJsonResponse() { + return """ + { + "jsonrpc": "2.0", + "id": 1, + "result": { + "number": "0x3", + "hash": "0x2fd76d5e649d0afd216aba87fa919e8850b1badbb34531b69e10ee00496ae7ca", + "parentHash": "0x5a5ff4628a01ccc79909eaea7004bc90620eefd1374f61c5c41928f780ad47df", + "sha3Uncles": "0xdd198e401a42bdb666d9c6c7307da23d739fe992ce68fea44e0a38ad0f7097e3", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x142bd02ce0eb3ced3a228dac11e9dccc6943d800e1ce0f80953b0d67b2a398f6", + "stateRoot": "0xcb694600670b9ea518c8b0d2c7ea391727663e98729e1badcd8a064b4814e982", + "receiptsRoot": "0x66cfdb731f620cd96e2c2cb0f7d3c3a2879c29b40014aa27efbbf3cf9cd3b0f6", + "miner": "0x1fab9a0e24ffc209b01faa5a61ad4366982d0b7f", + "difficulty": "0x20a3d", + "totalDifficulty": "0x2a0a3d", + "extraData": "0x", + "size": "0x27e4", + "gasLimit": "0x4c8485", + "gasUsed": "0x0", + "timestamp": "0x5d1f551e", + "transactions": [ + { + "hash": "0x694c5110568eeddab989eb9601cca00ab94dfccf3597f72e1627ca987d05691b", + "nonce": "0x2", + "blockHash": "0x2fd76d5e649d0afd216aba87fa919e8850b1badbb34531b69e10ee00496ae7ca", + "blockNumber": "0x3", + "transactionIndex": "0x0", + "from": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000001000008", + "gas": "0x0", + "gasPrice": "0x0", + "value": "0x0", + "input": "0x", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "type": "0x0" + } + ], + "uncles": [ + "0xda4b2d7e79a9f19881bdd61304fdab8ba3443c329b06ffab1460ebdd0950c736", + "0x5bb7eac6fe88a32f5853c1368b6a018fea312d99c49decf2b3d6a940966c75ab", + "0x08f15c6097d98d47d6f2e73d5fed5d42ecdf2d2f16760622bc189d604965fc02", + "0xe09fe2113298257469513d7ddd2b21b7af589bab51b9f965d464418d03232949", + "0x6c8d022cac09b0ff9914fcf3030c50fa6c38599ef97653d513ca5a76b981b54a", + "0x35a5b19b778919f281cea24a23f303b44d3490479a02649960ce3cd1af86feca", + "0x2639eb6de08773bd90bebbff9ce0de736cf4f805c2547a85682a4ed3c8bfd292", + "0x5257ae559f38ea63f481bf9fcb006d07e647a4d525935089c64d2a8d95a45e6a", + "0xc0b1aa843ee1e4b23f4c11382c3fbe5f0882b0768242efd20e16782e2ce9d25a", + "0xc8f8288c35b8c9efbe65306b7dd0eca37e5fbbbdff98387c914d13f32f030375" + ], + "minimumGasPrice": "0x0", + "bitcoinMergedMiningHeader": "0x000000204b830f1affa0f22955f4cba89f2b7856a2dfdb65c9a248145a020000000000002f6954241717b75ac7bef04fcff9dffdcd2f10c35a22fa9bda7772a10162cb5029551f5d531d041ac1787d31", + "bitcoinMergedMiningCoinbaseTransaction": "0x00000000000000801649d8b2989d0ce6109fbcdc38d4593275662b4d541fd08ce5f0740bd78736696088ac0000000000000000266a24aa21a9ed029d7e041decec4a9946a4ce1c067f74dc92709e90854432c9494a6428307f3700000000000000002a6a52534b424c4f434b3aa29b6de0f39c04c200d138e07758f2604783de7c1e0003abb60b3d28544bc1b600000000", + "bitcoinMergedMiningMerkleProof": "0x6829b0d13e9af3d9d763dcda91d55adbe513f453863ee268c0617d4bc4b1b4e2fb1ef1250e9a5abd35daded453f13ba08290c2eec04237f9586ab1918300b7482e9857a3839c94225bdec2ed3a6e49c8d407734cf70720fc8e79830ff2974688f895cee2a9687c1fb96f66eed9717454cd343e85483a502849d2e873c22afb2d665efa40b90f1518e8be178e3c0a58b44e78c4c400d6789fce043eb142d590171e2096ee4485d5a3035a887040432a09074cd1f700369da15f65520fdd3a093c602abc4746e0f936b3e01191bc30dee02aa4448d74689c7e03fdae2413f165fdca9727f5741c864bb138760dbd5a1033d007a766161b3e851e602e2d61ce7858", + "hashForMergedMining": "0xa29b6de0f39c04c200d138e07758f2604783de7c1e0003abb60b3d28544bc1b6", + "paidFees": "0x0", + "cumulativeDifficulty": "0x160a3d" + } + }"""; + } - @Override - public void checkServerTrusted(java.security.cert.X509Certificate[] chain, - String authType) throws CertificateException { - } + private static int getAvailablePort() { + try (ServerSocket socket = new ServerSocket(0)) { + return socket.getLocalPort(); + } catch (IOException e) { + throw new RuntimeException("Could not find available port", e); + } + } - @Override - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } - }; - // Install the all-trusting trust manager - final SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); - // Create an ssl socket factory with our all-trusting manager - final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - return new OkHttpClient() - .setSslSocketFactory(sslSocketFactory) - .setHostnameVerifier(new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }); + private static Web3 createMockWeb3(String mockResult) { + Web3 web3Mock = Mockito.mock(Web3.class); + try { + Mockito.when(web3Mock.web3_sha3(anyString())).thenReturn(mockResult); } catch (Exception e) { - throw new RuntimeException(e); + // This should not happen in tests } + return web3Mock; } - private String getMediumJsonResponse() { - return " {\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"id\": 1,\n" + - " \"result\": {\n" + - " \"number\": \"0x3\",\n" + - " \"hash\": \"0x2fd76d5e649d0afd216aba87fa919e8850b1badbb34531b69e10ee00496ae7ca\",\n" + - " \"parentHash\": \"0x5a5ff4628a01ccc79909eaea7004bc90620eefd1374f61c5c41928f780ad47df\",\n" + - " \"sha3Uncles\": \"0xdd198e401a42bdb666d9c6c7307da23d739fe992ce68fea44e0a38ad0f7097e3\",\n" + - " \"logsBloom\": \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\n" + - " \"transactionsRoot\": \"0x142bd02ce0eb3ced3a228dac11e9dccc6943d800e1ce0f80953b0d67b2a398f6\",\n" + - " \"stateRoot\": \"0xcb694600670b9ea518c8b0d2c7ea391727663e98729e1badcd8a064b4814e982\",\n" + - " \"receiptsRoot\": \"0x66cfdb731f620cd96e2c2cb0f7d3c3a2879c29b40014aa27efbbf3cf9cd3b0f6\",\n" + - " \"miner\": \"0x1fab9a0e24ffc209b01faa5a61ad4366982d0b7f\",\n" + - " \"difficulty\": \"0x20a3d\",\n" + - " \"totalDifficulty\": \"0x2a0a3d\",\n" + - " \"extraData\": \"0x\",\n" + - " \"size\": \"0x27e4\",\n" + - " \"gasLimit\": \"0x4c8485\",\n" + - " \"gasUsed\": \"0x0\",\n" + - " \"timestamp\": \"0x5d1f551e\",\n" + - " \"transactions\": [\n" + - " {\n" + - " \"hash\": \"0x694c5110568eeddab989eb9601cca00ab94dfccf3597f72e1627ca987d05691b\",\n" + - " \"nonce\": \"0x2\",\n" + - " \"blockHash\": \"0x2fd76d5e649d0afd216aba87fa919e8850b1badbb34531b69e10ee00496ae7ca\",\n" + - " \"blockNumber\": \"0x3\",\n" + - " \"transactionIndex\": \"0x0\",\n" + - " \"from\": \"0x0000000000000000000000000000000000000000\",\n" + - " \"to\": \"0x0000000000000000000000000000000001000008\",\n" + - " \"gas\": \"0x0\",\n" + - " \"gasPrice\": \"0x0\",\n" + - " \"value\": \"0x0\",\n" + - " \"input\": \"0x\",\n" + - " \"v\": \"0x0\",\n" + - " \"r\": \"0x0\",\n" + - " \"s\": \"0x0\",\n" + - " \"type\": \"0x0\"\n" + - " }\n" + - " ],\n" + - " \"uncles\": [\n" + - " \"0xda4b2d7e79a9f19881bdd61304fdab8ba3443c329b06ffab1460ebdd0950c736\",\n" + - " \"0x5bb7eac6fe88a32f5853c1368b6a018fea312d99c49decf2b3d6a940966c75ab\",\n" + - " \"0x08f15c6097d98d47d6f2e73d5fed5d42ecdf2d2f16760622bc189d604965fc02\",\n" + - " \"0xe09fe2113298257469513d7ddd2b21b7af589bab51b9f965d464418d03232949\",\n" + - " \"0x6c8d022cac09b0ff9914fcf3030c50fa6c38599ef97653d513ca5a76b981b54a\",\n" + - " \"0x35a5b19b778919f281cea24a23f303b44d3490479a02649960ce3cd1af86feca\",\n" + - " \"0x2639eb6de08773bd90bebbff9ce0de736cf4f805c2547a85682a4ed3c8bfd292\",\n" + - " \"0x5257ae559f38ea63f481bf9fcb006d07e647a4d525935089c64d2a8d95a45e6a\",\n" + - " \"0xc0b1aa843ee1e4b23f4c11382c3fbe5f0882b0768242efd20e16782e2ce9d25a\",\n" + - " \"0xc8f8288c35b8c9efbe65306b7dd0eca37e5fbbbdff98387c914d13f32f030375\"\n" + - " ],\n" + - " \"minimumGasPrice\": \"0x0\",\n" + - " \"bitcoinMergedMiningHeader\": \"0x000000204b830f1affa0f22955f4cba89f2b7856a2dfdb65c9a248145a020000000000002f6954241717b75ac7bef04fcff9dffdcd2f10c35a22fa9bda7772a10162cb5029551f5d531d041ac1787d31\",\n" + - " \"bitcoinMergedMiningCoinbaseTransaction\": \"0x00000000000000801649d8b2989d0ce6109fbcdc38d4593275662b4d541fd08ce5f0740bd78736696088ac0000000000000000266a24aa21a9ed029d7e041decec4a9946a4ce1c067f74dc92709e90854432c9494a6428307f3700000000000000002a6a52534b424c4f434b3aa29b6de0f39c04c200d138e07758f2604783de7c1e0003abb60b3d28544bc1b600000000\",\n" + - " \"bitcoinMergedMiningMerkleProof\": \"0x6829b0d13e9af3d9d763dcda91d55adbe513f453863ee268c0617d4bc4b1b4e2fb1ef1250e9a5abd35daded453f13ba08290c2eec04237f9586ab1918300b7482e9857a3839c94225bdec2ed3a6e49c8d407734cf70720fc8e79830ff2974688f895cee2a9687c1fb96f66eed9717454cd343e85483a502849d2e873c22afb2d665efa40b90f1518e8be178e3c0a58b44e78c4c400d6789fce043eb142d590171e2096ee4485d5a3035a887040432a09074cd1f700369da15f65520fdd3a093c602abc4746e0f936b3e01191bc30dee02aa4448d74689c7e03fdae2413f165fdca9727f5741c864bb138760dbd5a1033d007a766161b3e851e602e2d61ce7858\",\n" + - " \"hashForMergedMining\": \"0xa29b6de0f39c04c200d138e07758f2604783de7c1e0003abb60b3d28544bc1b6\",\n" + - " \"paidFees\": \"0x0\",\n" + - " \"cumulativeDifficulty\": \"0x160a3d\"\n" + - " }\n" + - " }"; - } -} \ No newline at end of file + private static CorsConfiguration createMockCorsConfiguration() { + CorsConfiguration mockCorsConfiguration = Mockito.mock(CorsConfiguration.class); + Mockito.when(mockCorsConfiguration.hasHeader()).thenReturn(true); + Mockito.when(mockCorsConfiguration.getHeader()).thenReturn("*"); + return mockCorsConfiguration; + } + + // Assertion Helpers + private void assertBadRequest(Response response, int expectedErrorCode, String expectedMessage) throws Exception { + String responseBody = response.body().string(); + JsonNode jsonRpcResponse = OBJECT_MAPPER.readTree(responseBody); + + assertThat(response.code(), is(HttpResponseStatus.BAD_REQUEST.code())); + Assertions.assertEquals(expectedErrorCode, jsonRpcResponse.get("error").get("code").asInt()); + Assertions.assertEquals(expectedMessage, jsonRpcResponse.get("error").get("message").asText()); + } + + // Test Execution Helpers + private void runServerTest(TestServerBuilder serverBuilder, String content, String contentType, String host, + java.util.function.Consumer assertions) throws Exception { + Web3HttpServer server = serverBuilder.build(); + server.start(); + try { + Response response = sendHugeJsonRpcMessage(serverBuilder.getPort(), contentType, host, content); + assertions.accept(response); + } finally { + server.stop(); + } + } + + private void runServerTest(TestServerBuilder serverBuilder, String content, + java.util.function.Consumer assertions) throws Exception { + runServerTest(serverBuilder, content, APPLICATION_JSON_RPC, LOCALHOST, assertions); + } + + // Test Data Builders + private static class JsonRpcRequestBuilder { + private final String jsonrpc = "2.0"; + private String method = WEB3_SHA3_METHOD; + private String[] params = { LATEST_PARAM }; + private Object id = 1; + + public JsonRpcRequestBuilder method(String method) { + this.method = method; + return this; + } + + public JsonRpcRequestBuilder params(String... params) { + this.params = params; + return this; + } + + public JsonRpcRequestBuilder id(Object id) { + this.id = id; + return this; + } + + public String build() { + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + sb.append(" \"method\": \"").append(method).append("\",\n"); + sb.append(" \"params\": ["); + for (int i = 0; i < params.length; i++) { + if (i > 0) + sb.append(", "); + sb.append("\"").append(params[i]).append("\""); + } + sb.append("],\n"); + sb.append(" \"id\": ").append(id).append(",\n"); + sb.append(" \"jsonrpc\": \"").append(jsonrpc).append("\"\n"); + sb.append("}"); + return sb.toString(); + } + } + + private static class BatchRequestBuilder { + private final List requests = new ArrayList<>(); + + public BatchRequestBuilder addRequest(String method, String... params) { + requests.add(new JsonRpcRequestBuilder().method(method).params(params).build()); + return this; + } + + public String build() { + return "[" + String.join(",", requests) + "]"; + } + } + + // Test Server Builder + private static class TestServerBuilder { + private final Web3 web3Mock; + private final CorsConfiguration corsConfiguration; + private final int port; + private final int maxResponseSize = MAX_RESPONSE_SIZE; + private final List modules = Collections.singletonList( + new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, + new HashMap<>())); + private final String host = "*"; + private final InetAddress rpcAddress = InetAddress.getLoopbackAddress(); + private final List rpcHosts = new ArrayList<>(); + private int maxBatchSize = DEFAULT_MAX_BATCH_SIZE; + + public TestServerBuilder() { + this.web3Mock = createMockWeb3(MOCK_RESULT); + this.corsConfiguration = createMockCorsConfiguration(); + this.port = getAvailablePort(); + } + + public TestServerBuilder maxBatchSize(int size) { + this.maxBatchSize = size; + return this; + } + + public Web3HttpServer build() { + JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler(host, rpcAddress, rpcHosts); + JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder() + .maxBatchRequestsSize(maxBatchSize) + .rpcModules(modules) + .build(); + JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); + return new Web3HttpServer(rpcAddress, port, 0, Boolean.TRUE, corsConfiguration, filterHandler, + serverHandler, maxResponseSize); + } + + public int getPort() { + return port; + } + } + + // Enhanced Smoke Test Builder + private class SmokeTestBuilder { + private final List modules = Collections.singletonList( + new ModuleDescription("web3", "1.0", true, Collections.emptyList(), Collections.emptyList(), 0, + new HashMap<>())); + private final String mockResult = MOCK_RESULT; + private final String method = WEB3_SHA3_METHOD; + private final int maxBatchSize = 5; + private final int maxResponseSize = MAX_RESPONSE_SIZE; + private String contentType = APPLICATION_JSON; + private String host = LOCALHOST; + private InetAddress rpcAddress = InetAddress.getLoopbackAddress(); + private List rpcHosts = new ArrayList<>(); + + public SmokeTestBuilder contentType(String contentType) { + this.contentType = contentType; + return this; + } + + public SmokeTestBuilder host(String host) { + this.host = host; + return this; + } + + public SmokeTestBuilder rpcAddress(InetAddress address) { + this.rpcAddress = address; + return this; + } + + public SmokeTestBuilder rpcHosts(List hosts) { + this.rpcHosts = hosts; + return this; + } + + public void execute() throws Exception { + Web3 web3Mock = createMockWeb3(mockResult); + CorsConfiguration mockCorsConfiguration = createMockCorsConfiguration(); + int randomPort = getAvailablePort(); + + JsonRpcWeb3FilterHandler filterHandler = new JsonRpcWeb3FilterHandler("*", rpcAddress, rpcHosts); + JsonRpcWeb3ServerProperties properties = JsonRpcWeb3ServerProperties.builder() + .maxBatchRequestsSize(maxBatchSize) + .rpcModules(modules) + .build(); + JsonRpcWeb3ServerHandler serverHandler = new JsonRpcWeb3ServerHandler(web3Mock, properties); + Web3HttpServer server = new Web3HttpServer(InetAddress.getLoopbackAddress(), randomPort, 0, Boolean.TRUE, + mockCorsConfiguration, filterHandler, serverHandler, maxResponseSize); + server.start(); + try { + Response response = sendJsonRpcMessage(randomPort, contentType, host, method); + String responseBody = response.body().string(); + JsonNode jsonRpcResponse = JacksonParserUtil.readTree(OBJECT_MAPPER, responseBody); + + assertThat(response.code(), is(HttpResponseStatus.OK.code())); + assertThat(jsonRpcResponse.get("result").asText(), is(mockResult)); + assertThat(jsonRpcResponse.get("id").asInt(), is(13)); + } finally { + server.stop(); + } + } + } +} diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java index f9122294537..6f43df5eafe 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java @@ -18,137 +18,146 @@ package org.ethereum.config.blockchain.upgrades; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; + +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP103; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP351; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP535; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP85; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP98; +import static org.ethereum.config.blockchain.upgrades.ConsensusRule.values; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.typesafe.config.*; -import org.hamcrest.MatcherAssert; -import org.junit.jupiter.api.Test; - class ActivationConfigTest { private static final Config BASE_CONFIG = ConfigFactory.parseString(String.join("\n", - "hardforkActivationHeights: {", - " genesis: 0", - " bahamas: 0", - " afterBridgeSync: 0,", - " orchid: 0,", - " orchid060: 0,", - " wasabi100: 0", - " papyrus200: 0", - " twoToThree: 0", - " iris300: 0", - " hop400: 0", - " hop401: 0", - " fingerroot500: 0", - " arrowhead600: 0", - " arrowhead631: 0", - " lovell700: 0", - " reed800: 0", - " reed810: 0", - " vetiver900: 0", - "},", - "consensusRules: {", - " areBridgeTxsPaid: afterBridgeSync,", - " rskip85: orchid,", - " rskip87: orchid,", - " rskip88: orchid,", - " rskip89: orchid,", - " rskip90: orchid,", - " rskip91: orchid,", - " rskip92: orchid,", - " rskip97: orchid,", - " rskip98: orchid,", - " rskip103: orchid060,", - " rskip106: wasabi100,", - " rskip110: wasabi100,", - " rskip119: wasabi100,", - " rskip120: wasabi100,", - " rskip122: wasabi100,", - " rskip123: wasabi100,", - " rskip124: wasabi100,", - " rskip125: wasabi100", - " rskip126: wasabi100", - " rskip132: wasabi100", - " rskip134: papyrus200", - " rskip136: bahamas", - " rskip137: papyrus200", - " rskip140: papyrus200,", - " rskip143: papyrus200", - " rskip144: reed810", - " rskip146: papyrus200", - " rskip150: twoToThree", - " rskip151: papyrus200", - " rskip152: papyrus200", - " rskip156: papyrus200", - " rskipUMM: papyrus200", - " rskip153: iris300", - " rskip169: iris300", - " rskip170: iris300", - " rskip171: iris300", - " rskip174: iris300", - " rskip176: iris300", - " rskip179: iris300", - " rskip180: iris300", - " rskip181: iris300", - " rskip185: iris300", - " rskip186: iris300", - " rskip191: iris300", - " rskip197: iris300", - " rskip199: iris300", - " rskip200: iris300", - " rskip201: iris300", - " rskip203: arrowhead600", - " rskip218: iris300", - " rskip219: iris300", - " rskip220: iris300", - " rskip252: fingerroot500", - " rskip271: hop400", - " rskip284: hop400", - " rskip290: hop400", - " rskip293: hop400", - " rskip294: hop400", - " rskip297: hop400", - " rskip305: reed800", - " rskip326: fingerroot500", - " rskip351: reed810", - " rskip353: hop401", - " rskip357: hop401", - " rskip374: fingerroot500", - " rskip375: fingerroot500", - " rskip376: arrowhead600", - " rskip377: fingerroot500", - " rskip379: arrowhead600", - " rskip383: fingerroot500", - " rskip385: fingerroot500", - " rskip398: arrowhead600", - " rskip400: arrowhead600", - " rskip412: arrowhead600", - " rskip415: arrowhead600", - " rskip417: arrowhead600", - " rskip419: lovell700", - " rskip427: lovell700", - " rskip428: lovell700", - " rskip434: arrowhead631", - " rskip438: lovell700", - " rskip445: lovell700", - " rskip446: lovell700", - " rskip453: lovell700", - " rskip454: lovell700", - " rskip459: lovell700", - " rskip460: lovell700", - " rskip502: reed810", - " rskip516: reed800", - " rskip529: reed810", - " rskip536: reed810", - "}" + "hardforkActivationHeights: {", + " genesis: 0", + " bahamas: 0", + " afterBridgeSync: 0,", + " orchid: 0,", + " orchid060: 0,", + " wasabi100: 0", + " papyrus200: 0", + " twoToThree: 0", + " iris300: 0", + " hop400: 0", + " hop401: 0", + " fingerroot500: 0", + " arrowhead600: 0", + " arrowhead631: 0", + " lovell700: 0", + " reed800: 0", + " reed810: 0", + " vetiver900: 0", + "},", + "consensusRules: {", + " areBridgeTxsPaid: afterBridgeSync,", + " rskip85: orchid,", + " rskip87: orchid,", + " rskip88: orchid,", + " rskip89: orchid,", + " rskip90: orchid,", + " rskip91: orchid,", + " rskip92: orchid,", + " rskip97: orchid,", + " rskip98: orchid,", + " rskip103: orchid060,", + " rskip106: wasabi100,", + " rskip110: wasabi100,", + " rskip119: wasabi100,", + " rskip120: wasabi100,", + " rskip122: wasabi100,", + " rskip123: wasabi100,", + " rskip124: wasabi100,", + " rskip125: wasabi100", + " rskip126: wasabi100", + " rskip132: wasabi100", + " rskip134: papyrus200", + " rskip136: bahamas", + " rskip137: papyrus200", + " rskip140: papyrus200,", + " rskip143: papyrus200", + " rskip144: reed810", + " rskip146: papyrus200", + " rskip150: twoToThree", + " rskip151: papyrus200", + " rskip152: papyrus200", + " rskip156: papyrus200", + " rskipUMM: papyrus200", + " rskip153: iris300", + " rskip169: iris300", + " rskip170: iris300", + " rskip171: iris300", + " rskip174: iris300", + " rskip176: iris300", + " rskip179: iris300", + " rskip180: iris300", + " rskip181: iris300", + " rskip185: iris300", + " rskip186: iris300", + " rskip191: iris300", + " rskip197: iris300", + " rskip199: iris300", + " rskip200: iris300", + " rskip201: iris300", + " rskip203: arrowhead600", + " rskip218: iris300", + " rskip219: iris300", + " rskip220: iris300", + " rskip252: fingerroot500", + " rskip271: hop400", + " rskip284: hop400", + " rskip290: hop400", + " rskip293: hop400", + " rskip294: hop400", + " rskip297: hop400", + " rskip305: reed800", + " rskip326: fingerroot500", + " rskip351: reed810", + " rskip353: hop401", + " rskip357: hop401", + " rskip374: fingerroot500", + " rskip375: fingerroot500", + " rskip376: arrowhead600", + " rskip377: fingerroot500", + " rskip379: arrowhead600", + " rskip383: fingerroot500", + " rskip385: fingerroot500", + " rskip398: arrowhead600", + " rskip400: arrowhead600", + " rskip412: arrowhead600", + " rskip415: arrowhead600", + " rskip417: arrowhead600", + " rskip419: lovell700", + " rskip427: lovell700", + " rskip428: lovell700", + " rskip434: arrowhead631", + " rskip438: lovell700", + " rskip445: lovell700", + " rskip446: lovell700", + " rskip453: lovell700", + " rskip454: lovell700", + " rskip459: lovell700", + " rskip460: lovell700", + " rskip502: reed810", + " rskip516: reed800", + " rskip529: reed810", + " rskip535: reed810", + " rskip536: reed810", + "}" )); @Test void readBaseConfig() { ActivationConfig config = ActivationConfig.read(BASE_CONFIG); - for (ConsensusRule value : ConsensusRule.values()) { + for (ConsensusRule value : values()) { MatcherAssert.assertThat(config.isActive(value, 42), is(true)); } } @@ -156,12 +165,12 @@ void readBaseConfig() { @Test void readWithTwoUpgradesInOrchid060() { ActivationConfig config = ActivationConfig.read(BASE_CONFIG - .withValue("hardforkActivationHeights.orchid060", ConfigValueFactory.fromAnyRef(200)) - .withValue("consensusRules.rskip98", ConfigValueFactory.fromAnyRef("orchid060")) + .withValue("hardforkActivationHeights.orchid060", ConfigValueFactory.fromAnyRef(200)) + .withValue("consensusRules.rskip98", ConfigValueFactory.fromAnyRef("orchid060")) ); - for (ConsensusRule value : ConsensusRule.values()) { - if (value == ConsensusRule.RSKIP98 || value == ConsensusRule.RSKIP103) { + for (ConsensusRule value : values()) { + if (value == RSKIP98 || value == RSKIP103) { MatcherAssert.assertThat(config.isActive(value, 100), is(false)); } else { MatcherAssert.assertThat(config.isActive(value, 100), is(true)); @@ -172,11 +181,11 @@ void readWithTwoUpgradesInOrchid060() { @Test void readWithOneHardcodedActivationNumber() { ActivationConfig config = ActivationConfig.read( - BASE_CONFIG.withValue("consensusRules.rskip85", ConfigValueFactory.fromAnyRef(200)) + BASE_CONFIG.withValue("consensusRules.rskip85", ConfigValueFactory.fromAnyRef(200)) ); - for (ConsensusRule value : ConsensusRule.values()) { - if (value == ConsensusRule.RSKIP85) { + for (ConsensusRule value : values()) { + if (value == RSKIP85) { MatcherAssert.assertThat(config.isActive(value, 100), is(false)); } else { MatcherAssert.assertThat(config.isActive(value, 100), is(true)); @@ -210,13 +219,19 @@ void failsReadingWithUnknownUpgradeConfiguration() { @Test void headerVersion0() { - ActivationConfig config = ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351); + ActivationConfig config = ActivationConfigsForTest.allBut(RSKIP351, RSKIP535); assertEquals((byte) 0x0, config.getHeaderVersion(10)); } @Test void headerVersion1() { - ActivationConfig config = ActivationConfigsForTest.all(); + ActivationConfig config = ActivationConfigsForTest.allBut(RSKIP535); assertEquals((byte) 0x1, config.getHeaderVersion(10)); } + + @Test + void headerVersion2() { + ActivationConfig config = ActivationConfigsForTest.all(); + assertEquals((byte) 0x2, config.getHeaderVersion(10)); + } } diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java index 3ad382a32cb..75caca08972 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigsForTest.java @@ -19,11 +19,17 @@ package org.ethereum.config.blockchain.upgrades; import com.typesafe.config.ConfigFactory; -import java.util.*; +import org.ethereum.config.SystemProperties; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.Nullable; -import org.ethereum.config.SystemProperties; @SuppressWarnings({"squid:S2187"}) // used from another class public class ActivationConfigsForTest { @@ -36,15 +42,15 @@ private static List getPaidBridgeTxsRskip() { private static List getOrchidRskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP85, - ConsensusRule.RSKIP87, - ConsensusRule.RSKIP88, - ConsensusRule.RSKIP89, - ConsensusRule.RSKIP90, - ConsensusRule.RSKIP91, - ConsensusRule.RSKIP92, - ConsensusRule.RSKIP97, - ConsensusRule.RSKIP98 + ConsensusRule.RSKIP85, + ConsensusRule.RSKIP87, + ConsensusRule.RSKIP88, + ConsensusRule.RSKIP89, + ConsensusRule.RSKIP90, + ConsensusRule.RSKIP91, + ConsensusRule.RSKIP92, + ConsensusRule.RSKIP97, + ConsensusRule.RSKIP98 )); } @@ -57,17 +63,17 @@ private static List getOrchid060Rskips() { private static List getWasabi100Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP103, - ConsensusRule.RSKIP106, - ConsensusRule.RSKIP110, - ConsensusRule.RSKIP119, - ConsensusRule.RSKIP120, - ConsensusRule.RSKIP122, - ConsensusRule.RSKIP123, - ConsensusRule.RSKIP124, - ConsensusRule.RSKIP125, - ConsensusRule.RSKIP126, - ConsensusRule.RSKIP132 + ConsensusRule.RSKIP103, + ConsensusRule.RSKIP106, + ConsensusRule.RSKIP110, + ConsensusRule.RSKIP119, + ConsensusRule.RSKIP120, + ConsensusRule.RSKIP122, + ConsensusRule.RSKIP123, + ConsensusRule.RSKIP124, + ConsensusRule.RSKIP125, + ConsensusRule.RSKIP126, + ConsensusRule.RSKIP132 )); } @@ -87,117 +93,118 @@ private static List getTwoToThreeRskips() { private static List getPapyrus200Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP134, - ConsensusRule.RSKIP137, - ConsensusRule.RSKIP140, - ConsensusRule.RSKIP143, - ConsensusRule.RSKIP146, - ConsensusRule.RSKIP150, - ConsensusRule.RSKIP151, - ConsensusRule.RSKIP152, - ConsensusRule.RSKIP156, - ConsensusRule.RSKIPUMM + ConsensusRule.RSKIP134, + ConsensusRule.RSKIP137, + ConsensusRule.RSKIP140, + ConsensusRule.RSKIP143, + ConsensusRule.RSKIP146, + ConsensusRule.RSKIP150, + ConsensusRule.RSKIP151, + ConsensusRule.RSKIP152, + ConsensusRule.RSKIP156, + ConsensusRule.RSKIPUMM )); } private static List getIris300Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP153, - ConsensusRule.RSKIP169, - ConsensusRule.RSKIP170, - ConsensusRule.RSKIP171, - ConsensusRule.RSKIP174, - ConsensusRule.RSKIP176, - ConsensusRule.RSKIP179, - ConsensusRule.RSKIP180, - ConsensusRule.RSKIP181, - ConsensusRule.RSKIP185, - ConsensusRule.RSKIP186, - ConsensusRule.RSKIP191, - ConsensusRule.RSKIP197, - ConsensusRule.RSKIP199, - ConsensusRule.RSKIP200, - ConsensusRule.RSKIP201, - ConsensusRule.RSKIP218, - ConsensusRule.RSKIP219, - ConsensusRule.RSKIP220 + ConsensusRule.RSKIP153, + ConsensusRule.RSKIP169, + ConsensusRule.RSKIP170, + ConsensusRule.RSKIP171, + ConsensusRule.RSKIP174, + ConsensusRule.RSKIP176, + ConsensusRule.RSKIP179, + ConsensusRule.RSKIP180, + ConsensusRule.RSKIP181, + ConsensusRule.RSKIP185, + ConsensusRule.RSKIP186, + ConsensusRule.RSKIP191, + ConsensusRule.RSKIP197, + ConsensusRule.RSKIP199, + ConsensusRule.RSKIP200, + ConsensusRule.RSKIP201, + ConsensusRule.RSKIP218, + ConsensusRule.RSKIP219, + ConsensusRule.RSKIP220 )); } private static List getHop400Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP271, - ConsensusRule.RSKIP284, - ConsensusRule.RSKIP290, - ConsensusRule.RSKIP293, - ConsensusRule.RSKIP294, - ConsensusRule.RSKIP297 + ConsensusRule.RSKIP271, + ConsensusRule.RSKIP284, + ConsensusRule.RSKIP290, + ConsensusRule.RSKIP293, + ConsensusRule.RSKIP294, + ConsensusRule.RSKIP297 )); } private static List getHop401Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP353, - ConsensusRule.RSKIP357 + ConsensusRule.RSKIP353, + ConsensusRule.RSKIP357 )); } private static List getFingerroot500Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP252, - ConsensusRule.RSKIP326, - ConsensusRule.RSKIP374, - ConsensusRule.RSKIP375, - ConsensusRule.RSKIP377, - ConsensusRule.RSKIP383, - ConsensusRule.RSKIP385 + ConsensusRule.RSKIP252, + ConsensusRule.RSKIP326, + ConsensusRule.RSKIP374, + ConsensusRule.RSKIP375, + ConsensusRule.RSKIP377, + ConsensusRule.RSKIP383, + ConsensusRule.RSKIP385 )); } private static List getArrowhead600Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP203, - ConsensusRule.RSKIP376, - ConsensusRule.RSKIP379, - ConsensusRule.RSKIP398, - ConsensusRule.RSKIP400, - ConsensusRule.RSKIP415, - ConsensusRule.RSKIP417 + ConsensusRule.RSKIP203, + ConsensusRule.RSKIP376, + ConsensusRule.RSKIP379, + ConsensusRule.RSKIP398, + ConsensusRule.RSKIP400, + ConsensusRule.RSKIP415, + ConsensusRule.RSKIP417 )); } private static List getArrowhead631Rskips() { return new ArrayList<>(Collections.singletonList( - ConsensusRule.RSKIP434 + ConsensusRule.RSKIP434 )); } private static List getLovell700Rskips() { return new ArrayList<>(Arrays.asList( - ConsensusRule.RSKIP419, - ConsensusRule.RSKIP427, - ConsensusRule.RSKIP428, - ConsensusRule.RSKIP438, - ConsensusRule.RSKIP454, - ConsensusRule.RSKIP459, - ConsensusRule.RSKIP460 + ConsensusRule.RSKIP419, + ConsensusRule.RSKIP427, + ConsensusRule.RSKIP428, + ConsensusRule.RSKIP438, + ConsensusRule.RSKIP454, + ConsensusRule.RSKIP459, + ConsensusRule.RSKIP460 )); } private static List getReed800Rskips() { return new ArrayList<>(List.of( - ConsensusRule.RSKIP305, - ConsensusRule.RSKIP516 + ConsensusRule.RSKIP305, + ConsensusRule.RSKIP516 )); } private static List getReed810Rskips() { return new ArrayList<>(List.of( - ConsensusRule.RSKIP144, - ConsensusRule.RSKIP351, - ConsensusRule.RSKIP502, - ConsensusRule.RSKIP529, - ConsensusRule.RSKIP536 + ConsensusRule.RSKIP144, + ConsensusRule.RSKIP351, + ConsensusRule.RSKIP502, + ConsensusRule.RSKIP529, + ConsensusRule.RSKIP535, + ConsensusRule.RSKIP536 )); } @@ -419,7 +426,9 @@ public static ActivationConfig reed800(List except) { return enableTheseDisableThose(rskips, except); } - public static ActivationConfig reed810() { return reed810(Collections.emptyList()); } + public static ActivationConfig reed810() { + return reed810(Collections.emptyList()); + } public static ActivationConfig reed810(List except) { List rskips = new ArrayList<>(); @@ -443,7 +452,9 @@ public static ActivationConfig reed810(List except) { return enableTheseDisableThose(rskips, except); } - public static ActivationConfig vetiver900() { return vetiver900(Collections.emptyList()); } + public static ActivationConfig vetiver900() { + return vetiver900(Collections.emptyList()); + } public static ActivationConfig vetiver900(List except) { List rskips = new ArrayList<>(); @@ -486,12 +497,12 @@ public static ActivationConfig all() { * @return */ public static ActivationConfig enableTheseDisableThose( - List enableThese, - @Nullable List disableThose) { + List enableThese, + @Nullable List disableThose) { Map consensusRules = EnumSet.allOf(ConsensusRule.class) - .stream() - .collect(Collectors.toMap(Function.identity(), ignored -> -1L)); + .stream() + .collect(Collectors.toMap(Function.identity(), ignored -> -1L)); for (ConsensusRule consensusRule : enableThese) { consensusRules.put(consensusRule, 0L); } @@ -506,8 +517,8 @@ public static ActivationConfig enableTheseDisableThose( public static ActivationConfig allBut(ConsensusRule... upgradesToDisable) { Map consensusRules = EnumSet.allOf(ConsensusRule.class) - .stream() - .collect(Collectors.toMap(Function.identity(), ignored -> 0L)); + .stream() + .collect(Collectors.toMap(Function.identity(), ignored -> 0L)); for (ConsensusRule consensusRule : upgradesToDisable) { consensusRules.put(consensusRule, -1L); } @@ -517,8 +528,8 @@ public static ActivationConfig allBut(ConsensusRule... upgradesToDisable) { public static ActivationConfig only(ConsensusRule... upgradesToEnable) { Map consensusRules = EnumSet.allOf(ConsensusRule.class) - .stream() - .collect(Collectors.toMap(Function.identity(), ignored -> -1L)); + .stream() + .collect(Collectors.toMap(Function.identity(), ignored -> -1L)); for (ConsensusRule consensusRule : upgradesToEnable) { consensusRules.put(consensusRule, 0L); } @@ -528,8 +539,8 @@ public static ActivationConfig only(ConsensusRule... upgradesToEnable) { public static ActivationConfig bridgeUnitTest() { Map allDisabled = EnumSet.allOf(ConsensusRule.class) - .stream() - .collect(Collectors.toMap(Function.identity(), ignored -> -1L)); + .stream() + .collect(Collectors.toMap(Function.identity(), ignored -> -1L)); allDisabled.put(ConsensusRule.ARE_BRIDGE_TXS_PAID, 10L); return new ActivationConfig(allDisabled); diff --git a/rskj-core/src/test/java/org/ethereum/core/BlockHeaderBuilderTest.java b/rskj-core/src/test/java/org/ethereum/core/BlockHeaderBuilderTest.java index 041e7de4e9d..31d879e0acf 100644 --- a/rskj-core/src/test/java/org/ethereum/core/BlockHeaderBuilderTest.java +++ b/rskj-core/src/test/java/org/ethereum/core/BlockHeaderBuilderTest.java @@ -42,7 +42,10 @@ import java.util.Arrays; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.AdditionalMatchers.geq; import static org.mockito.Mockito.when; @@ -61,8 +64,8 @@ void createsHeaderWithParentHash() { Keccak256 parentHash = TestUtils.generateHash("parentHash"); BlockHeader header = blockHeaderBuilder - .setParentHash(parentHash.getBytes()) - .build(); + .setParentHash(parentHash.getBytes()) + .build(); assertEquals(parentHash, header.getParentHash()); } @@ -183,7 +186,7 @@ void createsHeaderWithDifficulty() { @Test void createsHeaderWithDifficultyFromBytes() { - byte[] bDiffData = new byte[] { 0, 16 }; + byte[] bDiffData = new byte[]{0, 16}; BlockHeader header = blockHeaderBuilder .setDifficultyFromBytes(bDiffData) @@ -231,10 +234,10 @@ void createsHeaderWithEmptyMinimumGasPrice() { @Test void createsHeaderWithMiningFields() { - byte[] btcCoinbase = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcCoinbase",128); - byte[] btcHeader = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcHeader",80); - byte[] merkleProof = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "merkleProof",32); - byte[] extraData = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "extraData",32); + byte[] btcCoinbase = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcCoinbase", 128); + byte[] btcHeader = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcHeader", 80); + byte[] merkleProof = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "merkleProof", 32); + byte[] extraData = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "extraData", 32); BlockHeader header = blockHeaderBuilder .setBitcoinMergedMiningHeader(btcHeader) @@ -259,17 +262,20 @@ void createsHeaderWithEmptyMergedMiningFields() { assertArrayEquals(new byte[0], header.getExtraData()); } - @ParameterizedTest(name = "createHeader: when createConsensusCompliantHeader {0} and useRskip92Encoding {1} then expectedSize {2}") + @ParameterizedTest(name = "createHeader: when createConsensusCompliantHeader {0} and useRskip92Encoding {1} and useRSKIP351 {2} and useRSKIP535 {3} then expectedSize {4}") @ArgumentsSource(CreateHeaderArgumentsProvider.class) - void createsHeaderWith(boolean createConsensusCompliantHeader, boolean useRskip92Encoding, boolean useRSKIP351, int expectedSize) { - BlockHeaderBuilder blockHeaderBuilder = useRSKIP351 - ? this.blockHeaderBuilder - : new BlockHeaderBuilder(ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351)); - byte[] btcCoinbase = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcCoinbase",128); - byte[] btcHeader = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcHeader",80); - byte[] merkleProof = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "merkleProof",32); - - BlockHeader header = blockHeaderBuilder + void testHeaderCreationWithParameters(boolean createConsensusCompliantHeader, boolean useRskip92Encoding, boolean useRSKIP351, boolean useRSKIP535, int expectedSize) { + BlockHeaderBuilder builderForTest; + if (useRSKIP351) + builderForTest = useRSKIP535 ? this.blockHeaderBuilder : new BlockHeaderBuilder(ActivationConfigsForTest.allBut(ConsensusRule.RSKIP535)); + else + builderForTest = new BlockHeaderBuilder(useRSKIP535 ? ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351) : ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351, ConsensusRule.RSKIP535)); + + byte[] btcCoinbase = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcCoinbase", 128); + byte[] btcHeader = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "btcHeader", 80); + byte[] merkleProof = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "merkleProof", 32); + + BlockHeader header = builderForTest .setCreateConsensusCompliantHeader(createConsensusCompliantHeader) .setBitcoinMergedMiningHeader(btcHeader) .setBitcoinMergedMiningMerkleProof(merkleProof) @@ -283,7 +289,7 @@ void createsHeaderWith(boolean createConsensusCompliantHeader, boolean useRskip9 @Test void createsHeaderWithIncludeForkDetectionDataOn() { - byte[] expectedForkDetectionData = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + byte[] expectedForkDetectionData = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; BlockHeader header = blockHeaderBuilder .setCreateConsensusCompliantHeader(false) @@ -299,7 +305,7 @@ void createsHeaderWithIncludeForkDetectionDataOn() { @Test void createsHeaderWithIncludeForkDetectionDataOff() { - byte[] expectedForkDetectionData = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + byte[] expectedForkDetectionData = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; BlockHeader header = blockHeaderBuilder .setCreateConsensusCompliantHeader(false) @@ -315,7 +321,7 @@ void createsHeaderWithIncludeForkDetectionDataOff() { @Test void createsHeaderWithIncludeForkDetectionDataOffButConsensusCompliantOn() { - byte[] expectedForkDetectionData = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + byte[] expectedForkDetectionData = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; BlockHeader header = blockHeaderBuilder .setCreateConsensusCompliantHeader(true) @@ -340,7 +346,7 @@ void createsHeaderWithEmptyMergedMiningForkDetectionData() { @Test void createsHeaderWithUmmRoot() { - byte[] ummRoot = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "ummRoot",20); + byte[] ummRoot = TestUtils.generateBytes(BlockHeaderBuilderTest.class, "ummRoot", 20); BlockHeader header = blockHeaderBuilder .setUmmRoot(ummRoot) .build(); @@ -501,19 +507,6 @@ void createsHeaderWithParallelCompliantButWithNullEdges() { assertArrayEquals(null, header.getTxExecutionSublistsEdges()); } - private static class CreateHeaderArgumentsProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - Arguments.of(false, false, false, 21), - Arguments.of(false, true, false, 19), - Arguments.of(true, false, false, 19), - Arguments.of(false, false, true, 22), - Arguments.of(false, true, true, 20), - Arguments.of(true, false, true, 20) - ); - } - } @Test void createsHeaderWithVersion0WithNoRskip351() { @@ -534,7 +527,34 @@ void createsHeaderWithVersion0BeforeRskip351() { @Test void createHeaderWithVersion1AfterRskip351() { // RSKIP351 = 0 - BlockHeader header = new BlockHeaderBuilder(ActivationConfigsForTest.all()).build(); + BlockHeader header = new BlockHeaderBuilder(ActivationConfigsForTest.allBut(ConsensusRule.RSKIP535)).build(); assertEquals((byte) 0x1, header.getVersion()); } + + @Test + void createHeaderWithVersion2AfterRskip351() { + // RSKIP351 = 0 + BlockHeader header = new BlockHeaderBuilder(ActivationConfigsForTest.all()).build(); + assertEquals((byte) 0x2, header.getVersion()); + } + + private static class CreateHeaderArgumentsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(false, false, false, false, 21), + Arguments.of(false, false, false, true, 22), + Arguments.of(false, true, false, false, 19), + Arguments.of(false, true, false, true, 20), + Arguments.of(true, false, false, false, 19), + Arguments.of(true, false, false, true, 20), + Arguments.of(false, false, true, false, 22), + Arguments.of(false, false, true, true, 23), + Arguments.of(false, true, true, false, 20), + Arguments.of(false, true, true, true, 21), + Arguments.of(true, false, true, false, 20), + Arguments.of(true, false, true, true, 21) + ); + } + } } diff --git a/rskj-core/src/test/java/org/ethereum/core/BlockHeaderExtensionV2Test.java b/rskj-core/src/test/java/org/ethereum/core/BlockHeaderExtensionV2Test.java new file mode 100644 index 00000000000..cdb73037ee8 --- /dev/null +++ b/rskj-core/src/test/java/org/ethereum/core/BlockHeaderExtensionV2Test.java @@ -0,0 +1,258 @@ +/* + * This file is part of RskJ + * Copyright (C) 2025 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 org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class BlockHeaderExtensionV2Test { + + @Test + void constructorAndGetters() { + byte[] logsBloom = new byte[256]; + Arrays.fill(logsBloom, (byte) 0xAB); + short[] edges = new short[]{1, 2, 3}; + byte[] baseEvent = new byte[]{0x01, 0x02}; + + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(logsBloom, edges, baseEvent); + + assertArrayEquals(logsBloom, ext.getLogsBloom()); + assertArrayEquals(edges, ext.getTxExecutionSublistsEdges()); + assertArrayEquals(baseEvent, ext.getBaseEvent()); + assertEquals(0x2, ext.getVersion()); + } + + @Test + void setBaseEventCopiesArray() { + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(null, null, null); + byte[] hash = new byte[]{0x0A, 0x0B}; + ext.setBaseEvent(hash); + + assertArrayEquals(hash, ext.getBaseEvent()); + // Mutate original array to check for defensive copy + hash[0] = 0x00; + assertNotEquals(hash[0], ext.getBaseEvent()[0]); + } + + @Test + void getBaseEventReturnsCopy() { + byte[] hash = new byte[]{0x0A, 0x0B}; + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(null, null, hash); + byte[] returned = ext.getBaseEvent(); + returned[0] = 0x00; + assertNotEquals(returned[0], ext.getBaseEvent()[0]); + } + + @Test + void encodingAndDecoding() { + byte[] logsBloom = new byte[256]; + Arrays.fill(logsBloom, (byte) 0x01); + short[] edges = new short[]{10, 20}; + byte[] superChainDataHash = new byte[]{0x55, 0x66}; + + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(logsBloom, edges, superChainDataHash); + byte[] encoded = ext.getEncoded(); + + BlockHeaderExtensionV2 decoded = BlockHeaderExtensionV2.fromEncoded(encoded); + + assertArrayEquals(logsBloom, decoded.getLogsBloom()); + assertArrayEquals(edges, decoded.getTxExecutionSublistsEdges()); + assertArrayEquals(superChainDataHash, decoded.getBaseEvent()); + } + + @Test + void encodingWithNullFields() { + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(null, null, null); + byte[] encoded = ext.getEncoded(); + BlockHeaderExtensionV2 decoded = BlockHeaderExtensionV2.fromEncoded(encoded); + + assertNull(decoded.getLogsBloom()); + assertNull(decoded.getTxExecutionSublistsEdges()); + assertNull(decoded.getBaseEvent()); + } + + @Test + void decodeNullInputThrows() { + assertThrows(IllegalArgumentException.class, () -> BlockHeaderExtensionV2.fromEncoded(null)); + } + + @Test + void decodeOnlyLogsBloom() { + byte[] logsBloom = new byte[256]; + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(logsBloom, null, null); + byte[] encoded = ext.getEncoded(); + + BlockHeaderExtensionV2 decoded = BlockHeaderExtensionV2.fromEncoded(encoded); + + assertArrayEquals(logsBloom, decoded.getLogsBloom()); + assertNull(decoded.getTxExecutionSublistsEdges()); + assertNull(decoded.getBaseEvent()); + } + + @Test + void decodeLogsBloomAndEdges() { + byte[] logsBloom = new byte[256]; + short[] edges = new short[]{1, 2}; + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(logsBloom, edges, null); + byte[] encoded = ext.getEncoded(); + + BlockHeaderExtensionV2 decoded = BlockHeaderExtensionV2.fromEncoded(encoded); + + assertArrayEquals(logsBloom, decoded.getLogsBloom()); + assertArrayEquals(edges, decoded.getTxExecutionSublistsEdges()); + assertNull(decoded.getBaseEvent()); + } + + @Test + void decodeWithEmptyEdges() { + byte[] logsBloom = new byte[256]; + byte[] baseEvent = new byte[128]; + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(logsBloom, null, baseEvent); + byte[] encoded = ext.getEncoded(); + + BlockHeaderExtensionV2 decoded = BlockHeaderExtensionV2.fromEncoded(encoded); + + assertArrayEquals(logsBloom, decoded.getLogsBloom()); + assertArrayEquals(baseEvent, decoded.getBaseEvent()); + assertNull(decoded.getTxExecutionSublistsEdges()); + } + + @Test + void decodeWithEmptyBridgeEvent() { + byte[] logsBloom = new byte[256]; + short[] edges = new short[]{1}; + byte[] baseEvent = new byte[0]; + BlockHeaderExtensionV2 ext = new BlockHeaderExtensionV2(logsBloom, edges, baseEvent); + byte[] encoded = ext.getEncoded(); + + BlockHeaderExtensionV2 decoded = BlockHeaderExtensionV2.fromEncoded(encoded); + + assertArrayEquals(logsBloom, decoded.getLogsBloom()); + assertArrayEquals(edges, decoded.getTxExecutionSublistsEdges()); + assertNull(decoded.getBaseEvent()); + } + + @Test + void decodeMalformedRLPThrows() { + byte[] malformed = new byte[]{0x01, 0x02, 0x03}; + assertThrows(Exception.class, () -> BlockHeaderExtensionV2.fromEncoded(malformed)); + } + + @Test + void testSetBaseEventWithNull() { + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], new byte[0]); + extension.setBaseEvent(null); + assertNull(extension.getBaseEvent()); + } + + @Test + void testSetBaseEventWithEmptyArray() { + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], new byte[0]); + byte[] emptyArray = new byte[0]; + extension.setBaseEvent(emptyArray); + assertArrayEquals(emptyArray, extension.getBaseEvent()); + } + + @Test + void testSetBaseEventWithLargeValue() { + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], new byte[0]); + byte[] largeValue = new byte[1024]; + for (int i = 0; i < 1024; i++) { + largeValue[i] = (byte) (i % 256); + } + extension.setBaseEvent(largeValue); + assertArrayEquals(largeValue, extension.getBaseEvent()); + } + + @Test + void testSetBaseEventWithSpecialBytes() { + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], new byte[0]); + byte[] specialBytes = new byte[]{0x00, (byte) 0xFF, (byte) 0x80, (byte) 0x7F}; + extension.setBaseEvent(specialBytes); + assertArrayEquals(specialBytes, extension.getBaseEvent()); + } + + @Test + void testSetBaseEventMultipleTimes() { + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], new byte[0]); + + byte[] firstValue = new byte[]{1, 2, 3}; + extension.setBaseEvent(firstValue); + assertArrayEquals(firstValue, extension.getBaseEvent()); + + byte[] secondValue = new byte[]{4, 5, 6, 7, 8}; + extension.setBaseEvent(secondValue); + assertArrayEquals(secondValue, extension.getBaseEvent()); + } + + @Test + void testGetBaseEventReturnsCorrectValue() { + byte[] expectedBridgeEvent = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD}; + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], expectedBridgeEvent); + assertArrayEquals(expectedBridgeEvent, extension.getBaseEvent()); + } + + @Test + void testBridgeEventPersistenceAfterEncoding() { + byte[] originalBridgeEvent = new byte[]{0x11, 0x22, 0x33, 0x44, 0x55}; + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], originalBridgeEvent); + + // Encode and decode + byte[] encoded = extension.getEncoded(); + BlockHeaderExtensionV2 decoded = BlockHeaderExtensionV2.fromEncoded(encoded); + + // Verify bridge event is preserved + assertArrayEquals(originalBridgeEvent, decoded.getBaseEvent()); + } + + @Test + void testBridgeEventWithZeroBytes() { + byte[] zeroBytes = new byte[32]; + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], zeroBytes); + assertArrayEquals(zeroBytes, extension.getBaseEvent()); + } + + @Test + void testBridgeEventWithAllOnesBytes() { + byte[] onesBytes = new byte[16]; + for (int i = 0; i < 16; i++) { + onesBytes[i] = (byte) 0xFF; + } + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], onesBytes); + assertArrayEquals(onesBytes, extension.getBaseEvent()); + } + + @Test + void testBridgeEventWithMaxSize() { + // Test with maximum practical size (64KB) + byte[] maxSizeValue = new byte[65536]; + for (int i = 0; i < 65536; i++) { + maxSizeValue[i] = (byte) (i % 256); + } + BlockHeaderExtensionV2 extension = new BlockHeaderExtensionV2(new byte[256], new short[0], maxSizeValue); + assertArrayEquals(maxSizeValue, extension.getBaseEvent()); + } +} diff --git a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java index ec68c2c64f3..66a4241f621 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplLogsTest.java @@ -1185,7 +1185,7 @@ public static void addEmptyBlockToBlockchain( List txs = new ArrayList<>(); txs.add(tx); - Block block1 = new BlockBuilder(blockChain, null, blockStore, new BlockGenerator(Constants.regtest(), ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351))) + Block block1 = new BlockBuilder(blockChain, null, blockStore, new BlockGenerator(Constants.regtest(), ActivationConfigsForTest.allBut(ConsensusRule.RSKIP351, ConsensusRule.RSKIP535))) .trieStore(trieStore).parent(genesis).transactions(txs).build(); assertEquals(ImportResult.IMPORTED_BEST, blockChain.tryToConnect(block1)); } diff --git a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java index 46f25e7cca5..0a5656e1dc3 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/Web3ImplTest.java @@ -21,7 +21,12 @@ import co.rsk.blockchain.utils.BlockGenerator; import co.rsk.config.RskSystemProperties; import co.rsk.config.TestSystemProperties; -import co.rsk.core.*; +import co.rsk.core.Coin; +import co.rsk.core.ReversibleTransactionExecutor; +import co.rsk.core.RskAddress; +import co.rsk.core.TransactionExecutorFactory; +import co.rsk.core.Wallet; +import co.rsk.core.WalletFactory; import co.rsk.core.bc.BlockChainImpl; import co.rsk.core.bc.TransactionPoolImpl; import co.rsk.crypto.Keccak256; @@ -68,7 +73,20 @@ import com.typesafe.config.ConfigValueFactory; import org.bouncycastle.util.encoders.Hex; import org.ethereum.TestUtils; -import org.ethereum.core.*; +import org.ethereum.core.Account; +import org.ethereum.core.Block; +import org.ethereum.core.BlockFactory; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.BlockHeaderBuilder; +import org.ethereum.core.BlockTxSignatureCache; +import org.ethereum.core.Blockchain; +import org.ethereum.core.CallTransaction; +import org.ethereum.core.ImportResult; +import org.ethereum.core.ReceivedTxSignatureCache; +import org.ethereum.core.SignatureCache; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionPool; +import org.ethereum.core.TransactionPoolAddResult; import org.ethereum.core.genesis.BlockTag; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; @@ -83,12 +101,26 @@ import org.ethereum.net.client.ConfigCapabilities; import org.ethereum.net.server.ChannelManager; import org.ethereum.net.server.PeerServer; -import org.ethereum.rpc.Simples.*; +import org.ethereum.rpc.Simples.SimpleChannelManager; +import org.ethereum.rpc.Simples.SimpleConfigCapabilities; +import org.ethereum.rpc.Simples.SimpleEthereum; +import org.ethereum.rpc.Simples.SimpleMinerClient; +import org.ethereum.rpc.Simples.SimplePeerServer; import org.ethereum.rpc.dto.BlockResultDTO; import org.ethereum.rpc.dto.TransactionReceiptDTO; import org.ethereum.rpc.dto.TransactionResultDTO; import org.ethereum.rpc.exception.RskJsonRpcRequestException; -import org.ethereum.rpc.parameters.*; +import org.ethereum.rpc.parameters.BlockHashParam; +import org.ethereum.rpc.parameters.BlockIdentifierParam; +import org.ethereum.rpc.parameters.BlockRefParam; +import org.ethereum.rpc.parameters.CallArgumentsParam; +import org.ethereum.rpc.parameters.HexAddressParam; +import org.ethereum.rpc.parameters.HexDataParam; +import org.ethereum.rpc.parameters.HexDurationParam; +import org.ethereum.rpc.parameters.HexIndexParam; +import org.ethereum.rpc.parameters.HexKeyParam; +import org.ethereum.rpc.parameters.HexNumberParam; +import org.ethereum.rpc.parameters.TxHashParam; import org.ethereum.util.BuildInfo; import org.ethereum.util.ByteUtil; import org.ethereum.util.TransactionFactoryHelper; @@ -104,14 +136,33 @@ import org.mockito.Mockito; import java.math.BigInteger; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * Created by Ruben Altman on 09/06/2016. @@ -238,7 +289,7 @@ void eth_syncing_returnSyncingResultWhenSyncing() { Object result = web3.eth_syncing(); - assertTrue(result instanceof SyncingResult, "Node is syncing, must return sync manager"); + assertInstanceOf(SyncingResult.class, result, "Node is syncing, must return sync manager"); assertEquals(0, ((SyncingResult) result).getHighestBlock().compareTo("0x5"), "Highest block is 5"); assertEquals(0, ((SyncingResult) result).getCurrentBlock().compareTo("0x0"), "Simple blockchain starts from genesis block"); } @@ -1178,7 +1229,7 @@ void pendingTransactionsReturnsCorrectTransaction() { Web3Impl web3 = createWeb3WithMocks(ethModuleMock); Transaction mockTransaction1 = mockTransactionFrom("0x63a15ed8c3b83efc744f2e0a7824a00846c21860"); - List allTransactions = Arrays.asList(mockTransaction1); + List allTransactions = List.of(mockTransaction1); when(ethModuleMock.ethPendingTransactions()).thenReturn(allTransactions); @@ -1729,7 +1780,7 @@ void getUncleByBlockHashAndIndexBlockWithUncles() { blockE.setBitcoinMergedMiningHeader(new byte[]{0x05}); assertEquals(ImportResult.IMPORTED_NOT_BEST, world.getBlockChain().tryToConnect(blockE)); - List blockFUncles = Arrays.asList(blockE.getHeader()); + List blockFUncles = Collections.singletonList(blockE.getHeader()); Block blockF = new BlockBuilder(world.getBlockChain(), world.getBridgeSupportFactory(), world.getBlockStore()) .trieStore(world.getTrieStore()).difficulty(10).parent(blockD).uncles(blockFUncles).build(); blockF.setBitcoinMergedMiningHeader(new byte[]{0x06}); @@ -1798,7 +1849,7 @@ void getUncleByBlockHashAndIndexBlockWithUnclesCorrespondingToAnUnknownBlock() { assertEquals(1, blockE.getTransactionsList().size()); Assertions.assertFalse(Arrays.equals(blockC.getTxTrieRoot(), blockE.getTxTrieRoot())); - List blockFUncles = Arrays.asList(blockE.getHeader()); + List blockFUncles = Collections.singletonList(blockE.getHeader()); Block blockF = new BlockBuilder(world.getBlockChain(), world.getBridgeSupportFactory(), world.getBlockStore()) .trieStore(world.getTrieStore()).difficulty(10).parent(blockD).uncles(blockFUncles).build(); blockF.setBitcoinMergedMiningHeader(new byte[]{0x06}); @@ -1863,7 +1914,7 @@ void getUncleByBlockNumberAndIndexBlockWithUncles() { blockE.setBitcoinMergedMiningHeader(new byte[]{0x05}); assertEquals(ImportResult.IMPORTED_NOT_BEST, world.getBlockChain().tryToConnect(blockE)); - List blockFUncles = Arrays.asList(blockE.getHeader()); + List blockFUncles = Collections.singletonList(blockE.getHeader()); Block blockF = new BlockBuilder(world.getBlockChain(), world.getBridgeSupportFactory(), world.getBlockStore()) .trieStore(world.getTrieStore()).difficulty(10).parent(blockD).uncles(blockFUncles).build(); blockF.setBitcoinMergedMiningHeader(new byte[]{0x06}); @@ -1929,7 +1980,7 @@ void getUncleByBlockNumberAndIndexBlockWithUnclesCorrespondingToAnUnknownBlock() assertEquals(1, blockE.getTransactionsList().size()); Assertions.assertFalse(Arrays.equals(blockC.getTxTrieRoot(), blockE.getTxTrieRoot())); - List blockFUncles = Arrays.asList(blockE.getHeader()); + List blockFUncles = Collections.singletonList(blockE.getHeader()); Block blockF = new BlockBuilder(world.getBlockChain(), world.getBridgeSupportFactory(), world.getBlockStore()) .trieStore(world.getTrieStore()).difficulty(10).parent(blockD).uncles(blockFUncles).build(); blockF.setBitcoinMergedMiningHeader(new byte[]{0x06}); @@ -3107,6 +3158,7 @@ private void assertNonBlockHashWhenCanonical(Function toInvoke) { TestUtils.assertThrows(RskJsonRpcRequestException.class, () -> toInvoke.apply(blockRef)); } + private void assertNonBlockHashByBlockRefWhenCanonical(Function toInvoke) { final String nonExistentBlockHash = "0x" + String.join("", Collections.nCopies(64, "1")); // "0x1111..." Map blockRef = new HashMap() { @@ -3130,6 +3182,7 @@ private void assertNonBlockHashWhenIsNotCanonical(Function toInvoke TestUtils.assertThrows(RskJsonRpcRequestException.class, () -> toInvoke.apply(blockRef)); } + private void assertNonBlockHashByBlockRefParamWhenIsNotCanonical(Function toInvoke) { final String nonExistentBlockHash = "0x" + String.join("", Collections.nCopies(64, "1")); // "0x1111..." Map blockRef = new HashMap() { @@ -3152,6 +3205,7 @@ private void assertNonCanonicalBlockHashWhenCanonical(Block block, Function toInvoke.apply(blockRef)); } + private void assertNonCanonicalBlockHashByBlockRefParamWhenCanonical(Block block, Function toInvoke) { Map blockRef = new HashMap() { { @@ -3205,7 +3259,8 @@ private void assertNonCanonicalBlockHashByBlockRefParamWhenNotCanonical(String e }; BlockRefParam blockRefParam = new BlockRefParam(blockRef); - assertEquals(expected, toInvoke.apply(blockRefParam)); } + assertEquals(expected, toInvoke.apply(blockRefParam)); + } private void assertCanonicalBlockHashWhenNotCanonical(String expected, Block block, Function toInvoke) { Map blockRef = new HashMap() { @@ -3250,25 +3305,6 @@ private void assertInvalidInput(Function toInvoke) { TestUtils.assertThrows(RskJsonRpcRequestException.class, () -> toInvoke.apply(blockRef)); } - //Chain Param Object creations - private class ChainParams { - private final Web3Impl web3; - private final String accountAddress; - private final Block block; - private CallArguments argsForCall; // for call tests could be null - - private ChainParams(World world, String accountAddress, Block block) { - this.web3 = createWeb3(world); - this.accountAddress = accountAddress; - this.block = block; - } - - private ChainParams(World world, String accountAddress, Block block, CallArguments argsForCall) { - this(world, accountAddress, block); - this.argsForCall = argsForCall; - } - } - private ChainParams chainWithAccount10kBalance(boolean isCanonicalBlock) { final World world = new World(); final String accountAddress = createAccountWith10KBalance(world); @@ -3412,4 +3448,102 @@ void transactionReceiptAndResultHasTypeField() { assertEquals("0x0", txReceipt.getType()); assertEquals("0x0", txResult.getType()); } + + @Test + void eth_getBlockByNumber_returnsBaseEventInJsonResponse() { + // given + World world = new World(); + Web3Impl web3 = createWeb3(world); + + // when + createBlockWithBaseEvent(world, new byte[]{0x01, 0x02, 0x03}); + BlockResultDTO result = web3.eth_getBlockByNumber(new BlockIdentifierParam("latest"), false); + + // then + assertNotNull(result.getBaseEvent()); + assertEquals("0x010203", result.getBaseEvent()); + } + + @Test + void eth_getBlockByHash_returnsBaseEventInJsonResponse() { + // given + World world = new World(); + Web3Impl web3 = createWeb3(world); + Block block = createBlockWithBaseEvent(world, new byte[]{0x04, 0x05, 0x06}); + String blockHash = block.getHashJsonString(); + + // when + BlockHashParam blockHashParam = new BlockHashParam(blockHash); + BlockResultDTO result = web3.eth_getBlockByHash(blockHashParam, false); + + // then + assertNotNull(result.getBaseEvent()); + assertEquals("0x040506", result.getBaseEvent()); + } + + @Test + void eth_getBlockByNumber_returnsNullWhenBaseEventNotSet() { + // given + World world = new World(); + Web3Impl web3 = createWeb3(world); + + // when + createBlockWithBaseEvent(world, new byte[0]); + BlockResultDTO result = web3.eth_getBlockByNumber(new BlockIdentifierParam("latest"), false); + + // then + assertNull(result.getBaseEvent()); + } + + private Block createBlockWithBaseEvent(World world, byte[] baseEvent) { + final Block genesis = world.getBlockChain().getBestBlock(); + + // Create a block with baseEvent using BlockHeaderBuilder + BlockHeaderBuilder headerBuilder = new BlockHeaderBuilder(config.getActivationConfig()) + .setParentHash(genesis.getHash().getBytes()) + .setUnclesHash(genesis.getUnclesHash()) + .setCoinbase(genesis.getCoinbase()) + .setStateRoot(genesis.getStateRoot()) + .setTxTrieRoot(genesis.getTxTrieRoot()) + .setReceiptTrieRoot(genesis.getReceiptsRoot()) + .setLogsBloom(genesis.getHeader().getLogsBloom()) + .setDifficulty(genesis.getDifficulty()) + .setNumber(genesis.getNumber() + 1) + .setGasLimit(genesis.getGasLimit()) + .setGasUsed(genesis.getGasUsed()) + .setTimestamp(genesis.getTimestamp() + 1) + .setExtraData(genesis.getExtraData()) + .setBitcoinMergedMiningHeader(genesis.getBitcoinMergedMiningHeader()) + .setBitcoinMergedMiningMerkleProof(genesis.getBitcoinMergedMiningMerkleProof()) + .setBitcoinMergedMiningCoinbaseTransaction(genesis.getBitcoinMergedMiningCoinbaseTransaction()) + .setMergedMiningForkDetectionData(genesis.getHeader().getMiningForkDetectionData()) + .setMinimumGasPrice(genesis.getMinimumGasPrice()) + .setUncleCount(genesis.getHeader().getUncleCount()) + .setBaseEvent(baseEvent); + + BlockHeader header = headerBuilder.build(); + Block block = new Block(header, Collections.emptyList(), Collections.emptyList(), false, true); + + assertEquals(ImportResult.IMPORTED_BEST, world.getBlockChain().tryToConnect(block)); + return block; + } + + //Chain Param Object creations + private class ChainParams { + private final Web3Impl web3; + private final String accountAddress; + private final Block block; + private CallArguments argsForCall; // for call tests could be null + + private ChainParams(World world, String accountAddress, Block block) { + this.web3 = createWeb3(world); + this.accountAddress = accountAddress; + this.block = block; + } + + private ChainParams(World world, String accountAddress, Block block, CallArguments argsForCall) { + this(world, accountAddress, block); + this.argsForCall = argsForCall; + } + } } diff --git a/rskj-core/src/test/java/org/ethereum/rpc/dto/BlockResultDTOTest.java b/rskj-core/src/test/java/org/ethereum/rpc/dto/BlockResultDTOTest.java index 1476f3c4345..3dd01055c5c 100644 --- a/rskj-core/src/test/java/org/ethereum/rpc/dto/BlockResultDTOTest.java +++ b/rskj-core/src/test/java/org/ethereum/rpc/dto/BlockResultDTOTest.java @@ -23,7 +23,11 @@ import co.rsk.test.builders.BlockBuilder; import co.rsk.test.builders.TransactionBuilder; import co.rsk.util.HexUtils; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.BlockTxSignatureCache; +import org.ethereum.core.Blockchain; +import org.ethereum.core.ReceivedTxSignatureCache; +import org.ethereum.core.Transaction; import org.ethereum.core.genesis.GenesisLoader; import org.ethereum.db.BlockStore; import org.ethereum.util.RskTestFactory; @@ -47,17 +51,14 @@ class BlockResultDTOTest { - private Block block; - private BlockStore blockStore; - - @TempDir - public Path tempDir; - // todo(fedejinich) currently RemascTx(blockNumber) has a bug, thats why I initialize this way public static final RemascTransaction REMASC_TRANSACTION = new RemascTransaction(new RemascTransaction(1).getEncoded()); public static final Transaction TRANSACTION = new TransactionBuilder().buildRandomTransaction(); - private static final String HEX_ZERO = "0x0"; + @TempDir + public Path tempDir; + private Block block; + private BlockStore blockStore; @BeforeEach void setup() { @@ -90,7 +91,7 @@ void getBlockResultDTOWithoutRemascAndTransactionHashes() { @Test void getBlockResultDTOWithoutRemasc_emptyTransactions() { - Block block = buildBlockWithTransactions(Arrays.asList(REMASC_TRANSACTION)); + Block block = buildBlockWithTransactions(List.of(REMASC_TRANSACTION)); BlockResultDTO blockResultDTO = BlockResultDTO.fromBlock(block, false, blockStore, true, false, new BlockTxSignatureCache(new ReceivedTxSignatureCache())); Assertions.assertEquals(HexUtils.toUnformattedJsonHex(EMPTY_TRIE_HASH), blockResultDTO.getTransactionsRoot());