mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-09 21:17:54 -05:00
eth_simulateV1 - Add BlockSimulator feature (#7941)
Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
This commit is contained in:
committed by
GitHub
parent
0448af8d24
commit
a03c98bf9e
@@ -152,6 +152,7 @@ import org.hyperledger.besu.nat.NatMethod;
|
||||
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;
|
||||
@@ -178,6 +179,7 @@ import org.hyperledger.besu.plugin.services.transactionpool.TransactionPoolServi
|
||||
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;
|
||||
@@ -1288,6 +1290,15 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class BlockchainServiceImpl implements BlockchainService {
|
||||
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 =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -107,6 +108,18 @@ public class BlockResult implements JsonRpcResult {
|
||||
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();
|
||||
@@ -128,6 +141,7 @@ public class BlockResult implements JsonRpcResult {
|
||||
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 =
|
||||
@@ -282,4 +296,9 @@ public class BlockResult implements JsonRpcResult {
|
||||
public String getTargetBlobsPerBlock() {
|
||||
return targetBlobsPerBlock;
|
||||
}
|
||||
|
||||
@JsonGetter(value = "calls")
|
||||
public List<CallProcessingResult> getTransactionProcessingResults() {
|
||||
return callProcessingResults;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.results;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
|
||||
public class CallProcessingResult {
|
||||
@JsonProperty("status")
|
||||
private final String status;
|
||||
|
||||
@JsonProperty("returnData")
|
||||
private final String returnData;
|
||||
|
||||
@JsonProperty("gasUsed")
|
||||
private final String gasUsed;
|
||||
|
||||
@JsonProperty("error")
|
||||
private final ErrorDetails error;
|
||||
|
||||
@JsonProperty("logs")
|
||||
private final List<LogResult> logs;
|
||||
|
||||
public CallProcessingResult(
|
||||
@JsonProperty("status") final int status,
|
||||
@JsonProperty("returnData") final Bytes returnData,
|
||||
@JsonProperty("gasUsed") final long gasUsed,
|
||||
@JsonProperty("error") final ErrorDetails error,
|
||||
@JsonProperty("logs") final List<LogResult> logs) {
|
||||
this.status = Quantity.create(status);
|
||||
this.returnData = returnData.toString();
|
||||
|
||||
this.gasUsed = Quantity.create(gasUsed);
|
||||
this.error = error;
|
||||
this.logs = logs;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getReturnData() {
|
||||
return returnData;
|
||||
}
|
||||
|
||||
public String getGasUsed() {
|
||||
return gasUsed;
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public ErrorDetails getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public List<LogResult> getLogs() {
|
||||
return logs;
|
||||
}
|
||||
|
||||
public record ErrorDetails(
|
||||
@JsonProperty("code") long code,
|
||||
@JsonProperty("message") String message,
|
||||
@JsonProperty("data") Bytes data) {
|
||||
|
||||
@Override
|
||||
public long code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bytes data() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.hyperledger.besu.config.StubGenesisConfigOptions;
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext;
|
||||
import org.hyperledger.besu.ethereum.api.ApiConfiguration;
|
||||
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
|
||||
@@ -147,6 +148,8 @@ public abstract class AbstractJsonRpcHttpServiceTest {
|
||||
.thenReturn(ValidationResult.invalid(TransactionInvalidReason.NONCE_TOO_LOW));
|
||||
final PrivacyParameters privacyParameters = mock(PrivacyParameters.class);
|
||||
|
||||
when(miningConfiguration.getCoinbase()).thenReturn(Optional.of(Address.ZERO));
|
||||
|
||||
final BlockchainQueries blockchainQueries =
|
||||
new BlockchainQueries(
|
||||
blockchainSetupUtil.getProtocolSchedule(),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.transaction;
|
||||
|
||||
public class BlockSimulationException extends RuntimeException {
|
||||
public BlockSimulationException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.transaction;
|
||||
|
||||
import org.hyperledger.besu.ethereum.core.Block;
|
||||
import org.hyperledger.besu.plugin.data.BlockBody;
|
||||
import org.hyperledger.besu.plugin.data.BlockHeader;
|
||||
import org.hyperledger.besu.plugin.data.TransactionReceipt;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BlockSimulationResult {
|
||||
final Block block;
|
||||
final List<TransactionReceipt> receipts;
|
||||
List<TransactionSimulatorResult> transactionSimulationResults;
|
||||
|
||||
public BlockSimulationResult(
|
||||
final Block block,
|
||||
final List<? extends TransactionReceipt> receipts,
|
||||
final List<TransactionSimulatorResult> transactionSimulationResults) {
|
||||
this.block = block;
|
||||
this.receipts = new ArrayList<>(receipts);
|
||||
this.transactionSimulationResults = transactionSimulationResults;
|
||||
}
|
||||
|
||||
public BlockHeader getBlockHeader() {
|
||||
return block.getHeader();
|
||||
}
|
||||
|
||||
public BlockBody getBlockBody() {
|
||||
return block.getBody();
|
||||
}
|
||||
|
||||
public List<? extends TransactionReceipt> getReceipts() {
|
||||
return receipts;
|
||||
}
|
||||
|
||||
public List<TransactionSimulatorResult> getTransactionSimulations() {
|
||||
return transactionSimulationResults;
|
||||
}
|
||||
|
||||
public Block getBlock() {
|
||||
return block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* 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.transaction;
|
||||
|
||||
import org.hyperledger.besu.datatypes.AccountOverride;
|
||||
import org.hyperledger.besu.datatypes.AccountOverrideMap;
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.ethereum.core.Block;
|
||||
import org.hyperledger.besu.ethereum.core.BlockBody;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeader;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions;
|
||||
import org.hyperledger.besu.ethereum.core.Difficulty;
|
||||
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
|
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState;
|
||||
import org.hyperledger.besu.ethereum.core.ParsedExtraData;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
|
||||
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams;
|
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
|
||||
import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
|
||||
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
|
||||
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
|
||||
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
|
||||
import org.hyperledger.besu.evm.account.MutableAccount;
|
||||
import org.hyperledger.besu.evm.tracing.OperationTracer;
|
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
||||
import org.hyperledger.besu.plugin.data.BlockOverrides;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.apache.tuweni.units.bigints.UInt256;
|
||||
|
||||
/**
|
||||
* Simulates the execution of a block, processing transactions and applying state overrides. This
|
||||
* class is responsible for simulating the execution of a block, which involves processing
|
||||
* transactions and applying state overrides. It provides a way to test and validate the behavior of
|
||||
* a block without actually executing it on the blockchain. The simulator takes into account various
|
||||
* factors, such as the block header, transaction calls, and state overrides, to simulate the
|
||||
* execution of the block. It returns a list of simulation results, which include the final block
|
||||
* header, transaction receipts, and other relevant information.
|
||||
*/
|
||||
public class BlockSimulator {
|
||||
private final TransactionSimulator transactionSimulator;
|
||||
private final WorldStateArchive worldStateArchive;
|
||||
private final ProtocolSchedule protocolSchedule;
|
||||
private final MiningConfiguration miningConfiguration;
|
||||
|
||||
public BlockSimulator(
|
||||
final WorldStateArchive worldStateArchive,
|
||||
final ProtocolSchedule protocolSchedule,
|
||||
final TransactionSimulator transactionSimulator,
|
||||
final MiningConfiguration miningConfiguration) {
|
||||
this.worldStateArchive = worldStateArchive;
|
||||
this.protocolSchedule = protocolSchedule;
|
||||
this.miningConfiguration = miningConfiguration;
|
||||
this.transactionSimulator = transactionSimulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a list of BlockStateCalls sequentially, collecting the results.
|
||||
*
|
||||
* @param header The block header for all simulations.
|
||||
* @param blockStateCalls The list of BlockStateCalls to process.
|
||||
* @return A list of BlockSimulationResult objects from processing each BlockStateCall.
|
||||
*/
|
||||
public List<BlockSimulationResult> process(
|
||||
final BlockHeader header, final List<? extends BlockStateCall> blockStateCalls) {
|
||||
try (final MutableWorldState ws =
|
||||
worldStateArchive
|
||||
.getMutable(header, false)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
"Public world state not available for block " + header.toLogString()))) {
|
||||
return process(header, blockStateCalls, ws);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Error simulating block", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a list of BlockStateCalls sequentially, collecting the results.
|
||||
*
|
||||
* @param header The block header for all simulations.
|
||||
* @param blockStateCalls The list of BlockStateCalls to process.
|
||||
* @param worldState The initial MutableWorldState to start with.
|
||||
* @return A list of BlockSimulationResult objects from processing each BlockStateCall.
|
||||
*/
|
||||
public List<BlockSimulationResult> process(
|
||||
final BlockHeader header,
|
||||
final List<? extends BlockStateCall> blockStateCalls,
|
||||
final MutableWorldState worldState) {
|
||||
List<BlockSimulationResult> simulationResults = new ArrayList<>();
|
||||
for (BlockStateCall blockStateCall : blockStateCalls) {
|
||||
BlockSimulationResult simulationResult =
|
||||
processSingleBlockStateCall(header, blockStateCall, worldState);
|
||||
simulationResults.add(simulationResult);
|
||||
}
|
||||
return simulationResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single BlockStateCall, simulating the block execution.
|
||||
*
|
||||
* @param header The block header for the simulation.
|
||||
* @param blockStateCall The BlockStateCall to process.
|
||||
* @param ws The MutableWorldState to use for the simulation.
|
||||
* @return A BlockSimulationResult from processing the BlockStateCall.
|
||||
*/
|
||||
private BlockSimulationResult processSingleBlockStateCall(
|
||||
final BlockHeader header, final BlockStateCall blockStateCall, final MutableWorldState ws) {
|
||||
BlockOverrides blockOverrides = blockStateCall.getBlockOverrides();
|
||||
long timestamp = blockOverrides.getTimestamp().orElse(header.getTimestamp() + 1);
|
||||
ProtocolSpec newProtocolSpec = protocolSchedule.getForNextBlockHeader(header, timestamp);
|
||||
|
||||
// Apply block header overrides and state overrides
|
||||
BlockHeader blockHeader = applyBlockHeaderOverrides(header, newProtocolSpec, blockOverrides);
|
||||
blockStateCall.getAccountOverrides().ifPresent(overrides -> applyStateOverrides(overrides, ws));
|
||||
|
||||
// Override the mining beneficiary calculator if a fee recipient is specified, otherwise use the
|
||||
// default
|
||||
MiningBeneficiaryCalculator miningBeneficiaryCalculator =
|
||||
getMiningBeneficiaryCalculator(blockOverrides, newProtocolSpec);
|
||||
|
||||
List<TransactionSimulatorResult> transactionSimulatorResults =
|
||||
processTransactions(blockHeader, blockStateCall, ws, miningBeneficiaryCalculator);
|
||||
|
||||
return finalizeBlock(
|
||||
blockHeader, blockStateCall, ws, newProtocolSpec, transactionSimulatorResults);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected List<TransactionSimulatorResult> processTransactions(
|
||||
final BlockHeader blockHeader,
|
||||
final BlockStateCall blockStateCall,
|
||||
final MutableWorldState ws,
|
||||
final MiningBeneficiaryCalculator miningBeneficiaryCalculator) {
|
||||
|
||||
List<TransactionSimulatorResult> transactionSimulations = new ArrayList<>();
|
||||
|
||||
for (CallParameter callParameter : blockStateCall.getCalls()) {
|
||||
final WorldUpdater transactionUpdater = ws.updater();
|
||||
|
||||
final Optional<TransactionSimulatorResult> transactionSimulatorResult =
|
||||
transactionSimulator.processWithWorldUpdater(
|
||||
callParameter,
|
||||
Optional.empty(), // We have already applied state overrides on block level
|
||||
buildTransactionValidationParams(blockStateCall.isValidate()),
|
||||
OperationTracer.NO_TRACING,
|
||||
blockHeader,
|
||||
transactionUpdater,
|
||||
miningBeneficiaryCalculator);
|
||||
|
||||
if (transactionSimulatorResult.isEmpty()) {
|
||||
throw new BlockSimulationException("Transaction simulator result is empty");
|
||||
}
|
||||
|
||||
TransactionSimulatorResult result = transactionSimulatorResult.get();
|
||||
if (result.isInvalid()) {
|
||||
throw new BlockSimulationException(
|
||||
"Transaction simulator result is invalid: " + result.getInvalidReason().orElse(null));
|
||||
}
|
||||
transactionSimulations.add(transactionSimulatorResult.get());
|
||||
transactionUpdater.commit();
|
||||
}
|
||||
return transactionSimulations;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected BlockSimulationResult finalizeBlock(
|
||||
final BlockHeader blockHeader,
|
||||
final BlockStateCall blockStateCall,
|
||||
final MutableWorldState ws,
|
||||
final ProtocolSpec protocolSpec,
|
||||
final List<TransactionSimulatorResult> transactionSimulations) {
|
||||
|
||||
long currentGasUsed = 0;
|
||||
final var transactionReceiptFactory = protocolSpec.getTransactionReceiptFactory();
|
||||
|
||||
final List<TransactionReceipt> receipts = new ArrayList<>();
|
||||
final List<Transaction> transactions = new ArrayList<>();
|
||||
|
||||
for (TransactionSimulatorResult transactionSimulatorResult : transactionSimulations) {
|
||||
|
||||
TransactionProcessingResult transactionProcessingResult = transactionSimulatorResult.result();
|
||||
final Transaction transaction = transactionSimulatorResult.transaction();
|
||||
|
||||
currentGasUsed += transaction.getGasLimit() - transactionProcessingResult.getGasRemaining();
|
||||
|
||||
final TransactionReceipt transactionReceipt =
|
||||
transactionReceiptFactory.create(
|
||||
transaction.getType(), transactionProcessingResult, ws, currentGasUsed);
|
||||
|
||||
receipts.add(transactionReceipt);
|
||||
transactions.add(transaction);
|
||||
}
|
||||
|
||||
BlockHeader finalBlockHeader =
|
||||
createFinalBlockHeader(
|
||||
blockHeader,
|
||||
ws,
|
||||
transactions,
|
||||
blockStateCall.getBlockOverrides(),
|
||||
receipts,
|
||||
currentGasUsed);
|
||||
Block block = new Block(finalBlockHeader, new BlockBody(transactions, List.of()));
|
||||
return new BlockSimulationResult(block, receipts, transactionSimulations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies state overrides to the world state.
|
||||
*
|
||||
* @param accountOverrideMap The AccountOverrideMap containing the state overrides.
|
||||
* @param ws The MutableWorldState to apply the overrides to.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected void applyStateOverrides(
|
||||
final AccountOverrideMap accountOverrideMap, final MutableWorldState ws) {
|
||||
var updater = ws.updater();
|
||||
for (Address accountToOverride : accountOverrideMap.keySet()) {
|
||||
final AccountOverride override = accountOverrideMap.get(accountToOverride);
|
||||
MutableAccount account = updater.getOrCreate(accountToOverride);
|
||||
override.getNonce().ifPresent(account::setNonce);
|
||||
if (override.getBalance().isPresent()) {
|
||||
account.setBalance(override.getBalance().get());
|
||||
}
|
||||
override.getCode().ifPresent(n -> account.setCode(Bytes.fromHexString(n)));
|
||||
override
|
||||
.getStateDiff()
|
||||
.ifPresent(
|
||||
d ->
|
||||
d.forEach(
|
||||
(key, value) ->
|
||||
account.setStorageValue(
|
||||
UInt256.fromHexString(key), UInt256.fromHexString(value))));
|
||||
}
|
||||
updater.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies block header overrides to the block header.
|
||||
*
|
||||
* @param header The original block header.
|
||||
* @param newProtocolSpec The ProtocolSpec for the block.
|
||||
* @param blockOverrides The BlockOverrides to apply.
|
||||
* @return The modified block header.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected BlockHeader applyBlockHeaderOverrides(
|
||||
final BlockHeader header,
|
||||
final ProtocolSpec newProtocolSpec,
|
||||
final BlockOverrides blockOverrides) {
|
||||
long timestamp = blockOverrides.getTimestamp().orElse(header.getTimestamp() + 1);
|
||||
long blockNumber = blockOverrides.getBlockNumber().orElse(header.getNumber() + 1);
|
||||
|
||||
return BlockHeaderBuilder.createDefault()
|
||||
.parentHash(header.getHash())
|
||||
.timestamp(timestamp)
|
||||
.number(blockNumber)
|
||||
.coinbase(
|
||||
blockOverrides
|
||||
.getFeeRecipient()
|
||||
.orElseGet(() -> miningConfiguration.getCoinbase().orElseThrow()))
|
||||
.difficulty(
|
||||
blockOverrides.getDifficulty().isPresent()
|
||||
? Difficulty.of(blockOverrides.getDifficulty().get())
|
||||
: header.getDifficulty())
|
||||
.gasLimit(
|
||||
blockOverrides
|
||||
.getGasLimit()
|
||||
.orElseGet(() -> getNextGasLimit(newProtocolSpec, header, blockNumber)))
|
||||
.baseFee(
|
||||
blockOverrides
|
||||
.getBaseFeePerGas()
|
||||
.orElseGet(() -> getNextBaseFee(newProtocolSpec, header, blockNumber)))
|
||||
.mixHash(blockOverrides.getMixHashOrPrevRandao().orElse(Hash.EMPTY))
|
||||
.extraData(blockOverrides.getExtraData().orElse(Bytes.EMPTY))
|
||||
.blockHeaderFunctions(new SimulatorBlockHeaderFunctions(blockOverrides))
|
||||
.buildBlockHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the final block header after applying state changes and transaction processing.
|
||||
*
|
||||
* @param blockHeader The original block header.
|
||||
* @param ws The MutableWorldState after applying state overrides.
|
||||
* @param transactions The list of transactions in the block.
|
||||
* @param blockOverrides The BlockOverrides to apply.
|
||||
* @param receipts The list of transaction receipts.
|
||||
* @param currentGasUsed The total gas used in the block.
|
||||
* @return The final block header.
|
||||
*/
|
||||
private BlockHeader createFinalBlockHeader(
|
||||
final BlockHeader blockHeader,
|
||||
final MutableWorldState ws,
|
||||
final List<Transaction> transactions,
|
||||
final BlockOverrides blockOverrides,
|
||||
final List<TransactionReceipt> receipts,
|
||||
final long currentGasUsed) {
|
||||
|
||||
return BlockHeaderBuilder.createDefault()
|
||||
.populateFrom(blockHeader)
|
||||
.ommersHash(BodyValidation.ommersHash(List.of()))
|
||||
.stateRoot(blockOverrides.getStateRoot().orElse(ws.rootHash()))
|
||||
.transactionsRoot(BodyValidation.transactionsRoot(transactions))
|
||||
.receiptsRoot(BodyValidation.receiptsRoot(receipts))
|
||||
.logsBloom(BodyValidation.logsBloom(receipts))
|
||||
.gasUsed(currentGasUsed)
|
||||
.withdrawalsRoot(null)
|
||||
.requestsHash(null)
|
||||
.mixHash(blockOverrides.getMixHashOrPrevRandao().orElse(Hash.EMPTY))
|
||||
.extraData(blockOverrides.getExtraData().orElse(Bytes.EMPTY))
|
||||
.blockHeaderFunctions(new SimulatorBlockHeaderFunctions(blockOverrides))
|
||||
.buildBlockHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the TransactionValidationParams for the block simulation.
|
||||
*
|
||||
* @param shouldValidate Whether to validate transactions.
|
||||
* @return The TransactionValidationParams for the block simulation.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
ImmutableTransactionValidationParams buildTransactionValidationParams(
|
||||
final boolean shouldValidate) {
|
||||
|
||||
if (shouldValidate) {
|
||||
return ImmutableTransactionValidationParams.builder()
|
||||
.from(TransactionValidationParams.processingBlock())
|
||||
.build();
|
||||
}
|
||||
|
||||
return ImmutableTransactionValidationParams.builder()
|
||||
.from(TransactionValidationParams.transactionSimulator())
|
||||
.isAllowExceedingBalance(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
private long getNextGasLimit(
|
||||
final ProtocolSpec protocolSpec, final BlockHeader parentHeader, final long blockNumber) {
|
||||
return protocolSpec
|
||||
.getGasLimitCalculator()
|
||||
.nextGasLimit(
|
||||
parentHeader.getGasLimit(),
|
||||
miningConfiguration.getTargetGasLimit().orElse(parentHeader.getGasLimit()),
|
||||
blockNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the mining beneficiary calculator if a fee recipient is specified, otherwise use the
|
||||
* default
|
||||
*/
|
||||
private MiningBeneficiaryCalculator getMiningBeneficiaryCalculator(
|
||||
final BlockOverrides blockOverrides, final ProtocolSpec newProtocolSpec) {
|
||||
if (blockOverrides.getFeeRecipient().isPresent()) {
|
||||
return blockHeader -> blockOverrides.getFeeRecipient().get();
|
||||
} else {
|
||||
return newProtocolSpec.getMiningBeneficiaryCalculator();
|
||||
}
|
||||
}
|
||||
|
||||
private Wei getNextBaseFee(
|
||||
final ProtocolSpec protocolSpec, final BlockHeader parentHeader, final long blockNumber) {
|
||||
return Optional.of(protocolSpec.getFeeMarket())
|
||||
.filter(FeeMarket::implementsBaseFee)
|
||||
.map(BaseFeeMarket.class::cast)
|
||||
.map(
|
||||
feeMarket ->
|
||||
feeMarket.computeBaseFee(
|
||||
blockNumber,
|
||||
parentHeader.getBaseFee().orElse(Wei.ZERO),
|
||||
parentHeader.getGasUsed(),
|
||||
feeMarket.targetGasUsed(parentHeader)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private static class SimulatorBlockHeaderFunctions implements BlockHeaderFunctions {
|
||||
|
||||
private final BlockOverrides blockOverrides;
|
||||
private final MainnetBlockHeaderFunctions blockHeaderFunctions =
|
||||
new MainnetBlockHeaderFunctions();
|
||||
|
||||
private SimulatorBlockHeaderFunctions(final BlockOverrides blockOverrides) {
|
||||
this.blockOverrides = blockOverrides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hash hash(final BlockHeader header) {
|
||||
return blockOverrides.getBlockHash().orElseGet(() -> blockHeaderFunctions.hash(header));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsedExtraData parseExtraData(final BlockHeader header) {
|
||||
return blockHeaderFunctions.parseExtraData(header);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.transaction;
|
||||
|
||||
import org.hyperledger.besu.datatypes.AccountOverrideMap;
|
||||
import org.hyperledger.besu.plugin.data.BlockOverrides;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BlockStateCall {
|
||||
|
||||
private final BlockOverrides blockOverrides;
|
||||
|
||||
private final List<? extends CallParameter> calls;
|
||||
|
||||
private final AccountOverrideMap accountOverrides;
|
||||
|
||||
private final boolean validation;
|
||||
|
||||
public BlockStateCall(
|
||||
final List<? extends CallParameter> calls,
|
||||
final BlockOverrides blockOverrides,
|
||||
final AccountOverrideMap accountOverrides,
|
||||
final boolean validation) {
|
||||
this.calls = calls != null ? calls : new ArrayList<>();
|
||||
this.blockOverrides =
|
||||
blockOverrides != null ? blockOverrides : BlockOverrides.builder().build();
|
||||
this.accountOverrides = accountOverrides;
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public boolean isValidate() {
|
||||
return validation;
|
||||
}
|
||||
|
||||
public BlockOverrides getBlockOverrides() {
|
||||
return blockOverrides;
|
||||
}
|
||||
|
||||
public Optional<AccountOverrideMap> getAccountOverrides() {
|
||||
return Optional.ofNullable(accountOverrides);
|
||||
}
|
||||
|
||||
public List<? extends CallParameter> getCalls() {
|
||||
return calls;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState;
|
||||
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
|
||||
import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
|
||||
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
|
||||
@@ -341,6 +342,28 @@ public class TransactionSimulator {
|
||||
"Public world state not available for block " + header.toLogString()));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Optional<TransactionSimulatorResult> processWithWorldUpdater(
|
||||
final CallParameter callParams,
|
||||
final Optional<AccountOverrideMap> maybeStateOverrides,
|
||||
final TransactionValidationParams transactionValidationParams,
|
||||
final OperationTracer operationTracer,
|
||||
final BlockHeader header,
|
||||
final WorldUpdater updater,
|
||||
final MiningBeneficiaryCalculator miningBeneficiaryCalculator) {
|
||||
|
||||
final Address miningBeneficiary = miningBeneficiaryCalculator.calculateBeneficiary(header);
|
||||
|
||||
return processWithWorldUpdater(
|
||||
callParams,
|
||||
maybeStateOverrides,
|
||||
transactionValidationParams,
|
||||
operationTracer,
|
||||
header,
|
||||
updater,
|
||||
miningBeneficiary);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Optional<TransactionSimulatorResult> processWithWorldUpdater(
|
||||
final CallParameter callParams,
|
||||
|
||||
@@ -18,6 +18,8 @@ import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
|
||||
public record TransactionSimulatorResult(
|
||||
@@ -42,4 +44,8 @@ public record TransactionSimulatorResult(
|
||||
public ValidationResult<TransactionInvalidReason> getValidationResult() {
|
||||
return result.getValidationResult();
|
||||
}
|
||||
|
||||
public Optional<String> getInvalidReason() {
|
||||
return result.getInvalidReason();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.transaction;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.hyperledger.besu.datatypes.AccountOverride;
|
||||
import org.hyperledger.besu.datatypes.AccountOverrideMap;
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.ethereum.GasLimitCalculator;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeader;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
|
||||
import org.hyperledger.besu.ethereum.core.Difficulty;
|
||||
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
|
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams;
|
||||
import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
|
||||
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
|
||||
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
|
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
|
||||
import org.hyperledger.besu.evm.account.MutableAccount;
|
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
|
||||
import org.hyperledger.besu.plugin.data.BlockOverrides;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.apache.tuweni.units.bigints.UInt256;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class BlockSimulatorTest {
|
||||
|
||||
@Mock private WorldStateArchive worldStateArchive;
|
||||
@Mock private ProtocolSchedule protocolSchedule;
|
||||
@Mock private TransactionSimulator transactionSimulator;
|
||||
@Mock private MiningConfiguration miningConfiguration;
|
||||
@Mock private MutableWorldState mutableWorldState;
|
||||
private BlockHeader blockHeader;
|
||||
|
||||
private BlockSimulator blockSimulator;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
blockSimulator =
|
||||
new BlockSimulator(
|
||||
worldStateArchive, protocolSchedule, transactionSimulator, miningConfiguration);
|
||||
blockHeader = BlockHeaderBuilder.createDefault().buildBlockHeader();
|
||||
ProtocolSpec protocolSpec = mock(ProtocolSpec.class);
|
||||
when(miningConfiguration.getCoinbase())
|
||||
.thenReturn(Optional.ofNullable(Address.fromHexString("0x1")));
|
||||
when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(protocolSpec);
|
||||
when(protocolSpec.getMiningBeneficiaryCalculator())
|
||||
.thenReturn(mock(MiningBeneficiaryCalculator.class));
|
||||
GasLimitCalculator gasLimitCalculator = mock(GasLimitCalculator.class);
|
||||
when(protocolSpec.getGasLimitCalculator()).thenReturn(gasLimitCalculator);
|
||||
when(gasLimitCalculator.nextGasLimit(anyLong(), anyLong(), anyLong())).thenReturn(1L);
|
||||
when(protocolSpec.getFeeMarket()).thenReturn(mock(FeeMarket.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldProcessWithValidWorldState() {
|
||||
when(worldStateArchive.getMutable(any(BlockHeader.class), eq(false)))
|
||||
.thenReturn(Optional.of(mutableWorldState));
|
||||
|
||||
List<BlockSimulationResult> results =
|
||||
blockSimulator.process(blockHeader, Collections.emptyList());
|
||||
|
||||
assertNotNull(results);
|
||||
verify(worldStateArchive).getMutable(any(BlockHeader.class), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotProcessWithInvalidWorldState() {
|
||||
when(worldStateArchive.getMutable(any(BlockHeader.class), eq(false)))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
IllegalArgumentException exception =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> blockSimulator.process(blockHeader, Collections.emptyList()));
|
||||
|
||||
assertEquals(
|
||||
String.format("Public world state not available for block %s", blockHeader.toLogString()),
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldStopWhenTransactionSimulationIsInvalid() {
|
||||
|
||||
CallParameter callParameter = mock(CallParameter.class);
|
||||
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null, true);
|
||||
|
||||
TransactionSimulatorResult transactionSimulatorResult = mock(TransactionSimulatorResult.class);
|
||||
when(transactionSimulatorResult.isInvalid()).thenReturn(true);
|
||||
when(transactionSimulatorResult.getInvalidReason())
|
||||
.thenReturn(Optional.of("Invalid Transaction"));
|
||||
|
||||
when(transactionSimulator.processWithWorldUpdater(
|
||||
any(), any(), any(), any(), any(), any(), any(MiningBeneficiaryCalculator.class)))
|
||||
.thenReturn(Optional.of(transactionSimulatorResult));
|
||||
|
||||
BlockSimulationException exception =
|
||||
assertThrows(
|
||||
BlockSimulationException.class,
|
||||
() -> blockSimulator.process(blockHeader, List.of(blockStateCall), mutableWorldState));
|
||||
|
||||
assertEquals(
|
||||
"Transaction simulator result is invalid: Invalid Transaction", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldStopWhenTransactionSimulationIsEmpty() {
|
||||
|
||||
CallParameter callParameter = mock(CallParameter.class);
|
||||
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null, true);
|
||||
|
||||
when(transactionSimulator.processWithWorldUpdater(
|
||||
any(), any(), any(), any(), any(), any(), any(MiningBeneficiaryCalculator.class)))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
BlockSimulationException exception =
|
||||
assertThrows(
|
||||
BlockSimulationException.class,
|
||||
() -> blockSimulator.process(blockHeader, List.of(blockStateCall), mutableWorldState));
|
||||
|
||||
assertEquals("Transaction simulator result is empty", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyStateOverridesCorrectly() {
|
||||
AccountOverrideMap accountOverrideMap = mock(AccountOverrideMap.class);
|
||||
Address address = mock(Address.class);
|
||||
AccountOverride accountOverride = mock(AccountOverride.class);
|
||||
MutableAccount mutableAccount = mock(MutableAccount.class);
|
||||
|
||||
when(accountOverrideMap.keySet()).thenReturn(Set.of(address));
|
||||
when(accountOverrideMap.get(address)).thenReturn(accountOverride);
|
||||
|
||||
WorldUpdater worldUpdater = mock(WorldUpdater.class);
|
||||
when(mutableWorldState.updater()).thenReturn(worldUpdater);
|
||||
|
||||
when(worldUpdater.getOrCreate(address)).thenReturn(mutableAccount);
|
||||
|
||||
when(accountOverride.getNonce()).thenReturn(Optional.of(123L));
|
||||
when(accountOverride.getBalance()).thenReturn(Optional.of(Wei.of(456L)));
|
||||
when(accountOverride.getCode()).thenReturn(Optional.of(""));
|
||||
when(accountOverride.getStateDiff())
|
||||
.thenReturn(Optional.of(new HashMap<>(Map.of("0x0", "0x1"))));
|
||||
|
||||
blockSimulator.applyStateOverrides(accountOverrideMap, mutableWorldState);
|
||||
|
||||
verify(mutableAccount).setNonce(anyLong());
|
||||
verify(mutableAccount).setBalance(any(Wei.class));
|
||||
verify(mutableAccount).setCode(any(Bytes.class));
|
||||
verify(mutableAccount).setStorageValue(any(UInt256.class), any(UInt256.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyBlockHeaderOverridesCorrectly() {
|
||||
ProtocolSpec protocolSpec = mock(ProtocolSpec.class);
|
||||
|
||||
var expectedTimestamp = 1L;
|
||||
var expectedBlockNumber = 2L;
|
||||
var expectedFeeRecipient = Address.fromHexString("0x1");
|
||||
var expectedBaseFeePerGas = Wei.of(7L);
|
||||
var expectedGasLimit = 5L;
|
||||
var expectedDifficulty = BigInteger.ONE;
|
||||
var expectedMixHashOrPrevRandao = Hash.hash(Bytes.fromHexString("0x01"));
|
||||
var expectedExtraData = Bytes.fromHexString("0x02");
|
||||
|
||||
BlockOverrides blockOverrides =
|
||||
BlockOverrides.builder()
|
||||
.timestamp(expectedTimestamp)
|
||||
.blockNumber(expectedBlockNumber)
|
||||
.feeRecipient(expectedFeeRecipient)
|
||||
.baseFeePerGas(expectedBaseFeePerGas)
|
||||
.gasLimit(expectedGasLimit)
|
||||
.difficulty(expectedDifficulty)
|
||||
.mixHashOrPrevRandao(expectedMixHashOrPrevRandao)
|
||||
.extraData(expectedExtraData)
|
||||
.build();
|
||||
|
||||
BlockHeader result =
|
||||
blockSimulator.applyBlockHeaderOverrides(blockHeader, protocolSpec, blockOverrides);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(expectedTimestamp, result.getTimestamp());
|
||||
assertEquals(expectedBlockNumber, result.getNumber());
|
||||
assertEquals(expectedFeeRecipient, result.getCoinbase());
|
||||
assertEquals(Optional.of(expectedBaseFeePerGas), result.getBaseFee());
|
||||
assertEquals(expectedGasLimit, result.getGasLimit());
|
||||
assertThat(result.getDifficulty()).isEqualTo(Difficulty.of(expectedDifficulty));
|
||||
assertEquals(expectedMixHashOrPrevRandao, result.getMixHash());
|
||||
assertEquals(expectedExtraData, result.getExtraData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildTransactionValidationParams() {
|
||||
var configWhenValidate =
|
||||
ImmutableTransactionValidationParams.builder()
|
||||
.from(TransactionValidationParams.processingBlock())
|
||||
.build();
|
||||
|
||||
ImmutableTransactionValidationParams params =
|
||||
blockSimulator.buildTransactionValidationParams(true);
|
||||
assertThat(params).isEqualTo(configWhenValidate);
|
||||
assertThat(params.isAllowExceedingBalance()).isFalse();
|
||||
|
||||
params = blockSimulator.buildTransactionValidationParams(false);
|
||||
assertThat(params.isAllowExceedingBalance()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ Calculated : ${currentHash}
|
||||
tasks.register('checkAPIChanges', FileStateChecker) {
|
||||
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
|
||||
files = sourceSets.main.allJava.files
|
||||
knownHash = 'TPCo4SZ61OrJxRAa2SIcAIOAOjVTdRw+UOeHMuiJP84='
|
||||
knownHash = '+YR9PYN+gPCvXzK2w52ypz9dZ0FOy0G3I1PljZasOkU='
|
||||
}
|
||||
check.dependsOn('checkAPIChanges')
|
||||
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* 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.plugin.data;
|
||||
|
||||
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 java.math.BigInteger;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.apache.tuweni.bytes.Bytes32;
|
||||
|
||||
/** BlockOverrides represents the block overrides for a block. */
|
||||
public class BlockOverrides {
|
||||
private final Optional<Long> timestamp;
|
||||
private final Optional<Long> blockNumber;
|
||||
private final Optional<Hash> blockHash;
|
||||
private final Optional<Bytes32> prevRandao;
|
||||
private final Optional<Long> gasLimit;
|
||||
private final Optional<Address> feeRecipient;
|
||||
private final Optional<Wei> baseFeePerGas;
|
||||
private final Optional<Long> blobBaseFee;
|
||||
private final Optional<Hash> stateRoot;
|
||||
private final Optional<BigInteger> difficulty;
|
||||
private final Optional<Bytes> extraData;
|
||||
private final Optional<Hash> mixHashOrPrevRandao;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public BlockOverrides(
|
||||
final Optional<UnsignedLongParameter> timestamp,
|
||||
final Optional<UnsignedLongParameter> blockNumber,
|
||||
final Optional<Hash> blockHash,
|
||||
final Optional<Bytes32> prevRandao,
|
||||
final Optional<UnsignedLongParameter> gasLimit,
|
||||
final Optional<Address> feeRecipient,
|
||||
final Optional<Wei> baseFeePerGas,
|
||||
final Optional<UnsignedLongParameter> blobBaseFee,
|
||||
final Optional<Hash> stateRoot,
|
||||
final Optional<BigInteger> difficulty,
|
||||
final Optional<Bytes> extraData,
|
||||
final Optional<Hash> mixHashOrPrevRandao) {
|
||||
this.timestamp = timestamp.map(UnsignedLongParameter::getValue);
|
||||
this.blockNumber = blockNumber.map(UnsignedLongParameter::getValue);
|
||||
this.blockHash = blockHash;
|
||||
this.prevRandao = prevRandao;
|
||||
this.gasLimit = gasLimit.map(UnsignedLongParameter::getValue);
|
||||
this.feeRecipient = feeRecipient;
|
||||
this.baseFeePerGas = baseFeePerGas;
|
||||
this.blobBaseFee = blobBaseFee.map(UnsignedLongParameter::getValue);
|
||||
this.stateRoot = stateRoot;
|
||||
this.difficulty = difficulty;
|
||||
this.extraData = extraData;
|
||||
this.mixHashOrPrevRandao = mixHashOrPrevRandao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new BlockOverrides instance from a Builder.
|
||||
*
|
||||
* @param builder the builder to construct from
|
||||
*/
|
||||
private BlockOverrides(final Builder builder) {
|
||||
this.blockNumber = Optional.ofNullable(builder.blockNumber);
|
||||
this.blockHash = Optional.ofNullable(builder.blockHash);
|
||||
this.prevRandao = Optional.ofNullable(builder.prevRandao);
|
||||
this.timestamp = Optional.ofNullable(builder.timestamp);
|
||||
this.gasLimit = Optional.ofNullable(builder.gasLimit);
|
||||
this.feeRecipient = Optional.ofNullable(builder.feeRecipient);
|
||||
this.baseFeePerGas = Optional.ofNullable(builder.baseFeePerGas);
|
||||
this.blobBaseFee = Optional.ofNullable(builder.blobBaseFee);
|
||||
this.stateRoot = Optional.ofNullable(builder.stateRoot);
|
||||
this.difficulty = Optional.ofNullable(builder.difficulty);
|
||||
this.extraData = Optional.ofNullable(builder.extraData);
|
||||
this.mixHashOrPrevRandao = Optional.ofNullable(builder.mixHashOrPrevRandao);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block number.
|
||||
*
|
||||
* @return the optional block number
|
||||
*/
|
||||
public Optional<Long> getBlockNumber() {
|
||||
return blockNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block hash.
|
||||
*
|
||||
* @return the optional block hash
|
||||
*/
|
||||
public Optional<Hash> getBlockHash() {
|
||||
return blockHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous Randao.
|
||||
*
|
||||
* @return the optional previous Randao
|
||||
*/
|
||||
public Optional<Bytes32> getPrevRandao() {
|
||||
return prevRandao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp.
|
||||
*
|
||||
* @return the optional timestamp
|
||||
*/
|
||||
public Optional<Long> getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the gas limit.
|
||||
*
|
||||
* @return the optional gas limit
|
||||
*/
|
||||
public Optional<Long> getGasLimit() {
|
||||
return gasLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fee recipient.
|
||||
*
|
||||
* @return the optional fee recipient
|
||||
*/
|
||||
public Optional<Address> getFeeRecipient() {
|
||||
return feeRecipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base fee per gas.
|
||||
*
|
||||
* @return the optional base fee per gas
|
||||
*/
|
||||
public Optional<Wei> getBaseFeePerGas() {
|
||||
return baseFeePerGas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blob base fee.
|
||||
*
|
||||
* @return the optional blob base fee
|
||||
*/
|
||||
public Optional<Long> getBlobBaseFee() {
|
||||
return blobBaseFee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state root.
|
||||
*
|
||||
* @return the optional state root
|
||||
*/
|
||||
public Optional<Hash> getStateRoot() {
|
||||
return stateRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the difficulty.
|
||||
*
|
||||
* @return the optional difficulty
|
||||
*/
|
||||
public Optional<BigInteger> getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extra data.
|
||||
*
|
||||
* @return the optional extra data
|
||||
*/
|
||||
public Optional<Bytes> getExtraData() {
|
||||
return extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mix hash or previous Randao.
|
||||
*
|
||||
* @return the optional mix hash or previous Randao
|
||||
*/
|
||||
public Optional<Hash> getMixHashOrPrevRandao() {
|
||||
return mixHashOrPrevRandao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance.
|
||||
*
|
||||
* @return a new Builder
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/** Builder for BlockOverrides. */
|
||||
public static class Builder {
|
||||
private Long timestamp;
|
||||
private Long blockNumber;
|
||||
private Hash blockHash;
|
||||
private Bytes32 prevRandao;
|
||||
private Long gasLimit;
|
||||
private Address feeRecipient;
|
||||
private Wei baseFeePerGas;
|
||||
private Long blobBaseFee;
|
||||
private Hash stateRoot;
|
||||
private BigInteger difficulty;
|
||||
private Bytes extraData;
|
||||
private Hash mixHashOrPrevRandao;
|
||||
|
||||
/** Constructs a new Builder instance. */
|
||||
public Builder() {}
|
||||
|
||||
/**
|
||||
* Sets the timestamp.
|
||||
*
|
||||
* @param timestamp the timestamp to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder timestamp(final Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block number.
|
||||
*
|
||||
* @param blockNumber the block number to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder blockNumber(final Long blockNumber) {
|
||||
this.blockNumber = blockNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block hash.
|
||||
*
|
||||
* @param blockHash the block hash to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder blockHash(final Hash blockHash) {
|
||||
this.blockHash = blockHash;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the previous Randao.
|
||||
*
|
||||
* @param prevRandao the previous Randao to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder prevRandao(final Bytes32 prevRandao) {
|
||||
this.prevRandao = prevRandao;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gas limit.
|
||||
*
|
||||
* @param gasLimit the gas limit to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder gasLimit(final Long gasLimit) {
|
||||
this.gasLimit = gasLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fee recipient.
|
||||
*
|
||||
* @param feeRecipient the fee recipient to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder feeRecipient(final Address feeRecipient) {
|
||||
this.feeRecipient = feeRecipient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base fee per gas.
|
||||
*
|
||||
* @param baseFeePerGas the base fee per gas to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder baseFeePerGas(final Wei baseFeePerGas) {
|
||||
this.baseFeePerGas = baseFeePerGas;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blob base fee.
|
||||
*
|
||||
* @param blobBaseFee the blob base fee to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder blobBaseFee(final Long blobBaseFee) {
|
||||
this.blobBaseFee = blobBaseFee;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state root.
|
||||
*
|
||||
* @param stateRoot the state root to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder stateRoot(final Hash stateRoot) {
|
||||
this.stateRoot = stateRoot;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the difficulty.
|
||||
*
|
||||
* @param difficulty the difficulty to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder difficulty(final BigInteger difficulty) {
|
||||
this.difficulty = difficulty;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the extra data.
|
||||
*
|
||||
* @param extraData the extra data to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder extraData(final Bytes extraData) {
|
||||
this.extraData = extraData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mix hash or previous Randao.
|
||||
*
|
||||
* @param mixHashOrPrevRandao the mix hash or previous Randao to set
|
||||
* @return the builder instance
|
||||
*/
|
||||
public Builder mixHashOrPrevRandao(final Hash mixHashOrPrevRandao) {
|
||||
this.mixHashOrPrevRandao = mixHashOrPrevRandao;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new BlockOverrides instance.
|
||||
*
|
||||
* @return the new BlockOverrides instance
|
||||
*/
|
||||
public BlockOverrides build() {
|
||||
return new BlockOverrides(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.plugin.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** This class represents the result of simulating the processing of a block. */
|
||||
public class PluginBlockSimulationResult {
|
||||
final BlockHeader blockHeader;
|
||||
final BlockBody blockBody;
|
||||
final List<? extends TransactionReceipt> receipts;
|
||||
final List<TransactionSimulationResult> transactionSimulationResults;
|
||||
|
||||
/**
|
||||
* Constructs a new BlockSimulationResult instance.
|
||||
*
|
||||
* @param blockHeader the block header
|
||||
* @param blockBody the block body
|
||||
* @param receipts the list of transaction receipts
|
||||
* @param transactionSimulationResults the list of transaction simulation results
|
||||
*/
|
||||
public PluginBlockSimulationResult(
|
||||
final BlockHeader blockHeader,
|
||||
final BlockBody blockBody,
|
||||
final List<? extends TransactionReceipt> receipts,
|
||||
final List<TransactionSimulationResult> transactionSimulationResults) {
|
||||
this.blockHeader = blockHeader;
|
||||
this.blockBody = blockBody;
|
||||
this.receipts = receipts;
|
||||
this.transactionSimulationResults = transactionSimulationResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block header.
|
||||
*
|
||||
* @return the block header
|
||||
*/
|
||||
public BlockHeader getBlockHeader() {
|
||||
return blockHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block body.
|
||||
*
|
||||
* @return the block body
|
||||
*/
|
||||
public BlockBody getBlockBody() {
|
||||
return blockBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of transaction receipts.
|
||||
*
|
||||
* @return the list of transaction receipts
|
||||
*/
|
||||
public List<? extends TransactionReceipt> getReceipts() {
|
||||
return receipts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of transaction simulation results.
|
||||
*
|
||||
* @return the list of transaction simulation results
|
||||
*/
|
||||
public List<TransactionSimulationResult> getTransactionSimulationResults() {
|
||||
return transactionSimulationResults;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.plugin.services;
|
||||
|
||||
import org.hyperledger.besu.datatypes.AccountOverrideMap;
|
||||
import org.hyperledger.besu.datatypes.Transaction;
|
||||
import org.hyperledger.besu.plugin.Unstable;
|
||||
import org.hyperledger.besu.plugin.data.BlockOverrides;
|
||||
import org.hyperledger.besu.plugin.data.PluginBlockSimulationResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** This class is a service that simulates the processing of a block */
|
||||
public interface BlockSimulationService extends BesuService {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
PluginBlockSimulationResult simulate(
|
||||
long blockNumber,
|
||||
List<? extends Transaction> transactions,
|
||||
BlockOverrides blockOverrides,
|
||||
AccountOverrideMap accountOverrides);
|
||||
|
||||
/**
|
||||
* 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
|
||||
PluginBlockSimulationResult simulateAndPersistWorldState(
|
||||
long blockNumber,
|
||||
List<? extends Transaction> transactions,
|
||||
BlockOverrides blockOverrides,
|
||||
AccountOverrideMap accountOverrides);
|
||||
}
|
||||
@@ -59,7 +59,8 @@ public interface BlockchainService extends BesuService {
|
||||
* @param blockBody the block body
|
||||
* @param receipts the transaction receipts
|
||||
*/
|
||||
void storeBlock(BlockHeader blockHeader, BlockBody blockBody, List<TransactionReceipt> receipts);
|
||||
void storeBlock(
|
||||
BlockHeader blockHeader, BlockBody blockBody, List<? extends TransactionReceipt> receipts);
|
||||
|
||||
/**
|
||||
* Get the block header of the chain head
|
||||
|
||||
Reference in New Issue
Block a user