Skip to content

Commit

Permalink
eth_simulateV1 - Add BlockSimulator feature (#7941)
Browse files Browse the repository at this point in the history
Signed-off-by: Gabriel-Trintinalia <[email protected]>
  • Loading branch information
Gabriel-Trintinalia authored Dec 19, 2024
1 parent 0448af8 commit a03c98b
Show file tree
Hide file tree
Showing 19 changed files with 1,730 additions and 3 deletions.
11 changes: 11 additions & 0 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.plugin.services.BesuConfiguration;
import org.hyperledger.besu.plugin.services.BesuEvents;
import org.hyperledger.besu.plugin.services.BlockSimulationService;
import org.hyperledger.besu.plugin.services.BlockchainService;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.PermissioningService;
Expand All @@ -178,6 +179,7 @@
import org.hyperledger.besu.services.BesuConfigurationImpl;
import org.hyperledger.besu.services.BesuEventsImpl;
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.services.BlockSimulatorServiceImpl;
import org.hyperledger.besu.services.BlockchainServiceImpl;
import org.hyperledger.besu.services.MiningServiceImpl;
import org.hyperledger.besu.services.P2PServiceImpl;
Expand Down Expand Up @@ -1288,6 +1290,15 @@ private void startPlugins(final Runner runner) {
besuPluginContext.addService(
MiningService.class, new MiningServiceImpl(besuController.getMiningCoordinator()));

besuPluginContext.addService(
BlockSimulationService.class,
new BlockSimulatorServiceImpl(
besuController.getProtocolContext().getWorldStateArchive(),
miningParametersSupplier.get(),
besuController.getTransactionSimulator(),
besuController.getProtocolSchedule(),
besuController.getProtocolContext().getBlockchain()));

besuController.getAdditionalPluginServices().appendPluginServices(besuPluginContext);
besuPluginContext.startPlugins();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright contributors to 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.services;

import org.hyperledger.besu.datatypes.AccountOverrideMap;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.transaction.BlockSimulationResult;
import org.hyperledger.besu.ethereum.transaction.BlockSimulator;
import org.hyperledger.besu.ethereum.transaction.BlockStateCall;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.plugin.Unstable;
import org.hyperledger.besu.plugin.data.BlockOverrides;
import org.hyperledger.besu.plugin.data.PluginBlockSimulationResult;
import org.hyperledger.besu.plugin.data.TransactionSimulationResult;
import org.hyperledger.besu.plugin.services.BlockSimulationService;

import java.util.List;

/** This class is a service that simulates the processing of a block */
public class BlockSimulatorServiceImpl implements BlockSimulationService {
private final BlockSimulator blockSimulator;
private final WorldStateArchive worldStateArchive;
private final Blockchain blockchain;

/**
* This constructor creates a BlockSimulatorServiceImpl object
*
* @param worldStateArchive the world state archive
* @param miningConfiguration the mining configuration
* @param transactionSimulator the transaction simulator
* @param protocolSchedule the protocol schedule
* @param blockchain the blockchain
*/
public BlockSimulatorServiceImpl(
final WorldStateArchive worldStateArchive,
final MiningConfiguration miningConfiguration,
final TransactionSimulator transactionSimulator,
final ProtocolSchedule protocolSchedule,
final Blockchain blockchain) {
this.blockchain = blockchain;
blockSimulator =
new BlockSimulator(
worldStateArchive, protocolSchedule, transactionSimulator, miningConfiguration);
this.worldStateArchive = worldStateArchive;
}

/**
* Simulate the processing of a block given a header, a list of transactions, and blockOverrides.
*
* @param blockNumber the block number
* @param transactions the transactions to include in the block
* @param blockOverrides the blockSimulationOverride of the block
* @param accountOverrides state overrides of the block
* @return the block context
*/
@Override
public PluginBlockSimulationResult simulate(
final long blockNumber,
final List<? extends Transaction> transactions,
final BlockOverrides blockOverrides,
final AccountOverrideMap accountOverrides) {
return processSimulation(blockNumber, transactions, blockOverrides, accountOverrides, false);
}

/**
* This method is experimental and should be used with caution. Simulate the processing of a block
* given a header, a list of transactions, and blockOverrides and persist the WorldState
*
* @param blockNumber the block number
* @param transactions the transactions to include in the block
* @param blockOverrides block overrides for the block
* @param accountOverrides state overrides of the block
* @return the PluginBlockSimulationResult
*/
@Unstable
@Override
public PluginBlockSimulationResult simulateAndPersistWorldState(
final long blockNumber,
final List<? extends Transaction> transactions,
final BlockOverrides blockOverrides,
final AccountOverrideMap accountOverrides) {
return processSimulation(blockNumber, transactions, blockOverrides, accountOverrides, true);
}

private PluginBlockSimulationResult processSimulation(
final long blockNumber,
final List<? extends Transaction> transactions,
final BlockOverrides blockOverrides,
final AccountOverrideMap accountOverrides,
final boolean persistWorldState) {
BlockHeader header = getBlockHeader(blockNumber);
List<CallParameter> callParameters =
transactions.stream().map(CallParameter::fromTransaction).toList();
BlockStateCall blockStateCall =
new BlockStateCall(callParameters, blockOverrides, accountOverrides, true);
try (final MutableWorldState ws = getWorldState(header, persistWorldState)) {
List<BlockSimulationResult> results =
blockSimulator.process(header, List.of(blockStateCall), ws);
BlockSimulationResult result = results.getFirst();
if (persistWorldState) {
ws.persist(result.getBlock().getHeader());
}
return response(result);
} catch (final Exception e) {
throw new RuntimeException("Error simulating block", e);
}
}

private BlockHeader getBlockHeader(final long blockNumber) {
return blockchain
.getBlockHeader(blockNumber)
.orElseThrow(
() ->
new IllegalArgumentException(
"Block header not found for block number: " + blockNumber));
}

private MutableWorldState getWorldState(final BlockHeader header, final boolean isPersisting) {
return worldStateArchive
.getMutable(header, isPersisting)
.orElseThrow(
() ->
new IllegalArgumentException(
"World state not available for block number (block hash): "
+ header.toLogString()));
}

private PluginBlockSimulationResult response(final BlockSimulationResult result) {
return new PluginBlockSimulationResult(
result.getBlockHeader(),
result.getBlockBody(),
result.getReceipts(),
result.getTransactionSimulations().stream()
.map(
simulation ->
new TransactionSimulationResult(simulation.transaction(), simulation.result()))
.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Optional<List<TransactionReceipt>> getReceiptsByBlockHash(final Hash bloc
public void storeBlock(
final BlockHeader blockHeader,
final BlockBody blockBody,
final List<TransactionReceipt> receipts) {
final List<? extends TransactionReceipt> receipts) {
final org.hyperledger.besu.ethereum.core.BlockHeader coreHeader =
(org.hyperledger.besu.ethereum.core.BlockHeader) blockHeader;
final org.hyperledger.besu.ethereum.core.BlockBody coreBody =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright contributors to 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.parameters;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.datatypes.parameters.UnsignedLongParameter;
import org.hyperledger.besu.plugin.data.BlockOverrides;

import java.math.BigInteger;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;

public class BlockOverridesParameter extends BlockOverrides {
/**
* Constructs a new BlockOverrides instance.
*
* @param timestamp the optional timestamp
* @param blockNumber the optional block number
* @param blockHash the optional block hash
* @param prevRandao the optional previous Randao
* @param gasLimit the optional gas limit
* @param feeRecipient the optional fee recipient
* @param baseFeePerGas the optional base fee per gas
* @param blobBaseFee the optional blob base fee
* @param stateRoot the optional state root
* @param difficulty the optional difficulty
* @param extraData the optional extra data
* @param mixHashOrPrevRandao the optional mix hash or previous Randao
*/
@JsonCreator
public BlockOverridesParameter(
@JsonProperty("time") final Optional<UnsignedLongParameter> timestamp,
@JsonProperty("number") final Optional<UnsignedLongParameter> blockNumber,
@JsonProperty("hash") final Optional<Hash> blockHash,
@JsonProperty("prevRandao") final Optional<Bytes32> prevRandao,
@JsonProperty("gasLimit") final Optional<UnsignedLongParameter> gasLimit,
@JsonProperty("feeRecipient") final Optional<Address> feeRecipient,
@JsonProperty("baseFeePerGas") final Optional<Wei> baseFeePerGas,
@JsonProperty("blobBaseFee") final Optional<UnsignedLongParameter> blobBaseFee,
@JsonProperty("stateRoot") final Optional<Hash> stateRoot,
@JsonProperty("difficulty") final Optional<BigInteger> difficulty,
@JsonProperty("extraData") final Optional<Bytes> extraData,
@JsonProperty("mixHashOrPrevRandao") final Optional<Hash> mixHashOrPrevRandao) {
super(
timestamp,
blockNumber,
blockHash,
prevRandao,
gasLimit,
feeRecipient,
baseFeePerGas,
blobBaseFee,
stateRoot,
difficulty,
extraData,
mixHashOrPrevRandao);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public class BlockResult implements JsonRpcResult {
private final String excessBlobGas;
private final String parentBeaconBlockRoot;
private final String targetBlobsPerBlock;
private final List<CallProcessingResult> callProcessingResults;

public BlockResult(
final BlockHeader header,
Expand All @@ -107,6 +108,18 @@ public BlockResult(
final int size,
final boolean includeCoinbase,
final Optional<List<Withdrawal>> withdrawals) {
this(header, transactions, ommers, null, totalDifficulty, size, includeCoinbase, withdrawals);
}

public BlockResult(
final BlockHeader header,
final List<TransactionResult> transactions,
final List<JsonNode> ommers,
final List<CallProcessingResult> callProcessingResults,
final Difficulty totalDifficulty,
final int size,
final boolean includeCoinbase,
final Optional<List<Withdrawal>> withdrawals) {
this.number = Quantity.create(header.getNumber());
this.hash = header.getHash().toString();
this.mixHash = header.getMixHash().toString();
Expand All @@ -128,6 +141,7 @@ public BlockResult(
this.timestamp = Quantity.create(header.getTimestamp());
this.ommers = ommers;
this.transactions = transactions;
this.callProcessingResults = callProcessingResults;
this.coinbase = includeCoinbase ? header.getCoinbase().toString() : null;
this.withdrawalsRoot = header.getWithdrawalsRoot().map(Hash::toString).orElse(null);
this.withdrawals =
Expand Down Expand Up @@ -282,4 +296,9 @@ public String getParentBeaconBlockRoot() {
public String getTargetBlobsPerBlock() {
return targetBlobsPerBlock;
}

@JsonGetter(value = "calls")
public List<CallProcessingResult> getTransactionProcessingResults() {
return callProcessingResults;
}
}
Loading

0 comments on commit a03c98b

Please sign in to comment.