reth_rpc_eth_api/helpers/trace.rs
1//! Loads a pending block from database. Helper trait for `eth_` call and trace RPC methods.
2
3use super::{Call, LoadBlock, LoadState, LoadTransaction};
4use crate::{FromEthApiError, FromEvmError};
5use alloy_consensus::{transaction::TxHashRef, BlockHeader};
6use alloy_eip7928::bal::DecodedBal;
7use alloy_primitives::B256;
8use alloy_rpc_types_eth::{BlockId, TransactionInfo};
9use futures::Future;
10use reth_errors::RethError;
11use reth_evm::{
12 block::BlockExecutor, evm::EvmFactoryExt, tracing::TracingCtx, ConfigureEvm, Evm, EvmEnvFor,
13 EvmFor, HaltReasonFor, InspectorFor, IntoTxEnv, TxEnvFor,
14};
15use reth_primitives_traits::{BlockBody, BlockTy, Recovered, RecoveredBlock};
16use reth_rpc_eth_types::cache::db::{attach_bal_before_tx, StateCacheDb};
17use reth_storage_api::{ProviderBlock, ProviderTx};
18use revm::{context::Block, context_interface::result::ResultAndState, state::bal::Bal as RevmBal};
19use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
20use std::sync::Arc;
21
22/// Executes CPU heavy tasks.
23pub trait Trace: LoadState<Error: FromEvmError<Self::Evm>> + Call {
24 /// Executes the [`TxEnvFor`] with [`reth_evm::EvmEnv`] against the given [`StateCacheDb`]
25 /// without committing state changes.
26 fn inspect<'a>(
27 &self,
28 db: &'a mut StateCacheDb,
29 evm_env: EvmEnvFor<Self::Evm>,
30 tx_env: impl IntoTxEnv<TxEnvFor<Self::Evm>>,
31 inspector: impl InspectorFor<Self::Evm, &'a mut StateCacheDb>,
32 ) -> Result<ResultAndState<HaltReasonFor<Self::Evm>>, Self::Error> {
33 self.evm_config()
34 .evm_with_env_and_inspector(db, evm_env, inspector)
35 .transact(tx_env)
36 .map_err(Self::Error::from_evm_err)
37 }
38
39 /// Retrieves the transaction if it exists and returns its trace.
40 ///
41 /// Before the transaction is traced, the state is positioned right before the transaction,
42 /// either by attaching the block's cached BAL or by executing all previous transactions in
43 /// the block.
44 /// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
45 /// and the database that points to the beginning of the transaction. The database may have
46 /// the block's BAL attached and must only be used for reads, because an attached BAL takes
47 /// read precedence over state committed on top, see [`attach_bal_before_tx`].
48 ///
49 /// Note: Implementers should use a threadpool where blocking is allowed, such as
50 /// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
51 fn spawn_trace_transaction_in_block<F, R>(
52 &self,
53 hash: B256,
54 config: TracingInspectorConfig,
55 f: F,
56 ) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
57 where
58 Self: LoadTransaction,
59 F: FnOnce(
60 TransactionInfo,
61 TracingInspector,
62 ResultAndState<HaltReasonFor<Self::Evm>>,
63 StateCacheDb,
64 ) -> Result<R, Self::Error>
65 + Send
66 + 'static,
67 R: Send + 'static,
68 {
69 self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f)
70 }
71
72 /// Retrieves the transaction if it exists and returns its trace.
73 ///
74 /// Before the transaction is traced, the state is positioned right before the transaction,
75 /// either by attaching the block's cached BAL or by executing all previous transactions in
76 /// the block.
77 /// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
78 /// and the database that points to the beginning of the transaction. The database may have
79 /// the block's BAL attached and must only be used for reads, because an attached BAL takes
80 /// read precedence over state committed on top, see [`attach_bal_before_tx`].
81 ///
82 /// Note: Implementers should use a threadpool where blocking is allowed, such as
83 /// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
84 fn spawn_trace_transaction_in_block_with_inspector<Insp, F, R>(
85 &self,
86 hash: B256,
87 mut inspector: Insp,
88 f: F,
89 ) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
90 where
91 Self: LoadTransaction,
92 F: FnOnce(
93 TransactionInfo,
94 Insp,
95 ResultAndState<HaltReasonFor<Self::Evm>>,
96 StateCacheDb,
97 ) -> Result<R, Self::Error>
98 + Send
99 + 'static,
100 Insp: for<'a> InspectorFor<Self::Evm, &'a mut StateCacheDb> + Send + 'static,
101 R: Send + 'static,
102 {
103 async move {
104 let (transaction, block, bal) =
105 match self.transaction_and_block_and_maybe_bal(hash).await? {
106 None => return Ok(None),
107 Some(res) => res,
108 };
109 let (tx, tx_info) = transaction.split();
110
111 // we need to get the state of the parent block because we're essentially replaying the
112 // block the transaction is included in
113 let parent_block = block.parent_hash();
114
115 self.spawn_with_state_at_block(parent_block, move |this, mut db| {
116 let (res, _) = this.inspect_transaction_in_block(
117 &block,
118 &mut db,
119 &mut inspector,
120 // index should always be available because `transaction_and_block` only
121 // returns transactions included in a block
122 tx_info.index.expect("transaction_and_block only returns block transactions")
123 as usize,
124 tx,
125 bal.as_deref(),
126 )?;
127 f(tx_info, inspector, res, db)
128 })
129 .await
130 .map(Some)
131 }
132 }
133
134 /// Positions the state of `db` right before the transaction at the target index.
135 ///
136 /// If the block's cached BAL is given, it is attached to the database at the target index and
137 /// no transactions are executed, see [`attach_bal_before_tx`]. Otherwise all transactions
138 /// before the target transaction are executed and their changes are written to the
139 /// _runtime_ db ([`StateCacheDb`]).
140 ///
141 /// If the target index is greater than or equal to the block's transaction count, all
142 /// transactions are replayed.
143 fn replay_block_until(
144 &self,
145 db: &mut StateCacheDb,
146 block: &RecoveredBlock<BlockTy<Self::Primitives>>,
147 target_tx_index: usize,
148 bal: Option<&DecodedBal<Arc<RevmBal>>>,
149 ) -> Result<(), Self::Error> {
150 if let Some(bal) = bal {
151 attach_bal_before_tx(db, bal, target_tx_index);
152 return Ok(())
153 }
154
155 self.apply_pre_execution_changes(block, db)?;
156
157 let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
158 let mut evm = self.evm_config().evm_with_env(db, evm_env);
159 self.replay_transactions_until_with_evm(
160 &mut evm,
161 block.transactions_recovered(),
162 target_tx_index,
163 )
164 }
165
166 /// Executes the target transaction with the configured inspector on the state right before
167 /// the transaction.
168 ///
169 /// If the block's cached BAL is given, the state is positioned by attaching the BAL at the
170 /// target index, see [`attach_bal_before_tx`]. Otherwise all transactions before the target
171 /// transaction are replayed without inspection first.
172 #[expect(clippy::type_complexity)]
173 fn inspect_transaction_in_block<'a>(
174 &self,
175 block: &RecoveredBlock<BlockTy<Self::Primitives>>,
176 db: &'a mut StateCacheDb,
177 inspector: impl InspectorFor<Self::Evm, &'a mut StateCacheDb>,
178 target_tx_index: usize,
179 target_tx_env: impl IntoTxEnv<TxEnvFor<Self::Evm>>,
180 bal: Option<&DecodedBal<Arc<RevmBal>>>,
181 ) -> Result<(ResultAndState<HaltReasonFor<Self::Evm>>, EvmEnvFor<Self::Evm>), Self::Error> {
182 if let Some(bal) = bal {
183 // the BAL also covers the block's pre-execution changes
184 attach_bal_before_tx(db, bal, target_tx_index);
185 } else {
186 self.apply_pre_execution_changes(block, db)?;
187 }
188
189 let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
190 let mut evm = self.evm_config().evm_with_env_and_inspector(db, evm_env, inspector);
191
192 if bal.is_none() {
193 evm.disable_inspector();
194 self.replay_transactions_until_with_evm(
195 &mut evm,
196 block.transactions_recovered(),
197 target_tx_index,
198 )?;
199 evm.enable_inspector();
200 }
201
202 let res = evm.transact(target_tx_env).map_err(Self::Error::from_evm_err)?;
203
204 let (_, evm_env) = evm.finish();
205
206 Ok((res, evm_env))
207 }
208
209 /// Executes all transactions of a block up to a given index.
210 ///
211 /// If a `highest_index` is given, this will only execute the first `highest_index`
212 /// transactions, in other words, it will stop executing transactions after the
213 /// `highest_index`th transaction. If `highest_index` is `None`, all transactions
214 /// are executed.
215 fn trace_block_until<F, R>(
216 &self,
217 block_id: BlockId,
218 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
219 highest_index: Option<u64>,
220 config: TracingInspectorConfig,
221 f: F,
222 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
223 where
224 Self: LoadBlock,
225 F: Fn(
226 TransactionInfo,
227 TracingCtx<
228 '_,
229 Recovered<&ProviderTx<Self::Provider>>,
230 EvmFor<Self::Evm, &mut StateCacheDb, TracingInspector>,
231 >,
232 ) -> Result<R, Self::Error>
233 + Send
234 + 'static,
235 R: Send + 'static,
236 {
237 self.trace_block_until_with_inspector(
238 block_id,
239 block,
240 highest_index,
241 move || TracingInspector::new(config),
242 f,
243 )
244 }
245
246 /// Executes all transactions of a block.
247 ///
248 /// If a `highest_index` is given, this will only execute the first `highest_index`
249 /// transactions, in other words, it will stop executing transactions after the
250 /// `highest_index`th transaction.
251 ///
252 /// Note: This expect tx index to be 0-indexed, so the first transaction is at index 0.
253 ///
254 /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
255 /// the transactions.
256 fn trace_block_until_with_inspector<Setup, Insp, F, R>(
257 &self,
258 block_id: BlockId,
259 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
260 highest_index: Option<u64>,
261 mut inspector_setup: Setup,
262 f: F,
263 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
264 where
265 Self: LoadBlock,
266 F: Fn(
267 TransactionInfo,
268 TracingCtx<
269 '_,
270 Recovered<&ProviderTx<Self::Provider>>,
271 EvmFor<Self::Evm, &mut StateCacheDb, Insp>,
272 >,
273 ) -> Result<R, Self::Error>
274 + Send
275 + 'static,
276 Setup: FnMut() -> Insp + Send + 'static,
277 Insp: Clone + for<'a> InspectorFor<Self::Evm, &'a mut StateCacheDb>,
278 R: Send + 'static,
279 {
280 async move {
281 let block =
282 if block.is_some() { block } else { self.recovered_block(block_id).await? };
283
284 let Some(block) = block else { return Ok(None) };
285 let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
286
287 if block.body().transactions().is_empty() {
288 // nothing to trace
289 return Ok(Some(Vec::new()))
290 }
291
292 // replay all transactions of the block
293 // we need to get the state of the parent block because we're replaying this block
294 // on top of its parent block's state
295 self.spawn_with_state_at_block(block.parent_hash(), move |this, mut db| {
296 let block_hash = block.hash();
297
298 let block_number = evm_env.block_env.number().saturating_to();
299 let block_timestamp = evm_env.block_env.timestamp().saturating_to();
300 let base_fee = evm_env.block_env.basefee();
301
302 this.apply_pre_execution_changes(&block, &mut db)?;
303
304 // prepare transactions, we do everything upfront to reduce time spent with open
305 // state
306 let max_transactions = highest_index.map_or_else(
307 || block.body().transaction_count(),
308 |highest| {
309 // we need + 1 because the index is 0-based
310 highest as usize + 1
311 },
312 );
313
314 let mut idx = 0;
315
316 let results = this
317 .evm_config()
318 .evm_factory()
319 .create_tracer(&mut db, evm_env, inspector_setup())
320 .try_trace_many(block.transactions_recovered().take(max_transactions), |ctx| {
321 let tx_info = TransactionInfo {
322 hash: Some(*ctx.tx.tx_hash()),
323 index: Some(idx),
324 block_hash: Some(block_hash),
325 block_number: Some(block_number),
326 block_timestamp: Some(block_timestamp),
327 base_fee: Some(base_fee),
328 };
329 idx += 1;
330
331 f(tx_info, ctx)
332 })
333 .collect::<Result<_, _>>()?;
334
335 Ok(Some(results))
336 })
337 .await
338 }
339 }
340
341 /// Executes all transactions of a block and returns a list of callback results invoked for each
342 /// transaction in the block.
343 ///
344 /// This
345 /// 1. fetches all transactions of the block
346 /// 2. configures the EVM env
347 /// 3. loops over all transactions and executes them
348 /// 4. calls the callback with the transaction info, the execution result, the changed state
349 /// _after_ the transaction [`StateCacheDb`] and the database that points to the state right
350 /// _before_ the transaction.
351 fn trace_block_with<F, R>(
352 &self,
353 block_id: BlockId,
354 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
355 config: TracingInspectorConfig,
356 f: F,
357 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
358 where
359 Self: LoadBlock,
360 // This is the callback that's invoked for each transaction with the inspector, the result,
361 // state and db
362 F: Fn(
363 TransactionInfo,
364 TracingCtx<
365 '_,
366 Recovered<&ProviderTx<Self::Provider>>,
367 EvmFor<Self::Evm, &mut StateCacheDb, TracingInspector>,
368 >,
369 ) -> Result<R, Self::Error>
370 + Send
371 + 'static,
372 R: Send + 'static,
373 {
374 self.trace_block_until(block_id, block, None, config, f)
375 }
376
377 /// Executes all transactions of a block and returns a list of callback results invoked for each
378 /// transaction in the block.
379 ///
380 /// This
381 /// 1. fetches all transactions of the block
382 /// 2. configures the EVM env
383 /// 3. loops over all transactions and executes them
384 /// 4. calls the callback with the transaction info, the execution result, the changed state
385 /// _after_ the transaction `EvmState` and the database that points to the state right
386 /// _before_ the transaction, in other words the state the transaction was executed on:
387 /// `changed_state = tx(cached_state)`
388 ///
389 /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
390 /// a transaction. This is invoked for each transaction.
391 fn trace_block_inspector<Setup, Insp, F, R>(
392 &self,
393 block_id: BlockId,
394 block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
395 insp_setup: Setup,
396 f: F,
397 ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
398 where
399 Self: LoadBlock,
400 // This is the callback that's invoked for each transaction with the inspector, the result,
401 // state and db
402 F: Fn(
403 TransactionInfo,
404 TracingCtx<
405 '_,
406 Recovered<&ProviderTx<Self::Provider>>,
407 EvmFor<Self::Evm, &mut StateCacheDb, Insp>,
408 >,
409 ) -> Result<R, Self::Error>
410 + Send
411 + 'static,
412 Setup: FnMut() -> Insp + Send + 'static,
413 Insp: Clone + for<'a> InspectorFor<Self::Evm, &'a mut StateCacheDb>,
414 R: Send + 'static,
415 {
416 self.trace_block_until_with_inspector(block_id, block, None, insp_setup, f)
417 }
418
419 /// Applies chain-specific state transitions required before executing a block.
420 ///
421 /// Note: This should only be called when tracing an entire block vs individual transactions.
422 /// When tracing transactions on top of an already committed block state, those transitions are
423 /// already applied.
424 fn apply_pre_execution_changes(
425 &self,
426 block: &RecoveredBlock<ProviderBlock<Self::Provider>>,
427 db: &mut StateCacheDb,
428 ) -> Result<(), Self::Error> {
429 self.evm_config()
430 .executor_for_block(db, block.sealed_block())
431 .map_err(RethError::other)
432 .map_err(Self::Error::from_eth_err)?
433 .apply_pre_execution_changes()
434 .map_err(Self::Error::from_eth_err)?;
435 Ok(())
436 }
437}