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