This commit is contained in:
Arsenii Kulikov
2025-11-23 23:50:40 +04:00
parent ee63c7d6b4
commit 32fdf39747
7 changed files with 117 additions and 73 deletions

View File

@@ -4,9 +4,11 @@ use crate::ConfigureEvm;
use alloy_evm::{block::BlockExecutorFactory, Database, EvmEnv, EvmFactory};
use revm::{inspector::NoOpInspector, Inspector};
/// Helper to access [`BlockExecutorFactory`] for a given [`ConfigureEvm`].
pub type ExecFactoryFor<Evm> = <Evm as ConfigureEvm>::BlockExecutorFactory;
/// Helper to access [`EvmFactory`] for a given [`ConfigureEvm`].
pub type EvmFactoryFor<Evm> =
<<Evm as ConfigureEvm>::BlockExecutorFactory as BlockExecutorFactory>::EvmFactory;
pub type EvmFactoryFor<Evm> = <ExecFactoryFor<Evm> as BlockExecutorFactory>::EvmFactory;
/// Helper to access [`EvmFactory::Spec`] for a given [`ConfigureEvm`].
pub type SpecFor<Evm> = <EvmFactoryFor<Evm> as EvmFactory>::Spec;
@@ -30,8 +32,7 @@ pub type HaltReasonFor<Evm> = <EvmFactoryFor<Evm> as EvmFactory>::HaltReason;
pub type TxEnvFor<Evm> = <EvmFactoryFor<Evm> as EvmFactory>::Tx;
/// Helper to access [`BlockExecutorFactory::ExecutionCtx`] for a given [`ConfigureEvm`].
pub type ExecutionCtxFor<'a, Evm> =
<<Evm as ConfigureEvm>::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>;
pub type ExecutionCtxFor<'a, Evm> = <ExecFactoryFor<Evm> as BlockExecutorFactory>::ExecutionCtx<'a>;
/// Type alias for [`EvmEnv`] for a given [`ConfigureEvm`].
pub type EvmEnvFor<Evm> = EvmEnv<SpecFor<Evm>, BlockEnvFor<Evm>>;

View File

@@ -333,6 +333,23 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
Ok(self.create_executor(evm, ctx))
}
/// Creates a new [`BlockExecutor`] for executing a given block with the given inspector.
fn executor_for_block_with_inspector<'a, DB, I>(
&'a self,
db: &'a mut State<DB>,
block: &'a SealedBlock<<Self::Primitives as NodePrimitives>::Block>,
inspector: I,
) -> Result<impl BlockExecutorFor<'a, Self::BlockExecutorFactory, DB, I>, Self::Error>
where
DB: Database,
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
{
let evm_env = self.evm_env(block.header())?;
let evm = self.evm_with_env_and_inspector(db, evm_env, inspector);
let ctx = self.context_for_block(block)?;
Ok(self.create_executor(evm, ctx))
}
/// Creates a [`BlockBuilder`]. Should be used when building a new block.
///
/// Block builder wraps an inner [`alloy_evm::block::BlockExecutor`] and has a similar

View File

