Skip to main content

reth_rpc/
trace.rs

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