Skip to main content

reth_rpc/
trace.rs

1use alloy_consensus::BlockHeader as _;
2use alloy_eips::BlockId;
3use alloy_evm::block::calc::{base_block_reward_pre_merge, 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::StreamExt;
20use jsonrpsee::core::RpcResult;
21use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
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, utils::recover_raw_transaction, EthConfig};
30use reth_storage_api::{BlockNumReader, BlockReader};
31use reth_tasks::pool::BlockingTaskGuard;
32use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
33use revm::DatabaseCommit;
34use revm_inspectors::{
35    opcode::OpcodeGasInspector,
36    storage::StorageInspector,
37    tracing::{parity::populate_state_diff, 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            .await
117    }
118
119    /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces.
120    pub async fn trace_raw_transaction(
121        &self,
122        tx: Bytes,
123        trace_types: HashSet<TraceType>,
124        block_id: Option<BlockId>,
125    ) -> Result<TraceResults, Eth::Error> {
126        let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
127            .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
128
129        let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
130
131        self.eth_api()
132            .spawn_with_state_at_block(at, move |this, mut db| {
133                let mut inspector =
134                    TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
135                let res = this.inspect(&mut db, evm_env, tx, &mut inspector)?;
136
137                inspector
138                    .into_parity_builder()
139                    .into_trace_results_with_state(&res, &trace_types, &db)
140                    .map_err(Eth::Error::from_eth_err)
141            })
142            .await
143    }
144
145    /// Performs multiple call traces on top of the same block. i.e. transaction n will be executed
146    /// on top of a pending block with all n-1 transactions applied (traced) first.
147    ///
148    /// Note: Allows tracing dependent transactions, hence all transactions are traced in sequence
149    pub async fn trace_call_many(
150        &self,
151        calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
152        block_id: Option<BlockId>,
153    ) -> Result<Vec<TraceResults>, Eth::Error> {
154        let at = block_id.unwrap_or(BlockId::pending());
155        let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
156
157        // execute all transactions on top of each other and record the traces
158        self.eth_api()
159            .spawn_with_state_at_block(at, move |eth_api, mut db| {
160                let mut results = Vec::with_capacity(calls.len());
161                let mut calls = calls.into_iter().peekable();
162
163                while let Some((call, trace_types)) = calls.next() {
164                    let (evm_env, tx_env) = eth_api.prepare_call_env(
165                        evm_env.clone(),
166                        call,
167                        &mut db,
168                        Default::default(),
169                    )?;
170                    let config = TracingInspectorConfig::from_parity_config(&trace_types);
171                    let mut inspector = TracingInspector::new(config);
172                    let res = eth_api.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
173
174                    let trace_res = inspector
175                        .into_parity_builder()
176                        .into_trace_results_with_state(&res, &trace_types, &db)
177                        .map_err(Eth::Error::from_eth_err)?;
178
179                    results.push(trace_res);
180
181                    // need to apply the state changes of this call before executing the
182                    // next call
183                    if calls.peek().is_some() {
184                        db.commit(res.state)
185                    }
186                }
187
188                Ok(results)
189            })
190            .await
191    }
192
193    /// Replays a transaction, returning the traces.
194    pub async fn replay_transaction(
195        &self,
196        hash: B256,
197        trace_types: HashSet<TraceType>,
198    ) -> Result<TraceResults, Eth::Error> {
199        let config = TracingInspectorConfig::from_parity_config(&trace_types);
200        self.eth_api()
201            .spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
202                let trace_res = inspector
203                    .into_parity_builder()
204                    .into_trace_results_with_state(&res, &trace_types, &db)
205                    .map_err(Eth::Error::from_eth_err)?;
206                Ok(trace_res)
207            })
208            .await
209            .transpose()
210            .ok_or(EthApiError::TransactionNotFound)?
211    }
212
213    /// Returns transaction trace objects at the given index
214    ///
215    /// Note: For compatibility reasons this only supports 1 single index, since this method is
216    /// supposed to return a single trace. See also: <https://github.com/ledgerwatch/erigon/blob/862faf054b8a0fa15962a9c73839b619886101eb/turbo/jsonrpc/trace_filtering.go#L114-L133>
217    ///
218    /// This returns `None` if `indices` is empty
219    pub async fn trace_get(
220        &self,
221        hash: B256,
222        indices: Vec<usize>,
223    ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
224        if indices.len() != 1 {
225            // The OG impl failed if it gets more than a single index
226            return Ok(None)
227        }
228        self.trace_get_index(hash, indices[0]).await
229    }
230
231    /// Returns transaction trace object at the given index.
232    ///
233    /// Returns `None` if the trace object at that index does not exist
234    pub async fn trace_get_index(
235        &self,
236        hash: B256,
237        index: usize,
238    ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
239        Ok(self.trace_transaction(hash).await?.and_then(|traces| traces.into_iter().nth(index)))
240    }
241
242    /// Returns all traces for the given transaction hash
243    pub async fn trace_transaction(
244        &self,
245        hash: B256,
246    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
247        self.eth_api()
248            .spawn_trace_transaction_in_block(
249                hash,
250                TracingInspectorConfig::default_parity(),
251                move |tx_info, inspector, _, _| {
252                    let traces =
253                        inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
254                    Ok(traces)
255                },
256            )
257            .await
258    }
259
260    /// Returns all opcodes with their count and combined gas usage for the given transaction in no
261    /// particular order.
262    pub async fn trace_transaction_opcode_gas(
263        &self,
264        tx_hash: B256,
265    ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
266        self.eth_api()
267            .spawn_trace_transaction_in_block_with_inspector(
268                tx_hash,
269                OpcodeGasInspector::default(),
270                move |_tx_info, inspector, _res, _| {
271                    let trace = TransactionOpcodeGas {
272                        transaction_hash: tx_hash,
273                        opcode_gas: inspector.opcode_gas_iter().collect(),
274                    };
275                    Ok(trace)
276                },
277            )
278            .await
279    }
280
281    /// Calculates the base block reward for the given block:
282    ///
283    /// - if Paris hardfork is activated, no block rewards are given
284    /// - if Paris hardfork is not activated, calculate block rewards with block number only
285    fn calculate_base_block_reward<H: BlockHeader>(
286        &self,
287        header: &H,
288    ) -> Result<Option<u128>, Eth::Error> {
289        let chain_spec = self.provider().chain_spec();
290
291        if chain_spec.is_paris_active_at_block(header.number()) {
292            return Ok(None)
293        }
294
295        Ok(Some(base_block_reward_pre_merge(&chain_spec, header.number())))
296    }
297
298    /// Extracts the reward traces for the given block:
299    ///  - block reward
300    ///  - uncle rewards
301    fn extract_reward_traces<H: BlockHeader>(
302        &self,
303        header: &H,
304        block_hash: BlockHash,
305        ommers: Option<&[H]>,
306        base_block_reward: u128,
307    ) -> Vec<LocalizedTransactionTrace> {
308        let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
309        let mut traces = Vec::with_capacity(ommers_cnt + 1);
310
311        let block_reward = block_reward(base_block_reward, ommers_cnt);
312        traces.push(reward_trace(
313            block_hash,
314            header,
315            RewardAction {
316                author: header.beneficiary(),
317                reward_type: RewardType::Block,
318                value: U256::from(block_reward),
319            },
320        ));
321
322        let Some(ommers) = ommers else { return traces };
323
324        for uncle in ommers {
325            let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
326            traces.push(reward_trace(
327                block_hash,
328                header,
329                RewardAction {
330                    author: uncle.beneficiary(),
331                    reward_type: RewardType::Uncle,
332                    value: U256::from(uncle_reward),
333                },
334            ));
335        }
336        traces
337    }
338}
339
340impl<Eth> TraceApi<Eth>
341where
342    // tracing methods read from mempool, hence `LoadBlock` trait bound via
343    // `TraceExt`
344    Eth: TraceExt + 'static,
345{
346    /// Returns all transaction traces that match the given filter.
347    ///
348    /// This is similar to [`Self::trace_block`] but only returns traces for transactions that match
349    /// the filter.
350    pub async fn trace_filter(
351        &self,
352        filter: TraceFilter,
353    ) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
354        // We'll reuse the matcher across multiple blocks that are traced in parallel
355        let matcher = Arc::new(filter.matcher());
356        let TraceFilter { from_block, to_block, mut after, count, .. } = filter;
357        let start = from_block.unwrap_or(0);
358
359        let latest_block = self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?;
360        if start > latest_block {
361            // can't trace that range
362            return Err(EthApiError::HeaderNotFound(start.into()).into());
363        }
364        let end = to_block.unwrap_or(latest_block);
365        if end > latest_block {
366            return Err(EthApiError::HeaderNotFound(end.into()).into());
367        }
368
369        // Check if the requested range overlaps with pruned history (EIP-4444)
370        let earliest_block =
371            self.provider().earliest_block_number().map_err(Eth::Error::from_eth_err)?;
372        if start < earliest_block {
373            return Err(EthApiError::PrunedHistoryUnavailable {
374                requested: start,
375                earliest_available: earliest_block,
376            }
377            .into());
378        }
379
380        if start > end {
381            return Err(EthApiError::InvalidParams(
382                "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
383            )
384            .into())
385        }
386
387        // ensure that the range is not too large, since every block in the range may be replayed
388        let distance = end.saturating_sub(start);
389        if distance > self.inner.eth_config.max_trace_filter_blocks {
390            return Err(EthApiError::InvalidParams(format!(
391                "Block range too large; currently limited to {} blocks",
392                self.inner.eth_config.max_trace_filter_blocks
393            ))
394            .into())
395        }
396
397        let mut all_traces = Vec::new();
398        let block_buffer_size =
399            self.inner.eth_config.max_tracing_requests.clamp(1, TRACE_FILTER_BLOCK_BUFFER_SIZE);
400        let mut include_reward_traces = true;
401
402        for chunk_start in (start..=end).step_by(TRACE_FILTER_FETCH_CHUNK_SIZE) {
403            let chunk_end = (chunk_start + TRACE_FILTER_FETCH_CHUNK_SIZE as u64 - 1).min(end);
404
405            let blocks = self
406                .eth_api()
407                .spawn_blocking_io(move |this| {
408                    let blocks = this
409                        .provider()
410                        .recovered_block_range(chunk_start..=chunk_end)
411                        .map_err(Eth::Error::from_eth_err)?;
412
413                    Ok(blocks.into_iter().map(Arc::new).collect::<Vec<_>>())
414                })
415                .await?;
416
417            let mut block_replays = futures::stream::iter(blocks)
418                .map(|block| {
419                    let this = self.clone();
420                    let matcher = matcher.clone();
421
422                    let block_hash = block.hash();
423
424                    async move {
425                        let permit = this.acquire_trace_permit().await;
426                        let traces = this
427                            .eth_api()
428                            .trace_block_until(
429                                block_hash.into(),
430                                Some(block.clone()),
431                                None,
432                                TracingInspectorConfig::default_parity(),
433                                move |tx_info, mut ctx| {
434                                    // Keep the block replay permit inside the spawned replay task.
435                                    let _block_replay_permit = &permit;
436                                    let mut traces = ctx
437                                        .take_inspector()
438                                        .into_parity_builder()
439                                        .into_localized_transaction_traces(tx_info);
440                                    traces.retain(|trace| matcher.matches(&trace.trace));
441                                    Ok(Some(traces))
442                                },
443                            )
444                            .await?;
445
446                        Ok::<_, Eth::Error>((block, traces))
447                    }
448                })
449                .buffered(block_buffer_size);
450
451            while let Some(block_replay) = block_replays.next().await {
452                let (block, traces) = block_replay?;
453                let reward_traces = if include_reward_traces {
454                    if let Some(base_block_reward) =
455                        self.calculate_base_block_reward(block.header())?
456                    {
457                        self.extract_reward_traces(
458                            block.header(),
459                            block.hash(),
460                            block.body().ommers(),
461                            base_block_reward,
462                        )
463                        .into_iter()
464                        .filter(|trace| matcher.matches(&trace.trace))
465                        .collect::<Vec<_>>()
466                    } else {
467                        // Blocks are processed in ascending order, so once a historical range
468                        // reaches post-Paris blocks, later blocks in the range have no rewards.
469                        include_reward_traces = false;
470                        Vec::new()
471                    }
472                } else {
473                    Vec::new()
474                };
475
476                if let Some(traces) = traces {
477                    all_traces.extend(traces.into_iter().flatten().flatten());
478                }
479                all_traces.extend(reward_traces);
480
481                if let Some(traces) =
482                    apply_trace_filter_pagination(&mut all_traces, &mut after, count)
483                {
484                    return Ok(traces)
485                }
486            }
487        }
488
489        // If `after` is greater than or equal to the number of matched traces, it returns an
490        // empty array.
491        if let Some(cutoff) = after.map(|a| a as usize) &&
492            cutoff >= all_traces.len()
493        {
494            return Ok(vec![])
495        }
496
497        Ok(all_traces)
498    }
499
500    /// Returns traces created at given block.
501    pub async fn trace_block(
502        &self,
503        block_id: BlockId,
504    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
505        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
506            return Err(EthApiError::HeaderNotFound(block_id).into());
507        };
508
509        let mut traces = self
510            .eth_api()
511            .trace_block_with(
512                block_id,
513                Some(block.clone()),
514                TracingInspectorConfig::default_parity(),
515                |tx_info, mut ctx| {
516                    let traces = ctx
517                        .take_inspector()
518                        .into_parity_builder()
519                        .into_localized_transaction_traces(tx_info);
520                    Ok(traces)
521                },
522            )
523            .await?
524            .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
525
526        if let Some(traces) = traces.as_mut() &&
527            let Some(base_block_reward) = self.calculate_base_block_reward(block.header())?
528        {
529            traces.extend(self.extract_reward_traces(
530                block.header(),
531                block.hash(),
532                block.body().ommers(),
533                base_block_reward,
534            ));
535        }
536
537        Ok(traces)
538    }
539
540    /// Replays all transactions in a block
541    pub async fn replay_block_transactions(
542        &self,
543        block_id: BlockId,
544        trace_types: HashSet<TraceType>,
545    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
546        self.eth_api()
547            .trace_block_with(
548                block_id,
549                None,
550                TracingInspectorConfig::from_parity_config(&trace_types),
551                move |tx_info, mut ctx| {
552                    let mut full_trace = ctx
553                        .take_inspector()
554                        .into_parity_builder()
555                        .into_trace_results(&ctx.result, &trace_types);
556
557                    // If statediffs were requested, populate them with the account balance and
558                    // nonce from pre-state
559                    if let Some(ref mut state_diff) = full_trace.state_diff {
560                        populate_state_diff(state_diff, &ctx.db, ctx.state.iter())
561                            .map_err(Eth::Error::from_eth_err)?;
562                    }
563
564                    let trace = TraceResultsWithTransactionHash {
565                        transaction_hash: tx_info.hash.expect("tx hash is set"),
566                        full_trace,
567                    };
568                    Ok(trace)
569                },
570            )
571            .await
572    }
573
574    /// Returns the opcodes of all transactions in the given block.
575    ///
576    /// This is the same as [`Self::trace_transaction_opcode_gas`] but for all transactions in a
577    /// block.
578    pub async fn trace_block_opcode_gas(
579        &self,
580        block_id: BlockId,
581    ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
582        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
583            return Err(EthApiError::HeaderNotFound(block_id).into());
584        };
585
586        let Some(transactions) = self
587            .eth_api()
588            .trace_block_inspector(
589                block_id,
590                Some(block.clone()),
591                OpcodeGasInspector::default,
592                move |tx_info, ctx| {
593                    let trace = TransactionOpcodeGas {
594                        transaction_hash: tx_info.hash.expect("tx hash is set"),
595                        opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
596                    };
597                    Ok(trace)
598                },
599            )
600            .await?
601        else {
602            return Ok(None);
603        };
604
605        Ok(Some(BlockOpcodeGas {
606            block_hash: block.hash(),
607            block_number: block.number(),
608            transactions,
609        }))
610    }
611
612    /// Returns all storage slots accessed during transaction execution along with their access
613    /// counts.
614    pub async fn trace_block_storage_access(
615        &self,
616        block_id: BlockId,
617    ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
618        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
619            return Err(EthApiError::HeaderNotFound(block_id).into());
620        };
621
622        let Some(transactions) = self
623            .eth_api()
624            .trace_block_inspector(
625                block_id,
626                Some(block.clone()),
627                StorageInspector::default,
628                move |tx_info, mut ctx| {
629                    let unique_loads = ctx.inspector.unique_loads();
630                    let warm_loads = ctx.inspector.warm_loads();
631                    let trace = TransactionStorageAccess {
632                        transaction_hash: tx_info.hash.expect("tx hash is set"),
633                        storage_access: ctx.take_inspector().into_accessed_slots(),
634                        unique_loads,
635                        warm_loads,
636                    };
637                    Ok(trace)
638                },
639            )
640            .await?
641        else {
642            return Ok(None);
643        };
644
645        Ok(Some(BlockStorageAccess {
646            block_hash: block.hash(),
647            block_number: block.number(),
648            transactions,
649        }))
650    }
651}
652
653fn apply_trace_filter_pagination(
654    all_traces: &mut Vec<LocalizedTransactionTrace>,
655    after: &mut Option<u64>,
656    count: Option<u64>,
657) -> Option<Vec<LocalizedTransactionTrace>> {
658    // Skips the first `after` number of matching traces.
659    if let Some(cutoff) = after.map(|a| a as usize) &&
660        cutoff < all_traces.len()
661    {
662        all_traces.drain(..cutoff);
663        // we removed the first `after` traces
664        *after = None;
665    }
666
667    // Return at most `count` traces after `after` has been consumed.
668    if after.is_none() &&
669        let Some(count) = count
670    {
671        let count = count as usize;
672        if count < all_traces.len() {
673            all_traces.truncate(count);
674            return Some(std::mem::take(all_traces))
675        }
676    }
677
678    None
679}
680
681#[async_trait]
682impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
683where
684    Eth: TraceExt + 'static,
685{
686    /// Executes the given call and returns a number of possible traces for it.
687    ///
688    /// Handler for `trace_call`
689    async fn trace_call(
690        &self,
691        call: RpcTxReq<Eth::NetworkTypes>,
692        trace_types: HashSet<TraceType>,
693        block_id: Option<BlockId>,
694        state_overrides: Option<StateOverride>,
695        block_overrides: Option<Box<BlockOverrides>>,
696    ) -> RpcResult<TraceResults> {
697        let _permit = self.acquire_trace_permit().await;
698        let request =
699            TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
700        Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
701    }
702
703    /// Handler for `trace_callMany`
704    async fn trace_call_many(
705        &self,
706        calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
707        block_id: Option<BlockId>,
708    ) -> RpcResult<Vec<TraceResults>> {
709        let _permit = self.acquire_trace_permit().await;
710        Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
711    }
712
713    /// Handler for `trace_rawTransaction`
714    async fn trace_raw_transaction(
715        &self,
716        data: Bytes,
717        trace_types: HashSet<TraceType>,
718        block_id: Option<BlockId>,
719    ) -> RpcResult<TraceResults> {
720        let _permit = self.acquire_trace_permit().await;
721        Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
722            .await
723            .map_err(Into::into)?)
724    }
725
726    /// Handler for `trace_replayBlockTransactions`
727    async fn replay_block_transactions(
728        &self,
729        block_id: BlockId,
730        trace_types: HashSet<TraceType>,
731    ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
732        let _permit = self.acquire_trace_permit().await;
733        Ok(Self::replay_block_transactions(self, block_id, trace_types)
734            .await
735            .map_err(Into::into)?)
736    }
737
738    /// Handler for `trace_replayTransaction`
739    async fn replay_transaction(
740        &self,
741        transaction: B256,
742        trace_types: HashSet<TraceType>,
743    ) -> RpcResult<TraceResults> {
744        let _permit = self.acquire_trace_permit().await;
745        Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
746    }
747
748    /// Handler for `trace_block`
749    async fn trace_block(
750        &self,
751        block_id: BlockId,
752    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
753        let _permit = self.acquire_trace_permit().await;
754        Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
755    }
756
757    /// Handler for `trace_filter`
758    ///
759    /// This is similar to `eth_getLogs` but for traces.
760    ///
761    /// # Limitations
762    /// This currently requires block filter fields, since reth does not have address indices yet.
763    async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
764        Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
765    }
766
767    /// Returns transaction trace at given index.
768    /// Handler for `trace_get`
769    async fn trace_get(
770        &self,
771        hash: B256,
772        indices: Vec<Index>,
773    ) -> RpcResult<Option<LocalizedTransactionTrace>> {
774        let _permit = self.acquire_trace_permit().await;
775        Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
776            .await
777            .map_err(Into::into)?)
778    }
779
780    /// Handler for `trace_transaction`
781    async fn trace_transaction(
782        &self,
783        hash: B256,
784    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
785        let _permit = self.acquire_trace_permit().await;
786        Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
787    }
788
789    /// Handler for `trace_transactionOpcodeGas`
790    async fn trace_transaction_opcode_gas(
791        &self,
792        tx_hash: B256,
793    ) -> RpcResult<Option<TransactionOpcodeGas>> {
794        let _permit = self.acquire_trace_permit().await;
795        Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
796    }
797
798    /// Handler for `trace_blockOpcodeGas`
799    async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
800        let _permit = self.acquire_trace_permit().await;
801        Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
802    }
803}
804
805impl<Eth> std::fmt::Debug for TraceApi<Eth> {
806    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
807        f.debug_struct("TraceApi").finish_non_exhaustive()
808    }
809}
810impl<Eth> Clone for TraceApi<Eth> {
811    fn clone(&self) -> Self {
812        Self { inner: Arc::clone(&self.inner) }
813    }
814}
815
816struct TraceApiInner<Eth> {
817    /// Access to commonly used code of the `eth` namespace
818    eth_api: Eth,
819    // restrict the number of concurrent calls to `trace_*`
820    blocking_task_guard: BlockingTaskGuard,
821    // eth config settings
822    eth_config: EthConfig,
823}
824
825/// Response type for storage tracing that contains all accessed storage slots
826/// for a transaction.
827#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
828#[serde(rename_all = "camelCase")]
829pub struct TransactionStorageAccess {
830    /// Hash of the transaction
831    pub transaction_hash: B256,
832    /// Tracks storage slots and access counter.
833    pub storage_access: HashMap<Address, HashMap<B256, u64>>,
834    /// Number of unique storage loads
835    pub unique_loads: u64,
836    /// Number of warm storage loads
837    pub warm_loads: u64,
838}
839
840/// Response type for storage tracing that contains all accessed storage slots
841#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
842#[serde(rename_all = "camelCase")]
843pub struct BlockStorageAccess {
844    /// The block hash
845    pub block_hash: BlockHash,
846    /// The block's number
847    pub block_number: u64,
848    /// All executed transactions in the block in the order they were executed
849    pub transactions: Vec<TransactionStorageAccess>,
850}
851
852/// Helper to construct a [`LocalizedTransactionTrace`] that describes a reward to the block
853/// beneficiary.
854fn reward_trace<H: BlockHeader>(
855    block_hash: BlockHash,
856    header: &H,
857    reward: RewardAction,
858) -> LocalizedTransactionTrace {
859    LocalizedTransactionTrace {
860        block_hash: Some(block_hash),
861        block_number: Some(header.number()),
862        transaction_hash: None,
863        transaction_position: None,
864        trace: TransactionTrace {
865            trace_address: vec![],
866            subtraces: 0,
867            action: Action::Reward(reward),
868            error: None,
869            result: None,
870        },
871    }
872}
873
874#[cfg(test)]
875mod tests {
876    use super::*;
877
878    fn localized_transaction_trace(
879        block_number: u64,
880        transaction_position: u64,
881    ) -> LocalizedTransactionTrace {
882        LocalizedTransactionTrace {
883            block_hash: Some(B256::ZERO),
884            block_number: Some(block_number),
885            transaction_hash: Some(B256::ZERO),
886            transaction_position: Some(transaction_position),
887            trace: TransactionTrace::default(),
888        }
889    }
890
891    fn localized_reward_trace(block_number: u64) -> LocalizedTransactionTrace {
892        LocalizedTransactionTrace {
893            block_hash: Some(B256::ZERO),
894            block_number: Some(block_number),
895            transaction_hash: None,
896            transaction_position: None,
897            trace: TransactionTrace {
898                trace_address: vec![],
899                subtraces: 0,
900                action: Action::Reward(RewardAction {
901                    author: Address::ZERO,
902                    reward_type: RewardType::Block,
903                    value: U256::ZERO,
904                }),
905                error: None,
906                result: None,
907            },
908        }
909    }
910
911    fn trace_order(traces: &[LocalizedTransactionTrace]) -> Vec<(u64, Option<u64>, bool)> {
912        traces
913            .iter()
914            .map(|trace| {
915                (
916                    trace.block_number.unwrap(),
917                    trace.transaction_position,
918                    trace.trace.action.is_reward(),
919                )
920            })
921            .collect()
922    }
923
924    #[test]
925    fn trace_filter_paginates_after_per_block_reward_order() {
926        let mut all_traces = vec![
927            localized_transaction_trace(1, 0),
928            localized_reward_trace(1),
929            localized_transaction_trace(2, 0),
930            localized_reward_trace(2),
931        ];
932
933        let mut after = Some(1);
934        let paginated =
935            apply_trace_filter_pagination(&mut all_traces, &mut after, Some(1)).unwrap();
936
937        assert_eq!(trace_order(&paginated), vec![(1, None, true)]);
938    }
939}