@@ -20,23 +20,24 @@ use alloy_rpc_types_eth::{
use futures::Future;
use reth_errors::{ProviderError, RethError};
use reth_evm::{
env::BlockEnvironment, ConfigureEvm, Evm, EvmEnvFor, HaltReasonFor, InspectorFor,
TransactionEnv, TxEnvFor,
env::BlockEnvironment, execute::BlockExecutor, ConfigureEvm, Evm, EvmEnvFor, HaltReasonFor,
InspectorFor, TransactionEnv, TxEnvFor,
};
use reth_node_api::BlockBody;
use reth_primitives_traits::Recovered;
use reth_primitives_traits::{BlockTy, RecoveredBlock};
use reth_revm::{database::StateProviderDatabase, db::State};
use reth_rpc_convert::{RpcConvert, RpcTxReq};
use reth_rpc_eth_types::{
cache::db::StateProviderTraitObjWrapper,
cache::db::{RpcBlockExecutor, StateProviderTraitObjWrapper},
error::FromEthApiError,
simulate::{self, EthSimulateError},
EthApiError, StateCacheDb,
};
use reth_storage_api::{BlockIdReader, ProviderTx, StateProvider};
use reth_storage_api::{BlockIdReader, StateProvider};
use revm::{
context::Block,
context_interface::{result::ResultAndState, Transaction},
inspector::NoOpInspector,
Database, DatabaseCommit,
};
use revm_inspectors::{access_list::AccessListInspector, transfer::TransferInspector};
@@ -638,21 +639,15 @@ pub trait Call:
};
let (tx, tx_info) = transaction.split();
let (evm_env, _) = self.evm_env_at(block.hash().into()).await?;
// we need to get the state of the parent block because we're essentially replaying the
// block the transaction is included in
let parent_block = block.parent_hash();
self.spawn_with_state_at_block(parent_block, move |this, mut db| {
let block_txs = block.transactions_recovered();
self.spawn_with_state_at_block(block.parent_hash(), move |this, mut db| {
// replay all transactions prior to the targeted transaction
this.replay_transactions_until(&mut db, evm_env.clone(), block_txs, *tx.tx_hash())?;
let res = this
.executor_at_tx(&mut db, &block, *tx.tx_hash())?
.execute_transaction_without_commit(tx)
.map_err(Self::Error::from_eth_err)?;
let tx_env = RpcNodeCore::evm_config(&this).tx_env(tx);
let res = this.transact(&mut db, evm_env, tx_env)?;
f(tx_info, res, db)
})
.await
@@ -660,37 +655,54 @@ pub trait Call:
}
}
/// Replays all the transactions until the target transaction is found.
///
/// All transactions before the target transaction are executed and their changes are written to
/// the _runtime_ db ([`State`]).
///
/// Note: This assumes the target transaction is in the given iterator.
/// Returns the index of the target transaction in the given iterator.
fn replay_transactions_until<'a, DB, I>(
&self,
db: &mut DB,
evm_env: EvmEnvFor<Self::Evm>,
transactions: I,
/// Same as [`executor_at_tx_with_inspector`](Self::executor_at_tx_with_inspector) but the
/// returned executor does not have the inspector enabled.
fn executor_at_tx<'a>(
&'a self,
db: &'a mut StateCacheDb,
block: &'a RecoveredBlock<BlockTy<Self::Primitives>>,
target_tx_hash: B256,
) -> Result<usize, Self::Error>
) -> Result<impl RpcBlockExecutor<'a, Self::Evm>, Self::Error> {
let mut executor =
self.executor_at_tx_with_inspector(db, block, target_tx_hash, NoOpInspector)?;
// disable inspector before returning to avoid perf overhead
executor.evm_mut().disable_inspector();
Ok(executor)
}
/// Initializes a [`BlockExecutor`] at the given block, applies pre-execution changes and
/// executes all transactions prior the target transaction.
///
/// After that, returns the [`BlockExecutor`] with the configured inspector.
fn executor_at_tx_with_inspector<'a, I>(
&'a self,
db: &'a mut StateCacheDb,
block: &'a RecoveredBlock<BlockTy<Self::Primitives>>,
target_tx_hash: B256,
inspector: I,
) -> Result<impl RpcBlockExecutor<'a, Self::Evm, I>, Self::Error>
where
DB: Database<Error = ProviderError> + DatabaseCommit + core::fmt::Debug,
I: IntoIterator<Item = Recovered<&'a ProviderTx<Self::Provider>>>,
I: InspectorFor<Self::Evm, &'a mut StateCacheDb> + 'a,
{
let mut evm = self.evm_config().evm_with_env(db, evm_env);
let mut index = 0;
for tx in transactions {
let mut executor =
self.evm_config().executor_for_block_with_inspector(db, block, inspector)?;
// Disable inspector while handling pre-execution changes
executor.evm_mut().disable_inspector();
executor.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
for tx in block.transactions_recovered() {
if *tx.tx_hash() == target_tx_hash {
// reached the target transaction
break
}
let tx_env = self.evm_config().tx_env(tx);
evm.transact_commit(tx_env).map_err(Self::Error::from_evm_err)?;
index += 1;
executor.execute_transaction(tx).map_err(Self::Error::from_eth_err)?;
}
Ok(index)
// Enable inspector before returning
executor.evm_mut().enable_inspector();
Ok(executor)
}
///

View File

@@ -9,12 +9,12 @@ use futures::Future;
use reth_chainspec::ChainSpecProvider;
use reth_errors::ProviderError;
use reth_evm::{
evm::EvmFactoryExt, system_calls::SystemCaller, tracing::TracingCtx, ConfigureEvm, Database,
Evm, EvmEnvFor, EvmFor, HaltReasonFor, InspectorFor, TxEnvFor,
evm::EvmFactoryExt, execute::BlockExecutor, system_calls::SystemCaller, tracing::TracingCtx,
ConfigureEvm, Database, Evm, EvmEnvFor, EvmFor, HaltReasonFor, InspectorFor, TxEnvFor,
};
use reth_primitives_traits::{BlockBody, Recovered, RecoveredBlock};
use reth_revm::{database::StateProviderDatabase, db::State};
use reth_rpc_eth_types::{cache::db::StateCacheDb, EthApiError};
use reth_rpc_eth_types::{cache::db::StateCacheDb, error::FromEthApiError, EthApiError};
use reth_storage_api::{ProviderBlock, ProviderTx};
use revm::{context::Block, context_interface::result::ResultAndState, DatabaseCommit};
use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
@@ -168,22 +168,16 @@ pub trait Trace: LoadState<Error: FromEvmError<Self::Evm>> + Call {
};
let (tx, tx_info) = transaction.split();
let (evm_env, _) = self.evm_env_at(block.hash().into()).await?;
// we need to get the state of the parent block because we're essentially replaying the
// block the transaction is included in
let parent_block = block.parent_hash();
self.spawn_with_state_at_block(parent_block, move |this, mut db| {
let block_txs = block.transactions_recovered();
let res = this
.executor_at_tx_with_inspector(&mut db, &block, *tx.tx_hash(), &mut inspector)?
.execute_transaction_without_commit(tx)
.map_err(Self::Error::from_eth_err)?;
this.apply_pre_execution_changes(&block, &mut db, &evm_env)?;
// replay all transactions prior to the targeted transaction
this.replay_transactions_until(&mut db, evm_env.clone(), block_txs, *tx.tx_hash())?;
let tx_env = this.evm_config().tx_env(tx);
let res = this.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
f(tx_info, inspector, res, db)
})
.await

View File

@@ -4,10 +4,14 @@
use alloy_primitives::{Address, B256, U256};
use reth_errors::ProviderResult;
use reth_evm::{block::BlockExecutorFor, ConfigureEvm, ExecFactoryFor, InspectorFor};
use reth_revm::database::StateProviderDatabase;
use reth_storage_api::{BytecodeReader, HashedPostStateProvider, StateProvider, StateProviderBox};
use reth_trie::{HashedStorage, MultiProofTargets};
use revm::database::{BundleState, State};
use revm::{
database::{BundleState, State},
inspector::NoOpInspector,
};
/// Helper alias type for the state's [`State`]
pub type StateCacheDb = State<StateProviderDatabase<StateProviderTraitObjWrapper>>;
@@ -178,3 +182,25 @@ impl BytecodeReader for StateProviderTraitObjWrapper {
self.0.bytecode_by_hash(code_hash)
}
}
/// A trait alias for [`reth_evm::block::BlockExecutor`] implementation used in RPC code.
pub trait RpcBlockExecutor<'a, Evm, I = NoOpInspector>:
BlockExecutorFor<'a, ExecFactoryFor<Evm>, StateProviderDatabase<StateProviderTraitObjWrapper>, I>
where
Evm: ConfigureEvm,
I: InspectorFor<Evm, &'a mut StateCacheDb> + 'a,
{
}
impl<'a, Evm, I, T> RpcBlockExecutor<'a, Evm, I> for T
where
Evm: ConfigureEvm,
I: InspectorFor<Evm, &'a mut StateCacheDb> + 'a,
T: BlockExecutorFor<
'a,
ExecFactoryFor<Evm>,
StateProviderDatabase<StateProviderTraitObjWrapper>,
I,
>,
{
}

