Skip to content

Commit

Permalink
Add needed tooling support for execution-spec-tests (#5030)
Browse files Browse the repository at this point in the history
Adding the t8n and b11r tool allows Hyperledger besu to produce reference 
tests from the https://github.com/ethereum/execution-spec-tests repository.

* Add t8n tool, or transition tool. Calculates the state changes from a set 
  of transactions against a given parent context
* Add b11r tool, or block-builder tool. Given block parts it can generate a 
  RLP block.
* Change some tracing CLI flags to work better with retesteth

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>
  • Loading branch information
shemnon authored Feb 21, 2023
1 parent 11a4f73 commit 532797f
Show file tree
Hide file tree
Showing 50 changed files with 3,682 additions and 212 deletions.
14 changes: 12 additions & 2 deletions besu/src/main/java/org/hyperledger/besu/BesuInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,19 @@ public final class BesuInfo {
private BesuInfo() {}

/**
* Generate Besu version
* Generate version-only Besu version
*
* @return Besu version in format such as besu/v22.10.3/linux-aarch_64/openjdk-java-11
* @return Besu version in format such as "v23.1.0" or "v23.1.1-dev-ac23d311"
*/
public static String shortVersion() {
return VERSION;
}

/**
* Generate full Besu version
*
* @return Besu version in format such as "besu/v23.1.1-dev-ac23d311/osx-x86_64/graalvm-java-17"
* 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import org.apache.tuweni.units.bigints.UInt256;

/** The Stub genesis config options. */
public class StubGenesisConfigOptions implements GenesisConfigOptions {
public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable {

private OptionalLong homesteadBlockNumber = OptionalLong.empty();
private OptionalLong daoForkBlock = OptionalLong.empty();
Expand Down Expand Up @@ -76,6 +76,15 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
private TransitionsConfigOptions transitions = TransitionsConfigOptions.DEFAULT;
private static final DiscoveryOptions DISCOVERY_OPTIONS = DiscoveryOptions.DEFAULT;

@Override
public StubGenesisConfigOptions clone() {
try {
return (StubGenesisConfigOptions) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}

@Override
public String getConsensusEngine() {
return "ethash";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import org.apache.tuweni.bytes.Bytes32;

/** The Merge block creator. */
public class MergeBlockCreator extends AbstractBlockCreator {
class MergeBlockCreator extends AbstractBlockCreator {

/**
* Instantiates a new Merge block creator.
Expand All @@ -51,7 +51,7 @@ public class MergeBlockCreator extends AbstractBlockCreator {
* @param minBlockOccupancyRatio the min block occupancy ratio
* @param parentHeader the parent header
*/
MergeBlockCreator(
public MergeBlockCreator(
final Address coinbase,
final Supplier<Optional<Long>> targetGasLimitSupplier,
final ExtraDataCalculator extraDataCalculator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public List<String> traceTransactionToFile(
stackedUpdater,
transaction,
transactionProcessor,
new StandardJsonTracer(out, showMemory));
new StandardJsonTracer(out, showMemory, true, true));
out.println(
summaryTrace(
transaction, timer.stop().elapsed(TimeUnit.NANOSECONDS), result));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public static Difficulty fromHexString(final String str) {
return new Difficulty(str);
}

public static Difficulty fromHexOrDecimalString(final String str) {
return str.startsWith("0x") ? new Difficulty(str) : new Difficulty(new BigInteger(str));
}

@Override
public Number getValue() {
return getAsBigInteger();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ public ValidationResult<TransactionInvalidReason> validateForSender(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
String.format(
"transaction up-front cost %s exceeds transaction sender account balance %s",
transaction.getUpfrontCost(), senderBalance));
transaction.getUpfrontCost().toQuantityHexString(),
senderBalance.toQuantityHexString()));
}

if (transaction.getNonce() < senderNonce) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator;
import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator;

import com.google.common.base.Supplier;
import java.util.function.Supplier;

import org.apache.tuweni.units.bigints.UInt256;
import org.assertj.core.api.Assertions;
import org.junit.Before;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.evmtool;

import static org.hyperledger.besu.evmtool.B11rSubCommand.COMMAND_ALIAS;
import static org.hyperledger.besu.evmtool.B11rSubCommand.COMMAND_NAME;

import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.referencetests.BlockchainReferenceTestCaseSpec.ReferenceTestBlockHeader;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.tuweni.bytes.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

@Command(
name = COMMAND_NAME,
aliases = {COMMAND_ALIAS},
description = "Execute an Ethereum State Test.",
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class)
public class B11rSubCommand implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(B11rSubCommand.class);

static final String COMMAND_NAME = "block-builder";
static final String COMMAND_ALIAS = "b11r";
private static final Path stdoutPath = Path.of("stdout");
private static final Path stdinPath = Path.of("stdin");

@Option(
names = {"--input.header"},
paramLabel = "full path",
description = "The block header for the block")
private final Path header = stdinPath;

@Option(
names = {"--input.txs"},
paramLabel = "full path",
description = "The transactions to block")
private final Path txs = stdinPath;

@Option(
names = {"--input.ommers"},
paramLabel = "full path",
description = "The ommers for the block")
private final Path ommers = stdinPath;

@Option(
names = {"--seal.clique"},
paramLabel = "full path",
description = "The clique seal/signature for the block")
private final Path sealClique = stdinPath;

@SuppressWarnings("UnusedVariable")
@Option(
names = {"--seal.ethash"},
description = "Use Proof of Work to seal the block")
private final Boolean sealEthash = false;

@SuppressWarnings("UnusedVariable")
@Option(
names = {"--seal.ethash.mode"},
paramLabel = "full path",
description = "The ethash mode for the block")
private String sealEthashMode = "noproof";

@Option(
names = {"--output.basedir"},
paramLabel = "full path",
description = "The output ")
private final Path outDir = Path.of(".");

@Option(
names = {"--output.block"},
paramLabel = "file name",
description = "The account state after the transition")
private final Path outBlock = Path.of("block.json");

private static final ObjectMapper objectMapper = new ObjectMapper();

@CommandLine.ParentCommand private final EvmToolCommand parentCommand;

@SuppressWarnings("unused")
public B11rSubCommand() {
// PicoCLI requires this
parentCommand = null;
}

@SuppressWarnings("unused")
public B11rSubCommand(final EvmToolCommand parentCommand) {
// PicoCLI requires this too
this.parentCommand = parentCommand;
}

@Override
public void run() {
objectMapper.setDefaultPrettyPrinter(
(new DefaultPrettyPrinter())
.withSpacesInObjectEntries()
.withObjectIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(" "))
.withArrayIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(" ")));
final ObjectReader t8nReader = objectMapper.reader();
objectMapper.disable(Feature.AUTO_CLOSE_SOURCE);

ObjectNode config;
try {
if (header.equals(stdinPath)
|| txs.equals(stdinPath)
|| ommers.equals(stdinPath)
|| sealClique.equals(stdinPath)) {
config =
(ObjectNode)
t8nReader.readTree(new InputStreamReader(parentCommand.in, StandardCharsets.UTF_8));
} else {
config = objectMapper.createObjectNode();
}

if (!header.equals(stdinPath)) {
try (FileReader reader = new FileReader(header.toFile(), StandardCharsets.UTF_8)) {
config.set("header", t8nReader.readTree(reader));
}
}
if (!txs.equals(stdinPath)) {
try (FileReader reader = new FileReader(txs.toFile(), StandardCharsets.UTF_8)) {
config.set("txs", t8nReader.readTree(reader));
}
}
if (!ommers.equals(stdinPath)) {
try (FileReader reader = new FileReader(ommers.toFile(), StandardCharsets.UTF_8)) {
config.set("ommers", t8nReader.readTree(reader));
}
}
if (!sealClique.equals(stdinPath)) {
try (FileReader reader = new FileReader(sealClique.toFile(), StandardCharsets.UTF_8)) {
config.set("clique", t8nReader.readTree(reader));
}
}
} catch (final JsonProcessingException jpe) {
parentCommand.out.println("File content error: " + jpe);
jpe.printStackTrace();
return;
} catch (final IOException e) {
LOG.error("Unable to read state file", e);
return;
}

var testHeader = this.readHeader(config.get("header"));
Bytes txsBytes = null;

if (config.has("txs")) {
String txsString = config.get("txs").textValue();
if (txsString.length() > 0) {
txsBytes = Bytes.fromHexString(txsString);
}
}

var newHeader =
BlockHeaderBuilder.fromHeader(testHeader)
.blockHeaderFunctions(new MainnetBlockHeaderFunctions())
.buildBlockHeader();
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
rlpOut.startList();
newHeader.writeTo(rlpOut);
if (txsBytes != null && txsBytes.size() > 0) {
rlpOut.writeRaw(txsBytes);
} else {
rlpOut.startList();
rlpOut.endList();
}
// ommers
rlpOut.startList();
rlpOut.endList();

// withdrawals
// TODO - waiting on b11r spec to specify how withdrawals are added to blocks.

rlpOut.endList();

final ObjectNode resultObject = objectMapper.createObjectNode();
resultObject.put("rlp", rlpOut.encoded().toHexString());
resultObject.put("hash", newHeader.getHash().toHexString());
var writer = objectMapper.writerWithDefaultPrettyPrinter();
try {
var resultString = writer.writeValueAsString(resultObject);
if (outBlock.equals((stdoutPath))) {
parentCommand.out.println(resultString);
} else {
try (var fileOut =
new PrintStream(new FileOutputStream(outDir.resolve(outBlock).toFile()))) {
fileOut.println(resultString);
}
}
} catch (FileNotFoundException | JsonProcessingException e) {
throw new RuntimeException(e);
}
}

void maybeMoveField(final ObjectNode jsonObject, final String oldField, final String newField) {
if (jsonObject.has(oldField)) {
jsonObject.set(newField, jsonObject.remove(oldField));
}
}

private ReferenceTestBlockHeader readHeader(final JsonNode jsonObject) {
ObjectNode objectNode = (ObjectNode) jsonObject;
maybeMoveField(objectNode, "logsBloom", "bloom");
maybeMoveField(objectNode, "sha3Uncles", "uncleHash");
maybeMoveField(objectNode, "miner", "coinbase");
return objectMapper.convertValue(jsonObject, ReferenceTestBlockHeader.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@

import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;

import picocli.CommandLine;

public final class EvmTool {

public static void main(final String... args) {
SignatureAlgorithmFactory.setDefaultInstance();

final EvmToolCommand evmToolCommand = new EvmToolCommand();

evmToolCommand.parse(new CommandLine.RunLast(), args);
evmToolCommand.execute(args);
}
}
Loading

0 comments on commit 532797f

Please sign in to comment.