From 0ec335fbf1a5b2156a47c861a1f7a281287544a1 Mon Sep 17 00:00:00 2001 From: Matt Whitehead Date: Wed, 21 Aug 2024 09:42:52 +0100 Subject: [PATCH 01/12] BFT soak test - use both db modes (#7496) * For test node runners use provided data storage config Signed-off-by: Matthew Whitehead * Make the BFT soak mining tests use both Forest and Bonsai DBs Signed-off-by: Matthew Whitehead --------- Signed-off-by: Matthew Whitehead --- .../node/configuration/BesuNodeFactory.java | 19 +++++++++++++++++-- .../BftAcceptanceTestParameterization.java | 15 +++++++++++---- .../acceptance/bftsoak/BftMiningSoakTest.java | 9 +++++---- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index ed4a28422..efbf91246 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -32,7 +32,9 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import org.hyperledger.besu.tests.acceptance.dsl.node.Node; import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode; @@ -476,7 +478,9 @@ public class BesuNodeFactory { .build()); } - public BesuNode createIbft2Node(final String name, final boolean fixedPort) throws IOException { + public BesuNode createIbft2Node( + final String name, final boolean fixedPort, final DataStorageFormat storageFormat) + throws IOException { JsonRpcConfiguration rpcConfig = node.createJsonRpcWithIbft2EnabledConfig(false); rpcConfig.addRpcApi("ADMIN,TXPOOL"); if (fixedPort) { @@ -484,6 +488,7 @@ public class BesuNodeFactory { Math.abs(name.hashCode() % 60000) + 1024); // Generate a consistent port for p2p based on node name } + BesuNodeConfigurationBuilder builder = new BesuNodeConfigurationBuilder() .name(name) @@ -491,6 +496,10 @@ public class BesuNodeFactory { .jsonRpcConfiguration(rpcConfig) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .devMode(false) + .dataStorageConfiguration( + storageFormat == DataStorageFormat.FOREST + ? DataStorageConfiguration.DEFAULT_FOREST_CONFIG + : DataStorageConfiguration.DEFAULT_BONSAI_CONFIG) .genesisConfigProvider(GenesisConfigurationFactory::createIbft2GenesisConfig); if (fixedPort) { builder.p2pPort( @@ -527,7 +536,9 @@ public class BesuNodeFactory { return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_PKCS11); } - public BesuNode createQbftNode(final String name, final boolean fixedPort) throws IOException { + public BesuNode createQbftNode( + final String name, final boolean fixedPort, final DataStorageFormat storageFormat) + throws IOException { JsonRpcConfiguration rpcConfig = node.createJsonRpcWithQbftEnabledConfig(false); rpcConfig.addRpcApi("ADMIN,TXPOOL"); if (fixedPort) { @@ -543,6 +554,10 @@ public class BesuNodeFactory { .jsonRpcConfiguration(rpcConfig) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .devMode(false) + .dataStorageConfiguration( + storageFormat == DataStorageFormat.FOREST + ? DataStorageConfiguration.DEFAULT_FOREST_CONFIG + : DataStorageConfiguration.DEFAULT_BONSAI_CONFIG) .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig); if (fixedPort) { builder.p2pPort( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java index 15872070d..2351f740b 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.tests.acceptance.bft; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeFactory; @@ -38,7 +39,9 @@ public class BftAcceptanceTestParameterization { @FunctionalInterface public interface NodeCreator { - BesuNode create(BesuNodeFactory factory, String name, boolean fixedPort) throws Exception; + BesuNode create( + BesuNodeFactory factory, String name, boolean fixedPort, DataStorageFormat storageFormat) + throws Exception; } @FunctionalInterface @@ -64,11 +67,15 @@ public class BftAcceptanceTestParameterization { } public BesuNode createNode(BesuNodeFactory factory, String name) throws Exception { - return creatorFn.create(factory, name, false); + return creatorFn.create(factory, name, false, DataStorageFormat.FOREST); } - public BesuNode createNodeFixedPort(BesuNodeFactory factory, String name) throws Exception { - return creatorFn.create(factory, name, true); + public BesuNode createBonsaiNodeFixedPort(BesuNodeFactory factory, String name) throws Exception { + return creatorFn.create(factory, name, true, DataStorageFormat.BONSAI); + } + + public BesuNode createForestNodeFixedPort(BesuNodeFactory factory, String name) throws Exception { + return creatorFn.create(factory, name, true, DataStorageFormat.FOREST); } public BesuNode createNodeWithValidators( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bftsoak/BftMiningSoakTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bftsoak/BftMiningSoakTest.java index 9861a7dab..878e503ba 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bftsoak/BftMiningSoakTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bftsoak/BftMiningSoakTest.java @@ -60,10 +60,11 @@ public class BftMiningSoakTest extends ParameterizedBftTestBase { // in between certain steps. There should be no upper-limit to how long the test is run for assertThat(getTestDurationMins()).isGreaterThanOrEqualTo(MIN_TEST_TIME_MINS); - final BesuNode minerNode1 = nodeFactory.createNodeFixedPort(besu, "miner1"); - final BesuNode minerNode2 = nodeFactory.createNodeFixedPort(besu, "miner2"); - final BesuNode minerNode3 = nodeFactory.createNodeFixedPort(besu, "miner3"); - final BesuNode minerNode4 = nodeFactory.createNodeFixedPort(besu, "miner4"); + // Create a mix of Bonsai and Forest DB nodes + final BesuNode minerNode1 = nodeFactory.createBonsaiNodeFixedPort(besu, "miner1"); + final BesuNode minerNode2 = nodeFactory.createForestNodeFixedPort(besu, "miner2"); + final BesuNode minerNode3 = nodeFactory.createBonsaiNodeFixedPort(besu, "miner3"); + final BesuNode minerNode4 = nodeFactory.createForestNodeFixedPort(besu, "miner4"); // Each step should be given a minimum of 3 minutes to complete successfully. If the time // give to run the soak test results in a time-per-step lower than this then the time From 1598e6be6775a56ab42c7f27df4a59c00c7c118f Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Wed, 21 Aug 2024 13:01:35 -0700 Subject: [PATCH 02/12] EOF Differential Layout Fuzzer (#7488) Differential EOF Layout Fuzzer guided by Besu's layout parser. Signed-off-by: Danno Ferrin --- build.gradle | 4 + .../besu/evmtool/CodeValidateSubCommand.java | 10 +- .../evmtool/CodeValidationSubCommandTest.java | 20 +- .../evmtool/code-validate/createdata.json | 7 + .../besu/evmtool/code-validate/runtime.json | 2 +- .../besu/evmtool/trace/eof-section.json | 4 +- .../vm/GeneralStateReferenceTestTools.java | 4 +- .../besu/evm/tracing/StandardJsonTracer.java | 2 +- gradle/verification-metadata.xml | 21 ++ gradle/versions.gradle | 11 +- settings.gradle | 1 + testfuzz/README.md | 29 ++ testfuzz/build.gradle | 148 ++++++++++ .../hyperledger/besu/testfuzz/BesuFuzz.java | 38 +++ .../besu/testfuzz/BesuFuzzCommand.java | 78 ++++++ .../besu/testfuzz/EofContainerSubCommand.java | 258 ++++++++++++++++++ .../besu/testfuzz/ExternalClient.java | 22 ++ .../org/hyperledger/besu/testfuzz/Fuzzer.java | 239 ++++++++++++++++ .../besu/testfuzz/SingleQueryClient.java | 89 ++++++ .../besu/testfuzz/StreamingClient.java | 52 ++++ .../besu/testfuzz/VersionProvider.java | 47 ++++ testfuzz/src/main/scripts/unixStartScript.txt | 200 ++++++++++++++ .../src/main/scripts/windowsStartScript.txt | 91 ++++++ 23 files changed, 1351 insertions(+), 26 deletions(-) create mode 100644 ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/createdata.json create mode 100644 testfuzz/README.md create mode 100644 testfuzz/build.gradle create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzz.java create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzzCommand.java create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/EofContainerSubCommand.java create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/ExternalClient.java create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/Fuzzer.java create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/SingleQueryClient.java create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/StreamingClient.java create mode 100644 testfuzz/src/main/java/org/hyperledger/besu/testfuzz/VersionProvider.java create mode 100644 testfuzz/src/main/scripts/unixStartScript.txt create mode 100644 testfuzz/src/main/scripts/windowsStartScript.txt diff --git a/build.gradle b/build.gradle index 7d2293b61..6a4f27966 100644 --- a/build.gradle +++ b/build.gradle @@ -160,6 +160,10 @@ allprojects { url 'https://splunk.jfrog.io/splunk/ext-releases-local' content { includeGroupByRegex('com\\.splunk\\..*') } } + maven { + url 'https://gitlab.com/api/v4/projects/19871573/packages/maven' + content { includeGroupByRegex('com\\.gitlab\\.javafuzz(\\..*)?') } + } mavenCentral() diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java index 4c0411986..a3fff8e5d 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java @@ -36,8 +36,6 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; @@ -175,12 +173,8 @@ public class CodeValidateSubCommand implements Runnable { ((CodeV1) code).getEofLayout().containerMode().get())) { return "err: code is valid initcode. Runtime code expected"; } else { - return "OK " - + IntStream.range(0, code.getCodeSectionCount()) - .mapToObj(code::getCodeSection) - .map(cs -> code.getBytes().slice(cs.getEntryPoint(), cs.getLength())) - .map(Bytes::toUnprefixedHexString) - .collect(Collectors.joining(",")); + return "OK %d/%d/%d" + .formatted(code.getCodeSectionCount(), code.getSubcontainerCount(), code.getDataSize()); } } } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java index dd7133f86..d6b735941 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java @@ -47,7 +47,7 @@ class CodeValidationSubCommandTest { EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 00\n"); + assertThat(baos.toString(UTF_8)).contains("OK 1/0/0\n"); } @Test @@ -70,9 +70,9 @@ class CodeValidationSubCommandTest { assertThat(baos.toString(UTF_8)) .contains( """ - OK 00 + OK 1/0/0 err: layout - EOF header byte 1 incorrect - OK 5f5ff3 + OK 1/0/0 """); } @@ -85,7 +85,7 @@ class CodeValidationSubCommandTest { final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_STOP_ONLY); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 00\n"); + assertThat(baos.toString(UTF_8)).contains("OK 1/0/0\n"); } @Test @@ -112,9 +112,9 @@ class CodeValidationSubCommandTest { assertThat(baos.toString(UTF_8)) .contains( """ - OK 00 + OK 1/0/0 err: layout - EOF header byte 1 incorrect - OK 5f5ff3 + OK 1/0/0 """); } @@ -127,7 +127,7 @@ class CodeValidationSubCommandTest { final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_RETURN_ONLY); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 5f5ff3\n"); + assertThat(baos.toString(UTF_8)).contains("OK 1/0/0\n"); } @Test @@ -139,7 +139,7 @@ class CodeValidationSubCommandTest { final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_INTERIOR_COMMENTS); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 59595959e300015000,f8e4\n"); + assertThat(baos.toString(UTF_8)).contains("OK 2/0/0\n"); } @Test @@ -153,9 +153,9 @@ class CodeValidationSubCommandTest { assertThat(baos.toString(UTF_8)) .isEqualTo( """ - OK 00 + OK 1/0/0 err: layout - EOF header byte 1 incorrect - OK 5f5ff3 + OK 1/0/0 """); } } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/createdata.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/createdata.json new file mode 100644 index 000000000..9207cd65e --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/createdata.json @@ -0,0 +1,7 @@ +{ + "cli": [ + "code-validate" + ], + "stdin": "0xef0001010004020001000b0300010014040004000080000436600060ff6000ec005000ef000101000402000100010400000000800000feda7ac0de", + "stdout": "OK 1/1/4\n" +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/runtime.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/runtime.json index 796581555..fedeaa1bc 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/runtime.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/runtime.json @@ -3,5 +3,5 @@ "code-validate" ], "stdin": "ef00010100040200010001040000000080000000", - "stdout": "OK 00\n" + "stdout": "OK 1/0/0\n" } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof-section.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof-section.json index 061d8f094..439c69195 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof-section.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof-section.json @@ -12,8 +12,8 @@ "stdin": "", "stdout": [ {"pc":0,"section":0,"op":227,"immediate":"0x0002","gas":"0x2540be400","gasCost":"0x5","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"CALLF"}, - {"pc":0,"section":2,"op":229,"immediate":"0x0002","gas":"0x2540be3fb","gasCost":"0x5","memSize":0,"stack":[],"depth":1,"fdepth":1,"refund":0,"opName":"JUMPF"}, - {"pc":0,"section":1,"op":228,"gas":"0x2540be3f6","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"fdepth":1,"refund":0,"opName":"RETF"}, + {"pc":0,"section":2,"op":229,"immediate":"0x0002","gas":"0x2540be3fb","gasCost":"0x5","memSize":0,"stack":[],"depth":1,"functionDepth":1,"refund":0,"opName":"JUMPF"}, + {"pc":0,"section":1,"op":228,"gas":"0x2540be3f6","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"functionDepth":1,"refund":0,"opName":"RETF"}, {"pc":3,"section":0,"op":97,"immediate":"0x2015","gas":"0x2540be3f3","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}, {"pc":6,"section":0,"op":96,"immediate":"0x01","gas":"0x2540be3f0","gasCost":"0x3","memSize":0,"stack":["0x2015"],"depth":1,"refund":0,"opName":"PUSH1"}, {"pc":8,"section":0,"op":85,"gas":"0x2540be3ed","gasCost":"0x5654","memSize":0,"stack":["0x2015","0x1"],"depth":1,"refund":0,"opName":"SSTORE"}, diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java index 42934b612..354642f1a 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java @@ -151,11 +151,11 @@ public class GeneralStateReferenceTestTools { .blobGasPricePerGas(blockHeader.getExcessBlobGas().orElse(BlobGas.ZERO)); final TransactionProcessingResult result = processor.processTransaction( - worldStateUpdater, + worldStateUpdater, blockHeader, transaction, blockHeader.getCoinbase(), - new CachingBlockHashLookup(blockHeader, blockchain), + new CachingBlockHashLookup(blockHeader, blockchain), false, TransactionValidationParams.processingBlock(), blobGasPrice); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java index 2a71fb70c..a289c665d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java @@ -216,7 +216,7 @@ public class StandardJsonTracer implements OperationTracer { } sb.append("\"depth\":").append(depth).append(","); if (subdepth >= 1) { - sb.append("\"fdepth\":").append(subdepth).append(","); + sb.append("\"functionDepth\":").append(subdepth).append(","); } sb.append("\"refund\":").append(messageFrame.getGasRefund()).append(","); sb.append("\"opName\":\"").append(currentOp.getName()).append("\""); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 46029c87c..49c1e7f00 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -546,6 +546,19 @@ + + + + + + + + + + + + + @@ -5372,6 +5385,14 @@ + + + + + + + + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 57f2acae3..e0baf3bb4 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -41,6 +41,8 @@ dependencyManagement { dependency 'org.hyperledger.besu:besu-errorprone-checks:1.0.0' + dependency 'com.gitlab.javafuzz:core:1.26' + dependency 'com.google.guava:guava:33.0.0-jre' dependency 'com.graphql-java:graphql-java:21.5' @@ -153,8 +155,6 @@ dependencyManagement { } dependency 'org.fusesource.jansi:jansi:2.4.1' - dependency 'org.openjdk.jol:jol-core:0.17' - dependency 'tech.pegasys:jc-kzg-4844:1.0.0' dependencySet(group: 'org.hyperledger.besu', version: '0.9.4') { entry 'arithmetic' @@ -173,6 +173,9 @@ dependencyManagement { dependency 'org.java-websocket:Java-WebSocket:1.5.5' + dependency 'org.jacoco:org.jacoco.agent:0.8.11' + dependency 'org.jacoco:org.jacoco.core:0.8.11' + dependency 'org.jetbrains.kotlin:kotlin-stdlib:1.9.22' dependencySet(group: 'org.junit.jupiter', version: '5.10.1') { @@ -182,6 +185,8 @@ dependencyManagement { entry 'junit-jupiter-params' } + dependency 'org.openjdk.jol:jol-core:0.17' + dependency 'org.junit.platform:junit-platform-runner:1.9.2' dependency 'org.junit.vintage:junit-vintage-engine:5.10.1' @@ -232,6 +237,8 @@ dependencyManagement { dependency 'org.apache.maven:maven-artifact:3.9.6' + dependency 'tech.pegasys:jc-kzg-4844:1.0.0' + dependency 'tech.pegasys.discovery:discovery:22.12.0' } } diff --git a/settings.gradle b/settings.gradle index 09a8d20d4..322383b32 100644 --- a/settings.gradle +++ b/settings.gradle @@ -68,5 +68,6 @@ include 'privacy-contracts' include 'services:kvstore' include 'services:pipeline' include 'services:tasks' +include 'testfuzz' include 'testutil' include 'util' diff --git a/testfuzz/README.md b/testfuzz/README.md new file mode 100644 index 000000000..8f436d701 --- /dev/null +++ b/testfuzz/README.md @@ -0,0 +1,29 @@ +# BesuFuzz + +BesuFuzz is where all the besu guided fuzzing tools live. + +## eof-container + +Performs differential fuzzing between Ethereum clients based on +the [txparse eofparse](https://github.com/holiman/txparse/blob/main/README.md#eof-parser-eofparse) +format. Note that only the inital `OK` and `err` values are used to determine if +there is a difference. + +### Prototypical CLI Usage: + +```shell +BesuFuzz eof-container \ + --tests-dir=~/git/ethereum/tests/EOFTests \ + --client=evm1=evmone-eofparse \ + --client=revm=revme bytecode +``` + +### Prototypical Gradle usage: + +```shell +./gradlew fuzzEvmone fuzzReth +``` + +There are pre-written Gradle targets for `fuzzEthereumJS`, `fuzzEvmone`, +`fuzzGeth`, `fuzzNethermind`, and `fuzzReth`. Besu is always a fuzzing target. +The `fuzzAll` target will fuzz all clients. \ No newline at end of file diff --git a/testfuzz/build.gradle b/testfuzz/build.gradle new file mode 100644 index 000000000..1c9679815 --- /dev/null +++ b/testfuzz/build.gradle @@ -0,0 +1,148 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'application' +apply plugin: 'java-library' +apply plugin: 'jacoco' + +jar { + archiveBaseName = 'besu-test-fuzz' + manifest { + attributes( + 'Specification-Title': archiveBaseName, + 'Specification-Version': project.version, + 'Implementation-Title': archiveBaseName, + 'Implementation-Version': calculateVersion() + ) + } +} + +dependencies { + implementation project(':besu') + implementation project(':crypto:algorithms') + implementation project(':datatypes') + implementation project(':ethereum:referencetests') + implementation project(':evm') + implementation project(':util') + + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'com.gitlab.javafuzz:core' + implementation 'info.picocli:picocli' + implementation 'io.tmio:tuweni-bytes' + implementation 'org.jacoco:org.jacoco.agent' + implementation 'org.jacoco:org.jacoco.core' +} + +application { + applicationName = 'BesuFuzz' + mainClass = 'org.hyperledger.besu.testfuzz.BesuFuzz' + applicationDefaultJvmArgs = [ + '-javaagent:$APP_HOME/lib/jacocoagent.jar' + ] +} + +def corpusDir = "${buildDir}/generated/corpus" + +tasks.register("runFuzzer", JavaExec) { + classpath = sourceSets.main.runtimeClasspath + mainClass = 'org.hyperledger.besu.testfuzz.BesuFuzz' + + args = [ + "eof-container", + "--tests-dir=${projectDir}/../ethereum/referencetests/src/reference-test/external-resources/EOFTests", + "--corpus-dir=${corpusDir}" + ] + doFirst { + mkdir corpusDir + } +} + +tasks.register("fuzzEvmone") { + doLast { + runFuzzer.args += "--client=evm1=evmone-eofparse" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzEthereumJS") { + doLast { + runFuzzer.args += "--client=etjs=tsx ../../../ethereumjs/ethereumjs-monorepo/packages/evm/scripts/eofContainerValidator.ts" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzGeth") { + doLast { + runFuzzer.args += "--client=geth=eofdump eofparser" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzNethermind") { + doLast { + runFuzzer.args += "--client=neth=netheofparse -x" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzReth") { + doLast { + runFuzzer.args += "--client=revm=revme bytecode" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzAll") { + dependsOn fuzzEvm1, fuzzEthJS, fuzzGeth, fuzzNethermind, fuzzReth +} + +jacoco { + applyTo run + applyTo runFuzzer +} + +// Copies jacoco into the lib directory +tasks.register("copyJacoco", Copy) { + // The jacocoagent.jar is embedded within the jar + from zipTree(configurations.jacocoAgent.singleFile).filter { it.name == 'jacocoagent.jar' }.singleFile + into layout.buildDirectory.dir("install/${application.applicationName}/lib") +} + +installDist.finalizedBy copyJacoco + +startScripts { + defaultJvmOpts = [ + "-Dsecp256k1.randomize=false" + ] + unixStartScriptGenerator.template = resources.text.fromFile("${projectDir}/src/main/scripts/unixStartScript.txt") + windowsStartScriptGenerator.template = resources.text.fromFile("${projectDir}/src/main/scripts/windowsStartScript.txt") + doLast { tweakStartScript(startScripts) } +} + +static def tweakStartScript(createScriptTask) { + def shortenWindowsClasspath = { line -> + line.replaceAll(/^set CLASSPATH=.*$/, "set CLASSPATH=%APP_HOME%/lib/*") + } + + createScriptTask.unixScript.text = createScriptTask.unixScript.text.replace('BESU_HOME', '\$APP_HOME') + createScriptTask.windowsScript.text = createScriptTask.windowsScript.text.replace('BESU_HOME', '%~dp0..') + + // Prevent the error originating from the 8191 chars limit on Windows + createScriptTask.windowsScript.text = + createScriptTask.windowsScript + .readLines() + .collect(shortenWindowsClasspath) + .join('\r\n') +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzz.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzz.java new file mode 100644 index 000000000..6cde75880 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzz.java @@ -0,0 +1,38 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import org.hyperledger.besu.util.LogConfigurator; + +/** The main entry point for the EVM (Ethereum Virtual Machine) tool. */ +public final class BesuFuzz { + + /** Default constructor for the EvmTool class. */ + public BesuFuzz() { + // this is here only for Javadoc linting + } + + /** + * The main entry point for the EVM (Ethereum Virtual Machine) tool. + * + * @param args The command line arguments. + */ + public static void main(final String... args) { + LogConfigurator.setLevel("", "DEBUG"); + final BesuFuzzCommand besuFuzzCommand = new BesuFuzzCommand(); + + besuFuzzCommand.execute(args); + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzzCommand.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzzCommand.java new file mode 100644 index 000000000..22165b5ff --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzzCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.hyperledger.besu.util.LogConfigurator; + +import java.io.InputStream; +import java.io.PrintWriter; + +import picocli.CommandLine; +import picocli.CommandLine.Command; + +/** + * This is the root command for the `BesuFuzz` command line tool. It is a collection of fuzzers that + * are guided by Besu's implementations. + */ +@Command( + description = "Executes Besu based fuzz tests", + abbreviateSynopsis = true, + name = "evm", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class, + sortOptions = false, + header = "Usage:", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + optionListHeading = "%nOptions:%n", + footerHeading = "%n", + footer = "Hyperledger Besu is licensed under the Apache License 2.0", + subcommands = {EofContainerSubCommand.class}) +@SuppressWarnings("java:S106") +public class BesuFuzzCommand implements Runnable { + + PrintWriter out; + InputStream in; + + /** Default Constructor */ + BesuFuzzCommand() { + // this method is here only for JavaDoc linting + } + + void execute(final String... args) { + execute(System.in, new PrintWriter(System.out, true, UTF_8), args); + } + + void execute(final InputStream input, final PrintWriter output, final String[] args) { + final CommandLine commandLine = new CommandLine(this).setOut(output); + out = output; + in = input; + + // don't require exact case to match enum values + commandLine.setCaseInsensitiveEnumValuesAllowed(true); + + commandLine.setExecutionStrategy(new CommandLine.RunLast()); + commandLine.execute(args); + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + System.out.println("No default command, please select a subcommand"); + System.exit(1); + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/EofContainerSubCommand.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/EofContainerSubCommand.java new file mode 100644 index 000000000..efe84296c --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/EofContainerSubCommand.java @@ -0,0 +1,258 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import static org.hyperledger.besu.testfuzz.EofContainerSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonParser.Feature; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; +import com.fasterxml.jackson.core.util.Separators.Spacing; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.gitlab.javafuzz.core.AbstractFuzzTarget; +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; +import picocli.CommandLine.Option; + +/** Fuzzes the parsing and validation of an EOF container. */ +@SuppressWarnings({"java:S106", "CallToPrintStackTrace"}) // we use lots the console, on purpose +@CommandLine.Command( + name = COMMAND_NAME, + description = "Fuzzes EOF container parsing and validation", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class EofContainerSubCommand extends AbstractFuzzTarget implements Runnable { + + static final String COMMAND_NAME = "eof-container"; + + @Option( + names = {"--corpus-dir"}, + paramLabel = "", + description = "Directory to store corpus files") + private final Path corpusDir = Path.of("corpus"); + + @Option( + names = {"--tests-dir"}, + paramLabel = "", + description = "Directory where EOF tests references file tree lives") + private final Path testsDir = null; + + @Option( + names = {"--client"}, + paramLabel = "=", + description = "Add a client for differential fuzzing") + private final Map clients = new LinkedHashMap<>(); + + @CommandLine.ParentCommand private final BesuFuzzCommand parentCommand; + + static final ObjectMapper eofTestMapper = createObjectMapper(); + static final JavaType javaType = + eofTestMapper + .getTypeFactory() + .constructParametricType(Map.class, String.class, EOFTestCaseSpec.class); + + List externalClients = new ArrayList<>(); + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + long validContainers; + long totalContainers; + + /** + * Default constructor for the EofContainerSubCommand class. This constructor initializes the + * parentCommand to null. + */ + public EofContainerSubCommand() { + this(null); + } + + /** + * Constructs a new EofContainerSubCommand with the specified parent command. + * + * @param parentCommand The parent command for this subcommand. + */ + public EofContainerSubCommand(final BesuFuzzCommand parentCommand) { + this.parentCommand = parentCommand; + } + + private static ObjectMapper createObjectMapper() { + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setDefaultPrettyPrinter( + (new DefaultPrettyPrinter()) + .withSeparators( + Separators.createDefaultInstance().withObjectFieldValueSpacing(Spacing.BOTH)) + .withObjectIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(" ")) + .withArrayIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(" "))); + objectMapper.disable(Feature.AUTO_CLOSE_SOURCE); + SimpleModule serializers = new SimpleModule("Serializers"); + serializers.addSerializer(Address.class, ToStringSerializer.instance); + serializers.addSerializer(Bytes.class, ToStringSerializer.instance); + objectMapper.registerModule(serializers); + + return objectMapper; + } + + @Override + public void run() { + // load test dir into corpus dir + if (testsDir != null) { + File f = testsDir.toFile(); + if (f.isDirectory()) { + try (var files = Files.walk(f.toPath(), Integer.MAX_VALUE)) { + files.forEach( + ff -> { + File file = ff.toFile(); + if (file.isFile()) { + extractFile(file, corpusDir.toFile()); + } + }); + } catch (IOException e) { + parentCommand.out.println("Exception walking " + f + ": " + e.getMessage()); + } + } + } + + clients.forEach((k, v) -> externalClients.add(new StreamingClient(k, v.split(" ")))); + System.out.println("Fuzzing client set: " + clients.keySet()); + + try { + new Fuzzer(this, corpusDir.toString(), this::fuzzStats).start(); + } catch (NoSuchAlgorithmException + | ClassNotFoundException + | InvocationTargetException + | IllegalAccessException + | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private void extractFile(final File f, final File initialCorpus) { + final Map eofTests; + try { + eofTests = eofTestMapper.readValue(f, javaType); + } catch (IOException e) { + // presume parse failed because it's a corpus file + return; + } + for (var entry : eofTests.entrySet()) { + int index = 0; + for (var vector : entry.getValue().getVector().entrySet()) { + try (FileOutputStream fos = + new FileOutputStream( + new File( + initialCorpus, + f.toPath().getFileName() + "_" + (index++) + "_" + vector.getKey()))) { + Bytes codeBytes = Bytes.fromHexString(vector.getValue().code()); + evm.getCodeUncached(codeBytes); + fos.write(codeBytes.toArrayUnsafe()); + } catch (IOException e) { + parentCommand.out.println("Invalid file " + f + ": " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + } + } + + @Override + public void fuzz(final byte[] bytes) { + Bytes eofUnderTest = Bytes.wrap(bytes); + String eofUnderTestHexString = eofUnderTest.toHexString(); + Code code = evm.getCodeUncached(eofUnderTest); + Map results = new LinkedHashMap<>(); + boolean mismatch = false; + for (var client : externalClients) { + String value = client.differentialFuzz(eofUnderTestHexString); + results.put(client.getName(), value); + if (value == null || value.startsWith("fail: ")) { + mismatch = true; // if an external client fails, always report it as an error + } + } + boolean besuValid = false; + String besuReason; + if (!code.isValid()) { + besuReason = ((CodeInvalid) code).getInvalidReason(); + } else if (code.getEofVersion() != 1) { + EOFLayout layout = EOFLayout.parseEOF(eofUnderTest); + if (layout.isValid()) { + besuReason = "Besu Parsing Error"; + parentCommand.out.println(layout.version()); + parentCommand.out.println(layout.invalidReason()); + parentCommand.out.println(code.getEofVersion()); + parentCommand.out.println(code.getClass().getName()); + System.exit(1); + mismatch = true; + } else { + besuReason = layout.invalidReason(); + } + } else if (EOFContainerMode.INITCODE.equals( + ((CodeV1) code).getEofLayout().containerMode().get())) { + besuReason = "Code is initcode, not runtime"; + } else { + besuReason = "OK"; + besuValid = true; + } + for (var entry : results.entrySet()) { + mismatch = + mismatch + || besuValid != entry.getValue().toUpperCase(Locale.getDefault()).startsWith("OK"); + } + if (mismatch) { + parentCommand.out.println("besu: " + besuReason); + for (var entry : results.entrySet()) { + parentCommand.out.println(entry.getKey() + ": " + entry.getValue()); + } + parentCommand.out.println("code: " + eofUnderTest.toUnprefixedHexString()); + parentCommand.out.println("size: " + eofUnderTest.size()); + parentCommand.out.println(); + } else { + if (besuValid) { + validContainers++; + } + totalContainers++; + } + } + + String fuzzStats() { + return " / %5.2f%% valid %d/%d" + .formatted((100.0 * validContainers) / totalContainers, validContainers, totalContainers); + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/ExternalClient.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/ExternalClient.java new file mode 100644 index 000000000..e5505239a --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/ExternalClient.java @@ -0,0 +1,22 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +interface ExternalClient { + + String getName(); + + String differentialFuzz(String data); +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/Fuzzer.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/Fuzzer.java new file mode 100644 index 000000000..e4ebb68cc --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/Fuzzer.java @@ -0,0 +1,239 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import org.hyperledger.besu.crypto.Hash; +import org.hyperledger.besu.crypto.MessageDigestFactory; + +import java.io.ByteArrayInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import com.gitlab.javafuzz.core.AbstractFuzzTarget; +import com.gitlab.javafuzz.core.Corpus; +import org.apache.tuweni.bytes.Bytes; +import org.jacoco.core.data.ExecutionData; +import org.jacoco.core.data.ExecutionDataReader; +import org.jacoco.core.data.IExecutionDataVisitor; +import org.jacoco.core.data.ISessionInfoVisitor; +import org.jacoco.core.data.SessionInfo; + +/** Ported from javafuzz because JaCoCo APIs changed. */ +@SuppressWarnings({"java:S106", "CallToPrintStackTrace"}) // we use lots the console, on purpose +public class Fuzzer { + private final AbstractFuzzTarget target; + private final Corpus corpus; + private final Object agent; + private final Method getExecutionDataMethod; + private long executionsInSample; + private long lastSampleTime; + private long totalExecutions; + private long totalCoverage; + + Supplier fuzzStats; + + /** + * Create a new fuzzer + * + * @param target The target to fuzz + * @param dirs the list of corpus dirs and files, comma separated. + * @param fuzzStats additional fuzzing data from the client + * @throws ClassNotFoundException If Jacoco RT is not found (because jacocoagent.jar is not + * loaded) + * @throws NoSuchMethodException If the wrong version of Jacoco is loaded + * @throws InvocationTargetException If the wrong version of Jacoco is loaded + * @throws IllegalAccessException If the wrong version of Jacoco is loaded + * @throws NoSuchAlgorithmException If the SHA-256 crypto algo cannot be loaded. + */ + public Fuzzer( + final AbstractFuzzTarget target, final String dirs, final Supplier fuzzStats) + throws ClassNotFoundException, + NoSuchMethodException, + InvocationTargetException, + IllegalAccessException, + NoSuchAlgorithmException { + this.target = target; + this.corpus = new Corpus(dirs); + this.fuzzStats = fuzzStats; + Class c = Class.forName("org.jacoco.agent.rt.RT"); + Method getAgentMethod = c.getMethod("getAgent"); + this.agent = getAgentMethod.invoke(null); + this.getExecutionDataMethod = agent.getClass().getMethod("getExecutionData", boolean.class); + fileNameForBuffer(new byte[0]); + } + + void writeCrash(final byte[] buf) { + Bytes hash = Hash.sha256(Bytes.wrap(buf)); + String filepath = "crash-" + hash.toUnprefixedHexString(); + try (FileOutputStream fos = new FileOutputStream(filepath)) { + fos.write(buf); + System.out.printf("crash was written to %s%n", filepath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + void logStats(final String type) { + long rss = + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024; + long endTime = System.currentTimeMillis(); + long execs_per_second = -1; + if ((endTime - this.lastSampleTime) != 0) { + execs_per_second = (this.executionsInSample * 1000 / (endTime - this.lastSampleTime)); + } + this.lastSampleTime = endTime; + this.executionsInSample = 0; + + System.out.printf( + "#%d %s cov: %d corp: %d exec/s: %d rss: %d MB %s%n", + this.totalExecutions, + type, + this.totalCoverage, + this.corpus.getLength(), + execs_per_second, + rss, + fuzzStats.get()); + } + + /** + * Runs the fuzzer until the VM is shut down + * + * @throws InvocationTargetException if the wrong version of jacoco is loaded + * @throws IllegalAccessException if the wrong version of jacoco is loaded + * @throws NoSuchAlgorithmException if our favorite hash algo is not loaded + */ + @SuppressWarnings("java:S2189") // the endless loop is on purpose + public void start() + throws InvocationTargetException, IllegalAccessException, NoSuchAlgorithmException { + System.out.printf("#0 READ units: %d%n", this.corpus.getLength()); + this.totalCoverage = 0; + this.totalExecutions = 0; + this.executionsInSample = 0; + this.lastSampleTime = System.currentTimeMillis(); + + Map hitMap = new HashMap<>(); + + while (true) { + byte[] buf = this.corpus.generateInput(); + // The next version will run this in a different thread. + try { + this.target.fuzz(buf); + } catch (Exception e) { + e.printStackTrace(System.out); + this.writeCrash(buf); + System.exit(1); + break; + } + + this.totalExecutions++; + this.executionsInSample++; + + long newCoverage = getHitCount(hitMap); + if (newCoverage > this.totalCoverage) { + this.totalCoverage = newCoverage; + this.corpus.putBuffer(buf); + this.logStats("NEW"); + + // If you want hex strings of new hits, uncomment the following. + // String filename = fileNameForBuffer(buf); + // try (var pw = + // new PrintWriter( + // new BufferedWriter( + // new OutputStreamWriter(new FileOutputStream(filename), UTF_8)))) { + // pw.println(Bytes.wrap(buf).toHexString()); + // System.out.println(filename); + // } catch (IOException e) { + // e.printStackTrace(System.out); + // } + } else if ((System.currentTimeMillis() - this.lastSampleTime) > 30000) { + this.logStats("PULSE"); + } + } + } + + private static String fileNameForBuffer(final byte[] buf) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigestFactory.create(MessageDigestFactory.SHA256_ALG); + md.update(buf); + byte[] digest = md.digest(); + return String.format("./new-%064x.hex", new BigInteger(1, digest)); + } + + private long getHitCount(final Map hitMap) + throws IllegalAccessException, InvocationTargetException { + byte[] dumpData = (byte[]) this.getExecutionDataMethod.invoke(this.agent, false); + ExecutionDataReader edr = new ExecutionDataReader(new ByteArrayInputStream(dumpData)); + HitCounter hc = new HitCounter(hitMap); + edr.setExecutionDataVisitor(hc); + edr.setSessionInfoVisitor(hc); + try { + edr.read(); + } catch (IOException e) { + e.printStackTrace(); + this.writeCrash(dumpData); + } + + return hc.getHits(); + } + + static class HitCounter implements IExecutionDataVisitor, ISessionInfoVisitor { + long hits = 0; + Map hitMap; + + public HitCounter(final Map hitMap) { + this.hitMap = hitMap; + } + + @Override + public void visitClassExecution(final ExecutionData executionData) { + int hit = 0; + for (boolean b : executionData.getProbes()) { + if (executionData.getName().startsWith("org/hyperledger/besu/testfuzz/") + || executionData.getName().startsWith("org/bouncycastle/") + || executionData.getName().startsWith("com/gitlab/javafuzz/")) { + continue; + } + if (b) { + hit++; + } + } + String name = executionData.getName(); + if (hitMap.containsKey(name)) { + if (hitMap.get(name) < hit) { + hitMap.put(name, hit); + } + } else { + hitMap.put(name, hit); + } + hits += hit; + } + + public long getHits() { + return hits; + } + + @Override + public void visitSessionInfo(final SessionInfo sessionInfo) { + // nothing to do. Data parser requires a session listener. + } + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/SingleQueryClient.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/SingleQueryClient.java new file mode 100644 index 000000000..802a6011a --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/SingleQueryClient.java @@ -0,0 +1,89 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@SuppressWarnings({"java:S106", "CallToPrintStackTrace"}) // we use lots the console, on purpose +class SingleQueryClient implements ExternalClient { + final String name; + String[] command; + Pattern okRegexp; + String okRegexpStr; + int okGroup; + Pattern failRegexp; + int failGroup; + String failRegexpStr; + + public SingleQueryClient( + final String clientName, + final String okRegexp, + final int okGroup, + final String errorRegexp, + final int failGroup, + final String... command) { + this.name = clientName; + this.okRegexp = Pattern.compile(okRegexp); + this.okRegexpStr = okRegexp; + this.okGroup = okGroup; + this.failRegexp = Pattern.compile(errorRegexp); + this.failGroup = failGroup; + this.failRegexpStr = errorRegexp; + this.command = command; + } + + @Override + public String getName() { + return name; + } + + @Override + @SuppressWarnings("java:S2142") + public String differentialFuzz(final String data) { + if (!data.startsWith("0xef")) { + return "err: invalid_magic"; + } + try { + List localCommand = new ArrayList<>(command.length + 1); + localCommand.addAll(Arrays.asList(command)); + localCommand.add(data); + Process p = new ProcessBuilder().command(localCommand).redirectErrorStream(true).start(); + if (!p.waitFor(1, TimeUnit.SECONDS)) { + System.out.println("Process Hang for " + name); + return "fail: process took more than 1 sec " + p.pid(); + } + String s = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + Matcher m = okRegexp.matcher(s); + if (m.find()) { + return "OK " + m.group(okGroup); + } + m = failRegexp.matcher(s); + if (m.find()) { + return "err: " + m.group(failGroup); + } + return "fail: SingleClientQuery failed to get data"; + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + return "fail: " + e.getMessage(); + } + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/StreamingClient.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/StreamingClient.java new file mode 100644 index 000000000..a59cd9326 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/StreamingClient.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +class StreamingClient implements ExternalClient { + final String name; + final BufferedReader reader; + final PrintWriter writer; + + public StreamingClient(final String clientName, final String... command) { + try { + Process p = new ProcessBuilder().redirectErrorStream(true).command(command).start(); + this.name = clientName; + this.reader = new BufferedReader(p.inputReader(StandardCharsets.UTF_8)); + this.writer = new PrintWriter(p.getOutputStream(), true, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getName() { + return name; + } + + @Override + public String differentialFuzz(final String data) { + try { + writer.println(data); + return reader.readLine(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/VersionProvider.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/VersionProvider.java new file mode 100644 index 000000000..6a184dd90 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/VersionProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import org.hyperledger.besu.BesuInfo; + +import picocli.CommandLine; + +/** + * The VersionProvider class is responsible for providing the version of the Hyperledger Besu EVM + * tool. It implements the IVersionProvider interface from the picocli library. + * + *

The getVersion method returns a string array containing the version of the Hyperledger Besu + * EVM tool. + */ +public class VersionProvider implements CommandLine.IVersionProvider { + + /** + * Default constructor for the VersionProvider class. This constructor does not perform any + * operations. + */ + public VersionProvider() { + // this constructor is here only for javadoc linting + } + + /** + * This method returns the version of the Hyperledger Besu EVM tool. + * + * @return A string array containing the version of the Hyperledger Besu EVM tool. + */ + @Override + public String[] getVersion() { + return new String[] {"Hyperledger Besu evm " + BesuInfo.shortVersion()}; + } +} diff --git a/testfuzz/src/main/scripts/unixStartScript.txt b/testfuzz/src/main/scripts/unixStartScript.txt new file mode 100644 index 000000000..59d8b83fb --- /dev/null +++ b/testfuzz/src/main/scripts/unixStartScript.txt @@ -0,0 +1,200 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## ${applicationName} start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: \$0 may be a link +PRG="\$0" +# Need this for relative symlinks. +while [ -h "\$PRG" ] ; do + ls=`ls -ld "\$PRG"` + link=`expr "\$ls" : '.*-> \\(.*\\)\$'` + if expr "\$link" : '/.*' > /dev/null; then + PRG="\$link" + else + PRG=`dirname "\$PRG"`"/\$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"\$PRG\"`/${appHomeRelativePath}" >/dev/null +APP_HOME="`pwd -P`" +cd "\$SAVED" >/dev/null + +APP_NAME="${applicationName}" +APP_BASE_NAME=`basename "\$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +DEFAULT_JVM_OPTS=${defaultJvmOpts} + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "\$*" +} + +die () { + echo + echo "\$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$classpath +<% if ( mainClassName.startsWith('--module ') ) { %>MODULE_PATH=$modulePath<% } %> + +# Determine the Java command to use to start the JVM. +if [ -n "\$JAVA_HOME" ] ; then + if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="\$JAVA_HOME/jre/sh/java" + else + JAVACMD="\$JAVA_HOME/bin/java" + fi + if [ ! -x "\$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "\$cygwin" = "false" -a "\$darwin" = "false" -a "\$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ \$? -eq 0 ] ; then + if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then + MAX_FD="\$MAX_FD_LIMIT" + fi + ulimit -n \$MAX_FD + if [ \$? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: \$MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if \$darwin; then + GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "\$cygwin" = "true" -o "\$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "\$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"` +<% if ( mainClassName.startsWith('--module ') ) { %> MODULE_PATH=`cygpath --path --mixed "\$MODULE_PATH"`<% } %> + JAVACMD=`cygpath --unix "\$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in \$ROOTDIRSRAW ; do + ROOTDIRS="\$ROOTDIRS\$SEP\$dir" + SEP="|" + done + OURCYGPATTERN="(^(\$ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "\$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "\$@" ; do + CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -` + CHECK2=`echo "\$arg"|egrep -c "^-"` ### Determine if an option + + if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"` + else + eval `echo args\$i`="\"\$arg\"" + fi + i=`expr \$i + 1` + done + case \$i in + 0) set -- ;; + 1) set -- "\$args0" ;; + 2) set -- "\$args0" "\$args1" ;; + 3) set -- "\$args0" "\$args1" "\$args2" ;; + 4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;; + 5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;; + 6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;; + 7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;; + 8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;; + 9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\\\n "\$i" | sed "s/'/'\\\\\\\\''/g;1s/^/'/;\\\$s/\\\$/' \\\\\\\\/" ; done + echo " " +} +APP_ARGS=`save "\$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- -javaagent:\$APP_HOME/lib/jacocoagent.jar \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar} <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %>-classpath "\"\$CLASSPATH\"" <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "\"\$MODULE_PATH\"" <% } %>${mainClassName} "\$APP_ARGS" + +unset BESU_USING_JEMALLOC +if [ "\$darwin" = "false" -a "\$msys" = "false" ]; then + # check if jemalloc is available + TEST_JEMALLOC=\$(LD_PRELOAD=libjemalloc.so sh -c true 2>&1) + + # if jemalloc is available the output is empty, otherwise the output has an error line + if [ -z "\$TEST_JEMALLOC" ]; then + export LD_PRELOAD=libjemalloc.so + export BESU_USING_JEMALLOC=true + else + # jemalloc not available, as fallback limit malloc to 2 arenas + export MALLOC_ARENA_MAX=2 + fi +fi + +exec "\$JAVACMD" "\$@" diff --git a/testfuzz/src/main/scripts/windowsStartScript.txt b/testfuzz/src/main/scripts/windowsStartScript.txt new file mode 100644 index 000000000..2ce40892e --- /dev/null +++ b/testfuzz/src/main/scripts/windowsStartScript.txt @@ -0,0 +1,91 @@ +@rem +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem ${applicationName} startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=.\ + +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%${appHomeRelativePath} + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +set DEFAULT_JVM_OPTS=${defaultJvmOpts} + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=$classpath +<% if ( mainClassName.startsWith('--module ') ) { %>set MODULE_PATH=$modulePath<% } %> + +@rem Execute ${applicationName} +"%JAVA_EXE%" -javaagent:%APP_HOME%/lib/jacocoagent.jar %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> -classpath "%CLASSPATH%" <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "%MODULE_PATH%" <% } %>${mainClassName} %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%${exitEnvironmentVar}%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From c3f455c691da9f19ec4bc83e97203ad69004597c Mon Sep 17 00:00:00 2001 From: Ade Lucas <37906710+cloudspores@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:06:03 -0600 Subject: [PATCH 03/12] Fix ClassCastException in DebugMetrics nested structures [#7383] (#7499) * Fix ClassCastException in DebugMetrics nested structures This commit resolves an issue where Double values in nested metric structures were incorrectly cast to Map objects, causing a ClassCastException. The fix allows for proper handling of both direct values and nested structures at the same level. A comprehensive test case has been added to reproduce the bug and verify the fix, ensuring that complex, dynamically nested metric structures can be handled without errors. Resolves: #7383 Signed-off-by: Ade Lucas --------- Signed-off-by: Ade Lucas Signed-off-by: garyschulte Signed-off-by: Snazzy Signed-off-by: Danno Ferrin Signed-off-by: Matilda Clerke Signed-off-by: Matilda-Clerke Signed-off-by: Sally MacFarlane Co-authored-by: garyschulte Co-authored-by: Fabio Di Fabio Co-authored-by: gringsam Co-authored-by: Sally MacFarlane Co-authored-by: Danno Ferrin Co-authored-by: Matilda-Clerke --- CHANGELOG.md | 9 ++++++ .../internal/methods/DebugMetrics.java | 21 +++++++++++- .../internal/methods/DebugMetricsTest.java | 32 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82a60615a..1cd3f0126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [Unreleased] + +### Fixed +- **DebugMetrics**: Fixed a `ClassCastException` occurring in `DebugMetrics` when handling nested metric structures. Previously, `Double` values within these structures were incorrectly cast to `Map` objects, leading to errors. This update allows for proper handling of both direct values and nested structures at the same level. Issue# [#7383] + +### Tests +- Added a comprehensive test case to reproduce the bug and verify the fix for the `ClassCastException` in `DebugMetrics`. This ensures that complex, dynamically nested metric structures can be handled without errors. + ## Next release ### Upcoming Breaking Changes @@ -17,6 +25,7 @@ - Correctly release txpool save and restore lock in case of exceptions [#7473](https://github.com/hyperledger/besu/pull/7473) - Fix for `eth_gasPrice` could not retrieve block error [#7482](https://github.com/hyperledger/besu/pull/7482) + ## 24.8.0 ### Upcoming Breaking Changes diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java index 89e6e4a2a..b5c74245c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java @@ -71,7 +71,26 @@ public class DebugMetrics implements JsonRpcMethod { @SuppressWarnings("unchecked") private Map getNextMapLevel( final Map current, final String name) { + // Use compute to either return the existing map or create a new one return (Map) - current.computeIfAbsent(name, key -> new HashMap()); + current.compute( + name, + (k, v) -> { + if (v instanceof Map) { + // If the value is already a Map, return it as is + return v; + } else { + // If the value is not a Map, create a new Map + Map newMap = new HashMap<>(); + if (v != null) { + // If v is not null and not a Map, we store it as a leaf value + // If the original value was not null, store it under the "value" key + // This handles cases where a metric value (e.g., Double) was previously stored + // directly + newMap.put("value", v); + } + return newMap; + } + }); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java index 6e41fc9c1..d4830cb85 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.metrics.BesuMetricCategory.BLOCKCHAIN; import static org.hyperledger.besu.metrics.BesuMetricCategory.PEERS; import static org.hyperledger.besu.metrics.BesuMetricCategory.RPC; import static org.mockito.Mockito.mock; @@ -28,6 +29,7 @@ import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.Observation; import java.util.Collections; +import java.util.List; import java.util.stream.Stream; import com.google.common.collect.ImmutableMap; @@ -84,6 +86,36 @@ public class DebugMetricsTest { ImmutableMap.of("label2B", "value3"))))); } + @Test + public void shouldHandleDoubleValuesInNestedStructureWithoutClassCastException() { + // Tests fix for issue# 7383: debug_metrics method error + when(metricsSystem.streamObservations()) + .thenReturn( + Stream.of( + // This creates a double value for "a" + new Observation(BLOCKCHAIN, "nested_metric", 1.0, List.of("a")), + // This attempts to create a nested structure under "a", which was previously a + // double + new Observation(BLOCKCHAIN, "nested_metric", 2.0, asList("a", "b")), + // This adds another level of nesting + new Observation(BLOCKCHAIN, "nested_metric", 3.0, asList("a", "b", "c")))); + + assertResponse( + ImmutableMap.of( + BLOCKCHAIN.getName(), + ImmutableMap.of( + "nested_metric", + ImmutableMap.of( + "a", + ImmutableMap.of( + "value", + 1.0, + "b", + ImmutableMap.of( + "value", 2.0, + "c", 3.0)))))); + } + private void assertResponse(final ImmutableMap expectedResponse) { final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(REQUEST); assertThat(response.getResult()).isEqualTo(expectedResponse); From 4801106674288cc68d71cb2a3bb5eb0fd61c755e Mon Sep 17 00:00:00 2001 From: shubham kesri Date: Fri, 23 Aug 2024 06:12:12 +0530 Subject: [PATCH 04/12] Fixed | Initialising the encodedPubKey with empty String in case userInfo is null (#7508) Signed-off-by: kesrishubham2510 Co-authored-by: Sally MacFarlane --- CHANGELOG.md | 1 + .../hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd3f0126..b9fccf29c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - Correct entrypoint in Docker evmtool [#7430](https://github.com/hyperledger/besu/pull/7430) - Fix protocol schedule check for devnets [#7429](https://github.com/hyperledger/besu/pull/7429) - Fix behaviour when starting in a pre-merge network [#7431](https://github.com/hyperledger/besu/pull/7431) +- Fix Null pointer from DNS daemon [#7505](https://github.com/hyperledger/besu/issues/7505) ## 24.7.1 diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java index bb85a3612..cd439eea0 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java @@ -243,7 +243,7 @@ public interface DNSEntry { public ENRTreeLink(final String enrTreeLink) { final URI uri = URI.create(enrTreeLink); this.domainName = uri.getHost(); - this.encodedPubKey = uri.getUserInfo(); + this.encodedPubKey = uri.getUserInfo() == null ? "" : uri.getUserInfo(); this.pubKey = fromBase32(encodedPubKey); } From 58bb931efeab12f472ba7aee5f363fd0fcf4b141 Mon Sep 17 00:00:00 2001 From: neowangreal Date: Fri, 23 Aug 2024 07:45:16 +0630 Subject: [PATCH 05/12] update link (#7506) Signed-off-by: Neo Co-authored-by: Sally MacFarlane --- docs/tracing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tracing/README.md b/docs/tracing/README.md index 0acac729d..77deadd16 100644 --- a/docs/tracing/README.md +++ b/docs/tracing/README.md @@ -21,4 +21,4 @@ Open the Zipkin UI by browsing to http://localhost:9411/ You will be able to see the detail of your traces. References: -* [OpenTelemetry Environment Variable Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-environment-variables.md) +* [OpenTelemetry Environment Variable Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md) From d87650b944631cd645c2663edb7c00a9e145d842 Mon Sep 17 00:00:00 2001 From: garyschulte Date: Fri, 23 Aug 2024 16:35:18 -0700 Subject: [PATCH 06/12] Fleet-mode safe behavior for fcU in SynchronizationService (#7517) * fleet-mode safe behavior for fcU in SynchronizationService Signed-off-by: garyschulte * spotless Signed-off-by: garyschulte --------- Signed-off-by: garyschulte --- .../services/SynchronizationServiceImpl.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/services/SynchronizationServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/SynchronizationServiceImpl.java index 898ede5d0..2e15e2ab8 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/SynchronizationServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/SynchronizationServiceImpl.java @@ -74,17 +74,11 @@ public class SynchronizationServiceImpl implements SynchronizationService { @Override public void fireNewUnverifiedForkchoiceEvent( final Hash head, final Hash safeBlock, final Hash finalizedBlock) { - final MergeContext mergeContext = protocolContext.getConsensusContext(MergeContext.class); - if (mergeContext != null) { - mergeContext.fireNewUnverifiedForkchoiceEvent(head, safeBlock, finalizedBlock); - protocolContext.getBlockchain().setFinalized(finalizedBlock); - protocolContext.getBlockchain().setSafeBlock(safeBlock); - } else { - LOG.atWarn() - .setMessage( - "The merge context is unavailable, hence the fork choice event cannot be triggered") - .log(); - } + protocolContext + .safeConsensusContext(MergeContext.class) + .ifPresent(mc -> mc.fireNewUnverifiedForkchoiceEvent(head, safeBlock, finalizedBlock)); + protocolContext.getBlockchain().setFinalized(finalizedBlock); + protocolContext.getBlockchain().setSafeBlock(safeBlock); } @Override From a851507cb3ced6daaedb7596662efda23ca0a3e9 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Sun, 25 Aug 2024 22:33:05 -0600 Subject: [PATCH 07/12] evmtool was not respecting the --genesis option (#7518) * EVMTool should respect --genesis option Update the code so that the genesis file option will be respected when set. Also, default --fork options should set a rational base fee. Signed-off-by: Danno Ferrin --------- Signed-off-by: Danno Ferrin --- CHANGELOG.md | 3 ++- .../besu/datatypes/HardforkId.java | 16 ++++++++++++ .../besu/ethereum/core/BlockHeader.java | 21 ++++++++------- .../besu/evmtool/EvmToolCommand.java | 26 ++++++++++++------- .../besu/evmtool/GenesisFileModule.java | 4 ++- .../evmtool/MainnetGenesisFileModule.java | 14 +++++----- 6 files changed, 54 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9fccf29c..8c31d55fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## [Unreleased] ### Fixed -- **DebugMetrics**: Fixed a `ClassCastException` occurring in `DebugMetrics` when handling nested metric structures. Previously, `Double` values within these structures were incorrectly cast to `Map` objects, leading to errors. This update allows for proper handling of both direct values and nested structures at the same level. Issue# [#7383] +- **DebugMetrics**: Fixed a `ClassCastException` occurring in `DebugMetrics` when handling nested metric structures. Previously, `Double` values within these structures were incorrectly cast to `Map` objects, leading to errors. This update allows for proper handling of both direct values and nested structures at the same level. Issue# [#7383](https://github.com/hyperledger/besu/pull/7383) +- `evmtool` was not respecting the `--genesis` setting, resulting in unexpected trace results. [#7433](https://github.com/hyperledger/besu/pull/7433) ### Tests - Added a comprehensive test case to reproduce the bug and verify the fix for the `ClassCastException` in `DebugMetrics`. This ensures that complex, dynamically nested metric structures can be handled without errors. diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/HardforkId.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/HardforkId.java index 73eaddb7c..85ec84b4a 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/HardforkId.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/HardforkId.java @@ -14,6 +14,9 @@ */ package org.hyperledger.besu.datatypes; +import java.util.Comparator; +import java.util.stream.Stream; + /** Description and metadata for a hard fork */ public interface HardforkId { @@ -112,6 +115,19 @@ public interface HardforkId { public String description() { return description; } + + /** + * The most recent finalized mainnet hardfork Besu supports. This will change across versions + * and will be updated after mainnet activations. + * + * @return the most recently activated mainnet spec. + */ + public static MainnetHardforkId mostRecent() { + return Stream.of(MainnetHardforkId.values()) + .filter(MainnetHardforkId::finalized) + .max(Comparator.naturalOrder()) + .orElseThrow(); + } } /** List of all Ethereum Classic hard forks. */ diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java index 46d2f0847..42f99fafb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java @@ -158,22 +158,23 @@ public class BlockHeader extends SealableBlockHeader out.writeBytes(extraData); out.writeBytes(mixHashOrPrevRandao); out.writeLong(nonce); - if (baseFee != null) { + do { + if (baseFee == null) break; out.writeUInt256Scalar(baseFee); - } - if (withdrawalsRoot != null) { + + if (withdrawalsRoot == null) break; out.writeBytes(withdrawalsRoot); - } - if (excessBlobGas != null && blobGasUsed != null) { + + if (excessBlobGas == null || blobGasUsed == null) break; out.writeLongScalar(blobGasUsed); out.writeUInt64Scalar(excessBlobGas); - } - if (parentBeaconBlockRoot != null) { + + if (parentBeaconBlockRoot == null) break; out.writeBytes(parentBeaconBlockRoot); - } - if (requestsRoot != null) { + + if (requestsRoot == null) break; out.writeBytes(requestsRoot); - } + } while (false); out.endList(); } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java index 06ee96762..f9d7c0db4 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java @@ -478,7 +478,14 @@ public class EvmToolCommand implements Runnable { .mixHash(Hash.ZERO) .nonce(0) .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) - .baseFee(component.getBlockchain().getChainHeadHeader().getBaseFee().orElse(null)) + .baseFee( + component + .getBlockchain() + .getChainHeadHeader() + .getBaseFee() + .or(() -> genesisFileModule.providesGenesisConfigFile().getBaseFeePerGas()) + .orElse( + protocolSpec.getFeeMarket().implementsBaseFee() ? Wei.of(0xa) : null)) .buildBlockHeader(); Address contractAddress = @@ -519,13 +526,12 @@ public class EvmToolCommand implements Runnable { lastTime = stopwatch.elapsed().toNanos(); } if (lastLoop) { - if (messageFrame.getExceptionalHaltReason().isPresent()) { - out.println(messageFrame.getExceptionalHaltReason().get()); - } - if (messageFrame.getRevertReason().isPresent()) { - out.println( - new String(messageFrame.getRevertReason().get().toArrayUnsafe(), UTF_8)); - } + messageFrame + .getExceptionalHaltReason() + .ifPresent(haltReason -> out.println(haltReason)); + messageFrame + .getRevertReason() + .ifPresent(bytes -> out.println(new String(bytes.toArrayUnsafe(), UTF_8))); } } } @@ -572,7 +578,7 @@ public class EvmToolCommand implements Runnable { out.println("{"); worldState .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) - .sorted(Comparator.comparing(o -> o.getAddress().get().toHexString())) + .sorted(Comparator.comparing(o -> o.getAddress().orElse(Address.ZERO).toHexString())) .forEach( a -> { var account = worldState.get(a.getAddress().get()); @@ -585,7 +591,7 @@ public class EvmToolCommand implements Runnable { .map( e -> Map.entry( - e.getKey().get(), + e.getKey().orElse(UInt256.ZERO), account.getStorageValue(UInt256.fromBytes(e.getKey().get())))) .filter(e -> !e.getValue().isZero()) .sorted(Map.Entry.comparingByKey()) diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java index 6eae387a6..8512b5537 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; @@ -28,6 +29,7 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; +import java.util.Locale; import java.util.Optional; import javax.inject.Named; import javax.inject.Singleton; @@ -116,7 +118,7 @@ public class GenesisFileModule { final JsonObject config = new JsonObject(); genesis.put("config", config); config.put("chainId", 1337); - config.put("londonBlock", 0); + config.put(MainnetHardforkId.mostRecent().toString().toLowerCase(Locale.ROOT) + "Time", 0); genesis.put("baseFeePerGas", "0x3b9aca00"); genesis.put("gasLimit", "0x2540be400"); genesis.put("difficulty", "0x0"); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java index e36668f47..0ce7bf98f 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java @@ -27,7 +27,6 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; -import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -71,13 +70,12 @@ class MainnetGenesisFileModule extends GenesisFileModule { } } - var schedules = createSchedules(configOptions.getChainId().orElse(BigInteger.valueOf(1337))); - var schedule = - schedules.get( - fork.orElse(EvmSpecVersion.defaultVersion().getName()) - .toLowerCase(Locale.getDefault())); - if (schedule != null) { - return schedule.get(); + if (fork.isPresent()) { + var schedules = createSchedules(configOptions.getChainId().orElse(BigInteger.valueOf(1337))); + var schedule = schedules.get(fork.get().toLowerCase(Locale.getDefault())); + if (schedule != null) { + return schedule.get(); + } } return MainnetProtocolSchedule.fromConfig( From 4f51d4d696e803b85a937b67d8d4d08750ae9af6 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Sun, 25 Aug 2024 22:56:54 -0600 Subject: [PATCH 08/12] Fix fuzzing dependencies (#7519) The fuzzAll task depended on targets that didn't exist. Signed-off-by: Danno Ferrin Co-authored-by: Sally MacFarlane --- testfuzz/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testfuzz/build.gradle b/testfuzz/build.gradle index 1c9679815..e55cbe598 100644 --- a/testfuzz/build.gradle +++ b/testfuzz/build.gradle @@ -105,7 +105,7 @@ tasks.register("fuzzReth") { } tasks.register("fuzzAll") { - dependsOn fuzzEvm1, fuzzEthJS, fuzzGeth, fuzzNethermind, fuzzReth + dependsOn fuzzEvmone, fuzzEthereumJS, fuzzGeth, fuzzNethermind, fuzzReth } jacoco { From 725dcf1c872326c5cfa19d17e113ef9cd214472a Mon Sep 17 00:00:00 2001 From: Chaminda Divitotawela Date: Mon, 26 Aug 2024 16:19:30 +1000 Subject: [PATCH 09/12] Release checklist update (#7494) * Release checklist update More details on homebrew and docs releases in release checklist issue template Co-authored-by: Simon Dudley Signed-off-by: Chaminda Divitotawela * Remove release creation for besu-docs from checklist It has been agreed there is no value creating releases in the hyperledger/besu-docs repo. Remove the related instruction from checklist Signed-off-by: Chaminda Divitotawela --------- Signed-off-by: Chaminda Divitotawela Signed-off-by: Chaminda Divitotawela Co-authored-by: Simon Dudley --- .github/ISSUE_TEMPLATE/release-checklist.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md index 721c26edb..413d68933 100644 --- a/.github/ISSUE_TEMPLATE/release-checklist.md +++ b/.github/ISSUE_TEMPLATE/release-checklist.md @@ -29,9 +29,10 @@ assignees: '' - publishes the docker `latest` tag variants - [ ] Check binary SHAs are correct on the release page - [ ] Check "Container Verify" GitHub workflow has run successfully -- [ ] Create besu-docs release - https://github.com/hyperledger/besu-docs/releases/new - - Copy release notes from besu - - If publishing the release in github doesn't automatically trigger this workflow, then manually run https://github.com/hyperledger/besu-docs/actions/workflows/update-version.yml -- [ ] Create homebrew release - run GHA workflow directly https://github.com/hyperledger/homebrew-besu/actions/workflows/update-version.yml +- [ ] Update the besu-docs version [update-version workflow](https://github.com/hyperledger/besu-docs/actions/workflows/update-version.yml) + - If the PR has not been automatically created, create the PR manually using the created branch `besu-version-` +- [ ] Create homebrew release using [update-version workflow](https://github.com/hyperledger/homebrew-besu/actions/workflows/update-version.yml) + - If the PR has not been automatically created, create the PR manually using the created branch `update-` + - Run commands `brew tap hyperledger/besu && brew install besu` on MacOSX and verify latest version has been installed - [ ] Delete the burn-in nodes (unless required for further analysis eg performance) - [ ] Social announcements From b57310ffac2565a12594e3aa45d77bca7f6d3b35 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Mon, 26 Aug 2024 19:59:46 -0600 Subject: [PATCH 10/12] EOF testing error codes for layout (#7522) Update the eof layout error codes to match codes in reference tests. This includes support for multiple possible errors for a specific test. Signed-off-by: Danno Ferrin --- .../evmtool/CodeValidationSubCommandTest.java | 12 +- .../evmtool/pretty-print/dangling-data.json | 2 +- .../state-test/create-invalid-eof.json | 2 +- .../ethereum/eof/EOFReferenceTestTools.java | 92 ++++++---- .../hyperledger/besu/evm/code/EOFLayout.java | 79 +++++---- .../hyperledger/besu/evm/code/CodeV1Test.java | 8 +- .../besu/evm/code/EOFLayoutTest.java | 167 +++++++++++------- 7 files changed, 224 insertions(+), 138 deletions(-) diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java index d6b735941..9fe34b483 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java @@ -57,7 +57,8 @@ class CodeValidationSubCommandTest { EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("err: layout - EOF header byte 1 incorrect\n"); + assertThat(baos.toString(UTF_8)) + .contains("err: layout - invalid_magic EOF header byte 1 incorrect\n"); } @Test @@ -71,7 +72,7 @@ class CodeValidationSubCommandTest { .contains( """ OK 1/0/0 - err: layout - EOF header byte 1 incorrect + err: layout - invalid_magic EOF header byte 1 incorrect OK 1/0/0 """); } @@ -97,7 +98,8 @@ class CodeValidationSubCommandTest { final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_BAD_MAGIC); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("err: layout - EOF header byte 1 incorrect\n"); + assertThat(baos.toString(UTF_8)) + .contains("err: layout - invalid_magic EOF header byte 1 incorrect\n"); } @Test @@ -113,7 +115,7 @@ class CodeValidationSubCommandTest { .contains( """ OK 1/0/0 - err: layout - EOF header byte 1 incorrect + err: layout - invalid_magic EOF header byte 1 incorrect OK 1/0/0 """); } @@ -154,7 +156,7 @@ class CodeValidationSubCommandTest { .isEqualTo( """ OK 1/0/0 - err: layout - EOF header byte 1 incorrect + err: layout - invalid_magic EOF header byte 1 incorrect OK 1/0/0 """); } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/dangling-data.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/dangling-data.json index 27833a1bb..bdab829bb 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/dangling-data.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/dangling-data.json @@ -4,5 +4,5 @@ "ef00010100040200010001040000000080000000ff" ], "stdin": "", - "stdout": "EOF layout is invalid - Dangling data after end of all sections\n" + "stdout": "EOF layout is invalid - invalid_section_bodies_size data after end of all sections\n" } \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json index dc786d613..e2eb67602 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json @@ -71,7 +71,7 @@ } }, "stdout": [ - {"output":"","gasUsed":"0xd198","test":"create-eof","fork":"Prague","d":0,"g":0,"v":0,"postHash":"0x2a9c58298ba5d4ec86ca682b9fcc9ff67c3fc44dbd39f85a2f9b74bfe4e5178e","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"Invalid EOF Layout: Expected kind 1 but read kind 17"}, + {"output":"","gasUsed":"0xd198","test":"create-eof","fork":"Prague","d":0,"g":0,"v":0,"postHash":"0x2a9c58298ba5d4ec86ca682b9fcc9ff67c3fc44dbd39f85a2f9b74bfe4e5178e","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"Invalid EOF Layout: unexpected_header_kind expected 1 actual 17"}, {"pc":0,"op":239,"gas":"0x794068","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, {"output":"","gasUsed":"0x7a1200","test":"create-eof","fork":"Cancun","d":0,"g":0,"v":0,"postHash":"0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true,"error":"INVALID_OPERATION"} ] diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java index 8621b5d08..3c0c2dbbb 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java @@ -20,9 +20,11 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import com.google.common.base.Splitter; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; @@ -31,9 +33,7 @@ import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedul import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.code.CodeInvalid; -import org.hyperledger.besu.evm.code.CodeV1; import org.hyperledger.besu.evm.code.EOFLayout; -import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; import org.hyperledger.besu.testutil.JsonTestParameters; public class EOFReferenceTestTools { @@ -124,6 +124,25 @@ public class EOFReferenceTestTools { // hardwire in the magic byte transaction checks if (evm.getMaxEOFVersion() < 1) { assertThat(expected.exception()).isEqualTo("EOF_InvalidCode"); + } else if (code.size() > evm.getEvmVersion().getMaxInitcodeSize()) { + // this check is in EOFCREATE and Transaction validator, but unit tests sniff it out. + assertThat(false) + .withFailMessage( + () -> + "No Expected exception, actual exception - container_size_above_limit " + + code.size()) + .isEqualTo(expected.result()); + if (name.contains("eip7692")) { + // if the test is from EEST, validate the exception name. + assertThat("container_size_above_limit") + .withFailMessage( + () -> + "Expected exception: %s actual exception: %s %d" + .formatted( + expected.exception(), "container_size_above_limit ", code.size())) + .containsIgnoringCase(expected.exception().replace("EOFException.", "")); + } + } else { EOFLayout layout = EOFLayout.parseEOF(code); @@ -134,39 +153,27 @@ public class EOFReferenceTestTools { } else { parsedCode = evm.getCodeUncached(code); } - if ("EOF_IncompatibleContainerKind".equals(expected.exception()) && parsedCode.isValid()) { - EOFContainerMode expectedMode = - EOFContainerMode.valueOf(containerKind == null ? "RUNTIME" : containerKind); - EOFContainerMode containerMode = - ((CodeV1) parsedCode).getEofLayout().containerMode().get(); - EOFContainerMode actualMode = - containerMode == null ? EOFContainerMode.RUNTIME : containerMode; - assertThat(actualMode) - .withFailMessage("Code did not parse to valid containerKind of " + expectedMode) - .isNotEqualTo(expectedMode); - } else { - if (expected.result()) { + if (expected.result()) { assertThat(parsedCode.isValid()) .withFailMessage( - () -> "Valid code failed with " + ((CodeInvalid) parsedCode).getInvalidReason()) - .isTrue(); - } else { - assertThat(parsedCode.isValid()) - .withFailMessage("Invalid code expected " + expected.exception() + " but was valid") - .isFalse(); - if (name.contains("eip7692")) { - // if the test is from EEST, validate the exception name. - assertThat(((CodeInvalid) parsedCode).getInvalidReason()) - .withFailMessage( - () -> - "Expected exception :%s actual exception: %s" - .formatted( - expected.exception(), - (parsedCode.isValid() - ? null - : ((CodeInvalid) parsedCode).getInvalidReason()))) - .containsIgnoringCase(expected.exception().replace("EOFException.", "")); - } + () -> "Valid code failed with " + ((CodeInvalid) parsedCode).getInvalidReason()) + .isTrue(); + } else { + assertThat(parsedCode.isValid()) + .withFailMessage("Invalid code expected " + expected.exception() + " but was valid") + .isFalse(); + if (name.contains("eip7692")) { + // if the test is from EEST, validate the exception name. + assertThat(((CodeInvalid) parsedCode).getInvalidReason()) + .withFailMessage( + () -> + "Expected exception :%s actual exception: %s" + .formatted( + expected.exception(), + (parsedCode.isValid() + ? null + : ((CodeInvalid) parsedCode).getInvalidReason()))) + .containsIgnoringCase(expected.exception().replace("EOFException.", "")); } } } else { @@ -178,6 +185,25 @@ public class EOFReferenceTestTools { + " actual exception - " + (layout.isValid() ? null : layout.invalidReason())) .isEqualTo(expected.result()); + if (name.contains("eip7692")) { + // if the test is from EEST, validate the exception name. + boolean exceptionMatched = false; + for (String e : Splitter.on('|').split(expected.exception())) { + if (layout + .invalidReason() + .toLowerCase(Locale.ROOT) + .contains(e.replace("EOFException.", "").toLowerCase(Locale.ROOT))) { + exceptionMatched = true; + break; + } + } + assertThat(exceptionMatched) + .withFailMessage( + () -> + "Expected exception :%s actual exception: %s" + .formatted(expected.exception(), layout.invalidReason())) + .isTrue(); + } } } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java index d5abb05f8..7883b70d8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java @@ -131,10 +131,10 @@ public record EOFLayout( private static String readKind(final ByteArrayInputStream inputStream, final int expectedKind) { int kind = inputStream.read(); if (kind == -1) { - return "Improper section headers"; + return "missing_headers_terminator Improper section headers"; } if (kind != expectedKind) { - return "Expected kind " + expectedKind + " but read kind " + kind; + return "unexpected_header_kind expected " + expectedKind + " actual " + kind; } return null; } @@ -217,7 +217,10 @@ public record EOFLayout( // This ReferenceEquality check is correct if ((strictSize || result != parsedContainer) && step.container.size() != parsedContainer.container.size()) { - return invalidLayout(container, parsedContainer.version, "subcontainer size mismatch"); + return invalidLayout( + container, + parsedContainer.version, + "invalid_section_bodies_size subcontainer size mismatch"); } if (step.index >= 0) { step.parentSubcontainers[step.index] = parsedContainer; @@ -233,18 +236,18 @@ public record EOFLayout( new ByteArrayInputStream(step.container.toArrayUnsafe()); if (inputStream.available() < 3) { - return invalidLayout(step.container, -1, "EOF Container too small"); + return invalidLayout(step.container, -1, "invalid_magic EOF Container too small"); } if (inputStream.read() != 0xEF) { - return invalidLayout(step.container, -1, "EOF header byte 0 incorrect"); + return invalidLayout(step.container, -1, "invalid_magic EOF header byte 0 incorrect"); } if (inputStream.read() != 0x0) { - return invalidLayout(step.container, -1, "EOF header byte 1 incorrect"); + return invalidLayout(step.container, -1, "invalid_magic EOF header byte 1 incorrect"); } final int version = inputStream.read(); if (version > MAX_SUPPORTED_VERSION || version < 1) { - return invalidLayout(step.container, version, "Unsupported EOF Version " + version); + return invalidLayout(step.container, version, "invalid_version " + version); } String error = readKind(inputStream, SECTION_TYPES); @@ -252,8 +255,11 @@ public record EOFLayout( return invalidLayout(step.container, version, error); } int typesLength = readUnsignedShort(inputStream); - if (typesLength <= 0 || typesLength % 4 != 0) { - return invalidLayout(step.container, version, "Invalid Types section size"); + if (typesLength % 4 != 0) { + return invalidLayout( + step.container, + version, + "invalid_type_section_size Invalid Types section size (mod 4 != 0)"); } error = readKind(inputStream, SECTION_CODE); @@ -262,28 +268,29 @@ public record EOFLayout( } int codeSectionCount = readUnsignedShort(inputStream); if (codeSectionCount <= 0) { - return invalidLayout(step.container, version, "Invalid Code section count"); - } - if (codeSectionCount * 4 != typesLength) { return invalidLayout( - step.container, - version, - "Type section length incompatible with code section count - 0x" - + Integer.toHexString(codeSectionCount) - + " * 4 != 0x" - + Integer.toHexString(typesLength)); + step.container, version, "incomplete_section_number Too few code sections"); } if (codeSectionCount > 1024) { return invalidLayout( step.container, version, - "Too many code sections - 0x" + Integer.toHexString(codeSectionCount)); + "too_many_code_sections - 0x" + Integer.toHexString(codeSectionCount)); + } + if (codeSectionCount * 4 != typesLength) { + return invalidLayout( + step.container, + version, + "invalid_section_bodies_size Type section - 0x" + + Integer.toHexString(codeSectionCount) + + " * 4 != 0x" + + Integer.toHexString(typesLength)); } int[] codeSectionSizes = new int[codeSectionCount]; for (int i = 0; i < codeSectionCount; i++) { int size = readUnsignedShort(inputStream); if (size <= 0) { - return invalidLayout(step.container, version, "Invalid Code section size for section " + i); + return invalidLayout(step.container, version, "zero_section_size code " + i); } codeSectionSizes[i] = size; } @@ -303,7 +310,7 @@ public record EOFLayout( return invalidLayout( step.container, version, - "Too many container sections - 0x" + Integer.toHexString(containerSectionCount)); + "too_many_containers sections - 0x" + Integer.toHexString(containerSectionCount)); } containerSectionSizes = new int[containerSectionCount]; for (int i = 0; i < containerSectionCount; i++) { @@ -325,7 +332,7 @@ public record EOFLayout( } int dataSize = readUnsignedShort(inputStream); if (dataSize < 0) { - return invalidLayout(step.container, version, "Invalid Data section size"); + return invalidLayout(step.container, version, "incomplete_data_header"); } error = readKind(inputStream, SECTION_TERMINATOR); @@ -340,11 +347,12 @@ public record EOFLayout( typeData[i][2] = readUnsignedShort(inputStream); } if (typeData[codeSectionCount - 1][2] == -1) { - return invalidLayout(step.container, version, "Incomplete type section"); + return invalidLayout( + step.container, version, "invalid_section_bodies_size Incomplete type section"); } if (typeData[0][0] != 0 || (typeData[0][1] & 0x7f) != 0) { return invalidLayout( - step.container, version, "Code section does not have zero inputs and outputs"); + step.container, version, "invalid_first_section_type must be zero input non-returning"); } CodeSection[] codeSections = new CodeSection[codeSectionCount]; int pos = // calculate pos in stream... @@ -364,25 +372,28 @@ public record EOFLayout( for (int i = 0; i < codeSectionCount; i++) { int codeSectionSize = codeSectionSizes[i]; if (inputStream.skip(codeSectionSize) != codeSectionSize) { - return invalidLayout(step.container, version, "Incomplete code section " + i); + return invalidLayout( + step.container, version, "invalid_section_bodies_size code section " + i); } if (typeData[i][0] > 0x7f) { return invalidLayout( step.container, version, - "Type data input stack too large - 0x" + Integer.toHexString(typeData[i][0])); + "inputs_outputs_num_above_limit Type data input stack too large - 0x" + + Integer.toHexString(typeData[i][0])); } if (typeData[i][1] > 0x80) { return invalidLayout( step.container, version, - "Type data output stack too large - 0x" + Integer.toHexString(typeData[i][1])); + "inputs_outputs_num_above_limit - 0x" + Integer.toHexString(typeData[i][1])); } if (typeData[i][2] > 0x3ff) { return invalidLayout( step.container, version, - "Type data max stack too large - 0x" + Integer.toHexString(typeData[i][2])); + "max_stack_height_above_limit Type data max stack too large - 0x" + + Integer.toHexString(typeData[i][2])); } codeSections[i] = new CodeSection(codeSectionSize, typeData[i][0], typeData[i][1], typeData[i][2], pos); @@ -390,8 +401,7 @@ public record EOFLayout( return invalidLayout( step.container, version, - "Code section at zero expected non-returning flag, but had return stack of " - + typeData[0][1]); + "invalid_first_section_type want 0x80 (non-returning flag) has " + typeData[0][1]); } pos += codeSectionSize; } @@ -400,7 +410,7 @@ public record EOFLayout( for (int i = 0; i < containerSectionCount; i++) { int subcontainerSize = containerSectionSizes[i]; if (subcontainerSize != inputStream.skip(subcontainerSize)) { - return invalidLayout(step.container, version, "incomplete subcontainer"); + return invalidLayout(step.container, version, "invalid_section_bodies_size"); } Bytes subcontainer = step.container.slice(pos, subcontainerSize); pos += subcontainerSize; @@ -413,7 +423,8 @@ public record EOFLayout( Bytes completeContainer; if (inputStream.read() != -1) { if (step.strictSize) { - return invalidLayout(step.container, version, "Dangling data after end of all sections"); + return invalidLayout( + step.container, version, "invalid_section_bodies_size data after end of all sections"); } else { completeContainer = step.container.slice(0, pos + dataSize); } @@ -422,7 +433,9 @@ public record EOFLayout( } if (step.strictSize && dataSize != data.size()) { return invalidLayout( - step.container, version, "Truncated data section when a complete section was required"); + step.container, + version, + "toplevel_container_truncated Truncated data section when a complete section was required"); } return new EOFLayout(completeContainer, version, codeSections, subContainers, dataSize, data); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java index 9b7edf80b..5ae88a6cc 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java @@ -100,7 +100,7 @@ class CodeV1Test { assertThat(validationError) .isEqualTo( - "Invalid EOF container - Code section at zero expected non-returning flag, but had return stack of 0"); + "Invalid EOF container - invalid_first_section_type want 0x80 (non-returning flag) has 0"); } @ParameterizedTest @@ -751,7 +751,7 @@ class CodeV1Test { return Stream.of( Arguments.of( "0 outputs at section 0", - "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", + "EOF Layout invalid - invalid_first_section_type want 0x80 (non-returning flag) has 0", 0, List.of(List.of("e4", 0, 0, 0), List.of("e4", 0, 0, 0))), Arguments.of( @@ -766,12 +766,12 @@ class CodeV1Test { List.of(List.of("00", 0, 0x80, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), Arguments.of( "more than 0 outputs section 0", - "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", + "EOF Layout invalid - invalid_first_section_type want 0x80 (non-returning flag) has 0", 0, List.of(List.of("44 50 e4", 0, 0, 1), List.of("4400", 0, 1, 1))), Arguments.of( "more than 0 outputs section 0", - "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", + "EOF Layout invalid - invalid_first_section_type want 0x80 (non-returning flag) has 0", 1, List.of(List.of("00", 0, 0, 0), List.of("44 e4", 0, 1, 1))), Arguments.of( diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java index 006fe1632..2cce45830 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java @@ -29,162 +29,187 @@ public class EOFLayoutTest { public static Collection containersWithFormatErrors() { return Arrays.asList( new Object[][] { - {"EF", "No magic", "EOF Container too small", -1}, - {"FFFFFF", "Wrong magic", "EOF header byte 0 incorrect", -1}, - {"EFFF01010002020004006000AABBCCDD", "Invalid magic", "EOF header byte 1 incorrect", -1}, - {"EF00", "No version", "EOF Container too small", -1}, - {"EF0000010002020004006000AABBCCDD", "Invalid version", "Unsupported EOF Version 0", 0}, - {"EF0002010002020004006000AABBCCDD", "Invalid version", "Unsupported EOF Version 2", 2}, + {"EF", "No magic", "invalid_magic EOF Container too small", -1}, + {"FFFFFF", "Wrong magic", "invalid_magic EOF header byte 0 incorrect", -1}, { - "EF00FF010002020004006000AABBCCDD", - "Invalid version", - "Unsupported EOF Version 255", - 255 + "EFFF01010002020004006000AABBCCDD", + "Invalid magic", + "invalid_magic EOF header byte 1 incorrect", + -1 }, - {"EF0001", "No header", "Improper section headers", 1}, - {"EF0001 00", "No code section", "Expected kind 1 but read kind 0", 1}, - {"EF0001 01", "No code section size", "Invalid Types section size", 1}, - {"EF0001 0100", "Code section size incomplete", "Invalid Types section size", 1}, - {"EF0001 010004", "No section terminator", "Improper section headers", 1}, - {"EF0001 010004 00", "No code section contents", "Expected kind 2 but read kind 0", 1}, - {"EF0001 010004 02", "No code section count", "Invalid Code section count", 1}, - {"EF0001 010004 0200", "Short code section count", "Invalid Code section count", 1}, + {"EF00", "No version", "invalid_magic EOF Container too small", -1}, + {"EF0000010002020004006000AABBCCDD", "Invalid version", "invalid_version 0", 0}, + {"EF0002010002020004006000AABBCCDD", "Invalid version", "invalid_version 2", 2}, + {"EF00FF010002020004006000AABBCCDD", "Invalid version", "invalid_version 255", 255}, + {"EF0001", "No header", "missing_headers_terminator Improper section headers", 1}, + {"EF0001 00", "No code section", "unexpected_header_kind expected 1 actual 0", 1}, { - "EF0001 010004 020001", + "EF0001 01", "No code section size", - "Invalid Code section size for section 0", + "invalid_type_section_size Invalid Types section size (mod 4 != 0)", 1 }, { - "EF0001 010004 02000100", - "Short code section size", - "Invalid Code section size for section 0", + "EF0001 0100", + "Code section size incomplete", + "invalid_type_section_size Invalid Types section size (mod 4 != 0)", 1 }, + { + "EF0001 010004", + "No section terminator", + "missing_headers_terminator Improper section headers", + 1 + }, + { + "EF0001 010004 00", + "No code section contents", + "unexpected_header_kind expected 2 actual 0", + 1 + }, + { + "EF0001 010004 02", + "No code section count", + "incomplete_section_number Too few code sections", + 1 + }, + { + "EF0001 010004 0200", + "Short code section count", + "incomplete_section_number Too few code sections", + 1 + }, + {"EF0001 010004 020001", "No code section size", "zero_section_size code 0", 1}, + {"EF0001 010004 02000100", "Short code section size", "zero_section_size code 0", 1}, { "EF0001 010008 0200020001", "No code section size multiple codes", - "Invalid Code section size for section 1", + "zero_section_size code 1", 1 }, { "EF0001 010008 020002000100", "No code section size multiple codes", - "Invalid Code section size for section 1", + "zero_section_size code 1", 1 }, - {"EF0001 010004 0200010001 04", "No data section size", "Invalid Data section size", 1}, + {"EF0001 010004 0200010001 04", "No data section size", "incomplete_data_header", 1}, + {"EF0001 010004 0200010001 0400", "Short data section size", "incomplete_data_header", 1}, { - "EF0001 010004 0200010001 0400", - "Short data section size", - "Invalid Data section size", + "EF0001 010004 0200010001 040000", + "No Terminator", + "missing_headers_terminator Improper section headers", + 1 + }, + { + "EF0001 010004 0200010002 040000 00", + "No type section", + "invalid_section_bodies_size Incomplete type section", 1 }, - {"EF0001 010004 0200010001 040000", "No Terminator", "Improper section headers", 1}, - {"EF0001 010004 0200010002 040000 00", "No type section", "Incomplete type section", 1}, { "EF0001 010004 0200010002 040001 040001 00 DA DA", "Duplicate data sections", - "Expected kind 0 but read kind 4", + "unexpected_header_kind expected 0 actual 4", 1 }, { "EF0001 010004 0200010002 040000 00 00", "Incomplete type section", - "Incomplete type section", + "invalid_section_bodies_size Incomplete type section", 1 }, { "EF0001 010008 02000200020002 040000 00 00000000FE", "Incomplete type section", - "Incomplete type section", + "invalid_section_bodies_size Incomplete type section", 1 }, { "EF0001 010008 0200010001 040000 00 00000000 FE ", "Incorrect type section size", - "Type section length incompatible with code section count - 0x1 * 4 != 0x8", + "invalid_section_bodies_size Type section - 0x1 * 4 != 0x8", 1 }, { "EF0001 010008 02000200010001 040000 00 0100000000000000 FE FE", "Incorrect section zero type input", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { "EF0001 010008 02000200010001 040000 00 0001000000000000 FE FE", "Incorrect section zero type output", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { "EF0001 010004 0200010002 040000 00 00000000 ", "Incomplete code section", - "Incomplete code section 0", + "invalid_section_bodies_size code section 0", 1 }, { "EF0001 010004 0200010002 040000 00 00000000 FE", "Incomplete code section", - "Incomplete code section 0", + "invalid_section_bodies_size code section 0", 1 }, { "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE ", "No code section multiple", - "Incomplete code section 1", + "invalid_section_bodies_size code section 1", 1 }, { "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE FE", "Incomplete code section multiple", - "Incomplete code section 1", + "invalid_section_bodies_size code section 1", 1 }, { "EF0001 010004 0200010001 040003 00 00800000 FE DEADBEEF", "Excess data section", - "Dangling data after end of all sections", + "invalid_section_bodies_size data after end of all sections", 1 }, { "EF0001 0200010001 040001 00 FE DA", "type section missing", - "Expected kind 1 but read kind 2", + "unexpected_header_kind expected 1 actual 2", 1 }, { "EF0001 010004 040001 00 00000000 DA", "code section missing", - "Expected kind 2 but read kind 4", + "unexpected_header_kind expected 2 actual 4", 1 }, { "EF0001 010004 0200010001 00 00000000 FE", "data section missing", - "Expected kind 4 but read kind 0", + "unexpected_header_kind expected 4 actual 0", 1 }, { "EF0001 040001 00 DA", "type and code section missing", - "Expected kind 1 but read kind 4", + "unexpected_header_kind expected 1 actual 4", 1 }, { "EF0001 0200010001 00 FE", "type and data section missing", - "Expected kind 1 but read kind 2", + "unexpected_header_kind expected 1 actual 2", 1 }, { "EF0001 010004 00 00000000", "code and data sections missing", - "Expected kind 2 but read kind 0", + "unexpected_header_kind expected 2 actual 0", 1 }, - {"EF0001 00", "all sections missing", "Expected kind 1 but read kind 0", 1}, + {"EF0001 00", "all sections missing", "unexpected_header_kind expected 1 actual 0", 1}, { "EF0001 011004 020401" + " 0001".repeat(1025) @@ -192,18 +217,33 @@ public class EOFLayoutTest { + " 00000000".repeat(1025) + " FE".repeat(1025), "no data section, 1025 code sections", - "Too many code sections - 0x401", + "too_many_code_sections - 0x401", + 1 + }, + { + "ef000101000002000003000000", + "All kinds zero size", + "incomplete_section_number Too few code sections", + 1 + }, + { + "ef0001010000020001000103000000ef", + "Zero type size ", + "invalid_section_bodies_size Type section - 0x1 * 4 != 0x0", 1 }, - {"ef000101000002000003000000", "All kinds zero size", "Invalid Types section size", 1}, - {"ef0001010000020001000103000000ef", "Zero type size ", "Invalid Types section size", 1}, { "ef0001010004020001000003000000", "Zero code section length", - "Invalid Code section size for section 0", + "zero_section_size code 0", + 1 + }, + { + "ef000101000402000003000000", + "Zero code sections", + "incomplete_section_number Too few code sections", 1 }, - {"ef000101000402000003000000", "Zero code sections", "Invalid Code section count", 1}, }); } @@ -241,31 +281,31 @@ public class EOFLayoutTest { { "EF0001 010008 02000200020002 040000 00 0100000000000000", "Incorrect section zero type input", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { "EF0001 010008 02000200020002 040000 00 0001000000000000", "Incorrect section zero type output", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { "EF0001 010010 0200040001000200020002 040000 00 00800000 F0000000 00010000 02030000 FE 5000 3000 8000", "inputs too large", - "Type data input stack too large - 0xf0", + "inputs_outputs_num_above_limit Type data input stack too large - 0xf0", 1 }, { "EF0001 010010 0200040001000200020002 040000 00 00800000 01000000 00F00000 02030000 FE 5000 3000 8000", "outputs too large", - "Type data output stack too large - 0xf0", + "inputs_outputs_num_above_limit - 0xf0", 1 }, { "EF0001 010010 0200040001000200020002 040000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", "stack too large", - "Type data max stack too large - 0x400", + "max_stack_height_above_limit Type data max stack too large - 0x400", 1 }, { @@ -336,13 +376,13 @@ public class EOFLayoutTest { { "EF00 01 010004 0200010001 0300010015 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00ff)", "dangling data in subcontainer", - "subcontainer size mismatch", + "invalid_section_bodies_size subcontainer size mismatch", 1 }, { "EF00 01 010004 0200010001 0300010014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00ff)", "dangling data in container", - "Dangling data after end of all sections", + "invalid_section_bodies_size data after end of all sections", 1 }, }); @@ -363,6 +403,11 @@ public class EOFLayoutTest { final Bytes container = Bytes.fromHexString(containerString.replaceAll("[^a-fxA-F0-9]", "")); final EOFLayout layout = EOFLayout.parseEOF(container, true); + if (failureReason != null) { + assertThat(failureReason) + .withFailMessage("Error string should start with a reference test error code") + .matches("^[a-zA-Z]+_.*"); + } assertThat(layout.version()).isEqualTo(expectedVersion); assertThat(layout.invalidReason()).isEqualTo(failureReason); assertThat(layout.container()).isEqualTo(container); From e0aa4f6d4a8d5ba0cb79e286739d4d336247f2b3 Mon Sep 17 00:00:00 2001 From: Matilda-Clerke Date: Tue, 27 Aug 2024 14:49:12 +1000 Subject: [PATCH 11/12] add engine_getClientVersionV1 (#7512) * add engine_getClientVersionV1 Signed-off-by: Matilda Clerke --- CHANGELOG.md | 1 + .../java/org/hyperledger/besu/BesuInfo.java | 39 +++++++++- .../org/hyperledger/besu/RunnerBuilder.java | 2 + .../org/hyperledger/besu/BesuInfoTest.java | 7 +- build.gradle | 14 ++-- .../jsonrpc/JsonRpcTestMethodsFactory.java | 6 +- .../besu/ethereum/api/jsonrpc/RpcMethod.java | 1 + .../engine/EngineGetClientVersionV1.java | 57 +++++++++++++++ .../EngineGetClientVersionResultV1.java | 52 ++++++++++++++ .../ExecutionEngineJsonRpcMethods.java | 13 +++- .../methods/JsonRpcMethodsFactory.java | 10 ++- .../AbstractJsonRpcHttpServiceTest.java | 6 +- .../jsonrpc/AdminJsonRpcHttpServiceTest.java | 6 +- .../JsonRpcHttpServiceHostAllowlistTest.java | 6 +- .../jsonrpc/JsonRpcHttpServiceLoginTest.java | 6 +- .../JsonRpcHttpServiceRpcApisTest.java | 8 ++- .../api/jsonrpc/JsonRpcHttpServiceTest.java | 26 +++---- .../jsonrpc/JsonRpcHttpServiceTestBase.java | 6 +- .../JsonRpcHttpServiceTlsClientAuthTest.java | 6 +- ...RpcHttpServiceTlsMisconfigurationTest.java | 6 +- .../jsonrpc/JsonRpcHttpServiceTlsTest.java | 6 +- .../engine/EngineGetClientVersionV1Test.java | 72 +++++++++++++++++++ .../websocket/WebSocketServiceLoginTest.java | 6 +- 23 files changed, 316 insertions(+), 46 deletions(-) create mode 100644 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java create mode 100644 ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetClientVersionResultV1.java create mode 100644 ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1Test.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c31d55fb..c00417cab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Add 'inbound' field to admin_peers JSON-RPC Call [#7461](https://github.com/hyperledger/besu/pull/7461) - Add pending block header to `TransactionEvaluationContext` plugin API [#7483](https://github.com/hyperledger/besu/pull/7483) - Add bootnode to holesky config [#7500](https://github.com/hyperledger/besu/pull/7500) +- Implement engine_getClientVersionV1 ### Bug fixes - Fix tracing in precompiled contracts when halting for out of gas [#7318](https://github.com/hyperledger/besu/issues/7318) diff --git a/besu/src/main/java/org/hyperledger/besu/BesuInfo.java b/besu/src/main/java/org/hyperledger/besu/BesuInfo.java index 54447a596..aa5163c2a 100644 --- a/besu/src/main/java/org/hyperledger/besu/BesuInfo.java +++ b/besu/src/main/java/org/hyperledger/besu/BesuInfo.java @@ -17,6 +17,8 @@ package org.hyperledger.besu; import org.hyperledger.besu.util.platform.PlatformDetector; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Represent Besu information such as version, OS etc. Used with --version option and during Besu @@ -24,9 +26,29 @@ import java.util.Optional; */ public final class BesuInfo { private static final String CLIENT = "besu"; - private static final String VERSION = BesuInfo.class.getPackage().getImplementationVersion(); private static final String OS = PlatformDetector.getOS(); private static final String VM = PlatformDetector.getVM(); + private static final String VERSION; + private static final String COMMIT; + + static { + String projectVersion = BesuInfo.class.getPackage().getImplementationVersion(); + if (projectVersion == null) { + // protect against unset project version (e.g. unit tests being run, etc) + VERSION = null; + COMMIT = null; + } else { + Pattern pattern = + Pattern.compile("(?\\d+\\.\\d+\\.?\\d?-?\\w*)-(?[0-9a-fA-F]{8})"); + Matcher matcher = pattern.matcher(projectVersion); + if (matcher.find()) { + VERSION = matcher.group("version"); + COMMIT = matcher.group("commit"); + } else { + throw new RuntimeException("Invalid project version: " + projectVersion); + } + } + } private BesuInfo() {} @@ -46,7 +68,7 @@ public final class BesuInfo { * or "besu/v23.1.0/osx-aarch_64/corretto-java-19" */ public static String version() { - return String.format("%s/v%s/%s/%s", CLIENT, VERSION, OS, VM); + return String.format("%s/v%s-%s/%s/%s", CLIENT, VERSION, COMMIT, OS, VM); } /** @@ -57,7 +79,18 @@ public final class BesuInfo { */ public static String nodeName(final Optional maybeIdentity) { return maybeIdentity - .map(identity -> String.format("%s/%s/v%s/%s/%s", CLIENT, identity, VERSION, OS, VM)) + .map( + identity -> + String.format("%s/%s/v%s-%s/%s/%s", CLIENT, identity, VERSION, COMMIT, OS, VM)) .orElse(version()); } + + /** + * Generate the commit hash for this besu version + * + * @return the commit hash for this besu version + */ + public static String commit() { + return COMMIT; + } } diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index e34c0115f..9eaf254bb 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -1291,6 +1291,8 @@ public class RunnerBuilder { new JsonRpcMethodsFactory() .methods( BesuInfo.nodeName(identityString), + BesuInfo.shortVersion(), + BesuInfo.commit(), ethNetworkConfig.networkId(), besuController.getGenesisConfigOptions(), network, diff --git a/besu/src/test/java/org/hyperledger/besu/BesuInfoTest.java b/besu/src/test/java/org/hyperledger/besu/BesuInfoTest.java index 5a1cec440..b7799f5ac 100644 --- a/besu/src/test/java/org/hyperledger/besu/BesuInfoTest.java +++ b/besu/src/test/java/org/hyperledger/besu/BesuInfoTest.java @@ -33,7 +33,8 @@ public final class BesuInfoTest { */ @Test public void versionStringIsEthstatsFriendly() { - assertThat(BesuInfo.version()).matches("[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null)/[^/]+/[^/]+"); + assertThat(BesuInfo.version()) + .matches("[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null-null)/[^/]+/[^/]+"); } /** @@ -45,7 +46,7 @@ public final class BesuInfoTest { @Test public void noIdentityNodeNameIsEthstatsFriendly() { assertThat(BesuInfo.nodeName(Optional.empty())) - .matches("[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null)/[^/]+/[^/]+"); + .matches("[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null-null)/[^/]+/[^/]+"); } /** @@ -58,6 +59,6 @@ public final class BesuInfoTest { @Test public void userIdentityNodeNameIsEthstatsFriendly() { assertThat(BesuInfo.nodeName(Optional.of("TestUserIdentity"))) - .matches("[^/]+/[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null)/[^/]+/[^/]+"); + .matches("[^/]+/[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null-null)/[^/]+/[^/]+"); } } diff --git a/build.gradle b/build.gradle index 6a4f27966..003f6d63c 100644 --- a/build.gradle +++ b/build.gradle @@ -820,7 +820,7 @@ task distDocker { dockerPlatform = "--platform ${project.getProperty('docker-platform')}" println "Building for platform ${project.getProperty('docker-platform')}" } - def gitDetails = getGitCommitDetails(7) + def gitDetails = getGitCommitDetails() executable shell workingDir dockerBuildDir args "-c", "docker build ${dockerPlatform} --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${gitDetails.hash} -t ${image} ." @@ -988,17 +988,13 @@ def buildTime() { def calculateVersion() { // Regex pattern for basic calendar versioning, with provision to omit patch rev def calVerPattern = ~/\d+\.\d+(\.\d+)?(-.*)?/ - + def gitDetails = getGitCommitDetails() // Adjust length as needed if (project.hasProperty('version') && (project.version =~ calVerPattern)) { - if (project.hasProperty('versionappendcommit') && project.versionappendcommit == "true") { - def gitDetails = getGitCommitDetails(7) // Adjust length as needed - return "${project.version}-${gitDetails.hash}" - } - return "${project.version}" + println("Generating project version using supplied version: ${project.version}-${gitDetails.hash}") + return "${project.version}-${gitDetails.hash}" } else { // If no version is supplied or it doesn't match the semantic versioning, calculate from git - println("Generating project version as supplied is version not semver: ${project.version}") - def gitDetails = getGitCommitDetails(7) // Adjust length as needed + println("Generating project version using date (${gitDetails.date}-develop-${gitDetails.hash}), as supplied version is not semver: ${project.version}") return "${gitDetails.date}-develop-${gitDetails.hash}" } } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java index a8226a6a6..b5f65e6d4 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java @@ -64,7 +64,9 @@ import io.vertx.core.VertxOptions; /** Provides a facade to construct the JSON-RPC component. */ public class JsonRpcTestMethodsFactory { - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger NETWORK_ID = BigInteger.valueOf(123); private final BlockchainImporter importer; @@ -175,7 +177,9 @@ public class JsonRpcTestMethodsFactory { return new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, NETWORK_ID, new StubGenesisConfigOptions(), peerDiscovery, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 63fa5b3ce..75da09048 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -63,6 +63,7 @@ public enum RpcMethod { ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"), ENGINE_FORKCHOICE_UPDATED_V3("engine_forkchoiceUpdatedV3"), ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"), + ENGINE_GET_CLIENT_VERSION_V1("engine_getClientVersionV1"), ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1("engine_getPayloadBodiesByHashV1"), ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1("engine_getPayloadBodiesByRangeV1"), ENGINE_EXCHANGE_CAPABILITIES("engine_exchangeCapabilities"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java new file mode 100644 index 000000000..5b1142eee --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java @@ -0,0 +1,57 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetClientVersionResultV1; + +import io.vertx.core.Vertx; + +public class EngineGetClientVersionV1 extends ExecutionEngineJsonRpcMethod { + private static final String ENGINE_CLIENT_CODE = "BU"; + private static final String ENGINE_CLIENT_NAME = "Besu"; + + private final String clientVersion; + private final String commit; + + public EngineGetClientVersionV1( + final Vertx vertx, + final ProtocolContext protocolContext, + final EngineCallListener engineCallListener, + final String clientVersion, + final String commit) { + super(vertx, protocolContext, engineCallListener); + this.clientVersion = clientVersion; + this.commit = commit; + } + + @Override + public String getName() { + return RpcMethod.ENGINE_GET_CLIENT_VERSION_V1.getMethodName(); + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { + return new JsonRpcSuccessResponse( + request.getRequest().getId(), + new EngineGetClientVersionResultV1( + ENGINE_CLIENT_CODE, ENGINE_CLIENT_NAME, clientVersion, commit)); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetClientVersionResultV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetClientVersionResultV1.java new file mode 100644 index 000000000..8251de6dc --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetClientVersionResultV1.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; + +import com.fasterxml.jackson.annotation.JsonGetter; + +public class EngineGetClientVersionResultV1 { + private final String code; + private final String name; + private final String version; + private final String commit; + + public EngineGetClientVersionResultV1( + final String code, final String name, final String version, final String commit) { + this.code = code; + this.name = name; + this.version = version; + this.commit = commit; + } + + @JsonGetter(value = "code") + public String getCode() { + return code; + } + + @JsonGetter(value = "name") + public String getName() { + return name; + } + + @JsonGetter(value = "version") + public String getVersion() { + return version; + } + + @JsonGetter(value = "commit") + public String getCommit() { + return commit; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java index ca6692665..6dda09bce 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineE import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV3; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetClientVersionV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByHashV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByRangeV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV1; @@ -57,13 +58,17 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { private final ProtocolContext protocolContext; private final EthPeers ethPeers; private final Vertx consensusEngineServer; + private final String clientVersion; + private final String commit; ExecutionEngineJsonRpcMethods( final MiningCoordinator miningCoordinator, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthPeers ethPeers, - final Vertx consensusEngineServer) { + final Vertx consensusEngineServer, + final String clientVersion, + final String commit) { this.mergeCoordinator = Optional.ofNullable(miningCoordinator) .filter(mc -> mc.isCompatibleWithEngineApi()) @@ -72,6 +77,8 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { this.protocolContext = protocolContext; this.ethPeers = ethPeers; this.consensusEngineServer = consensusEngineServer; + this.clientVersion = clientVersion; + this.commit = commit; } @Override @@ -147,7 +154,9 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { new EngineExchangeCapabilities( consensusEngineServer, protocolContext, engineQosTimer), new EnginePreparePayloadDebug( - consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get()))); + consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get()), + new EngineGetClientVersionV1( + consensusEngineServer, protocolContext, engineQosTimer, clientVersion, commit))); if (protocolSchedule.anyMatch(p -> p.spec().getName().equalsIgnoreCase("cancun"))) { executionEngineApisSupported.add( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index 924889ef4..41227c2ca 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -54,7 +54,9 @@ import io.vertx.core.Vertx; public class JsonRpcMethodsFactory { public Map methods( + final String clientNodeName, final String clientVersion, + final String commit, final BigInteger networkId, final GenesisConfigOptions genesisConfigOptions, final P2PNetwork p2pNetwork, @@ -89,7 +91,7 @@ public class JsonRpcMethodsFactory { final List availableApiGroups = List.of( new AdminJsonRpcMethods( - clientVersion, + clientNodeName, networkId, genesisConfigOptions, p2pNetwork, @@ -115,7 +117,9 @@ public class JsonRpcMethodsFactory { protocolSchedule, protocolContext, ethPeers, - consensusEngineServer), + consensusEngineServer, + clientVersion, + commit), new EthJsonRpcMethods( blockchainQueries, synchronizer, @@ -141,7 +145,7 @@ public class JsonRpcMethodsFactory { filterManager), new PrivxJsonRpcMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), - new Web3JsonRpcMethods(clientVersion), + new Web3JsonRpcMethods(clientNodeName), new TraceJsonRpcMethods( blockchainQueries, protocolSchedule, protocolContext, apiConfiguration), new TxPoolJsonRpcMethods(transactionPool), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java index 9193574c4..862e17b89 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java @@ -75,7 +75,9 @@ public abstract class AbstractJsonRpcHttpServiceTest { protected BlockchainSetupUtil blockchainSetupUtil; - protected static String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger NETWORK_ID = BigInteger.valueOf(123); protected static final Collection JSON_RPC_APIS = Arrays.asList( @@ -168,7 +170,9 @@ public abstract class AbstractJsonRpcHttpServiceTest { return new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, NETWORK_ID, new StubGenesisConfigOptions(), peerDiscoveryMock, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java index 061d2a2d6..4a03b2eb9 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java @@ -58,13 +58,13 @@ public class AdminJsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { final List peerList = new ArrayList<>(); final PeerInfo info1 = new PeerInfo( - 4, CLIENT_VERSION, caps, 30302, Bytes.fromHexString(String.format("%0128x", 1))); + 4, CLIENT_NODE_NAME, caps, 30302, Bytes.fromHexString(String.format("%0128x", 1))); final PeerInfo info2 = new PeerInfo( - 4, CLIENT_VERSION, caps, 60302, Bytes.fromHexString(String.format("%0128x", 2))); + 4, CLIENT_NODE_NAME, caps, 60302, Bytes.fromHexString(String.format("%0128x", 2))); final PeerInfo info3 = new PeerInfo( - 4, CLIENT_VERSION, caps, 60303, Bytes.fromHexString(String.format("%0128x", 3))); + 4, CLIENT_NODE_NAME, caps, 60303, Bytes.fromHexString(String.format("%0128x", 3))); final InetSocketAddress addr30301 = new InetSocketAddress("localhost", 30301); final InetSocketAddress addr30302 = new InetSocketAddress("localhost", 30302); final InetSocketAddress addr60301 = new InetSocketAddress("localhost", 60301); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java index 80626ef2f..25077bbf8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java @@ -79,7 +79,9 @@ public class JsonRpcHttpServiceHostAllowlistTest { private static OkHttpClient client; private static String baseUrl; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private final JsonRpcConfiguration jsonRpcConfig = createJsonRpcConfig(); @@ -100,7 +102,9 @@ public class JsonRpcHttpServiceHostAllowlistTest { rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, new StubGenesisConfigOptions(), peerDiscoveryMock, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java index 0fb55fdf3..eb23b054d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -100,7 +100,9 @@ public class JsonRpcHttpServiceLoginTest { protected static OkHttpClient client; protected static String baseUrl; protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - protected static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(123); protected static P2PNetwork peerDiscoveryMock; protected static BlockchainQueries blockchainQueries; @@ -131,7 +133,9 @@ public class JsonRpcHttpServiceLoginTest { rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, genesisConfigOptions, peerDiscoveryMock, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java index 8323f61b2..f35893490 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java @@ -94,7 +94,9 @@ public class JsonRpcHttpServiceRpcApisTest { private JsonRpcHttpService service; private static String baseUrl; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger NETWORK_ID = BigInteger.valueOf(123); private JsonRpcConfiguration configuration; private static final List netServices = @@ -202,7 +204,9 @@ public class JsonRpcHttpServiceRpcApisTest { final Map rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, NETWORK_ID, new StubGenesisConfigOptions(), mock(P2PNetwork.class), @@ -310,7 +314,9 @@ public class JsonRpcHttpServiceRpcApisTest { final Map rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, NETWORK_ID, new StubGenesisConfigOptions(), p2pNetwork, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java index bb5cb68a2..52f2ee050 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java @@ -202,7 +202,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1127,7 +1127,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1143,7 +1143,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { final JsonObject json = new JsonObject(resp.body().string()); testHelper.assertValidJsonRpcResult(json, id); final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1175,7 +1175,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, null); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1197,7 +1197,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1218,7 +1218,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1242,7 +1242,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1268,7 +1268,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1289,7 +1289,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1353,7 +1353,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { final JsonObject json = new JsonObject(resp.body().string()); testHelper.assertValidJsonRpcResult(json, id); final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1485,7 +1485,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { // Check result web3_clientVersion final JsonObject jsonClientVersion = responses.get(clientVersionRequestId); testHelper.assertValidJsonRpcResult(jsonClientVersion, clientVersionRequestId); - assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_VERSION); + assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_NODE_NAME); // Check result unknown method final JsonObject jsonError = responses.get(brokenRequestId); @@ -1540,7 +1540,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { // Check result web3_clientVersion final JsonObject jsonClientVersion = responses.get(clientVersionRequestId); testHelper.assertValidJsonRpcResult(jsonClientVersion, clientVersionRequestId); - assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_VERSION); + assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_NODE_NAME); // Check invalid request final JsonObject jsonError = responses.get(invalidId); @@ -1605,7 +1605,7 @@ public class JsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase { // Check result web3_clientVersion final JsonObject jsonClientVersion = responses.get(clientVersionRequestId); testHelper.assertValidJsonRpcResult(jsonClientVersion, clientVersionRequestId); - assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_VERSION); + assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_NODE_NAME); // Check result net_version final JsonObject jsonNetVersion = responses.get(netVersionRequestId); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java index a2a856333..5e5dc36bb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java @@ -78,7 +78,9 @@ public class JsonRpcHttpServiceTestBase { protected static OkHttpClient client; protected static String baseUrl; protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - protected static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(123); protected static P2PNetwork peerDiscoveryMock; protected static EthPeers ethPeersMock; @@ -108,7 +110,9 @@ public class JsonRpcHttpServiceTestBase { rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, new StubGenesisConfigOptions(), peerDiscoveryMock, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java index 1d3a3a087..5cf13a457 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java @@ -85,7 +85,9 @@ public class JsonRpcHttpServiceTlsClientAuthTest { protected static final Vertx vertx = Vertx.vertx(); private static final String JSON_HEADER = "application/json; charset=utf-8"; - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private static final NatService natService = new NatService(Optional.empty()); @@ -114,7 +116,9 @@ public class JsonRpcHttpServiceTlsClientAuthTest { rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, new StubGenesisConfigOptions(), peerDiscoveryMock, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java index 684f843d2..c2661141a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java @@ -75,7 +75,9 @@ class JsonRpcHttpServiceTlsMisconfigurationTest { protected static final Vertx vertx = Vertx.vertx(); - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private static final NatService natService = new NatService(Optional.empty()); private final SelfSignedP12Certificate besuCertificate = SelfSignedP12Certificate.create(); @@ -102,7 +104,9 @@ class JsonRpcHttpServiceTlsMisconfigurationTest { rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, new StubGenesisConfigOptions(), peerDiscoveryMock, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java index b6d7fa67f..c0846ed9f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java @@ -81,7 +81,9 @@ public class JsonRpcHttpServiceTlsTest { protected static final Vertx vertx = Vertx.vertx(); private static final String JSON_HEADER = "application/json; charset=utf-8"; - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private static final NatService natService = new NatService(Optional.empty()); private JsonRpcHttpService service; @@ -103,7 +105,9 @@ public class JsonRpcHttpServiceTlsTest { rpcMethods = new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, new StubGenesisConfigOptions(), peerDiscoveryMock, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1Test.java new file mode 100644 index 000000000..1aa0def7e --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1Test.java @@ -0,0 +1,72 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetClientVersionResultV1; + +import io.vertx.core.Vertx; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class EngineGetClientVersionV1Test { + + private static final String ENGINE_CLIENT_CODE = "BU"; + private static final String ENGINE_CLIENT_NAME = "Besu"; + + private static final String CLIENT_VERSION = "v25.6.7-dev-abcdef12"; + private static final String COMMIT = "abcdef12"; + + private EngineGetClientVersionV1 getClientVersion; + + @BeforeEach + void before() { + getClientVersion = + new EngineGetClientVersionV1( + Mockito.mock(Vertx.class), + Mockito.mock(ProtocolContext.class), + Mockito.mock(EngineCallListener.class), + CLIENT_VERSION, + COMMIT); + } + + @Test + void testGetName() { + assertThat(getClientVersion.getName()).isEqualTo("engine_getClientVersionV1"); + } + + @Test + void testSyncResponse() { + JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("v", "m", null)); + JsonRpcResponse actualResult = getClientVersion.syncResponse(request); + + assertThat(actualResult).isInstanceOf(JsonRpcSuccessResponse.class); + JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) actualResult; + assertThat(successResponse.getResult()).isInstanceOf(EngineGetClientVersionResultV1.class); + EngineGetClientVersionResultV1 actualEngineGetClientVersionResultV1 = + (EngineGetClientVersionResultV1) successResponse.getResult(); + assertThat(actualEngineGetClientVersionResultV1.getName()).isEqualTo(ENGINE_CLIENT_NAME); + assertThat(actualEngineGetClientVersionResultV1.getCode()).isEqualTo(ENGINE_CLIENT_CODE); + assertThat(actualEngineGetClientVersionResultV1.getVersion()).isEqualTo(CLIENT_VERSION); + assertThat(actualEngineGetClientVersionResultV1.getCommit()).isEqualTo(COMMIT); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java index 75927c451..342941d0e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java @@ -115,7 +115,9 @@ public class WebSocketServiceLoginTest { protected static OkHttpClient client; protected static String baseUrl; protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - protected static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(123); protected static P2PNetwork peerDiscoveryMock; protected static BlockchainQueries blockchainQueries; @@ -167,7 +169,9 @@ public class WebSocketServiceLoginTest { spy( new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, genesisConfigOptions, peerDiscoveryMock, From c656ece8fca77bb0acb15f578de86aac98dec7d2 Mon Sep 17 00:00:00 2001 From: Suyash Nayan <89125422+7suyash7@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:47:47 +0530 Subject: [PATCH 12/12] build: Add Spotless Fail Fast to the build process (#7515) * Add Spotless Fail Fast to the build process --------- Signed-off-by: 7suyash7 Co-authored-by: Usman Saleem --- build.gradle | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 003f6d63c..690c0fb39 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,10 @@ sonarqube { } } +tasks.register('spotlessCheckFast') { + dependsOn subprojects.collect { it.tasks.withType(com.diffplug.gradle.spotless.SpotlessCheck) } +} + project.tasks["sonarqube"].dependsOn "jacocoRootReport" if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21)) { @@ -432,6 +436,12 @@ allprojects { options.addStringOption('Xwerror', '-html5') options.encoding = 'UTF-8' } + + plugins.withType(JavaPlugin) { + tasks.withType(JavaCompile) { + it.dependsOn(rootProject.tasks.named('spotlessCheckFast')) + } + } } task deploy() {} @@ -455,7 +465,7 @@ task checkMavenCoordinateCollisions { tasks.register('checkPluginAPIChanges', DefaultTask) {} checkPluginAPIChanges.dependsOn(':plugin-api:checkAPIChanges') -check.dependsOn('checkPluginAPIChanges', 'checkMavenCoordinateCollisions') +check.dependsOn('checkPluginAPIChanges', 'checkMavenCoordinateCollisions', 'spotlessCheckFast') subprojects {