Skip to main content

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}