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