Skip to main content

reth_rpc/
trace.rs

1use alloy_consensus::BlockHeader as _;
2use alloy_eips::{BlockId, BlockNumHash};
3use alloy_evm::block::calc::{base_block_reward, block_reward, ommer_reward};
4use alloy_primitives::{
5    map::{HashMap, HashSet},
6    Address, BlockHash, Bytes, B256, U256,
7};
8use alloy_rpc_types_eth::{
9    state::{EvmOverrides, StateOverride},
10    BlockOverrides, Index,
11};
12use alloy_rpc_types_trace::{
13    filter::TraceFilter,
14    opcode::{BlockOpcodeGas, TransactionOpcodeGas},
15    parity::*,
16    tracerequest::TraceCallRequest,
17};
18use async_trait::async_trait;
19use futures::{FutureExt, StreamExt};
20use jsonrpsee::core::RpcResult;
21use reth_chainspec::ChainSpecProvider;
22use reth_primitives_traits::{BlockBody, BlockHeader};
23use reth_rpc_api::TraceApiServer;
24use reth_rpc_convert::RpcTxReq;
25use reth_rpc_eth_api::{
26    helpers::{Call, LoadPendingBlock, LoadTransaction, Trace, TraceExt},
27    FromEthApiError, RpcNodeCore,
28};
29use reth_rpc_eth_types::{error::EthApiError, EthConfig};
30use reth_storage_api::{BlockNumReader, BlockReader};
31use reth_tasks::{cancel::is_cancelled, pool::BlockingTaskGuard};
32use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
33use revm::DatabaseCommit;
34use revm_inspectors::{
35    opcode::OpcodeGasInspector,
36    storage::StorageInspector,
37    tracing::{TracingInspector, TracingInspectorConfig},
38};
39use serde::{Deserialize, Serialize};
40use std::sync::Arc;
41use tokio::sync::{AcquireError, OwnedSemaphorePermit};
42
43/// Maximum number of `trace_filter` blocks replayed concurrently.
44const TRACE_FILTER_BLOCK_BUFFER_SIZE: usize = 4;
45/// Number of blocks fetched per provider range read in `trace_filter`.
46const TRACE_FILTER_FETCH_CHUNK_SIZE: usize = 16;
47
48/// `trace` API implementation.
49///
50/// This type provides the functionality for handling `trace` related requests.
51pub struct TraceApi<Eth> {
52    inner: Arc<TraceApiInner<Eth>>,
53}
54
55// === impl TraceApi ===
56
57impl<Eth> TraceApi<Eth> {
58    /// Create a new instance of the [`TraceApi`]
59    pub fn new(
60        eth_api: Eth,
61        blocking_task_guard: BlockingTaskGuard,
62        eth_config: EthConfig,
63    ) -> Self {
64        let inner = Arc::new(TraceApiInner { eth_api, blocking_task_guard, eth_config });
65        Self { inner }
66    }
67
68    /// Acquires a permit to execute a tracing call.
69    async fn acquire_trace_permit(
70        &self,
71    ) -> std::result::Result<OwnedSemaphorePermit, AcquireError> {
72        self.inner.blocking_task_guard.clone().acquire_owned().await
73    }
74
75    /// Access the underlying `Eth` API.
76    pub fn eth_api(&self) -> &Eth {
77        &self.inner.eth_api
78    }
79}
80
81impl<Eth: RpcNodeCore> TraceApi<Eth> {
82    /// Access the underlying provider.
83    pub fn provider(&self) -> &Eth::Provider {
84        self.inner.eth_api.provider()
85    }
86}
87
88// === impl TraceApi === //
89
90impl<Eth> TraceApi<Eth>
91where
92    // tracing methods do _not_ read from mempool, hence no `LoadBlock` trait
93    // bound
94    Eth: Trace + Call + LoadPendingBlock + LoadTransaction + 'static,
95{
96    /// Executes the given call and returns a number of possible traces for it.
97    pub async fn trace_call(
98        &self,
99        trace_request: TraceCallRequest<RpcTxReq<Eth::NetworkTypes>>,
100    ) -> Result<TraceResults, Eth::Error> {
101        let at = trace_request.block_id.unwrap_or_default();
102        let config = TracingInspectorConfig::from_parity_config(&trace_request.trace_types);
103        let overrides =
104            EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides);
105        let mut inspector = TracingInspector::new(config);
106        let this = self.clone();
107        self.eth_api()
108            .spawn_with_call_at(trace_request.call, at, overrides, move |db, evm_env, tx_env| {
109                let res = this.eth_api().inspect(&mut *db, evm_env, tx_env, &mut inspector)?;
110                let trace_res = inspector
111                    .into_parity_builder()
112                    .into_trace_results_with_state(&res, &trace_request.trace_types, &db)
113                    .map_err(Eth::Error::from_eth_err)?;
114                Ok(trace_res)
115            })
116            .boxed()
117            .await
118    }
119
120    /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces.
121    pub async fn trace_raw_transaction(
122        &self,
123        tx: Bytes,
124        trace_types: HashSet<TraceType>,
125        block_id: Option<BlockId>,
126    ) -> Result<TraceResults, Eth::Error> {
127        let tx = self
128            .eth_api()
129            .recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
130            .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
131
132        let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
133
134        self.eth_api()
135            .spawn_with_state_at_block(at, move |this, mut db| {
136                let mut inspector =
137                    TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
138                let res = this.inspect(&mut db, evm_env, tx, &mut inspector)?;
139
140                inspector
141                    .into_parity_builder()
142                    .into_trace_results_with_state(&res, &trace_types, &db)
143                    .map_err(Eth::Error::from_eth_err)
144            })
145            .await
146    }
147
148    /// Performs multiple call traces on top of the same block. Defaults to the latest block when
149    /// no block is specified. Each call is executed with the preceding calls applied first.
150    ///
151    /// Note: Allows tracing dependent transactions, hence all transactions are traced in sequence
152    pub async fn trace_call_many(
153        &self,
154        calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
155        block_id: Option<BlockId>,
156    ) -> Result<Vec<TraceResults>, Eth::Error> {
157        let at = block_id.unwrap_or_default();
158        let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
159
160        // execute all transactions on top of each other and record the traces
161        self.eth_api()
162            .spawn_with_state_at_block(at, move |eth_api, mut db| {
163                let mut results = Vec::with_capacity(calls.len());
164                let mut calls = calls.into_iter().peekable();
165
166                while let Some((call, trace_types)) = calls.next() {
167                    if is_cancelled() {
168                        return Err(EthApiError::InternalEthError.into())
169                    }
170                    let (evm_env, tx_env) = eth_api.prepare_call_env(
171                        evm_env.clone(),
172                        call,
173                        &mut db,
174                        Default::default(),
175                    )?;
176                    let config = TracingInspectorConfig::from_parity_config(&trace_types);
177                    let mut inspector = TracingInspector::new(config);
178                    let res = eth_api.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
179
180                    let trace_res = inspector
181                        .into_parity_builder()
182                        .into_trace_results_with_state(&res, &trace_types, &db)
183                        .map_err(Eth::Error::from_eth_err)?;
184
185                    results.push(trace_res);
186
187                    // need to apply the state changes of this call before executing the
188                    // next call
189                    if calls.peek().is_some() {
190                        db.commit(res.state)
191                    }
192                }
193
194                Ok(results)
195            })
196            .await
197    }
198
199    /// Replays a transaction, returning the traces or `None` if the transaction does not exist.
200    pub async fn replay_transaction(
201        &self,
202        hash: B256,
203        trace_types: HashSet<TraceType>,
204    ) -> Result<Option<TraceResultsWithTransactionHash>, Eth::Error> {
205        let config = TracingInspectorConfig::from_parity_config(&trace_types);
206        self.eth_api()
207            .spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
208                let trace_res = inspector
209                    .into_parity_builder()
210                    .into_trace_results_with_state(&res, &trace_types, &db)
211                    .map_err(Eth::Error::from_eth_err)?;
212                Ok(TraceResultsWithTransactionHash {
213                    transaction_hash: hash,
214                    full_trace: trace_res,
215                })
216            })
217            .await
218    }
219
220    /// Returns the transaction trace at the given `traceAddress` path.
221    ///
222    /// An empty path selects the root. Returns `None` if the transaction or path does not exist.
223    pub async fn trace_get(
224        &self,
225        hash: B256,
226        indices: Vec<usize>,
227    ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
228        self.eth_api()
229            .spawn_trace_transaction_in_block(
230                hash,
231                TracingInspectorConfig::default_parity(),
232                move |tx_info, inspector, _, _| {
233                    Ok(inspector
234                        .into_parity_builder()
235                        .into_localized_transaction_traces_iter(tx_info)
236                        .find(|trace| trace.trace.trace_address == indices))
237                },
238            )
239            .await
240            .map(Option::flatten)
241    }
242
243    /// Returns the transaction trace at the given flat index, unlike [`Self::trace_get`], which
244    /// selects a `traceAddress` path.
245    ///
246    /// Returns `None` if the trace object at that index does not exist
247    pub async fn trace_get_index(
248        &self,
249        hash: B256,
250        index: usize,
251    ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
252        self.eth_api()
253            .spawn_trace_transaction_in_block(
254                hash,
255                TracingInspectorConfig::default_parity(),
256                move |tx_info, inspector, _, _| {
257                    Ok(inspector
258                        .into_parity_builder()
259                        .into_localized_transaction_traces_iter(tx_info)
260                        .nth(index))
261                },
262            )
263            .await
264            .map(Option::flatten)
265    }
266
267    /// Returns all traces for the given transaction hash
268    pub async fn trace_transaction(
269        &self,
270        hash: B256,
271    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
272        self.eth_api()
273            .spawn_trace_transaction_in_block(
274                hash,
275                TracingInspectorConfig::default_parity(),
276                move |tx_info, inspector, _, _| {
277                    let traces =
278                        inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
279                    Ok(traces)
280                },
281            )
282            .await
283    }
284
285    /// Returns all opcodes with their count and combined gas usage for the given transaction in no
286    /// particular order.
287    pub async fn trace_transaction_opcode_gas(
288        &self,
289        tx_hash: B256,
290    ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
291        self.eth_api()
292            .spawn_trace_transaction_in_block_with_inspector(
293                tx_hash,
294                OpcodeGasInspector::default(),
295                move |_tx_info, inspector, _res, _| {
296                    let trace = TransactionOpcodeGas {
297                        transaction_hash: tx_hash,
298                        opcode_gas: inspector.opcode_gas_iter().collect(),
299                    };
300                    Ok(trace)
301                },
302            )
303            .await
304    }
305
306    /// Calculates the base block reward for the given block:
307    ///
308    /// - the genesis block is not mined, so no block rewards are given
309    /// - if Paris hardfork is activated, no block rewards are given
310    /// - if Paris hardfork is not activated, calculate block rewards with block number only
311    fn calculate_base_block_reward<H: BlockHeader>(&self, header: &H) -> Option<u128> {
312        if header.number() == 0 {
313            return None
314        }
315
316        base_block_reward(self.provider().chain_spec(), header.number())
317    }
318
319    /// Extracts the reward traces for the given block:
320    ///  - block reward
321    ///  - uncle rewards
322    fn extract_reward_traces<H: BlockHeader>(
323        &self,
324        header: &H,
325        block_hash: BlockHash,
326        ommers: Option<&[H]>,
327        base_block_reward: u128,
328    ) -> Vec<LocalizedTransactionTrace> {
329        let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
330        let mut traces = Vec::with_capacity(ommers_cnt + 1);
331
332        let block = BlockNumHash::new(header.number(), block_hash);
333        let block_reward = block_reward(base_block_reward, ommers_cnt);
334        traces.push(
335            RewardAction {
336                author: header.beneficiary(),
337                reward_type: RewardType::Block,
338                value: U256::from(block_reward),
339            }
340            .into_localized_trace(block),
341        );
342
343        let Some(ommers) = ommers else { return traces };
344
345        for uncle in ommers {
346            let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
347            traces.push(
348                RewardAction {
349                    author: uncle.beneficiary(),
350                    reward_type: RewardType::Uncle,
351                    value: U256::from(uncle_reward),
352                }
353                .into_localized_trace(block),
354            );
355        }
356        traces
357    }
358}
359
360impl<Eth> TraceApi<Eth>
361where
362    // tracing methods read from mempool, hence `LoadBlock` trait bound via
363    // `TraceExt`
364    Eth: TraceExt + 'static,
365{
366    /// Returns all transaction traces that match the given filter.
367    ///
368    /// This is similar to [`Self::trace_block`] but only returns traces for transactions that match
369    /// the filter. Omitted range bounds default to the latest block.
370    pub async fn trace_filter(
371        &self,
372        filter: TraceFilter,
373    ) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
374        // We'll reuse the matcher across multiple blocks that are traced in parallel
375        let matcher = Arc::new(filter.matcher());
376        let TraceFilter { from_block, to_block, mut after, count, .. } = filter;
377
378        let latest_block = self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?;
379        let start = from_block.unwrap_or(latest_block);
380        if start > latest_block {
381            // can't trace that range
382            return Err(EthApiError::HeaderNotFound(start.into()).into());
383        }
384        let end = to_block.unwrap_or(latest_block);
385        if end > latest_block {
386            return Err(EthApiError::HeaderNotFound(end.into()).into());
387        }
388
389        // Check if the requested range overlaps with pruned history (EIP-4444)
390        let earliest_block =
391            self.provider().earliest_block_number().map_err(Eth::Error::from_eth_err)?;
392        if start < earliest_block {
393            return Err(EthApiError::PrunedHistoryUnavailable {
394                requested: start,
395                earliest_available: earliest_block,
396            }
397            .into());
398        }
399
400        if start > end {
401            return Err(EthApiError::InvalidParams(
402                "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
403            )
404            .into())
405        }
406
407        // ensure that the range is not too large, since every block in the range may be replayed
408        let distance = end.saturating_sub(start);
409        if distance > self.inner.eth_config.max_trace_filter_blocks {
410            return Err(EthApiError::InvalidParams(format!(
411                "Block range too large; currently limited to {} blocks",
412                self.inner.eth_config.max_trace_filter_blocks
413            ))
414            .into())
415        }
416
417        let mut all_traces = Vec::new();
418        let block_buffer_size =
419            self.inner.eth_config.max_tracing_requests.clamp(1, TRACE_FILTER_BLOCK_BUFFER_SIZE);
420        let mut include_reward_traces = true;
421
422        for chunk_start in (start..=end).step_by(TRACE_FILTER_FETCH_CHUNK_SIZE) {
423            let chunk_end = (chunk_start + TRACE_FILTER_FETCH_CHUNK_SIZE as u64 - 1).min(end);
424
425            let blocks = self
426                .eth_api()
427                .spawn_blocking_io(move |this| {
428                    let blocks = this
429                        .provider()
430                        .recovered_block_range(chunk_start..=chunk_end)
431                        .map_err(Eth::Error::from_eth_err)?;
432
433                    Ok(blocks.into_iter().map(Arc::new).collect::<Vec<_>>())
434                })
435                .await?;
436
437            let mut block_replays = futures::stream::iter(blocks)
438                .map(|block| {
439                    let this = self.clone();
440                    let matcher = matcher.clone();
441
442                    let block_hash = block.hash();
443
444                    async move {
445                        let permit = this.acquire_trace_permit().await;
446                        let traces = this
447                            .eth_api()
448                            .trace_block_until(
449                                block_hash.into(),
450                                Some(block.clone()),
451                                None,
452                                TracingInspectorConfig::default_parity(),
453                                move |tx_info, mut ctx| {
454                                    // Keep the block replay permit inside the spawned replay task.
455                                    let _block_replay_permit = &permit;
456                                    let mut traces = ctx
457                                        .take_inspector()
458                                        .into_parity_builder()
459                                        .into_localized_transaction_traces(tx_info);
460                                    traces.retain(|trace| matcher.matches(&trace.trace));
461                                    Ok(Some(traces))
462                                },
463                            )
464                            .await?;
465
466                        Ok::<_, Eth::Error>((block, traces))
467                    }
468                })
469                .buffered(block_buffer_size);
470
471            while let Some(block_replay) = block_replays.next().await {
472                let (block, traces) = block_replay?;
473                let reward_traces = if include_reward_traces {
474                    if let Some(base_block_reward) =
475                        self.calculate_base_block_reward(block.header())
476                    {
477                        self.extract_reward_traces(
478                            block.header(),
479                            block.hash(),
480                            block.body().ommers(),
481                            base_block_reward,
482                        )
483                        .into_iter()
484                        .filter(|trace| matcher.matches(&trace.trace))
485                        .collect::<Vec<_>>()
486                    } else {
487                        // Blocks are processed in ascending order, so once a historical range
488                        // reaches post-Paris blocks, later blocks in the range have no rewards.
489                        // The genesis block has no reward, but later pre-Paris blocks do.
490                        include_reward_traces = block.number() == 0;
491                        Vec::new()
492                    }
493                } else {
494                    Vec::new()
495                };
496
497                if let Some(traces) = traces {
498                    all_traces.extend(traces.into_iter().flatten().flatten());
499                }
500                all_traces.extend(reward_traces);
501
502                if let Some(traces) =
503                    apply_trace_filter_pagination(&mut all_traces, &mut after, count)
504                {
505                    return Ok(traces)
506                }
507            }
508        }
509
510        // If `after` is greater than or equal to the number of matched traces, it returns an
511        // empty array.
512        if let Some(cutoff) = after.map(|a| a as usize) &&
513            cutoff >= all_traces.len()
514        {
515            return Ok(vec![])
516        }
517
518        Ok(all_traces)
519    }
520
521    /// Returns traces created at given block.
522    pub async fn trace_block(
523        &self,
524        block_id: BlockId,
525    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
526        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
527            return Err(EthApiError::HeaderNotFound(block_id).into());
528        };
529
530        let mut traces = self
531            .eth_api()
532            .trace_block_with(
533                block_id,
534                Some(block.clone()),
535                TracingInspectorConfig::default_parity(),
536                |tx_info, mut ctx| {
537                    let traces = ctx
538                        .take_inspector()
539                        .into_parity_builder()
540                        .into_localized_transaction_traces(tx_info);
541                    Ok(traces)
542                },
543            )
544            .await?
545            .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
546
547        if let Some(traces) = traces.as_mut() &&
548            let Some(base_block_reward) = self.calculate_base_block_reward(block.header())
549        {
550            traces.extend(self.extract_reward_traces(
551                block.header(),
552                block.hash(),
553                block.body().ommers(),
554                base_block_reward,
555            ));
556        }
557
558        Ok(traces)
559    }
560
561    /// Replays all transactions in a block
562    pub async fn replay_block_transactions(
563        &self,
564        block_id: BlockId,
565        trace_types: HashSet<TraceType>,
566    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
567        self.eth_api()
568            .trace_block_with(
569                block_id,
570                None,
571                TracingInspectorConfig::from_parity_config(&trace_types),
572                move |tx_info, mut ctx| {
573                    let full_trace = ctx
574                        .take_inspector()
575                        .into_parity_builder()
576                        .into_trace_results_with_state_parts(
577                            &ctx.result,
578                            ctx.state,
579                            &trace_types,
580                            &ctx.db,
581                        )
582                        .map_err(Eth::Error::from_eth_err)?;
583
584                    let trace = TraceResultsWithTransactionHash {
585                        transaction_hash: tx_info.hash.expect("tx hash is set"),
586                        full_trace,
587                    };
588                    Ok(trace)
589                },
590            )
591            .await
592    }
593
594    /// Returns the opcodes of all transactions in the given block.
595    ///
596    /// This is the same as [`Self::trace_transaction_opcode_gas`] but for all transactions in a
597    /// block.
598    pub async fn trace_block_opcode_gas(
599        &self,
600        block_id: BlockId,
601    ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
602        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
603            return Err(EthApiError::HeaderNotFound(block_id).into());
604        };
605
606        let Some(transactions) = self
607            .eth_api()
608            .trace_block_inspector(
609                block_id,
610                Some(block.clone()),
611                OpcodeGasInspector::default,
612                move |tx_info, ctx| {
613                    let trace = TransactionOpcodeGas {
614                        transaction_hash: tx_info.hash.expect("tx hash is set"),
615                        opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
616                    };
617                    Ok(trace)
618                },
619            )
620            .await?
621        else {
622            return Ok(None);
623        };
624
625        Ok(Some(BlockOpcodeGas {
626            block_hash: block.hash(),
627            block_number: block.number(),
628            transactions,
629        }))
630    }
631
632    /// Returns all storage slots accessed during transaction execution along with their access
633    /// counts.
634    pub async fn trace_block_storage_access(
635        &self,
636        block_id: BlockId,
637    ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
638        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
639            return Err(EthApiError::HeaderNotFound(block_id).into());
640        };
641
642        let Some(transactions) = self
643            .eth_api()
644            .trace_block_inspector(
645                block_id,
646                Some(block.clone()),
647                StorageInspector::default,
648                move |tx_info, mut ctx| {
649                    let unique_loads = ctx.inspector.unique_loads();
650                    let warm_loads = ctx.inspector.warm_loads();
651                    let trace = TransactionStorageAccess {
652                        transaction_hash: tx_info.hash.expect("tx hash is set"),
653                        storage_access: ctx.take_inspector().into_accessed_slots(),
654                        unique_loads,
655                        warm_loads,
656                    };
657                    Ok(trace)
658                },
659            )
660            .await?
661        else {
662            return Ok(None);
663        };
664
665        Ok(Some(BlockStorageAccess {
666            block_hash: block.hash(),
667            block_number: block.number(),
668            transactions,
669        }))
670    }
671}
672
673fn apply_trace_filter_pagination(
674    all_traces: &mut Vec<LocalizedTransactionTrace>,
675    after: &mut Option<u64>,
676    count: Option<u64>,
677) -> Option<Vec<LocalizedTransactionTrace>> {
678    // Skips the first `after` number of matching traces.
679    if let Some(cutoff) = after.map(|a| a as usize) &&
680        cutoff < all_traces.len()
681    {
682        all_traces.drain(..cutoff);
683        // we removed the first `after` traces
684        *after = None;
685    }
686
687    // Return at most `count` traces after `after` has been consumed.
688    if after.is_none() &&
689        let Some(count) = count
690    {
691        let count = count as usize;
692        if count < all_traces.len() {
693            all_traces.truncate(count);
694            return Some(std::mem::take(all_traces))
695        }
696    }
697
698    None
699}
700
701#[async_trait]
702impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
703where
704    Eth: TraceExt + 'static,
705{
706    /// Executes the given call and returns a number of possible traces for it.
707    ///
708    /// Handler for `trace_call`
709    async fn trace_call(
710        &self,
711        call: RpcTxReq<Eth::NetworkTypes>,
712        trace_types: HashSet<TraceType>,
713        block_id: Option<BlockId>,
714        state_overrides: Option<StateOverride>,
715        block_overrides: Option<Box<BlockOverrides>>,
716    ) -> RpcResult<TraceResults> {
717        let _permit = self.acquire_trace_permit().await;
718        let request =
719            TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
720        Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
721    }
722
723    /// Handler for `trace_callMany`
724    async fn trace_call_many(
725        &self,
726        calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
727        block_id: Option<BlockId>,
728    ) -> RpcResult<Vec<TraceResults>> {
729        let _permit = self.acquire_trace_permit().await;
730        Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
731    }
732
733    /// Handler for `trace_rawTransaction`
734    async fn trace_raw_transaction(
735        &self,
736        data: Bytes,
737        trace_types: HashSet<TraceType>,
738        block_id: Option<BlockId>,
739    ) -> RpcResult<TraceResults> {
740        let _permit = self.acquire_trace_permit().await;
741        Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
742            .await
743            .map_err(Into::into)?)
744    }
745
746    /// Handler for `trace_replayBlockTransactions`
747    async fn replay_block_transactions(
748        &self,
749        block_id: BlockId,
750        trace_types: HashSet<TraceType>,
751    ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
752        let _permit = self.acquire_trace_permit().await;
753        Ok(Self::replay_block_transactions(self, block_id, trace_types)
754            .await
755            .map_err(Into::into)?)
756    }
757
758    /// Handler for `trace_replayTransaction`
759    async fn replay_transaction(
760        &self,
761        transaction: B256,
762        trace_types: HashSet<TraceType>,
763    ) -> RpcResult<Option<TraceResultsWithTransactionHash>> {
764        let _permit = self.acquire_trace_permit().await;
765        Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
766    }
767
768    /// Handler for `trace_block`
769    async fn trace_block(
770        &self,
771        block_id: BlockId,
772    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
773        let _permit = self.acquire_trace_permit().await;
774        Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
775    }
776
777    /// Handler for `trace_filter`
778    ///
779    /// This is similar to `eth_getLogs` but for traces.
780    ///
781    /// # Limitations
782    /// This currently requires block filter fields, since reth does not have address indices yet.
783    async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
784        Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
785    }
786
787    /// Returns transaction trace at given index.
788    /// Handler for `trace_get`
789    async fn trace_get(
790        &self,
791        hash: B256,
792        indices: Vec<Index>,
793    ) -> RpcResult<Option<LocalizedTransactionTrace>> {
794        let _permit = self.acquire_trace_permit().await;
795        Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
796            .await
797            .map_err(Into::into)?)
798    }
799
800    /// Handler for `trace_transaction`
801    async fn trace_transaction(
802        &self,
803        hash: B256,
804    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
805        let _permit = self.acquire_trace_permit().await;
806        Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
807    }
808
809    /// Handler for `trace_transactionOpcodeGas`
810    async fn trace_transaction_opcode_gas(
811        &self,
812        tx_hash: B256,
813    ) -> RpcResult<Option<TransactionOpcodeGas>> {
814        let _permit = self.acquire_trace_permit().await;
815        Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
816    }
817
818    /// Handler for `trace_blockOpcodeGas`
819    async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
820        let _permit = self.acquire_trace_permit().await;
821        Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
822    }
823}
824
825impl<Eth> std::fmt::Debug for TraceApi<Eth> {
826    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
827        f.debug_struct("TraceApi").finish_non_exhaustive()
828    }
829}
830impl<Eth> Clone for TraceApi<Eth> {
831    fn clone(&self) -> Self {
832        Self { inner: Arc::clone(&self.inner) }
833    }
834}
835
836struct TraceApiInner<Eth> {
837    /// Access to commonly used code of the `eth` namespace
838    eth_api: Eth,
839    // restrict the number of concurrent calls to `trace_*`
840    blocking_task_guard: BlockingTaskGuard,
841    // eth config settings
842    eth_config: EthConfig,
843}
844
845/// Response type for storage tracing that contains all accessed storage slots
846/// for a transaction.
847#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
848#[serde(rename_all = "camelCase")]
849pub struct TransactionStorageAccess {
850    /// Hash of the transaction
851    pub transaction_hash: B256,
852    /// Tracks storage slots and access counter.
853    pub storage_access: HashMap<Address, HashMap<B256, u64>>,
854    /// Number of unique storage loads
855    pub unique_loads: u64,
856    /// Number of warm storage loads
857    pub warm_loads: u64,
858}
859
860/// Response type for storage tracing that contains all accessed storage slots
861#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
862#[serde(rename_all = "camelCase")]
863pub struct BlockStorageAccess {
864    /// The block hash
865    pub block_hash: BlockHash,
866    /// The block's number
867    pub block_number: u64,
868    /// All executed transactions in the block in the order they were executed
869    pub transactions: Vec<TransactionStorageAccess>,
870}
871
872#[cfg(test)]
873mod tests {
874    use super::*;
875    use crate::EthApiBuilder;
876    use alloy_consensus::Header;
877    use alloy_genesis::Genesis;
878    use alloy_rpc_types_eth::TransactionRequest;
879    use reth_chainspec::ChainSpecBuilder;
880    use reth_db_common::init::init_genesis;
881    use reth_ethereum_primitives::{Block, BlockBody};
882    use reth_evm_ethereum::EthEvmConfig;
883    use reth_network_api::noop::NoopNetwork;
884    use reth_primitives_traits::Block as _;
885    use reth_provider::{
886        providers::BlockchainProvider,
887        test_utils::{
888            create_test_provider_factory_with_chain_spec, ExtendedAccount, MockEthProvider,
889        },
890        BlockWriter, StageCheckpointWriter,
891    };
892    use reth_transaction_pool::test_utils::testing_pool;
893
894    #[tokio::test]
895    async fn trace_call_many_defaults_to_latest() {
896        let provider = MockEthProvider::default();
897        let target = Address::with_last_byte(0x42);
898        // Return NUMBER as a 32-byte word.
899        provider.add_account(
900            target,
901            ExtendedAccount::new(0, U256::ZERO)
902                .with_bytecode("4360005260206000f3".parse().unwrap()),
903        );
904        let header = Header { number: 1, gas_limit: 30_000_000, ..Default::default() };
905        provider.add_block(header.hash_slow(), Block { header, body: BlockBody::default() });
906        let eth_api = EthApiBuilder::new(
907            provider.clone(),
908            testing_pool(),
909            NoopNetwork::default(),
910            EthEvmConfig::new(provider.chain_spec()),
911        )
912        .build();
913        let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
914        let calls = vec![(TransactionRequest::default().to(target), HashSet::default())];
915
916        let omitted = api.trace_call_many(calls.clone(), None).await.unwrap();
917        let latest = api.trace_call_many(calls.clone(), Some(BlockId::latest())).await.unwrap();
918        let request = serde_json::json!({
919            "jsonrpc": "2.0", "id": 1, "method": "trace_callMany", "params": [calls.clone()],
920        });
921        let pending = api.trace_call_many(calls, Some(BlockId::pending())).await.unwrap();
922        assert_eq!(omitted, latest);
923        assert_ne!(omitted, pending);
924        assert_eq!(U256::from_be_slice(&omitted[0].output), U256::from(1));
925        assert_eq!(U256::from_be_slice(&pending[0].output), U256::from(2));
926
927        let module = api.into_rpc();
928        let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
929        let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
930        assert_eq!(response["result"], serde_json::to_value(latest).unwrap());
931    }
932
933    #[tokio::test]
934    async fn trace_filter_defaults_to_latest() {
935        let provider = MockEthProvider::default();
936        for number in [1, 2] {
937            let header = Header { number, gas_limit: 30_000_000, ..Default::default() };
938            provider.add_block(header.hash_slow(), Block { header, body: BlockBody::default() });
939        }
940        let eth_api = EthApiBuilder::new(
941            provider.clone(),
942            testing_pool(),
943            NoopNetwork::default(),
944            EthEvmConfig::new(provider.chain_spec()),
945        )
946        .build();
947        let api = TraceApi::new(
948            eth_api,
949            BlockingTaskGuard::new(1),
950            EthConfig::default().max_trace_filter_blocks(1),
951        );
952
953        let omitted = api.trace_filter(TraceFilter::default()).await.unwrap();
954        let latest =
955            api.trace_filter(TraceFilter::default().from_block(2).to_block(2)).await.unwrap();
956        assert_eq!(omitted, latest);
957        assert!(api.trace_filter(TraceFilter::default().from_block(1).to_block(2)).await.is_ok());
958
959        let module = api.into_rpc();
960        let request = serde_json::json!({
961            "jsonrpc": "2.0", "id": 1, "method": "trace_filter", "params": [{"toBlock": "0x1"}],
962        });
963        let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
964        let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
965        assert_eq!(
966            response["error"],
967            serde_json::json!({
968                "code": -32602,
969                "message": "invalid parameters: fromBlock cannot be greater than toBlock",
970            })
971        );
972    }
973
974    #[tokio::test]
975    async fn trace_get_selects_tree_paths() {
976        use crate::EthApiBuilder;
977        use alloy_consensus::{Header, TxLegacy};
978        use alloy_primitives::{Signature, TxKind};
979        use reth_chain_state::CanonStateNotification;
980        use reth_ethereum_primitives::{Block, BlockBody, TransactionSigned};
981        use reth_evm_ethereum::EthEvmConfig;
982        use reth_execution_types::{Chain, ExecutionOutcome};
983        use reth_network_api::noop::NoopNetwork;
984        use reth_primitives_traits::{RecoveredBlock, SignerRecoverable};
985        use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
986        use reth_rpc_eth_types::cache::cache_new_blocks_task;
987        use reth_transaction_pool::test_utils::testing_pool;
988
989        let provider = MockEthProvider::default();
990        // The root calls 0x43 (which calls 0x44), then calls 0x45.
991        for (address, code) in [
992            (0x42, "60006000600060006000604361fffff15060006000600060006000604561fffff15000"),
993            (0x43, "600060006000600060006044611000f15000"),
994        ] {
995            provider.add_account(
996                Address::with_last_byte(address),
997                ExtendedAccount::new(0, U256::ZERO).with_bytecode(code.parse().unwrap()),
998            );
999        }
1000        let tx = TransactionSigned::new_unhashed(
1001            TxLegacy {
1002                gas_limit: 500_000,
1003                to: TxKind::Call(Address::with_last_byte(0x42)),
1004                value: U256::from(1),
1005                ..Default::default()
1006            }
1007            .into(),
1008            Signature::test_signature(),
1009        );
1010        let hash = *tx.hash();
1011        let sender = tx.recover_signer().unwrap();
1012        provider.add_account(sender, ExtendedAccount::new(0, U256::from(1_000_000)));
1013        let parent = Header { gas_limit: 30_000_000, ..Default::default() };
1014        let parent_hash = parent.hash_slow();
1015        provider.add_header(parent_hash, parent);
1016        let block = Block {
1017            header: Header { parent_hash, number: 1, gas_limit: 30_000_000, ..Default::default() },
1018            body: BlockBody { transactions: vec![tx], ..Default::default() },
1019        };
1020        let block_hash = block.header.hash_slow();
1021        provider.add_block(block_hash, block.clone());
1022        let eth_api = EthApiBuilder::new(
1023            provider.clone(),
1024            testing_pool(),
1025            NoopNetwork::default(),
1026            EthEvmConfig::new(provider.chain_spec()),
1027        )
1028        .build();
1029        // MockEthProvider does not implement recovered_block, so seed the RPC cache.
1030        cache_new_blocks_task(
1031            eth_api.cache().clone(),
1032            futures::stream::iter([CanonStateNotification::Commit {
1033                new: Arc::new(Chain::new(
1034                    [RecoveredBlock::new_unhashed(block, vec![sender])],
1035                    ExecutionOutcome {
1036                        receipts: vec![vec![]],
1037                        first_block: 1,
1038                        ..Default::default()
1039                    },
1040                    Default::default(),
1041                )),
1042            }]),
1043        )
1044        .await;
1045        let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1046        let module = api.clone().into_rpc();
1047        let traces = api.trace_transaction(hash).await.unwrap().unwrap();
1048        let paths: Vec<_> = traces.iter().map(|trace| trace.trace.trace_address.clone()).collect();
1049        assert_eq!(paths, [vec![], vec![0], vec![0, 0], vec![1]]);
1050        for (path, expected) in [
1051            (vec![], Some(&traces[0])),
1052            (vec![0], Some(&traces[1])),
1053            (vec![0, 0], Some(&traces[2])),
1054            (vec![1], Some(&traces[3])),
1055            (vec![2], None),
1056            (vec![0, 1], None),
1057            (vec![1, 0], None),
1058        ] {
1059            let indices: Vec<_> = path.iter().map(|index| format!("0x{index:x}")).collect();
1060            let request = serde_json::json!({
1061                "jsonrpc": "2.0", "id": 1, "method": "trace_get", "params": [hash, indices],
1062            });
1063            let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
1064            let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
1065            assert!(response.get("error").is_none(), "{response}");
1066            assert_eq!(response["result"], serde_json::to_value(expected).unwrap());
1067        }
1068        assert!(api.trace_get(B256::ZERO, vec![]).await.unwrap().is_none());
1069        assert!(api.trace_get(B256::ZERO, vec![0, 0]).await.unwrap().is_none());
1070        assert_eq!(api.trace_get_index(hash, 2).await.unwrap().as_ref(), Some(&traces[2]));
1071    }
1072
1073    #[tokio::test]
1074    async fn replay_missing_transaction_returns_null() {
1075        use crate::EthApiBuilder;
1076        use reth_evm_ethereum::EthEvmConfig;
1077        use reth_network_api::noop::NoopNetwork;
1078        use reth_provider::test_utils::MockEthProvider;
1079        use reth_transaction_pool::test_utils::testing_pool;
1080
1081        let provider = MockEthProvider::default();
1082        let eth_api = EthApiBuilder::new(
1083            provider.clone(),
1084            testing_pool(),
1085            NoopNetwork::default(),
1086            EthEvmConfig::new(provider.chain_spec()),
1087        )
1088        .build();
1089        let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1090        let module = api.into_rpc();
1091        for types in [serde_json::json!([]), serde_json::json!(["trace", "stateDiff", "vmTrace"])] {
1092            let request = serde_json::json!({
1093                "jsonrpc": "2.0",
1094                "id": 1,
1095                "method": "trace_replayTransaction",
1096                "params": [B256::with_last_byte(1), types],
1097            });
1098            let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
1099            let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
1100            assert_eq!(response, serde_json::json!({"jsonrpc": "2.0", "id": 1, "result": null}));
1101        }
1102    }
1103
1104    #[tokio::test]
1105    async fn replay_transaction_includes_transaction_hash() {
1106        use crate::EthApiBuilder;
1107        use alloy_consensus::{Header, TxLegacy};
1108        use alloy_primitives::{Signature, TxKind};
1109        use reth_chain_state::CanonStateNotification;
1110        use reth_ethereum_primitives::{Block, BlockBody, TransactionSigned};
1111        use reth_evm_ethereum::EthEvmConfig;
1112        use reth_execution_types::{Chain, ExecutionOutcome};
1113        use reth_network_api::noop::NoopNetwork;
1114        use reth_primitives_traits::{RecoveredBlock, SignerRecoverable};
1115        use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1116        use reth_rpc_eth_types::cache::cache_new_blocks_task;
1117        use reth_transaction_pool::test_utils::testing_pool;
1118
1119        let provider = MockEthProvider::default();
1120        let tx = TransactionSigned::new_unhashed(
1121            TxLegacy {
1122                gas_limit: 21_000,
1123                to: TxKind::Call(Address::with_last_byte(0x42)),
1124                value: U256::from(1),
1125                ..Default::default()
1126            }
1127            .into(),
1128            Signature::test_signature(),
1129        );
1130        let hash = *tx.hash();
1131        let sender = tx.recover_signer().unwrap();
1132        provider.add_account(sender, ExtendedAccount::new(0, U256::from(1_000_000)));
1133        let parent = Header { gas_limit: 30_000_000, ..Default::default() };
1134        let parent_hash = parent.hash_slow();
1135        provider.add_header(parent_hash, parent);
1136        let block = Block {
1137            header: Header { parent_hash, number: 1, gas_limit: 30_000_000, ..Default::default() },
1138            body: BlockBody { transactions: vec![tx], ..Default::default() },
1139        };
1140        let block_hash = block.header.hash_slow();
1141        provider.add_block(block_hash, block.clone());
1142        let eth_api = EthApiBuilder::new(
1143            provider.clone(),
1144            testing_pool(),
1145            NoopNetwork::default(),
1146            EthEvmConfig::new(provider.chain_spec()),
1147        )
1148        .build();
1149        // MockEthProvider does not implement recovered_block, so seed the RPC cache.
1150        cache_new_blocks_task(
1151            eth_api.cache().clone(),
1152            futures::stream::iter([CanonStateNotification::Commit {
1153                new: Arc::new(Chain::new(
1154                    [RecoveredBlock::new_unhashed(block, vec![sender])],
1155                    ExecutionOutcome {
1156                        receipts: vec![vec![]],
1157                        first_block: 1,
1158                        ..Default::default()
1159                    },
1160                    Default::default(),
1161                )),
1162            }]),
1163        )
1164        .await;
1165        let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1166        let module = api.clone().into_rpc();
1167        for types in
1168            [HashSet::default(), HashSet::from_iter([TraceType::Trace, TraceType::StateDiff])]
1169        {
1170            let request = serde_json::json!({
1171                "jsonrpc": "2.0", "id": 1, "method": "trace_replayTransaction",
1172                "params": [hash, types],
1173            });
1174            let (response, _) = module.raw_json_request(&request.to_string(), 1).await.unwrap();
1175            let response: serde_json::Value = serde_json::from_str(response.get()).unwrap();
1176            assert!(response.get("error").is_none(), "{response}");
1177            assert_eq!(response["result"]["transactionHash"], serde_json::json!(hash));
1178
1179            let block_replay =
1180                api.replay_block_transactions(block_hash.into(), types).await.unwrap().unwrap();
1181            assert_eq!(response["result"], serde_json::to_value(&block_replay[0]).unwrap());
1182        }
1183    }
1184
1185    #[tokio::test]
1186    async fn genesis_block_has_no_reward_trace() {
1187        // Paris is not active at genesis, so blocks after genesis carry block rewards.
1188        let coinbase = Address::repeat_byte(0x11);
1189        let genesis = Genesis::default().with_gas_limit(30_000_000).with_coinbase(coinbase);
1190        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().genesis(genesis).build());
1191        let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1192        let genesis_hash = init_genesis(&factory).unwrap();
1193        let block = Block {
1194            header: Header {
1195                parent_hash: genesis_hash,
1196                number: 1,
1197                beneficiary: coinbase,
1198                gas_limit: 30_000_000,
1199                ..Default::default()
1200            },
1201            body: BlockBody::default(),
1202        };
1203        let provider_rw = factory.provider_rw().unwrap();
1204        provider_rw.insert_block(&block.seal_slow().try_recover().unwrap()).unwrap();
1205        provider_rw.update_pipeline_stages(1, false).unwrap();
1206        provider_rw.commit().unwrap();
1207
1208        let provider = BlockchainProvider::new(factory).unwrap();
1209        let eth_api = EthApiBuilder::new(
1210            provider.clone(),
1211            testing_pool(),
1212            NoopNetwork::default(),
1213            EthEvmConfig::new(provider.chain_spec()),
1214        )
1215        .build();
1216        let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1217
1218        assert_eq!(api.trace_block(0.into()).await.unwrap(), Some(vec![]));
1219        assert_eq!(
1220            trace_order(&api.trace_block(1.into()).await.unwrap().unwrap()),
1221            [(1, None, true)]
1222        );
1223        for (to_block, expected) in [(0, vec![]), (1, vec![(1, None, true)])] {
1224            let filter =
1225                TraceFilter { from_block: Some(0), to_block: Some(to_block), ..Default::default() };
1226            assert_eq!(trace_order(&api.trace_filter(filter).await.unwrap()), expected);
1227        }
1228    }
1229
1230    fn localized_transaction_trace(
1231        block_number: u64,
1232        transaction_position: u64,
1233    ) -> LocalizedTransactionTrace {
1234        LocalizedTransactionTrace {
1235            block_hash: Some(B256::ZERO),
1236            block_number: Some(block_number),
1237            transaction_hash: Some(B256::ZERO),
1238            transaction_position: Some(transaction_position),
1239            trace: TransactionTrace::default(),
1240        }
1241    }
1242
1243    fn localized_reward_trace(block_number: u64) -> LocalizedTransactionTrace {
1244        RewardAction { author: Address::ZERO, reward_type: RewardType::Block, value: U256::ZERO }
1245            .into_localized_trace(BlockNumHash::new(block_number, B256::ZERO))
1246    }
1247
1248    fn trace_order(traces: &[LocalizedTransactionTrace]) -> Vec<(u64, Option<u64>, bool)> {
1249        traces
1250            .iter()
1251            .map(|trace| {
1252                (
1253                    trace.block_number.unwrap(),
1254                    trace.transaction_position,
1255                    trace.trace.action.is_reward(),
1256                )
1257            })
1258            .collect()
1259    }
1260
1261    #[test]
1262    fn trace_filter_paginates_after_per_block_reward_order() {
1263        let mut all_traces = vec![
1264            localized_transaction_trace(1, 0),
1265            localized_reward_trace(1),
1266            localized_transaction_trace(2, 0),
1267            localized_reward_trace(2),
1268        ];
1269
1270        let mut after = Some(1);
1271        let paginated =
1272            apply_trace_filter_pagination(&mut all_traces, &mut after, Some(1)).unwrap();
1273
1274        assert_eq!(trace_order(&paginated), vec![(1, None, true)]);
1275    }
1276
1277    #[tokio::test]
1278    async fn replay_block_vmtrace_includes_root_and_callcode_bytecode() {
1279        use crate::EthApiBuilder;
1280        use alloy_consensus::{Header, TxLegacy};
1281        use alloy_primitives::{hex, Signature, TxKind};
1282        use reth_chain_state::CanonStateNotification;
1283        use reth_ethereum_primitives::{Block, BlockBody, TransactionSigned};
1284        use reth_evm_ethereum::EthEvmConfig;
1285        use reth_execution_types::{Chain, ExecutionOutcome};
1286        use reth_network_api::noop::NoopNetwork;
1287        use reth_primitives_traits::{RecoveredBlock, SignerRecoverable};
1288        use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1289        use reth_rpc_eth_types::cache::cache_new_blocks_task;
1290        use reth_transaction_pool::test_utils::testing_pool;
1291
1292        let provider = MockEthProvider::default();
1293        let target = Address::with_last_byte(0x42);
1294        let child = Address::with_last_byte(0x43);
1295        // Increment slot zero, CALLCODE the child, then return the incremented value.
1296        let root_code: Bytes =
1297            hex!("60005460010160005560006000600060006000604361fffff25060005460005260206000f3")
1298                .into();
1299        let child_code: Bytes = hex!("60025000").into();
1300        provider.add_account(
1301            target,
1302            ExtendedAccount::new(0, U256::ZERO).with_bytecode(root_code.clone()),
1303        );
1304        provider.add_account(
1305            child,
1306            ExtendedAccount::new(0, U256::ZERO).with_bytecode(child_code.clone()),
1307        );
1308        let mut transactions = Vec::new();
1309        for nonce in 0..2 {
1310            let tx = TransactionSigned::new_unhashed(
1311                TxLegacy {
1312                    nonce,
1313                    gas_limit: 500_000,
1314                    to: TxKind::Call(target),
1315                    ..Default::default()
1316                }
1317                .into(),
1318                Signature::test_signature(),
1319            );
1320            provider.add_account(
1321                tx.recover_signer().unwrap(),
1322                ExtendedAccount::new(nonce, U256::from(1_000_000)),
1323            );
1324            transactions.push(tx);
1325        }
1326        let parent = Header { gas_limit: 30_000_000, ..Default::default() };
1327        let parent_hash = parent.hash_slow();
1328        provider.add_header(parent_hash, parent);
1329        let block = Block {
1330            header: Header { parent_hash, number: 1, gas_limit: 30_000_000, ..Default::default() },
1331            body: BlockBody { transactions, ..Default::default() },
1332        };
1333        let block_hash = block.header.hash_slow();
1334        let senders =
1335            block.body.transactions.iter().map(|tx| tx.recover_signer().unwrap()).collect();
1336        provider.add_block(block_hash, block.clone());
1337        let recovered = RecoveredBlock::new_unhashed(block, senders);
1338        let eth_api = EthApiBuilder::new(
1339            provider.clone(),
1340            testing_pool(),
1341            NoopNetwork::default(),
1342            EthEvmConfig::new(provider.chain_spec()),
1343        )
1344        .build();
1345        // Seed the RPC cache: MockEthProvider does not implement recovered_block.
1346        cache_new_blocks_task(
1347            eth_api.cache().clone(),
1348            futures::stream::iter([CanonStateNotification::Commit {
1349                new: Arc::new(Chain::new(
1350                    [recovered],
1351                    ExecutionOutcome {
1352                        receipts: vec![vec![]],
1353                        first_block: 1,
1354                        ..Default::default()
1355                    },
1356                    Default::default(),
1357                )),
1358            }]),
1359        )
1360        .await;
1361        let api = TraceApi::new(eth_api, BlockingTaskGuard::new(1), EthConfig::default());
1362        for types in [
1363            HashSet::from_iter([TraceType::VmTrace]),
1364            HashSet::from_iter([TraceType::VmTrace, TraceType::Trace, TraceType::StateDiff]),
1365            HashSet::from_iter([TraceType::Trace, TraceType::StateDiff]),
1366        ] {
1367            let traces = api
1368                .replay_block_transactions(block_hash.into(), types.clone())
1369                .await
1370                .unwrap()
1371                .unwrap();
1372            assert_eq!(traces.len(), 2);
1373            for (index, trace) in traces.into_iter().enumerate() {
1374                assert_eq!(
1375                    trace.full_trace.output.as_ref(),
1376                    U256::from(index + 1).to_be_bytes::<32>()
1377                );
1378                if types.contains(&TraceType::VmTrace) {
1379                    let vm = trace.full_trace.vm_trace.as_ref().unwrap();
1380                    assert_eq!(vm.code, root_code);
1381                    assert_eq!(
1382                        vm.ops.iter().find_map(|op| op.sub.as_ref()).unwrap().code,
1383                        child_code
1384                    );
1385                }
1386                let individual = api
1387                    .replay_transaction(trace.transaction_hash, types.clone())
1388                    .await
1389                    .unwrap()
1390                    .unwrap();
1391                assert_eq!(individual.transaction_hash, trace.transaction_hash);
1392                assert_eq!(trace.full_trace, individual.full_trace);
1393            }
1394        }
1395    }
1396}