View File

@@ -84,7 +84,10 @@ impl AsEthApiError for EthApiError {
/// Helper trait to convert from revm errors.
pub trait FromEvmError<Evm: ConfigureEvm>:
From<EvmErrorFor<Evm, ProviderError>> + FromEvmHalt<HaltReasonFor<Evm>> + FromRevert
From<EvmErrorFor<Evm, ProviderError>>
+ FromEvmHalt<HaltReasonFor<Evm>>
+ From<Evm::Error>
+ FromRevert
{
/// Converts from EVM error to this type.
fn from_evm_err(err: EvmErrorFor<Evm, ProviderError>) -> Self {
@@ -105,7 +108,10 @@ pub trait FromEvmError<Evm: ConfigureEvm>:
impl<T, Evm> FromEvmError<Evm> for T
where
T: From<EvmErrorFor<Evm, ProviderError>> + FromEvmHalt<HaltReasonFor<Evm>> + FromRevert,
T: From<EvmErrorFor<Evm, ProviderError>>
+ FromEvmHalt<HaltReasonFor<Evm>>
+ From<Evm::Error>
+ FromRevert,
Evm: ConfigureEvm,
{
}

View File

@@ -220,22 +220,10 @@ where
let this = self.clone();
self.eth_api()
.spawn_with_state_at_block(state_at, move |eth_api, mut db| {
let block_txs = block.transactions_recovered();
// configure env for the target transaction
let tx = transaction.into_recovered();
eth_api.apply_pre_execution_changes(&block, &mut db, &evm_env)?;
// replay all transactions prior to the targeted transaction
let index = eth_api.replay_transactions_until(
&mut db,
evm_env.clone(),
block_txs,
*tx.tx_hash(),
)?;
let executor = eth_api.executor_at_tx(&mut db, &block, *tx.tx_hash())?;
let tx_env = eth_api.evm_config().tx_env(&tx);
let (tx, info) = transaction.split();
this.trace_transaction(
&opts,
@@ -244,7 +232,7 @@ where
&mut db,
Some(TransactionContext {
block_hash: Some(block_hash),
tx_index: Some(index),
tx_index: info.index.map(|index| index as usize),
tx_hash: Some(*tx.tx_hash()),
}),
&mut None,