Skip to content

Commit 6a3ebbe

Browse files
authored
feat: tracing helpers (#89)
* feat: TxTracer * BlockTracer * wip * iterator * commit * TracingCtx * skip_last_commit * formatting * clippy * fix
1 parent dce752f commit 6a3ebbe

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

crates/evm/src/evm.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Abstraction over EVM.
22
3-
use crate::{EvmEnv, EvmError, IntoTxEnv};
3+
use crate::{tracing::TxTracer, EvmEnv, EvmError, IntoTxEnv};
44
use alloy_primitives::{Address, Bytes};
55
use core::{error::Error, fmt::Debug, hash::Hash};
66
use revm::{
@@ -194,3 +194,22 @@ pub trait EvmFactory {
194194
inspector: I,
195195
) -> Self::Evm<DB, I>;
196196
}
197+
198+
/// An extension trait for [`EvmFactory`] providing useful non-overridable methods.
199+
pub trait EvmFactoryExt: EvmFactory {
200+
/// Creates a new [`TxTracer`] instance with the given database, input and fused inspector.
201+
fn create_tracer<DB, I>(
202+
&self,
203+
db: DB,
204+
input: EvmEnv<Self::Spec>,
205+
fused_inspector: I,
206+
) -> TxTracer<Self::Evm<DB, I>>
207+
where
208+
DB: Database + DatabaseCommit,
209+
I: Inspector<Self::Context<DB>> + Clone,
210+
{
211+
TxTracer::new(self.create_evm_with_inspector(db, input, fused_inspector))
212+
}
213+
}
214+
215+
impl<T: EvmFactory> EvmFactoryExt for T {}

crates/evm/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub use error::*;
2121
pub mod tx;
2222
pub use tx::*;
2323
pub mod precompiles;
24+
pub mod tracing;
2425

2526
mod either;
2627

crates/evm/src/tracing.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! Helpers for tracing.
2+
3+
use crate::{Evm, IntoTxEnv};
4+
use core::{fmt::Debug, iter::Peekable};
5+
use revm::{
6+
context::result::{ExecutionResult, ResultAndState},
7+
state::EvmState,
8+
DatabaseCommit,
9+
};
10+
11+
/// A helper type for tracing transactions.
12+
#[derive(Debug, Clone)]
13+
pub struct TxTracer<E: Evm> {
14+
evm: E,
15+
fused_inspector: E::Inspector,
16+
}
17+
18+
/// Container type for context exposed in [`TxTracer`].
19+
#[derive(Debug)]
20+
pub struct TracingCtx<'a, T, E: Evm> {
21+
/// The transaction that was just executed.
22+
pub tx: T,
23+
/// Result of transaction execution.
24+
pub result: ExecutionResult<E::HaltReason>,
25+
/// State changes after transaction.
26+
pub state: &'a EvmState,
27+
/// Inspector state after transaction.
28+
pub inspector: E::Inspector,
29+
/// Database used when executing the transaction, _before_ committing the state changes.
30+
pub db: &'a mut E::DB,
31+
}
32+
33+
/// Output of tracing a transaction.
34+
#[derive(Debug, Clone)]
35+
pub struct TraceOutput<H, I> {
36+
/// Inner EVM output.
37+
pub result: ExecutionResult<H>,
38+
/// Inspector state at the end of the execution.
39+
pub inspector: I,
40+
}
41+
42+
impl<E: Evm<Inspector: Clone, DB: DatabaseCommit>> TxTracer<E> {
43+
/// Creates a new [`TxTracer`] instance.
44+
pub fn new(mut evm: E) -> Self {
45+
Self { fused_inspector: evm.inspector_mut().clone(), evm }
46+
}
47+
48+
fn fuse_inspector(&mut self) -> E::Inspector {
49+
core::mem::replace(self.evm.inspector_mut(), self.fused_inspector.clone())
50+
}
51+
52+
/// Executes a transaction, and returns its outcome along with the inspector state.
53+
pub fn trace(
54+
&mut self,
55+
tx: impl IntoTxEnv<E::Tx>,
56+
) -> Result<TraceOutput<E::HaltReason, E::Inspector>, E::Error> {
57+
let result = self.evm.transact_commit(tx);
58+
let inspector = self.fuse_inspector();
59+
Ok(TraceOutput { result: result?, inspector })
60+
}
61+
62+
/// Executes multiple transactions, applies the closure to each transaction result, and returns
63+
/// the outcomes.
64+
#[expect(clippy::type_complexity)]
65+
pub fn trace_many<Txs, T, F, O>(
66+
&mut self,
67+
txs: Txs,
68+
mut f: F,
69+
) -> TracerIter<'_, E, Txs::IntoIter, impl FnMut(TracingCtx<'_, T, E>) -> Result<O, E::Error>>
70+
where
71+
T: IntoTxEnv<E::Tx> + Clone,
72+
Txs: IntoIterator<Item = T>,
73+
F: FnMut(TracingCtx<'_, Txs::Item, E>) -> O,
74+
{
75+
self.try_trace_many(txs, move |ctx| Ok(f(ctx)))
76+
}
77+
78+
/// Same as [`TxTracer::trace_many`], but operates on closures returning [`Result`]s.
79+
pub fn try_trace_many<Txs, T, F, O, Err>(
80+
&mut self,
81+
txs: Txs,
82+
hook: F,
83+
) -> TracerIter<'_, E, Txs::IntoIter, F>
84+
where
85+
T: IntoTxEnv<E::Tx> + Clone,
86+
Txs: IntoIterator<Item = T>,
87+
F: FnMut(TracingCtx<'_, T, E>) -> Result<O, Err>,
88+
Err: From<E::Error>,
89+
{
90+
TracerIter { inner: self, txs: txs.into_iter().peekable(), hook, skip_last_commit: true }
91+
}
92+
}
93+
94+
/// Iterator used by tracer.
95+
#[derive(derive_more::Debug)]
96+
#[debug(bound(E::Inspector: Debug))]
97+
pub struct TracerIter<'a, E: Evm, Txs: Iterator, F> {
98+
inner: &'a mut TxTracer<E>,
99+
txs: Peekable<Txs>,
100+
hook: F,
101+
skip_last_commit: bool,
102+
}
103+
104+
impl<E: Evm, Txs: Iterator, F> TracerIter<'_, E, Txs, F> {
105+
/// Flips the `skip_last_commit` flag thus making sure all transaction are commited.
106+
///
107+
/// We are skipping last commit by default as it's expected that when tracing users are mostly
108+
/// interested in tracer output rather than in a state after it.
109+
pub fn commit_last_tx(mut self) -> Self {
110+
self.skip_last_commit = false;
111+
self
112+
}
113+
}
114+
115+
impl<'a, E, T, Txs, F, O, Err> Iterator for TracerIter<'a, E, Txs, F>
116+
where
117+
E: Evm<DB: DatabaseCommit, Inspector: Clone>,
118+
T: IntoTxEnv<E::Tx> + Clone,
119+
Txs: Iterator<Item = T>,
120+
Err: From<E::Error>,
121+
F: FnMut(TracingCtx<'_, T, E>) -> Result<O, Err>,
122+
{
123+
type Item = Result<O, Err>;
124+
125+
fn next(&mut self) -> Option<Self::Item> {
126+
let tx = self.txs.next()?;
127+
let result = self.inner.evm.transact(tx.clone());
128+
129+
let inspector = self.inner.fuse_inspector();
130+
let Ok(ResultAndState { result, state }) = result else {
131+
return None;
132+
};
133+
let output = (self.hook)(TracingCtx {
134+
tx,
135+
result,
136+
state: &state,
137+
inspector,
138+
db: self.inner.evm.db_mut(),
139+
});
140+
141+
// Only commit next transaction if `skip_last_commit` is disabled or there is a next
142+
// transaction.
143+
if !self.skip_last_commit || self.txs.peek().is_some() {
144+
self.inner.evm.db_mut().commit(state);
145+
}
146+
147+
Some(output)
148+
}
149+
}

0 commit comments

Comments
 (0)