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