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 crate::{FromEvmError, RpcNodeCore};
4use alloy_consensus::{BlockHeader, Typed2718};
5use alloy_primitives::B256;
6use alloy_rpc_types_eth::{BlockId, TransactionInfo};
7use futures::Future;
8use reth_chainspec::ChainSpecProvider;
9use reth_evm::{system_calls::SystemCaller, ConfigureEvm, ConfigureEvmEnv};
10use reth_primitives::SealedBlockWithSenders;
11use reth_primitives_traits::{BlockBody, SignedTransaction};
12use reth_provider::{BlockReader, ProviderBlock, ProviderHeader, ProviderTx};
13use reth_revm::database::StateProviderDatabase;
14use reth_rpc_eth_types::{
15    cache::db::{StateCacheDb, StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper},
16    EthApiError,
17};
18use revm::{db::CacheDB, Database, DatabaseCommit, GetInspector, Inspector};
19use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
20use revm_primitives::{
21    BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, EvmState, ExecutionResult, ResultAndState,
22};
23use std::{fmt::Display, sync::Arc};
24
25use super::{Call, LoadBlock, LoadPendingBlock, LoadState, LoadTransaction};
26
27/// Executes CPU heavy tasks.
28pub trait Trace:
29    LoadState<
30    Provider: BlockReader,
31    Evm: ConfigureEvm<
32        Header = ProviderHeader<Self::Provider>,
33        Transaction = ProviderTx<Self::Provider>,
34    >,
35>
36{
37    /// Executes the [`EnvWithHandlerCfg`] against the given [Database] without committing state
38    /// changes.
39    fn inspect<DB, I>(
40        &self,
41        db: DB,
42        env: EnvWithHandlerCfg,
43        inspector: I,
44    ) -> Result<(ResultAndState, EnvWithHandlerCfg), Self::Error>
45    where
46        DB: Database,
47        EthApiError: From<DB::Error>,
48        I: GetInspector<DB>,
49    {
50        self.inspect_and_return_db(db, env, inspector).map(|(res, env, _)| (res, env))
51    }
52
53    /// Same as [`inspect`](Self::inspect) but also returns the database again.
54    ///
55    /// Even though [Database] is also implemented on `&mut`
56    /// this is still useful if there are certain trait bounds on the Inspector's database generic
57    /// type
58    fn inspect_and_return_db<DB, I>(
59        &self,
60        db: DB,
61        env: EnvWithHandlerCfg,
62        inspector: I,
63    ) -> Result<(ResultAndState, EnvWithHandlerCfg, DB), Self::Error>
64    where
65        DB: Database,
66        EthApiError: From<DB::Error>,
67
68        I: GetInspector<DB>,
69    {
70        let mut evm = self.evm_config().evm_with_env_and_inspector(db, env, inspector);
71        let res = evm.transact().map_err(Self::Error::from_evm_err)?;
72        let (db, env) = evm.into_db_and_env_with_handler_cfg();
73        Ok((res, env, db))
74    }
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 [`EnvWithHandlerCfg`] was inspected.
81    ///
82    /// Caution: this is blocking
83    fn trace_at<F, R>(
84        &self,
85        env: EnvWithHandlerCfg,
86        config: TracingInspectorConfig,
87        at: BlockId,
88        f: F,
89    ) -> Result<R, Self::Error>
90    where
91        Self: Call,
92        F: FnOnce(TracingInspector, ResultAndState) -> Result<R, Self::Error>,
93    {
94        self.with_state_at_block(at, |state| {
95            let mut db = CacheDB::new(StateProviderDatabase::new(state));
96            let mut inspector = TracingInspector::new(config);
97            let (res, _) = self.inspect(&mut db, env, &mut inspector)?;
98            f(inspector, res)
99        })
100    }
101
102    /// Same as [`trace_at`](Self::trace_at) but also provides the used database to the callback.
103    ///
104    /// Executes the transaction on top of the given [`BlockId`] with a tracer configured by the
105    /// config.
106    ///
107    /// The callback is then called with the [`TracingInspector`] and the [`ResultAndState`] after
108    /// the configured [`EnvWithHandlerCfg`] was inspected.
109    fn spawn_trace_at_with_state<F, R>(
110        &self,
111        env: EnvWithHandlerCfg,
112        config: TracingInspectorConfig,
113        at: BlockId,
114        f: F,
115    ) -> impl Future<Output = Result<R, Self::Error>> + Send
116    where
117        Self: LoadPendingBlock + Call,
118        F: FnOnce(TracingInspector, ResultAndState, StateCacheDb<'_>) -> Result<R, Self::Error>
119            + Send
120            + 'static,
121        R: Send + 'static,
122    {
123        let this = self.clone();
124        self.spawn_with_state_at_block(at, move |state| {
125            let mut db = CacheDB::new(StateProviderDatabase::new(state));
126            let mut inspector = TracingInspector::new(config);
127            let (res, _) = this.inspect(&mut db, env, &mut inspector)?;
128            f(inspector, res, db)
129        })
130    }
131
132    /// Retrieves the transaction if it exists and returns its trace.
133    ///
134    /// Before the transaction is traced, all previous transaction in the block are applied to the
135    /// state by executing them first.
136    /// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
137    /// and the database that points to the beginning of the transaction.
138    ///
139    /// Note: Implementers should use a threadpool where blocking is allowed, such as
140    /// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
141    fn spawn_trace_transaction_in_block<F, R>(
142        &self,
143        hash: B256,
144        config: TracingInspectorConfig,
145        f: F,
146    ) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
147    where
148        Self: LoadPendingBlock + LoadTransaction + Call,
149        F: FnOnce(
150                TransactionInfo,
151                TracingInspector,
152                ResultAndState,
153                StateCacheDb<'_>,
154            ) -> Result<R, Self::Error>
155            + Send
156            + 'static,
157        R: Send + 'static,
158    {
159        self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f)
160    }
161
162    /// Retrieves the transaction if it exists and returns its trace.
163    ///
164    /// Before the transaction is traced, all previous transaction in the block are applied to the
165    /// state by executing them first.
166    /// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
167    /// and the database that points to the beginning of the transaction.
168    ///
169    /// Note: Implementers should use a threadpool where blocking is allowed, such as
170    /// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
171    fn spawn_trace_transaction_in_block_with_inspector<Insp, F, R>(
172        &self,
173        hash: B256,
174        mut inspector: Insp,
175        f: F,
176    ) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
177    where
178        Self: LoadPendingBlock + LoadTransaction + Call,
179        F: FnOnce(
180                TransactionInfo,
181                Insp,
182                ResultAndState,
183                StateCacheDb<'_>,
184            ) -> Result<R, Self::Error>
185            + Send
186            + 'static,
187        Insp: for<'a, 'b> Inspector<StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
188        R: Send + 'static,
189    {
190        async move {
191            let (transaction, block) = match self.transaction_and_block(hash).await? {
192                None => return Ok(None),
193                Some(res) => res,
194            };
195            let (tx, tx_info) = transaction.split();
196
197            let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?;
198
199            // we need to get the state of the parent block because we're essentially replaying the
200            // block the transaction is included in
201            let parent_block = block.parent_hash();
202
203            let this = self.clone();
204            self.spawn_with_state_at_block(parent_block.into(), move |state| {
205                let mut db = CacheDB::new(StateProviderDatabase::new(state));
206                let block_txs = block.transactions_with_sender();
207
208                this.apply_pre_execution_changes(&block, &mut db, &cfg, &block_env)?;
209
210                // replay all transactions prior to the targeted transaction
211                this.replay_transactions_until(
212                    &mut db,
213                    cfg.clone(),
214                    block_env.clone(),
215                    block_txs,
216                    *tx.tx_hash(),
217                )?;
218
219                let env = EnvWithHandlerCfg::new_with_cfg_env(
220                    cfg,
221                    block_env,
222                    RpcNodeCore::evm_config(&this)
223                        .tx_env(tx.as_signed(), tx.signer())
224                        .map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?,
225                );
226                let (res, _) =
227                    this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?;
228                f(tx_info, inspector, res, db)
229            })
230            .await
231            .map(Some)
232        }
233    }
234
235    /// Executes all transactions of a block up to a given index.
236    ///
237    /// If a `highest_index` is given, this will only execute the first `highest_index`
238    /// transactions, in other words, it will stop executing transactions after the
239    /// `highest_index`th transaction. If `highest_index` is `None`, all transactions
240    /// are executed.
241    fn trace_block_until<F, R>(
242        &self,
243        block_id: BlockId,
244        block: Option<Arc<SealedBlockWithSenders<ProviderBlock<Self::Provider>>>>,
245        highest_index: Option<u64>,
246        config: TracingInspectorConfig,
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                TracingInspector,
254                ExecutionResult,
255                &EvmState,
256                &StateCacheDb<'_>,
257            ) -> Result<R, Self::Error>
258            + Send
259            + 'static,
260        R: Send + 'static,
261    {
262        self.trace_block_until_with_inspector(
263            block_id,
264            block,
265            highest_index,
266            move || TracingInspector::new(config),
267            f,
268        )
269    }
270
271    /// Executes all transactions of a block.
272    ///
273    /// If a `highest_index` is given, this will only execute the first `highest_index`
274    /// transactions, in other words, it will stop executing transactions after the
275    /// `highest_index`th transaction.
276    ///
277    /// Note: This expect tx index to be 0-indexed, so the first transaction is at index 0.
278    ///
279    /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
280    /// the transactions.
281    fn trace_block_until_with_inspector<Setup, Insp, F, R>(
282        &self,
283        block_id: BlockId,
284        block: Option<Arc<SealedBlockWithSenders<ProviderBlock<Self::Provider>>>>,
285        highest_index: Option<u64>,
286        mut inspector_setup: Setup,
287        f: F,
288    ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
289    where
290        Self: LoadBlock,
291        F: Fn(
292                TransactionInfo,
293                Insp,
294                ExecutionResult,
295                &EvmState,
296                &StateCacheDb<'_>,
297            ) -> Result<R, Self::Error>
298            + Send
299            + 'static,
300        Setup: FnMut() -> Insp + Send + 'static,
301        Insp: for<'a, 'b> Inspector<StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
302        R: Send + 'static,
303    {
304        async move {
305            let block = async {
306                if block.is_some() {
307                    return Ok(block)
308                }
309                self.block_with_senders(block_id).await
310            };
311
312            let ((cfg, block_env, _), block) =
313                futures::try_join!(self.evm_env_at(block_id), block)?;
314
315            let Some(block) = block else { return Ok(None) };
316
317            if block.body.transactions().is_empty() {
318                // nothing to trace
319                return Ok(Some(Vec::new()))
320            }
321
322            // replay all transactions of the block
323            self.spawn_tracing(move |this| {
324                // we need to get the state of the parent block because we're replaying this block
325                // on top of its parent block's state
326                let state_at = block.parent_hash();
327                let block_hash = block.hash();
328
329                let block_number = block_env.number.saturating_to::<u64>();
330                let base_fee = block_env.basefee.saturating_to::<u128>();
331
332                // now get the state
333                let state = this.state_at_block_id(state_at.into())?;
334                let mut db =
335                    CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state)));
336
337                this.apply_pre_execution_changes(&block, &mut db, &cfg, &block_env)?;
338
339                // prepare transactions, we do everything upfront to reduce time spent with open
340                // state
341                let max_transactions =
342                    highest_index.map_or(block.body.transactions().len(), |highest| {
343                        // we need + 1 because the index is 0-based
344                        highest as usize + 1
345                    });
346                let mut results = Vec::with_capacity(max_transactions);
347
348                let mut transactions = block
349                    .transactions_with_sender()
350                    .take(max_transactions)
351                    .enumerate()
352                    .map(|(idx, (signer, tx))| {
353                        let tx_info = TransactionInfo {
354                            hash: Some(*tx.tx_hash()),
355                            index: Some(idx as u64),
356                            block_hash: Some(block_hash),
357                            block_number: Some(block_number),
358                            base_fee: Some(base_fee),
359                            tx_type: Some(tx.ty() as isize),
360                        };
361                        let tx_env = this
362                            .evm_config()
363                            .tx_env(tx, *signer)
364                            .map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?;
365                        Ok((tx_info, tx_env))
366                    })
367                    .peekable();
368
369                while let Some(res) = transactions.next() {
370                    match res {
371                        Ok((tx_info, tx)) => {
372                            let env = EnvWithHandlerCfg::new_with_cfg_env(
373                                cfg.clone(),
374                                block_env.clone(),
375                                tx,
376                            );
377
378                            let mut inspector = inspector_setup();
379                            let (res, _) = this.inspect(
380                                StateCacheDbRefMutWrapper(&mut db),
381                                env,
382                                &mut inspector,
383                            )?;
384                            let ResultAndState { result, state } = res;
385                            results.push(f(tx_info, inspector, result, &state, &db)?);
386
387                            // need to apply the state changes of this transaction before executing
388                            // the next transaction, but only if there's
389                            // a next transaction
390                            if transactions.peek().is_some() {
391                                // commit the state changes to the DB
392                                db.commit(state)
393                            }
394                        }
395                        Err(err) => return Err(err),
396                    }
397                }
398
399                Ok(Some(results))
400            })
401            .await
402        }
403    }
404
405    /// Executes all transactions of a block and returns a list of callback results invoked for each
406    /// transaction in the block.
407    ///
408    /// This
409    /// 1. fetches all transactions of the block
410    /// 2. configures the EVM evn
411    /// 3. loops over all transactions and executes them
412    /// 4. calls the callback with the transaction info, the execution result, the changed state
413    ///    _after_ the transaction [`StateProviderDatabase`] and the database that points to the
414    ///    state right _before_ the transaction.
415    fn trace_block_with<F, R>(
416        &self,
417        block_id: BlockId,
418        block: Option<Arc<SealedBlockWithSenders<ProviderBlock<Self::Provider>>>>,
419        config: TracingInspectorConfig,
420        f: F,
421    ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
422    where
423        Self: LoadBlock,
424        // This is the callback that's invoked for each transaction with the inspector, the result,
425        // state and db
426        F: Fn(
427                TransactionInfo,
428                TracingInspector,
429                ExecutionResult,
430                &EvmState,
431                &StateCacheDb<'_>,
432            ) -> Result<R, Self::Error>
433            + Send
434            + 'static,
435        R: Send + 'static,
436    {
437        self.trace_block_until(block_id, block, None, config, f)
438    }
439
440    /// Executes all transactions of a block and returns a list of callback results invoked for each
441    /// transaction in the block.
442    ///
443    /// This
444    /// 1. fetches all transactions of the block
445    /// 2. configures the EVM evn
446    /// 3. loops over all transactions and executes them
447    /// 4. calls the callback with the transaction info, the execution result, the changed state
448    ///    _after_ the transaction [`EvmState`] and the database that points to the state right
449    ///    _before_ the transaction, in other words the state the transaction was executed on:
450    ///    `changed_state = tx(cached_state)`
451    ///
452    /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
453    /// a transaction. This is invoked for each transaction.
454    fn trace_block_inspector<Setup, Insp, F, R>(
455        &self,
456        block_id: BlockId,
457        block: Option<Arc<SealedBlockWithSenders<ProviderBlock<Self::Provider>>>>,
458        insp_setup: Setup,
459        f: F,
460    ) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
461    where
462        Self: LoadBlock,
463        // This is the callback that's invoked for each transaction with the inspector, the result,
464        // state and db
465        F: Fn(
466                TransactionInfo,
467                Insp,
468                ExecutionResult,
469                &EvmState,
470                &StateCacheDb<'_>,
471            ) -> Result<R, Self::Error>
472            + Send
473            + 'static,
474        Setup: FnMut() -> Insp + Send + 'static,
475        Insp: for<'a, 'b> Inspector<StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
476        R: Send + 'static,
477    {
478        self.trace_block_until_with_inspector(block_id, block, None, insp_setup, f)
479    }
480
481    /// Applies chain-specific state transitions required before executing a block.
482    ///
483    /// Note: This should only be called when tracing an entire block vs individual transactions.
484    /// When tracing transaction on top of an already committed block state, those transitions are
485    /// already applied.
486    fn apply_pre_execution_changes<DB: Send + Database<Error: Display> + DatabaseCommit>(
487        &self,
488        block: &SealedBlockWithSenders<ProviderBlock<Self::Provider>>,
489        db: &mut DB,
490        cfg: &CfgEnvWithHandlerCfg,
491        block_env: &BlockEnv,
492    ) -> Result<(), Self::Error> {
493        let mut system_caller =
494            SystemCaller::new(self.evm_config().clone(), self.provider().chain_spec());
495        // apply relevant system calls
496        system_caller
497            .pre_block_beacon_root_contract_call(
498                db,
499                cfg,
500                block_env,
501                block.header.parent_beacon_block_root(),
502            )
503            .map_err(|_| EthApiError::EvmCustom("failed to apply 4788 system call".to_string()))?;
504
505        system_caller
506            .pre_block_blockhashes_contract_call(db, cfg, block_env, block.header.parent_hash())
507            .map_err(|_| {
508                EthApiError::EvmCustom("failed to apply blockhashes system call".to_string())
509            })?;
510
511        Ok(())
512    }
513}