Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit aa0ad5f

Browse files
Gabriel-Trintinaliagaryschulte
authored andcommitted
eth_simulateV1 - Add BlockSimulator feature (#7941)
Signed-off-by: Gabriel-Trintinalia <[email protected]>
1 parent 63d9090 commit aa0ad5f

File tree

19 files changed

+1730
-3
lines changed

19 files changed

+1730
-3
lines changed

besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
import org.hyperledger.besu.plugin.data.EnodeURL;
153153
import org.hyperledger.besu.plugin.services.BesuConfiguration;
154154
import org.hyperledger.besu.plugin.services.BesuEvents;
155+
import org.hyperledger.besu.plugin.services.BlockSimulationService;
155156
import org.hyperledger.besu.plugin.services.BlockchainService;
156157
import org.hyperledger.besu.plugin.services.MetricsSystem;
157158
import org.hyperledger.besu.plugin.services.PermissioningService;
@@ -178,6 +179,7 @@
178179
import org.hyperledger.besu.services.BesuConfigurationImpl;
179180
import org.hyperledger.besu.services.BesuEventsImpl;
180181
import org.hyperledger.besu.services.BesuPluginContextImpl;
182+
import org.hyperledger.besu.services.BlockSimulatorServiceImpl;
181183
import org.hyperledger.besu.services.BlockchainServiceImpl;
182184
import org.hyperledger.besu.services.MiningServiceImpl;
183185
import org.hyperledger.besu.services.P2PServiceImpl;
@@ -1288,6 +1290,15 @@ private void startPlugins(final Runner runner) {
12881290
besuPluginContext.addService(
12891291
MiningService.class, new MiningServiceImpl(besuController.getMiningCoordinator()));
12901292

1293+
besuPluginContext.addService(
1294+
BlockSimulationService.class,
1295+
new BlockSimulatorServiceImpl(
1296+
besuController.getProtocolContext().getWorldStateArchive(),
1297+
miningParametersSupplier.get(),
1298+
besuController.getTransactionSimulator(),
1299+
besuController.getProtocolSchedule(),
1300+
besuController.getProtocolContext().getBlockchain()));
1301+
12911302
besuController.getAdditionalPluginServices().appendPluginServices(besuPluginContext);
12921303
besuPluginContext.startPlugins();
12931304
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.services;
16+
17+
import org.hyperledger.besu.datatypes.AccountOverrideMap;
18+
import org.hyperledger.besu.datatypes.Transaction;
19+
import org.hyperledger.besu.ethereum.chain.Blockchain;
20+
import org.hyperledger.besu.ethereum.core.BlockHeader;
21+
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
22+
import org.hyperledger.besu.ethereum.core.MutableWorldState;
23+
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
24+
import org.hyperledger.besu.ethereum.transaction.BlockSimulationResult;
25+
import org.hyperledger.besu.ethereum.transaction.BlockSimulator;
26+
import org.hyperledger.besu.ethereum.transaction.BlockStateCall;
27+
import org.hyperledger.besu.ethereum.transaction.CallParameter;
28+
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
29+
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
30+
import org.hyperledger.besu.plugin.Unstable;
31+
import org.hyperledger.besu.plugin.data.BlockOverrides;
32+
import org.hyperledger.besu.plugin.data.PluginBlockSimulationResult;
33+
import org.hyperledger.besu.plugin.data.TransactionSimulationResult;
34+
import org.hyperledger.besu.plugin.services.BlockSimulationService;
35+
36+
import java.util.List;
37+
38+
/** This class is a service that simulates the processing of a block */
39+
public class BlockSimulatorServiceImpl implements BlockSimulationService {
40+
private final BlockSimulator blockSimulator;
41+
private final WorldStateArchive worldStateArchive;
42+
private final Blockchain blockchain;
43+
44+
/**
45+
* This constructor creates a BlockSimulatorServiceImpl object
46+
*
47+
* @param worldStateArchive the world state archive
48+
* @param miningConfiguration the mining configuration
49+
* @param transactionSimulator the transaction simulator
50+
* @param protocolSchedule the protocol schedule
51+
* @param blockchain the blockchain
52+
*/
53+
public BlockSimulatorServiceImpl(
54+
final WorldStateArchive worldStateArchive,
55+
final MiningConfiguration miningConfiguration,
56+
final TransactionSimulator transactionSimulator,
57+
final ProtocolSchedule protocolSchedule,
58+
final Blockchain blockchain) {
59+
this.blockchain = blockchain;
60+
blockSimulator =
61+
new BlockSimulator(
62+
worldStateArchive, protocolSchedule, transactionSimulator, miningConfiguration);
63+
this.worldStateArchive = worldStateArchive;
64+
}
65+
66+
/**
67+
* Simulate the processing of a block given a header, a list of transactions, and blockOverrides.
68+
*
69+
* @param blockNumber the block number
70+
* @param transactions the transactions to include in the block
71+
* @param blockOverrides the blockSimulationOverride of the block
72+
* @param accountOverrides state overrides of the block
73+
* @return the block context
74+
*/
75+
@Override
76+
public PluginBlockSimulationResult simulate(
77+
final long blockNumber,
78+
final List<? extends Transaction> transactions,
79+
final BlockOverrides blockOverrides,
80+
final AccountOverrideMap accountOverrides) {
81+
return processSimulation(blockNumber, transactions, blockOverrides, accountOverrides, false);
82+
}
83+
84+
/**
85+
* This method is experimental and should be used with caution. Simulate the processing of a block
86+
* given a header, a list of transactions, and blockOverrides and persist the WorldState
87+
*
88+
* @param blockNumber the block number
89+
* @param transactions the transactions to include in the block
90+
* @param blockOverrides block overrides for the block
91+
* @param accountOverrides state overrides of the block
92+
* @return the PluginBlockSimulationResult
93+
*/
94+
@Unstable
95+
@Override
96+
public PluginBlockSimulationResult simulateAndPersistWorldState(
97+
final long blockNumber,
98+
final List<? extends Transaction> transactions,
99+
final BlockOverrides blockOverrides,
100+
final AccountOverrideMap accountOverrides) {
101+
return processSimulation(blockNumber, transactions, blockOverrides, accountOverrides, true);
102+
}
103+
104+
private PluginBlockSimulationResult processSimulation(
105+
final long blockNumber,
106+
final List<? extends Transaction> transactions,
107+
final BlockOverrides blockOverrides,
108+
final AccountOverrideMap accountOverrides,
109+
final boolean persistWorldState) {
110+
BlockHeader header = getBlockHeader(blockNumber);
111+
List<CallParameter> callParameters =
112+
transactions.stream().map(CallParameter::fromTransaction).toList();
113+
BlockStateCall blockStateCall =
114+
new BlockStateCall(callParameters, blockOverrides, accountOverrides, true);
115+
try (final MutableWorldState ws = getWorldState(header, persistWorldState)) {
116+
List<BlockSimulationResult> results =
117+
blockSimulator.process(header, List.of(blockStateCall), ws);
118+
BlockSimulationResult result = results.getFirst();
119+
if (persistWorldState) {
120+
ws.persist(result.getBlock().getHeader());
121+
}
122+
return response(result);
123+
} catch (final Exception e) {
124+
throw new RuntimeException("Error simulating block", e);
125+
}
126+
}
127+
128+
private BlockHeader getBlockHeader(final long blockNumber) {
129+
return blockchain
130+
.getBlockHeader(blockNumber)
131+
.orElseThrow(
132+
() ->
133+
new IllegalArgumentException(
134+
"Block header not found for block number: " + blockNumber));
135+
}
136+
137+
private MutableWorldState getWorldState(final BlockHeader header, final boolean isPersisting) {
138+
return worldStateArchive
139+
.getMutable(header, isPersisting)
140+
.orElseThrow(
141+
() ->
142+
new IllegalArgumentException(
143+
"World state not available for block number (block hash): "
144+
+ header.toLogString()));
145+
}
146+
147+
private PluginBlockSimulationResult response(final BlockSimulationResult result) {
148+
return new PluginBlockSimulationResult(
149+
result.getBlockHeader(),
150+
result.getBlockBody(),
151+
result.getReceipts(),
152+
result.getTransactionSimulations().stream()
153+
.map(
154+
simulation ->
155+
new TransactionSimulationResult(simulation.transaction(), simulation.result()))
156+
.toList());
157+
}
158+
}

besu/src/main/java/org/hyperledger/besu/services/BlockchainServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public Optional<List<TransactionReceipt>> getReceiptsByBlockHash(final Hash bloc
108108
public void storeBlock(
109109
final BlockHeader blockHeader,
110110
final BlockBody blockBody,
111-
final List<TransactionReceipt> receipts) {
111+
final List<? extends TransactionReceipt> receipts) {
112112
final org.hyperledger.besu.ethereum.core.BlockHeader coreHeader =
113113
(org.hyperledger.besu.ethereum.core.BlockHeader) blockHeader;
114114
final org.hyperledger.besu.ethereum.core.BlockBody coreBody =
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters;
16+
17+
import org.hyperledger.besu.datatypes.Address;
18+
import org.hyperledger.besu.datatypes.Hash;
19+
import org.hyperledger.besu.datatypes.Wei;
20+
import org.hyperledger.besu.datatypes.parameters.UnsignedLongParameter;
21+
import org.hyperledger.besu.plugin.data.BlockOverrides;
22+
23+
import java.math.BigInteger;
24+
import java.util.Optional;
25+
26+
import com.fasterxml.jackson.annotation.JsonCreator;
27+
import com.fasterxml.jackson.annotation.JsonProperty;
28+
import org.apache.tuweni.bytes.Bytes;
29+
import org.apache.tuweni.bytes.Bytes32;
30+
31+
public class BlockOverridesParameter extends BlockOverrides {
32+
/**
33+
* Constructs a new BlockOverrides instance.
34+
*
35+
* @param timestamp the optional timestamp
36+
* @param blockNumber the optional block number
37+
* @param blockHash the optional block hash
38+
* @param prevRandao the optional previous Randao
39+
* @param gasLimit the optional gas limit
40+
* @param feeRecipient the optional fee recipient
41+
* @param baseFeePerGas the optional base fee per gas
42+
* @param blobBaseFee the optional blob base fee
43+
* @param stateRoot the optional state root
44+
* @param difficulty the optional difficulty
45+
* @param extraData the optional extra data
46+
* @param mixHashOrPrevRandao the optional mix hash or previous Randao
47+
*/
48+
@JsonCreator
49+
public BlockOverridesParameter(
50+
@JsonProperty("time") final Optional<UnsignedLongParameter> timestamp,
51+
@JsonProperty("number") final Optional<UnsignedLongParameter> blockNumber,
52+
@JsonProperty("hash") final Optional<Hash> blockHash,
53+
@JsonProperty("prevRandao") final Optional<Bytes32> prevRandao,
54+
@JsonProperty("gasLimit") final Optional<UnsignedLongParameter> gasLimit,
55+
@JsonProperty("feeRecipient") final Optional<Address> feeRecipient,
56+
@JsonProperty("baseFeePerGas") final Optional<Wei> baseFeePerGas,
57+
@JsonProperty("blobBaseFee") final Optional<UnsignedLongParameter> blobBaseFee,
58+
@JsonProperty("stateRoot") final Optional<Hash> stateRoot,
59+
@JsonProperty("difficulty") final Optional<BigInteger> difficulty,
60+
@JsonProperty("extraData") final Optional<Bytes> extraData,
61+
@JsonProperty("mixHashOrPrevRandao") final Optional<Hash> mixHashOrPrevRandao) {
62+
super(
63+
timestamp,
64+
blockNumber,
65+
blockHash,
66+
prevRandao,
67+
gasLimit,
68+
feeRecipient,
69+
baseFeePerGas,
70+
blobBaseFee,
71+
stateRoot,
72+
difficulty,
73+
extraData,
74+
mixHashOrPrevRandao);
75+
}
76+
}

ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResult.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public class BlockResult implements JsonRpcResult {
8989
private final String excessBlobGas;
9090
private final String parentBeaconBlockRoot;
9191
private final String targetBlobsPerBlock;
92+
private final List<CallProcessingResult> callProcessingResults;
9293

9394
public BlockResult(
9495
final BlockHeader header,
@@ -107,6 +108,18 @@ public BlockResult(
107108
final int size,
108109
final boolean includeCoinbase,
109110
final Optional<List<Withdrawal>> withdrawals) {
111+
this(header, transactions, ommers, null, totalDifficulty, size, includeCoinbase, withdrawals);
112+
}
113+
114+
public BlockResult(
115+
final BlockHeader header,
116+
final List<TransactionResult> transactions,
117+
final List<JsonNode> ommers,
118+
final List<CallProcessingResult> callProcessingResults,
119+
final Difficulty totalDifficulty,
120+
final int size,
121+
final boolean includeCoinbase,
122+
final Optional<List<Withdrawal>> withdrawals) {
110123
this.number = Quantity.create(header.getNumber());
111124
this.hash = header.getHash().toString();
112125
this.mixHash = header.getMixHash().toString();
@@ -128,6 +141,7 @@ public BlockResult(
128141
this.timestamp = Quantity.create(header.getTimestamp());
129142
this.ommers = ommers;
130143
this.transactions = transactions;
144+
this.callProcessingResults = callProcessingResults;
131145
this.coinbase = includeCoinbase ? header.getCoinbase().toString() : null;
132146
this.withdrawalsRoot = header.getWithdrawalsRoot().map(Hash::toString).orElse(null);
133147
this.withdrawals =
@@ -282,4 +296,9 @@ public String getParentBeaconBlockRoot() {
282296
public String getTargetBlobsPerBlock() {
283297
return targetBlobsPerBlock;
284298
}
299+
300+
@JsonGetter(value = "calls")
301+
public List<CallProcessingResult> getTransactionProcessingResults() {
302+
return callProcessingResults;
303+
}
285304
}

0 commit comments

Comments
 (0)