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