From 6654ded06de731a3f72bbfee87957b3e583457c8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jan 2026 16:58:41 +0100 Subject: [PATCH 1/4] feat(tracing): add BlockTracer for block-level transaction tracing Adds BlockTracer, BlockTracerIter, BlockTracingError, and BlockExecutorFactoryExt to support tracing transactions within block execution context. Key features: - BlockTracer wraps BlockExecutor and provides trace/trace_many methods - BlockTracerIter automatically calls apply_pre_execution_changes with inspector disabled - BlockTracingError separates PreExecution, Evm, and Hook errors - BlockExecutorFactoryExt provides create_block_tracer convenience method Amp-Thread-ID: https://ampcode.com/threads/T-019c0014-f406-730d-9c85-f3fb1e5faf7c Co-authored-by: Amp --- crates/evm/src/lib.rs | 1 + crates/evm/src/tracing.rs | 290 +++++++++++++++++++++++++++++++++++++- 2 files changed, 289 insertions(+), 2 deletions(-) diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 573ac2ba..1659656d 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -32,6 +32,7 @@ pub mod precompiles; #[cfg(feature = "rpc")] pub mod rpc; pub mod tracing; +pub use tracing::{BlockExecutorFactoryExt, BlockTracer, BlockTracerIter, BlockTracingError}; mod either; diff --git a/crates/evm/src/tracing.rs b/crates/evm/src/tracing.rs index 98c5a73c..6c69c397 100644 --- a/crates/evm/src/tracing.rs +++ b/crates/evm/src/tracing.rs @@ -1,13 +1,57 @@ //! Helpers for tracing. -use crate::{Evm, IntoTxEnv}; +use crate::{ + block::{BlockExecutionError, BlockExecutor, BlockExecutorFactory, BlockExecutorFor}, + Database, Evm, EvmFactory, IntoTxEnv, +}; use core::{fmt::Debug, iter::Peekable}; use revm::{ context::result::{ExecutionResult, ResultAndState}, + database::State, state::EvmState, - DatabaseCommit, + DatabaseCommit, Inspector, }; +/// Error that can occur during block tracing iteration. +#[derive(Debug, thiserror::Error)] +pub enum BlockTracingError { + /// Error during pre-execution changes (system calls, etc.) + #[error("pre-execution error: {0}")] + PreExecution(BlockExecutionError), + /// EVM error during transaction execution. + #[error("evm error: {0}")] + Evm(EvmErr), + /// Error from the tracing hook. + #[error(transparent)] + Hook(HookErr), +} + +impl BlockTracingError { + /// Returns the pre-execution error if this is a [`BlockTracingError::PreExecution`] variant. + pub const fn as_pre_execution(&self) -> Option<&BlockExecutionError> { + match self { + Self::PreExecution(err) => Some(err), + _ => None, + } + } + + /// Returns the EVM error if this is a [`BlockTracingError::Evm`] variant. + pub const fn as_evm(&self) -> Option<&EvmErr> { + match self { + Self::Evm(err) => Some(err), + _ => None, + } + } + + /// Returns the hook error if this is a [`BlockTracingError::Hook`] variant. + pub const fn as_hook(&self) -> Option<&HookErr> { + match self { + Self::Hook(err) => Some(err), + _ => None, + } + } +} + /// A helper type for tracing transactions. #[derive(Debug, Clone)] pub struct TxTracer { @@ -181,3 +225,245 @@ where Some(output) } } + +/// A helper type for tracing transactions in the context of block execution. +/// +/// This type wraps a [`BlockExecutor`] and provides tracing capabilities similar to [`TxTracer`], +/// but operates within a block execution context. It allows: +/// - Calling [`BlockExecutor::apply_pre_execution_changes`] before tracing transactions +/// - Accessing the block executor's state during tracing +/// - Finishing block execution after tracing with [`BlockExecutor::finish`] +#[derive(derive_more::Debug)] +#[debug(bound(E: Debug, ::Inspector: Debug))] +pub struct BlockTracer { + executor: E, + fused_inspector: ::Inspector, +} + +impl BlockTracer +where + E: BlockExecutor, + E::Evm: Evm, +{ + /// Creates a new [`BlockTracer`] instance. + pub fn new(mut executor: E) -> Self { + Self { fused_inspector: executor.evm_mut().inspector_mut().clone(), executor } + } + + fn fuse_inspector(&mut self) -> ::Inspector { + core::mem::replace(self.executor.evm_mut().inspector_mut(), self.fused_inspector.clone()) + } + + /// Returns a reference to the underlying [`BlockExecutor`]. + pub fn executor(&self) -> &E { + &self.executor + } + + /// Returns a mutable reference to the underlying [`BlockExecutor`]. + pub fn executor_mut(&mut self) -> &mut E { + &mut self.executor + } + + /// Applies any necessary changes before executing the block's transactions. + /// + /// This delegates to [`BlockExecutor::apply_pre_execution_changes`]. + pub fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + self.executor.apply_pre_execution_changes() + } + + /// Finishes block execution and returns the underlying EVM along with execution result. + /// + /// This delegates to [`BlockExecutor::finish`]. + pub fn finish( + self, + ) -> Result<(E::Evm, crate::block::BlockExecutionResult), BlockExecutionError> { + self.executor.finish() + } +} + +impl BlockTracer +where + E: BlockExecutor, + E::Evm: Evm, +{ + /// Executes a transaction, and returns its outcome along with the inspector state. + pub fn trace( + &mut self, + tx: impl IntoTxEnv<::Tx>, + ) -> Result< + TraceOutput<::HaltReason, ::Inspector>, + ::Error, + > { + let result = self.executor.evm_mut().transact_commit(tx); + let inspector = self.fuse_inspector(); + Ok(TraceOutput { result: result?, inspector }) + } + + /// Executes multiple transactions, applies the closure to each transaction result, and returns + /// the outcomes. + #[expect(clippy::type_complexity)] + pub fn trace_many( + &mut self, + txs: Txs, + mut f: F, + ) -> BlockTracerIter< + '_, + E, + Txs::IntoIter, + impl FnMut(TracingCtx<'_, T, E::Evm>) -> Result::Error>, + > + where + T: IntoTxEnv<::Tx> + Clone, + Txs: IntoIterator, + F: FnMut(TracingCtx<'_, Txs::Item, E::Evm>) -> O, + { + self.try_trace_many(txs, move |ctx| Ok(f(ctx))) + } + + /// Same as [`BlockTracer::trace_many`], but operates on closures returning [`Result`]s. + pub fn try_trace_many( + &mut self, + txs: Txs, + hook: F, + ) -> BlockTracerIter<'_, E, Txs::IntoIter, F> + where + T: IntoTxEnv<::Tx> + Clone, + Txs: IntoIterator, + F: FnMut(TracingCtx<'_, T, E::Evm>) -> Result, + { + BlockTracerIter { + inner: self, + txs: txs.into_iter().peekable(), + hook, + skip_last_commit: true, + fuse: true, + apply_pre_execution: true, + done: false, + } + } +} + +/// Iterator used by block tracer. +#[derive(derive_more::Debug)] +#[debug(bound(::Inspector: Debug))] +pub struct BlockTracerIter<'a, E: BlockExecutor, Txs: Iterator, F> { + inner: &'a mut BlockTracer, + txs: Peekable, + hook: F, + skip_last_commit: bool, + fuse: bool, + apply_pre_execution: bool, + done: bool, +} + +impl BlockTracerIter<'_, E, Txs, F> { + /// Flips the `skip_last_commit` flag thus making sure all transaction are committed. + pub const fn commit_last_tx(mut self) -> Self { + self.skip_last_commit = false; + self + } + + /// Disables inspector fusing on every transaction and expects user to fuse it manually. + pub const fn no_fuse(mut self) -> Self { + self.fuse = false; + self + } + + /// Disables automatic `apply_pre_execution_changes` call on first iteration. + /// + /// By default, the iterator will call [`BlockExecutor::apply_pre_execution_changes`] before + /// processing the first transaction, with the inspector disabled during this call. + /// Use this method if you want to handle pre-execution changes manually. + pub const fn skip_pre_execution(mut self) -> Self { + self.apply_pre_execution = false; + self + } +} + +impl Iterator for BlockTracerIter<'_, E, Txs, F> +where + E: BlockExecutor, + E::Evm: Evm, + T: IntoTxEnv<::Tx> + Clone, + Txs: Iterator, + F: FnMut(TracingCtx<'_, T, E::Evm>) -> Result, +{ + type Item = Result::Error, HookErr>>; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + + if self.apply_pre_execution { + self.apply_pre_execution = false; + self.inner.executor.evm_mut().disable_inspector(); + let result = self.inner.executor.apply_pre_execution_changes(); + self.inner.executor.evm_mut().enable_inspector(); + if let Err(err) = result { + self.done = true; + return Some(Err(BlockTracingError::PreExecution(err))); + } + } + + let tx = self.txs.next()?; + let result = self.inner.executor.evm_mut().transact(tx.clone()); + + let BlockTracer { executor, fused_inspector } = self.inner; + let (db, inspector, _) = executor.evm_mut().components_mut(); + + let ResultAndState { result, state } = match result { + Ok(res) => res, + Err(err) => { + self.done = true; + return Some(Err(BlockTracingError::Evm(err))); + } + }; + + let mut was_fused = false; + let output = (self.hook)(TracingCtx { + tx, + result, + state: &state, + inspector, + db, + fused_inspector: &*fused_inspector, + was_fused: &mut was_fused, + }); + + if !self.skip_last_commit || self.txs.peek().is_some() { + db.commit(state); + } + + if self.fuse && !was_fused { + self.inner.fuse_inspector(); + } + + Some(output.map_err(BlockTracingError::Hook)) + } +} + +/// An extension trait for [`BlockExecutorFactory`] providing tracing helpers. +pub trait BlockExecutorFactoryExt: BlockExecutorFactory { + /// Creates a new [`BlockTracer`] instance with the given state, input, execution context and + /// inspector. + fn create_block_tracer<'a, DB, I>( + &'a self, + state: &'a mut State, + input: crate::EvmEnv< + ::Spec, + ::BlockEnv, + >, + ctx: Self::ExecutionCtx<'a>, + fused_inspector: I, + ) -> BlockTracer + 'a> + where + DB: Database + 'a, + I: Inspector<::Context<&'a mut State>> + Clone + 'a, + { + let evm = self.evm_factory().create_evm_with_inspector(state, input, fused_inspector); + BlockTracer::new(self.create_executor(evm, ctx)) + } +} + +impl BlockExecutorFactoryExt for T {} From 48ae716c9d3ee97bd24cb7b53a6b3509bafdd146 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jan 2026 17:02:22 +0100 Subject: [PATCH 2/4] chore: fix clippy warnings Amp-Thread-ID: https://ampcode.com/threads/T-019c0014-f406-730d-9c85-f3fb1e5faf7c Co-authored-by: Amp --- crates/evm/src/tracing.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/evm/src/tracing.rs b/crates/evm/src/tracing.rs index 6c69c397..3ec27073 100644 --- a/crates/evm/src/tracing.rs +++ b/crates/evm/src/tracing.rs @@ -255,12 +255,12 @@ where } /// Returns a reference to the underlying [`BlockExecutor`]. - pub fn executor(&self) -> &E { + pub const fn executor(&self) -> &E { &self.executor } /// Returns a mutable reference to the underlying [`BlockExecutor`]. - pub fn executor_mut(&mut self) -> &mut E { + pub const fn executor_mut(&mut self) -> &mut E { &mut self.executor } @@ -287,6 +287,7 @@ where E::Evm: Evm, { /// Executes a transaction, and returns its outcome along with the inspector state. + #[expect(clippy::type_complexity)] pub fn trace( &mut self, tx: impl IntoTxEnv<::Tx>, From dea8c53f204f0f0d2ce32a80f0059c7e21dd3594 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jan 2026 17:04:04 +0100 Subject: [PATCH 3/4] refactor: move BlockTracingError near BlockTracerIter Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c0014-f406-730d-9c85-f3fb1e5faf7c --- crates/evm/src/tracing.rs | 80 +++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/evm/src/tracing.rs b/crates/evm/src/tracing.rs index 3ec27073..727f296d 100644 --- a/crates/evm/src/tracing.rs +++ b/crates/evm/src/tracing.rs @@ -12,46 +12,6 @@ use revm::{ DatabaseCommit, Inspector, }; -/// Error that can occur during block tracing iteration. -#[derive(Debug, thiserror::Error)] -pub enum BlockTracingError { - /// Error during pre-execution changes (system calls, etc.) - #[error("pre-execution error: {0}")] - PreExecution(BlockExecutionError), - /// EVM error during transaction execution. - #[error("evm error: {0}")] - Evm(EvmErr), - /// Error from the tracing hook. - #[error(transparent)] - Hook(HookErr), -} - -impl BlockTracingError { - /// Returns the pre-execution error if this is a [`BlockTracingError::PreExecution`] variant. - pub const fn as_pre_execution(&self) -> Option<&BlockExecutionError> { - match self { - Self::PreExecution(err) => Some(err), - _ => None, - } - } - - /// Returns the EVM error if this is a [`BlockTracingError::Evm`] variant. - pub const fn as_evm(&self) -> Option<&EvmErr> { - match self { - Self::Evm(err) => Some(err), - _ => None, - } - } - - /// Returns the hook error if this is a [`BlockTracingError::Hook`] variant. - pub const fn as_hook(&self) -> Option<&HookErr> { - match self { - Self::Hook(err) => Some(err), - _ => None, - } - } -} - /// A helper type for tracing transactions. #[derive(Debug, Clone)] pub struct TxTracer { @@ -468,3 +428,43 @@ pub trait BlockExecutorFactoryExt: BlockExecutorFactory { } impl BlockExecutorFactoryExt for T {} + +/// Error that can occur during block tracing iteration. +#[derive(Debug, thiserror::Error)] +pub enum BlockTracingError { + /// Error during pre-execution changes (system calls, etc.) + #[error("pre-execution error: {0}")] + PreExecution(BlockExecutionError), + /// EVM error during transaction execution. + #[error("evm error: {0}")] + Evm(EvmErr), + /// Error from the tracing hook. + #[error(transparent)] + Hook(HookErr), +} + +impl BlockTracingError { + /// Returns the pre-execution error if this is a [`BlockTracingError::PreExecution`] variant. + pub const fn as_pre_execution(&self) -> Option<&BlockExecutionError> { + match self { + Self::PreExecution(err) => Some(err), + _ => None, + } + } + + /// Returns the EVM error if this is a [`BlockTracingError::Evm`] variant. + pub const fn as_evm(&self) -> Option<&EvmErr> { + match self { + Self::Evm(err) => Some(err), + _ => None, + } + } + + /// Returns the hook error if this is a [`BlockTracingError::Hook`] variant. + pub const fn as_hook(&self) -> Option<&HookErr> { + match self { + Self::Hook(err) => Some(err), + _ => None, + } + } +} From 6c6d8c2aba231980d62f8972d5968610f33061a6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jan 2026 17:09:06 +0100 Subject: [PATCH 4/4] feat: add trace_transaction method to BlockTracer Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c0014-f406-730d-9c85-f3fb1e5faf7c --- crates/evm/src/tracing.rs | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/crates/evm/src/tracing.rs b/crates/evm/src/tracing.rs index 727f296d..7a0b0b73 100644 --- a/crates/evm/src/tracing.rs +++ b/crates/evm/src/tracing.rs @@ -4,6 +4,8 @@ use crate::{ block::{BlockExecutionError, BlockExecutor, BlockExecutorFactory, BlockExecutorFor}, Database, Evm, EvmFactory, IntoTxEnv, }; +use alloy_consensus::transaction::TxHashRef; +use alloy_primitives::B256; use core::{fmt::Debug, iter::Peekable}; use revm::{ context::result::{ExecutionResult, ResultAndState}, @@ -302,6 +304,62 @@ where done: false, } } + +} + +impl BlockTracer +where + E: BlockExecutor, + E::Evm: Evm, +{ + /// Traces a single transaction in the block by its hash. + /// + /// This method: + /// 1. Applies pre-execution changes with inspector disabled + /// 2. Replays all transactions before the target with inspector disabled + /// 3. Enables the inspector and traces the target transaction + /// + /// Note: The target transaction is executed but not committed to state. + /// + /// Returns `None` if the target transaction was not found. + #[expect(clippy::type_complexity)] + pub fn trace_transaction( + mut self, + transactions: I, + target_tx_hash: B256, + ) -> Result< + Option::HaltReason, ::Inspector>>, + BlockTracingError<::Error, core::convert::Infallible>, + > + where + I: IntoIterator, + T: IntoTxEnv<::Tx> + TxHashRef, + { + self.executor.evm_mut().disable_inspector(); + if let Err(err) = self.executor.apply_pre_execution_changes() { + return Err(BlockTracingError::PreExecution(err)); + } + + for tx in transactions { + if *tx.tx_hash() == target_tx_hash { + self.executor.evm_mut().enable_inspector(); + let ResultAndState { result, state: _ } = self + .executor + .evm_mut() + .transact(tx) + .map_err(BlockTracingError::Evm)?; + let inspector = + core::mem::replace(self.executor.evm_mut().inspector_mut(), self.fused_inspector); + return Ok(Some(TraceOutput { result, inspector })); + } + self.executor + .evm_mut() + .transact_commit(tx) + .map_err(BlockTracingError::Evm)?; + } + + Ok(None) + } } /// Iterator used by block tracer.