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