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