mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: eth_simulateV1 support (#10829)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -8512,6 +8512,7 @@ dependencies = [
|
||||
name = "reth-rpc-eth-types"
|
||||
version = "1.0.7"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-primitives",
|
||||
"alloy-sol-types",
|
||||
"derive_more",
|
||||
|
||||
28
Cargo.toml
28
Cargo.toml
@ -583,3 +583,31 @@ test-fuzz = "5"
|
||||
tikv-jemalloc-ctl = "0.6"
|
||||
tikv-jemallocator = "0.6"
|
||||
tracy-client = "0.17.3"
|
||||
|
||||
#[patch.crates-io]
|
||||
#alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
#alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
|
||||
|
||||
5
book/cli/reth/node.md
vendored
5
book/cli/reth/node.md
vendored
@ -347,6 +347,11 @@ RPC:
|
||||
|
||||
[default: 50000000]
|
||||
|
||||
--rpc.max-simulate-blocks <BLOCKS_COUNT>
|
||||
Maximum number of blocks for `eth_simulateV1` call
|
||||
|
||||
[default: 256]
|
||||
|
||||
--rpc.eth-proof-window <RPC_ETH_PROOF_WINDOW>
|
||||
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`)
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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)))
|
||||
if let Some(block_overrides) = overrides.block {
|
||||
apply_block_overrides(*block_overrides, db, &mut block);
|
||||
}
|
||||
apply_block_overrides(*block_overrides, &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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
302
crates/rpc/rpc-eth-types/src/simulate.rs
Normal file
302
crates/rpc/rpc-eth-types/src/simulate.rs
Normal 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 })
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user