feat: eth_simulateV1 support (#10829)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Arsenii Kulikov
2024-09-18 20:38:04 +03:00
committed by GitHub
parent 06b9792792
commit 91eb50c59f
20 changed files with 607 additions and 39 deletions

View File

@ -156,6 +156,14 @@ pub struct RpcServerArgs {
)]
pub rpc_gas_cap: u64,
/// Maximum number of blocks for `eth_simulateV1` call.
#[arg(
long = "rpc.max-simulate-blocks",
value_name = "BLOCKS_COUNT",
default_value_t = constants::DEFAULT_MAX_SIMULATE_BLOCKS
)]
pub rpc_max_simulate_blocks: u64,
/// The maximum proof window for historical proof generation.
/// This value allows for generating historical proofs up to
/// configured number of blocks from current tip (up to `tip - window`).
@ -300,6 +308,7 @@ impl Default for RpcServerArgs {
rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(),
rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(),
rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP,
rpc_max_simulate_blocks: constants::DEFAULT_MAX_SIMULATE_BLOCKS,
rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW,
gas_price_oracle: GasPriceOracleArgs::default(),
rpc_state_cache: RpcStateCacheArgs::default(),

View File

@ -33,6 +33,11 @@ where
self.inner.gas_cap()
}
#[inline]
fn max_simulate_blocks(&self) -> u64 {
self.inner.max_simulate_blocks()
}
#[inline]
fn evm_config(&self) -> &impl ConfigureEvm<Header = Header> {
self.inner.evm_config()

View File

@ -87,6 +87,7 @@ where
ctx.cache.clone(),
ctx.new_gas_price_oracle(),
ctx.config.rpc_gas_cap,
ctx.config.rpc_max_simulate_blocks,
ctx.config.eth_proof_window,
blocking_task_pool,
ctx.new_fee_history_cache(),

View File

@ -95,6 +95,7 @@ impl RethRpcServerConfig for RpcServerArgs {
.max_logs_per_response(self.rpc_max_logs_per_response.unwrap_or_max() as usize)
.eth_proof_window(self.rpc_eth_proof_window)
.rpc_gas_cap(self.rpc_gas_cap)
.rpc_max_simulate_blocks(self.rpc_max_simulate_blocks)
.state_cache(self.state_cache_config())
.gpo_config(self.gas_price_oracle_config())
.proof_permits(self.rpc_proof_permits)

View File

@ -9,7 +9,7 @@ use reth_primitives::{transaction::AccessListResult, BlockId, BlockNumberOrTag};
use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult};
use reth_rpc_types::{
serde_helpers::JsonStorageKey,
simulate::{SimBlock, SimulatedBlock},
simulate::{SimulatePayload, SimulatedBlock},
state::{EvmOverrides, StateOverride},
AnyTransactionReceipt, BlockOverrides, Bundle, EIP1186AccountProofResponse, EthCallResponse,
FeeHistory, Header, Index, StateContext, SyncStatus, TransactionRequest, Work,
@ -211,9 +211,9 @@ pub trait EthApi<T: RpcObject, B: RpcObject, R: RpcObject> {
#[method(name = "simulateV1")]
async fn simulate_v1(
&self,
opts: SimBlock,
opts: SimulatePayload,
block_number: Option<BlockId>,
) -> RpcResult<Vec<SimulatedBlock>>;
) -> RpcResult<Vec<SimulatedBlock<B>>>;
/// Executes a new message call immediately without creating a transaction on the block chain.
#[method(name = "call")]
@ -618,11 +618,11 @@ where
/// Handler for: `eth_simulateV1`
async fn simulate_v1(
&self,
opts: SimBlock,
payload: SimulatePayload,
block_number: Option<BlockId>,
) -> RpcResult<Vec<SimulatedBlock>> {
) -> RpcResult<Vec<SimulatedBlock<RpcBlock<T::NetworkTypes>>>> {
trace!(target: "rpc::eth", ?block_number, "Serving eth_simulateV1");
Ok(EthCall::simulate_v1(self, opts, block_number).await?)
Ok(EthCall::simulate_v1(self, payload, block_number).await?)
}
/// Handler for: `eth_call`

View File

@ -7,6 +7,7 @@ use futures::Future;
use reth_chainspec::MIN_TRANSACTION_GAS;
use reth_evm::{ConfigureEvm, ConfigureEvmEnv};
use reth_primitives::{
basefee::calc_next_block_base_fee,
revm_primitives::{
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason,
ResultAndState, TransactTo, TxEnv,
@ -14,7 +15,7 @@ use reth_primitives::{
transaction::AccessListResult,
Header, TransactionSignedEcRecovered,
};
use reth_provider::{ChainSpecProvider, StateProvider};
use reth_provider::{ChainSpecProvider, HeaderProvider, StateProvider};
use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef};
use reth_rpc_eth_types::{
cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper},
@ -23,16 +24,18 @@ use reth_rpc_eth_types::{
apply_block_overrides, apply_state_overrides, caller_gas_allowance,
cap_tx_gas_limit_with_caller_allowance, get_precompiles, CallFees,
},
simulate::{self, EthSimulateError},
EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb,
};
use reth_rpc_server_types::constants::gas_oracle::{CALL_STIPEND_GAS, ESTIMATE_GAS_ERROR_RATIO};
use reth_rpc_types::{
simulate::{SimBlock, SimulatedBlock},
simulate::{SimBlock, SimulatePayload, SimulatedBlock},
state::{EvmOverrides, StateOverride},
BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo, TransactionRequest,
Block, BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo, TransactionRequest,
WithOtherFields,
};
use revm::{Database, DatabaseCommit};
use revm_inspectors::access_list::AccessListInspector;
use revm::{Database, DatabaseCommit, GetInspector};
use revm_inspectors::{access_list::AccessListInspector, transfer::TransferInspector};
use tracing::trace;
use super::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace};
@ -56,10 +59,158 @@ pub trait EthCall: Call + LoadPendingBlock {
/// See also: <https://github.com/ethereum/go-ethereum/pull/27720>
fn simulate_v1(
&self,
_opts: SimBlock,
_block_number: Option<BlockId>,
) -> impl Future<Output = Result<Vec<SimulatedBlock>, Self::Error>> + Send {
async move { Err(EthApiError::Unsupported("eth_simulateV1 is not supported.").into()) }
payload: SimulatePayload,
block: Option<BlockId>,
) -> impl Future<
Output = Result<
Vec<SimulatedBlock<Block<WithOtherFields<reth_rpc_types::Transaction>>>>,
Self::Error,
>,
> + Send
where
Self: LoadBlock,
{
async move {
if payload.block_state_calls.len() > self.max_simulate_blocks() as usize {
return Err(EthApiError::InvalidParams("too many blocks.".to_string()).into())
}
let SimulatePayload {
block_state_calls,
trace_transfers,
validation,
return_full_transactions,
} = payload;
if block_state_calls.is_empty() {
return Err(EthApiError::InvalidParams(String::from("calls are empty.")).into())
}
// Build cfg and block env, we'll reuse those.
let (mut cfg, mut block_env, block) =
self.evm_env_at(block.unwrap_or_default()).await?;
// Gas cap for entire operation
let total_gas_limit = self.call_gas_limit() as u128;
let base_block = self.block(block).await?.ok_or(EthApiError::HeaderNotFound(block))?;
let mut parent_hash = base_block.header.hash();
let total_difficulty = LoadPendingBlock::provider(self)
.header_td_by_number(block_env.number.to())
.map_err(Self::Error::from_eth_err)?
.ok_or(EthApiError::HeaderNotFound(block))?;
// Only enforce base fee if validation is enabled
cfg.disable_base_fee = !validation;
// Always disable EIP-3607
cfg.disable_eip3607 = true;
let this = self.clone();
self.spawn_with_state_at_block(block, move |state| {
let mut db = CacheDB::new(StateProviderDatabase::new(state));
let mut blocks: Vec<
SimulatedBlock<Block<WithOtherFields<reth_rpc_types::Transaction>>>,
> = Vec::with_capacity(block_state_calls.len());
let mut gas_used = 0;
for block in block_state_calls {
// Increase number and timestamp for every new block
block_env.number += U256::from(1);
block_env.timestamp += U256::from(1);
if validation {
let chain_spec = LoadPendingBlock::provider(&this).chain_spec();
let base_fee_params =
chain_spec.base_fee_params_at_timestamp(block_env.timestamp.to());
let base_fee = if let Some(latest) = blocks.last() {
let header = &latest.inner.header;
calc_next_block_base_fee(
header.gas_used,
header.gas_limit,
header.base_fee_per_gas.unwrap_or_default(),
base_fee_params,
)
} else {
base_block
.header
.next_block_base_fee(base_fee_params)
.unwrap_or_default() as u128
};
block_env.basefee = U256::from(base_fee);
} else {
block_env.basefee = U256::ZERO;
}
let SimBlock { block_overrides, state_overrides, mut calls } = block;
if let Some(block_overrides) = block_overrides {
apply_block_overrides(block_overrides, &mut db, &mut block_env);
}
if let Some(state_overrides) = state_overrides {
apply_state_overrides(state_overrides, &mut db)?;
}
if (total_gas_limit - gas_used) < block_env.gas_limit.to() {
return Err(
EthApiError::Other(Box::new(EthSimulateError::GasLimitReached)).into()
)
}
// Resolve transactions, populate missing fields and enforce calls correctness.
let transactions = simulate::resolve_transactions(
&mut calls,
validation,
block_env.gas_limit.to(),
cfg.chain_id,
&mut db,
)?;
let mut calls = calls.into_iter().peekable();
let mut results = Vec::with_capacity(calls.len());
while let Some(tx) = calls.next() {
let env = this.build_call_evm_env(cfg.clone(), block_env.clone(), tx)?;
let (res, env) = {
if trace_transfers {
this.transact_with_inspector(
&mut db,
env,
TransferInspector::new(false).with_logs(true),
)?
} else {
this.transact(&mut db, env)?
}
};
if calls.peek().is_some() {
// need to apply the state changes of this call before executing the
// next call
db.commit(res.state);
}
results.push((env.tx.caller, res.result));
}
let block = simulate::build_block(
results,
transactions,
&block_env,
parent_hash,
total_difficulty,
return_full_transactions,
&db,
)?;
parent_hash = block.inner.header.hash;
gas_used += block.inner.header.gas_used;
blocks.push(block);
}
Ok(blocks)
})
.await
}
}
/// Executes the call request (`eth_call`) and returns the output
@ -303,6 +454,9 @@ pub trait Call: LoadState + SpawnBlocking {
/// Data access in default trait method implementations.
fn call_gas_limit(&self) -> u64;
/// Returns the maximum number of blocks accepted for `eth_simulateV1`.
fn max_simulate_blocks(&self) -> u64;
/// Returns a handle for reading evm config.
///
/// Data access in default (L1) trait method implementations.
@ -334,6 +488,24 @@ pub trait Call: LoadState + SpawnBlocking {
Ok((res, env))
}
/// Executes the [`EnvWithHandlerCfg`] against the given [Database] without committing state
/// changes.
fn transact_with_inspector<DB>(
&self,
db: DB,
env: EnvWithHandlerCfg,
inspector: impl GetInspector<DB>,
) -> Result<(ResultAndState, EnvWithHandlerCfg), Self::Error>
where
DB: Database,
EthApiError: From<DB::Error>,
{
let mut evm = self.evm_config().evm_with_env_and_inspector(db, env, inspector);
let res = evm.transact().map_err(Self::Error::from_evm_err)?;
let (_, env) = evm.into_db_and_env_with_handler_cfg();
Ok((res, env))
}
/// Executes the call request at the given [`BlockId`].
fn transact_call_at(
&self,
@ -854,7 +1026,8 @@ pub trait Call: LoadState + SpawnBlocking {
blob_versioned_hashes,
max_fee_per_blob_gas,
authorization_list,
..
transaction_type: _,
sidecar: _,
} = request;
let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } =
@ -956,25 +1129,16 @@ pub trait Call: LoadState + SpawnBlocking {
// set nonce to None so that the correct nonce is chosen by the EVM
request.nonce = None;
// apply block overrides, we need to apply them first so that they take effect when we we
// create the evm env via `build_call_evm_env`, e.g. basefee
if let Some(mut block_overrides) = overrides.block {
if let Some(block_hashes) = block_overrides.block_hash.take() {
// override block hashes
db.block_hashes
.extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash)))
}
apply_block_overrides(*block_overrides, &mut block);
if let Some(block_overrides) = overrides.block {
apply_block_overrides(*block_overrides, db, &mut block);
}
if let Some(state_overrides) = overrides.state {
apply_state_overrides(state_overrides, db)?;
}
let request_gas = request.gas;
let mut env = self.build_call_evm_env(cfg, block, request)?;
// apply state overrides
if let Some(state_overrides) = overrides.state {
apply_state_overrides(state_overrides, db)?;
}
if request_gas.is_none() {
// No gas limit was provided in the request, so we need to cap the transaction gas limit
if env.tx.gas_price > U256::ZERO {

View File

@ -30,6 +30,7 @@ reth-trie.workspace = true
# ethereum
alloy-primitives.workspace = true
alloy-consensus.workspace = true
alloy-sol-types.workspace = true
revm.workspace = true
revm-inspectors.workspace = true

View File

@ -7,7 +7,7 @@ use crate::{
};
use reth_rpc_server_types::constants::{
default_max_tracing_requests, DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKS_PER_FILTER,
DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_PROOF_PERMITS,
DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS,
};
use serde::{Deserialize, Serialize};
@ -33,6 +33,8 @@ pub struct EthConfig {
///
/// Defaults to [`RPC_DEFAULT_GAS_CAP`]
pub rpc_gas_cap: u64,
/// Max number of blocks for `eth_simulateV1`.
pub rpc_max_simulate_blocks: u64,
///
/// Sets TTL for stale filters
pub stale_filter_ttl: Duration,
@ -62,6 +64,7 @@ impl Default for EthConfig {
max_blocks_per_filter: DEFAULT_MAX_BLOCKS_PER_FILTER,
max_logs_per_response: DEFAULT_MAX_LOGS_PER_RESPONSE,
rpc_gas_cap: RPC_DEFAULT_GAS_CAP.into(),
rpc_max_simulate_blocks: DEFAULT_MAX_SIMULATE_BLOCKS,
stale_filter_ttl: DEFAULT_STALE_FILTER_TTL,
fee_history_cache: FeeHistoryCacheConfig::default(),
proof_permits: DEFAULT_PROOF_PERMITS,
@ -106,6 +109,12 @@ impl EthConfig {
self
}
/// Configures the maximum gas limit for `eth_call` and call tracing RPC methods
pub const fn rpc_max_simulate_blocks(mut self, max_blocks: u64) -> Self {
self.rpc_max_simulate_blocks = max_blocks;
self
}
/// Configures the maximum proof window for historical proof generation.
pub const fn eth_proof_window(mut self, window: u64) -> Self {
self.eth_proof_window = window;

View File

@ -422,7 +422,7 @@ impl RpcInvalidTransactionError {
impl RpcInvalidTransactionError {
/// Returns the rpc error code for this error.
const fn error_code(&self) -> i32 {
pub const fn error_code(&self) -> i32 {
match self {
Self::InvalidChainId | Self::GasTooLow | Self::GasTooHigh => {
EthRpcErrorCode::InvalidInput.code()
@ -576,7 +576,8 @@ impl RevertError {
}
}
const fn error_code(&self) -> i32 {
/// Returns error code to return for this error.
pub const fn error_code(&self) -> i32 {
EthRpcErrorCode::ExecutionError.code()
}
}

View File

@ -18,6 +18,7 @@ pub mod logs_utils;
pub mod pending_block;
pub mod receipt;
pub mod revm_utils;
pub mod simulate;
pub mod transaction;
pub mod utils;

View File

@ -206,8 +206,12 @@ impl CallFees {
}
}
/// Applies the given block overrides to the env
pub fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) {
/// Applies the given block overrides to the env and updates overridden block hashes in the db.
pub fn apply_block_overrides<DB>(
overrides: BlockOverrides,
db: &mut CacheDB<DB>,
env: &mut BlockEnv,
) {
let BlockOverrides {
number,
difficulty,
@ -216,9 +220,14 @@ pub fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) {
coinbase,
random,
base_fee,
block_hash: _,
block_hash,
} = overrides;
if let Some(block_hashes) = block_hash {
// override block hashes
db.block_hashes.extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash)))
}
if let Some(number) = number {
env.number = number;
}

View File

@ -0,0 +1,302 @@
//! Utilities for serving `eth_simulateV1`
use alloy_consensus::{TxEip4844Variant, TxType, TypedTransaction};
use jsonrpsee_types::ErrorObject;
use reth_primitives::{
logs_bloom,
proofs::{calculate_receipt_root, calculate_transaction_root},
BlockWithSenders, Receipt, Signature, Transaction, TransactionSigned, TransactionSignedNoHash,
};
use reth_revm::database::StateProviderDatabase;
use reth_rpc_server_types::result::rpc_err;
use reth_rpc_types::{
simulate::{SimCallResult, SimulateError, SimulatedBlock},
Block, BlockTransactionsKind, ToRpcError, TransactionRequest, WithOtherFields,
};
use reth_rpc_types_compat::block::from_block;
use reth_storage_api::StateRootProvider;
use reth_trie::{HashedPostState, HashedStorage};
use revm::{db::CacheDB, Database};
use revm_primitives::{keccak256, Address, BlockEnv, Bytes, ExecutionResult, TxKind, B256, U256};
use crate::{
cache::db::StateProviderTraitObjWrapper, EthApiError, RevertError, RpcInvalidTransactionError,
};
/// Errors which may occur during `eth_simulateV1` execution.
#[derive(Debug, thiserror::Error)]
pub enum EthSimulateError {
/// Total gas limit of transactions for the block exceeds the block gas limit.
#[error("Block gas limit exceeded by the block's transactions")]
BlockGasLimitExceeded,
/// Max gas limit for entire operation exceeded.
#[error("Client adjustable limit reached")]
GasLimitReached,
}
impl EthSimulateError {
const fn error_code(&self) -> i32 {
match self {
Self::BlockGasLimitExceeded => -38015,
Self::GasLimitReached => -38026,
}
}
}
impl ToRpcError for EthSimulateError {
fn to_rpc_error(&self) -> ErrorObject<'static> {
rpc_err(self.error_code(), self.to_string(), None)
}
}
/// Goes over the list of [`TransactionRequest`]s and populates missing fields trying to resolve
/// them into [`TransactionSigned`].
///
/// If validation is enabled, the function will return error if any of the transactions can't be
/// built right away.
pub fn resolve_transactions<DB: Database>(
txs: &mut [TransactionRequest],
validation: bool,
block_gas_limit: u128,
chain_id: u64,
db: &mut DB,
) -> Result<Vec<TransactionSigned>, EthApiError>
where
EthApiError: From<DB::Error>,
{
let mut transactions = Vec::with_capacity(txs.len());
let default_gas_limit = {
let total_specified_gas = txs.iter().filter_map(|tx| tx.gas).sum::<u128>();
let txs_without_gas_limit = txs.iter().filter(|tx| tx.gas.is_none()).count();
if total_specified_gas > block_gas_limit {
return Err(EthApiError::Other(Box::new(EthSimulateError::BlockGasLimitExceeded)))
}
if txs_without_gas_limit > 0 {
(block_gas_limit - total_specified_gas) / txs_without_gas_limit as u128
} else {
0
}
};
for tx in txs {
if tx.buildable_type().is_none() && validation {
return Err(EthApiError::TransactionConversionError);
}
// If we're missing any fields and validation is disabled, we try filling nonce, gas and
// gas price.
let tx_type = tx.preferred_type();
let from = if let Some(from) = tx.from {
from
} else {
tx.from = Some(Address::ZERO);
Address::ZERO
};
if tx.nonce.is_none() {
tx.nonce = Some(db.basic(from)?.map(|acc| acc.nonce).unwrap_or_default());
}
if tx.gas.is_none() {
tx.gas = Some(default_gas_limit);
}
if tx.chain_id.is_none() {
tx.chain_id = Some(chain_id);
}
if tx.to.is_none() {
tx.to = Some(TxKind::Create);
}
match tx_type {
TxType::Legacy | TxType::Eip2930 => {
if tx.gas_price.is_none() {
tx.gas_price = Some(0);
}
}
_ => {
if tx.max_fee_per_gas.is_none() {
tx.max_fee_per_gas = Some(0);
tx.max_priority_fee_per_gas = Some(0);
}
}
}
let Ok(tx) = tx.clone().build_typed_tx() else {
return Err(EthApiError::TransactionConversionError)
};
// Create an empty signature for the transaction.
let signature =
Signature { odd_y_parity: false, r: Default::default(), s: Default::default() };
let tx = match tx {
TypedTransaction::Legacy(tx) => {
TransactionSignedNoHash { transaction: Transaction::Legacy(tx), signature }
.with_hash()
}
TypedTransaction::Eip2930(tx) => {
TransactionSignedNoHash { transaction: Transaction::Eip2930(tx), signature }
.with_hash()
}
TypedTransaction::Eip1559(tx) => {
TransactionSignedNoHash { transaction: Transaction::Eip1559(tx), signature }
.with_hash()
}
TypedTransaction::Eip4844(tx) => {
let tx = match tx {
TxEip4844Variant::TxEip4844(tx) => tx,
TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
};
TransactionSignedNoHash { transaction: Transaction::Eip4844(tx), signature }
.with_hash()
}
TypedTransaction::Eip7702(tx) => {
TransactionSignedNoHash { transaction: Transaction::Eip7702(tx), signature }
.with_hash()
}
};
transactions.push(tx);
}
Ok(transactions)
}
/// Handles outputs of the calls execution and builds a [`SimulatedBlock`].
pub fn build_block(
results: Vec<(Address, ExecutionResult)>,
transactions: Vec<TransactionSigned>,
block_env: &BlockEnv,
parent_hash: B256,
total_difficulty: U256,
full_transactions: bool,
db: &CacheDB<StateProviderDatabase<StateProviderTraitObjWrapper<'_>>>,
) -> Result<SimulatedBlock<Block<WithOtherFields<reth_rpc_types::Transaction>>>, EthApiError> {
let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
let mut senders = Vec::with_capacity(results.len());
let mut receipts = Vec::new();
let mut log_index = 0;
for (transaction_index, ((sender, result), tx)) in
results.into_iter().zip(transactions.iter()).enumerate()
{
senders.push(sender);
let call = match result {
ExecutionResult::Halt { reason, gas_used } => {
let error = RpcInvalidTransactionError::halt(reason, tx.gas_limit());
SimCallResult {
return_value: Bytes::new(),
error: Some(SimulateError {
code: error.error_code(),
message: error.to_string(),
}),
gas_used,
logs: Vec::new(),
status: false,
}
}
ExecutionResult::Revert { output, gas_used } => {
let error = RevertError::new(output.clone());
SimCallResult {
return_value: output,
error: Some(SimulateError {
code: error.error_code(),
message: error.to_string(),
}),
gas_used,
status: false,
logs: Vec::new(),
}
}
ExecutionResult::Success { output, gas_used, logs, .. } => SimCallResult {
return_value: output.into_data(),
error: None,
gas_used,
logs: logs
.into_iter()
.map(|log| {
log_index += 1;
reth_rpc_types::Log {
inner: log,
log_index: Some(log_index - 1),
transaction_index: Some(transaction_index as u64),
transaction_hash: Some(tx.hash()),
block_number: Some(block_env.number.to()),
block_timestamp: Some(block_env.timestamp.to()),
..Default::default()
}
})
.collect(),
status: true,
},
};
receipts.push(
#[allow(clippy::needless_update)]
Receipt {
tx_type: tx.tx_type(),
success: call.status,
cumulative_gas_used: call.gas_used + calls.iter().map(|c| c.gas_used).sum::<u64>(),
logs: call.logs.iter().map(|log| &log.inner).cloned().collect(),
..Default::default()
}
.into(),
);
calls.push(call);
}
let mut hashed_state = HashedPostState::default();
for (address, account) in &db.accounts {
let hashed_address = keccak256(address);
hashed_state.accounts.insert(hashed_address, Some(account.info.clone().into()));
let storage = hashed_state
.storages
.entry(hashed_address)
.or_insert_with(|| HashedStorage::new(account.account_state.is_storage_cleared()));
for (slot, value) in &account.storage {
let slot = B256::from(*slot);
let hashed_slot = keccak256(slot);
storage.storage.insert(hashed_slot, *value);
}
}
let state_root = db.db.0.state_root(hashed_state)?;
let header = reth_primitives::Header {
beneficiary: block_env.coinbase,
difficulty: block_env.difficulty,
number: block_env.number.to(),
timestamp: block_env.timestamp.to(),
base_fee_per_gas: Some(block_env.basefee.to()),
gas_limit: block_env.gas_limit.to(),
gas_used: calls.iter().map(|c| c.gas_used).sum::<u64>(),
blob_gas_used: Some(0),
parent_hash,
receipts_root: calculate_receipt_root(&receipts),
transactions_root: calculate_transaction_root(&transactions),
state_root,
logs_bloom: logs_bloom(receipts.iter().flat_map(|r| r.receipt.logs.iter())),
mix_hash: block_env.prevrandao.unwrap_or_default(),
..Default::default()
};
let block = BlockWithSenders {
block: reth_primitives::Block { header, body: transactions, ..Default::default() },
senders,
};
let txs_kind =
if full_transactions { BlockTransactionsKind::Full } else { BlockTransactionsKind::Hashes };
let block = from_block(block, total_difficulty, txs_kind, None)?;
Ok(SimulatedBlock { inner: block, calls })
}

View File

@ -45,6 +45,9 @@ pub const DEFAULT_ENGINE_API_IPC_ENDPOINT: &str = r"\\.\pipe\reth_engine_api.ipc
#[cfg(not(windows))]
pub const DEFAULT_ENGINE_API_IPC_ENDPOINT: &str = "/tmp/reth_engine_api.ipc";
/// The default limit for blocks count in `eth_simulateV1`.
pub const DEFAULT_MAX_SIMULATE_BLOCKS: u64 = 256;
/// The default eth historical proof window.
pub const DEFAULT_ETH_PROOF_WINDOW: u64 = 0;

View File

@ -57,6 +57,7 @@ where
eth_cache: EthStateCache,
gas_oracle: GasPriceOracle<Provider>,
gas_cap: impl Into<GasCap>,
max_simulate_blocks: u64,
eth_proof_window: u64,
blocking_task_pool: BlockingTaskPool,
fee_history_cache: FeeHistoryCache,
@ -70,6 +71,7 @@ where
eth_cache,
gas_oracle,
gas_cap,
max_simulate_blocks,
eth_proof_window,
blocking_task_pool,
fee_history_cache,
@ -107,6 +109,7 @@ where
ctx.cache.clone(),
ctx.new_gas_price_oracle(),
ctx.config.rpc_gas_cap,
ctx.config.rpc_max_simulate_blocks,
ctx.config.eth_proof_window,
blocking_task_pool,
ctx.new_fee_history_cache(),
@ -186,6 +189,8 @@ pub struct EthApiInner<Provider, Pool, Network, EvmConfig> {
gas_oracle: GasPriceOracle<Provider>,
/// Maximum gas limit for `eth_call` and call tracing RPC methods.
gas_cap: u64,
/// Maximum number of blocks for `eth_simulateV1`.
max_simulate_blocks: u64,
/// The maximum number of blocks into the past for generating state proofs.
eth_proof_window: u64,
/// The block number at which the node started
@ -218,6 +223,7 @@ where
eth_cache: EthStateCache,
gas_oracle: GasPriceOracle<Provider>,
gas_cap: impl Into<GasCap>,
max_simulate_blocks: u64,
eth_proof_window: u64,
blocking_task_pool: BlockingTaskPool,
fee_history_cache: FeeHistoryCache,
@ -244,6 +250,7 @@ where
eth_cache,
gas_oracle,
gas_cap: gas_cap.into().into(),
max_simulate_blocks,
eth_proof_window,
starting_block,
task_spawner: Box::new(task_spawner),
@ -305,6 +312,12 @@ impl<Provider, Pool, Network, EvmConfig> EthApiInner<Provider, Pool, Network, Ev
self.gas_cap
}
/// Returns the `max_simulate_blocks`.
#[inline]
pub const fn max_simulate_blocks(&self) -> u64 {
self.max_simulate_blocks
}
/// Returns a handle to the gas oracle.
#[inline]
pub const fn gas_oracle(&self) -> &GasPriceOracle<Provider> {
@ -364,7 +377,9 @@ mod tests {
use reth_rpc_eth_types::{
EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
};
use reth_rpc_server_types::constants::{DEFAULT_ETH_PROOF_WINDOW, DEFAULT_PROOF_PERMITS};
use reth_rpc_server_types::constants::{
DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS,
};
use reth_rpc_types::FeeHistory;
use reth_tasks::pool::BlockingTaskPool;
use reth_testing_utils::{generators, generators::Rng};
@ -397,6 +412,7 @@ mod tests {
cache.clone(),
GasPriceOracle::new(provider, Default::default(), cache),
gas_cap,
DEFAULT_MAX_SIMULATE_BLOCKS,
DEFAULT_ETH_PROOF_WINDOW,
BlockingTaskPool::build().expect("failed to build tracing pool"),
fee_history_cache,

View File

@ -21,6 +21,11 @@ where
self.inner.gas_cap()
}
#[inline]
fn max_simulate_blocks(&self) -> u64 {
self.inner.max_simulate_blocks()
}
#[inline]
fn evm_config(&self) -> &impl ConfigureEvm<Header = Header> {
self.inner.evm_config()

View File

@ -52,7 +52,9 @@ mod tests {
use reth_rpc_eth_types::{
EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
};
use reth_rpc_server_types::constants::{DEFAULT_ETH_PROOF_WINDOW, DEFAULT_PROOF_PERMITS};
use reth_rpc_server_types::constants::{
DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS,
};
use reth_tasks::pool::BlockingTaskPool;
use reth_transaction_pool::test_utils::{testing_pool, TestPool};
use std::collections::HashMap;
@ -70,6 +72,7 @@ mod tests {
cache.clone(),
GasPriceOracle::new(NoopProvider::default(), Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
DEFAULT_MAX_SIMULATE_BLOCKS,
DEFAULT_ETH_PROOF_WINDOW,
BlockingTaskPool::build().expect("failed to build tracing pool"),
FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()),
@ -96,6 +99,7 @@ mod tests {
cache.clone(),
GasPriceOracle::new(mock_provider, Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
DEFAULT_MAX_SIMULATE_BLOCKS,
DEFAULT_ETH_PROOF_WINDOW,
BlockingTaskPool::build().expect("failed to build tracing pool"),
FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()),

View File

@ -62,7 +62,9 @@ mod tests {
use reth_rpc_eth_types::{
EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
};
use reth_rpc_server_types::constants::{DEFAULT_ETH_PROOF_WINDOW, DEFAULT_PROOF_PERMITS};
use reth_rpc_server_types::constants::{
DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS,
};
use reth_tasks::pool::BlockingTaskPool;
use reth_transaction_pool::{test_utils::testing_pool, TransactionPool};
@ -86,6 +88,7 @@ mod tests {
cache.clone(),
GasPriceOracle::new(noop_provider, Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
DEFAULT_MAX_SIMULATE_BLOCKS,
DEFAULT_ETH_PROOF_WINDOW,
BlockingTaskPool::build().expect("failed to build tracing pool"),
fee_history_cache,