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 jsonrpsee::core::RpcResult;
20use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
21use reth_evm::ConfigureEvm;
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/// `trace` API implementation.
44///
45/// This type provides the functionality for handling `trace` related requests.
46pub struct TraceApi<Eth> {
47    inner: Arc<TraceApiInner<Eth>>,
48}
49
50// === impl TraceApi ===
51
52impl<Eth> TraceApi<Eth> {
53    /// Create a new instance of the [`TraceApi`]
54    pub fn new(
55        eth_api: Eth,
56        blocking_task_guard: BlockingTaskGuard,
57        eth_config: EthConfig,
58    ) -> Self {
59        let inner = Arc::new(TraceApiInner { eth_api, blocking_task_guard, eth_config });
60        Self { inner }
61    }
62
63    /// Acquires a permit to execute a tracing call.
64    async fn acquire_trace_permit(
65        &self,
66    ) -> std::result::Result<OwnedSemaphorePermit, AcquireError> {
67        self.inner.blocking_task_guard.clone().acquire_owned().await
68    }
69
70    /// Access the underlying `Eth` API.
71    pub fn eth_api(&self) -> &Eth {
72        &self.inner.eth_api
73    }
74}
75
76impl<Eth: RpcNodeCore> TraceApi<Eth> {
77    /// Access the underlying provider.
78    pub fn provider(&self) -> &Eth::Provider {
79        self.inner.eth_api.provider()
80    }
81}
82
83// === impl TraceApi === //
84
85impl<Eth> TraceApi<Eth>
86where
87    // tracing methods do _not_ read from mempool, hence no `LoadBlock` trait
88    // bound
89    Eth: Trace + Call + LoadPendingBlock + LoadTransaction + 'static,
90{
91    /// Executes the given call and returns a number of possible traces for it.
92    pub async fn trace_call(
93        &self,
94        trace_request: TraceCallRequest<RpcTxReq<Eth::NetworkTypes>>,
95    ) -> Result<TraceResults, Eth::Error> {
96        let at = trace_request.block_id.unwrap_or_default();
97        let config = TracingInspectorConfig::from_parity_config(&trace_request.trace_types);
98        let overrides =
99            EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides);
100        let mut inspector = TracingInspector::new(config);
101        let this = self.clone();
102        self.eth_api()
103            .spawn_with_call_at(trace_request.call, at, overrides, move |db, evm_env, tx_env| {
104                let res = this.eth_api().inspect(&mut *db, evm_env, tx_env, &mut inspector)?;
105                let trace_res = inspector
106                    .into_parity_builder()
107                    .into_trace_results_with_state(&res, &trace_request.trace_types, &db)
108                    .map_err(Eth::Error::from_eth_err)?;
109                Ok(trace_res)
110            })
111            .await
112    }
113
114    /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces.
115    pub async fn trace_raw_transaction(
116        &self,
117        tx: Bytes,
118        trace_types: HashSet<TraceType>,
119        block_id: Option<BlockId>,
120    ) -> Result<TraceResults, Eth::Error> {
121        let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
122            .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
123
124        let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
125        let tx_env = self.eth_api().evm_config().tx_env(tx);
126
127        let config = TracingInspectorConfig::from_parity_config(&trace_types);
128
129        self.eth_api()
130            .spawn_trace_at_with_state(evm_env, tx_env, config, at, move |inspector, res, db| {
131                inspector
132                    .into_parity_builder()
133                    .into_trace_results_with_state(&res, &trace_types, &db)
134                    .map_err(Eth::Error::from_eth_err)
135            })
136            .await
137    }
138
139    /// Performs multiple call traces on top of the same block. i.e. transaction n will be executed
140    /// on top of a pending block with all n-1 transactions applied (traced) first.
141    ///
142    /// Note: Allows tracing dependent transactions, hence all transactions are traced in sequence
143    pub async fn trace_call_many(
144        &self,
145        calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
146        block_id: Option<BlockId>,
147    ) -> Result<Vec<TraceResults>, Eth::Error> {
148        let at = block_id.unwrap_or(BlockId::pending());
149        let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
150
151        // execute all transactions on top of each other and record the traces
152        self.eth_api()
153            .spawn_with_state_at_block(at, move |eth_api, mut db| {
154                let mut results = Vec::with_capacity(calls.len());
155                let mut calls = calls.into_iter().peekable();
156
157                while let Some((call, trace_types)) = calls.next() {
158                    let (evm_env, tx_env) = eth_api.prepare_call_env(
159                        evm_env.clone(),
160                        call,
161                        &mut db,
162                        Default::default(),
163                    )?;
164                    let config = TracingInspectorConfig::from_parity_config(&trace_types);
165                    let mut inspector = TracingInspector::new(config);
166                    let res = eth_api.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
167
168                    let trace_res = inspector
169                        .into_parity_builder()
170                        .into_trace_results_with_state(&res, &trace_types, &db)
171                        .map_err(Eth::Error::from_eth_err)?;
172
173                    results.push(trace_res);
174
175                    // need to apply the state changes of this call before executing the
176                    // next call
177                    if calls.peek().is_some() {
178                        db.commit(res.state)
179                    }
180                }
181
182                Ok(results)
183            })
184            .await
185    }
186
187    /// Replays a transaction, returning the traces.
188    pub async fn replay_transaction(
189        &self,
190        hash: B256,
191        trace_types: HashSet<TraceType>,
192    ) -> Result<TraceResults, Eth::Error> {
193        let config = TracingInspectorConfig::from_parity_config(&trace_types);
194        self.eth_api()
195            .spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
196                let trace_res = inspector
197                    .into_parity_builder()
198                    .into_trace_results_with_state(&res, &trace_types, &db)
199                    .map_err(Eth::Error::from_eth_err)?;
200                Ok(trace_res)
201            })
202            .await
203            .transpose()
204            .ok_or(EthApiError::TransactionNotFound)?
205    }
206
207    /// Returns transaction trace objects at the given index
208    ///
209    /// Note: For compatibility reasons this only supports 1 single index, since this method is
210    /// supposed to return a single trace. See also: <https://github.com/ledgerwatch/erigon/blob/862faf054b8a0fa15962a9c73839b619886101eb/turbo/jsonrpc/trace_filtering.go#L114-L133>
211    ///
212    /// This returns `None` if `indices` is empty
213    pub async fn trace_get(
214        &self,
215        hash: B256,
216        indices: Vec<usize>,
217    ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
218        if indices.len() != 1 {
219            // The OG impl failed if it gets more than a single index
220            return Ok(None)
221        }
222        self.trace_get_index(hash, indices[0]).await
223    }
224
225    /// Returns transaction trace object at the given index.
226    ///
227    /// Returns `None` if the trace object at that index does not exist
228    pub async fn trace_get_index(
229        &self,
230        hash: B256,
231        index: usize,
232    ) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
233        Ok(self.trace_transaction(hash).await?.and_then(|traces| traces.into_iter().nth(index)))
234    }
235
236    /// Returns all traces for the given transaction hash
237    pub async fn trace_transaction(
238        &self,
239        hash: B256,
240    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
241        self.eth_api()
242            .spawn_trace_transaction_in_block(
243                hash,
244                TracingInspectorConfig::default_parity(),
245                move |tx_info, inspector, _, _| {
246                    let traces =
247                        inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
248                    Ok(traces)
249                },
250            )
251            .await
252    }
253
254    /// Returns all opcodes with their count and combined gas usage for the given transaction in no
255    /// particular order.
256    pub async fn trace_transaction_opcode_gas(
257        &self,
258        tx_hash: B256,
259    ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
260        self.eth_api()
261            .spawn_trace_transaction_in_block_with_inspector(
262                tx_hash,
263                OpcodeGasInspector::default(),
264                move |_tx_info, inspector, _res, _| {
265                    let trace = TransactionOpcodeGas {
266                        transaction_hash: tx_hash,
267                        opcode_gas: inspector.opcode_gas_iter().collect(),
268                    };
269                    Ok(trace)
270                },
271            )
272            .await
273    }
274
275    /// Calculates the base block reward for the given block:
276    ///
277    /// - if Paris hardfork is activated, no block rewards are given
278    /// - if Paris hardfork is not activated, calculate block rewards with block number only
279    fn calculate_base_block_reward<H: BlockHeader>(
280        &self,
281        header: &H,
282    ) -> Result<Option<u128>, Eth::Error> {
283        let chain_spec = self.provider().chain_spec();
284
285        if chain_spec.is_paris_active_at_block(header.number()) {
286            return Ok(None)
287        }
288
289        Ok(Some(base_block_reward_pre_merge(&chain_spec, header.number())))
290    }
291
292    /// Extracts the reward traces for the given block:
293    ///  - block reward
294    ///  - uncle rewards
295    fn extract_reward_traces<H: BlockHeader>(
296        &self,
297        header: &H,
298        block_hash: BlockHash,
299        ommers: Option<&[H]>,
300        base_block_reward: u128,
301    ) -> Vec<LocalizedTransactionTrace> {
302        let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
303        let mut traces = Vec::with_capacity(ommers_cnt + 1);
304
305        let block_reward = block_reward(base_block_reward, ommers_cnt);
306        traces.push(reward_trace(
307            block_hash,
308            header,
309            RewardAction {
310                author: header.beneficiary(),
311                reward_type: RewardType::Block,
312                value: U256::from(block_reward),
313            },
314        ));
315
316        let Some(ommers) = ommers else { return traces };
317
318        for uncle in ommers {
319            let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
320            traces.push(reward_trace(
321                block_hash,
322                header,
323                RewardAction {
324                    author: uncle.beneficiary(),
325                    reward_type: RewardType::Uncle,
326                    value: U256::from(uncle_reward),
327                },
328            ));
329        }
330        traces
331    }
332}
333
334impl<Eth> TraceApi<Eth>
335where
336    // tracing methods read from mempool, hence `LoadBlock` trait bound via
337    // `TraceExt`
338    Eth: TraceExt + 'static,
339{
340    /// Returns all transaction traces that match the given filter.
341    ///
342    /// This is similar to [`Self::trace_block`] but only returns traces for transactions that match
343    /// the filter.
344    pub async fn trace_filter(
345        &self,
346        filter: TraceFilter,
347    ) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
348        // We'll reuse the matcher across multiple blocks that are traced in parallel
349        let matcher = Arc::new(filter.matcher());
350        let TraceFilter { from_block, to_block, mut after, count, .. } = filter;
351        let start = from_block.unwrap_or(0);
352
353        let latest_block = self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?;
354        if start > latest_block {
355            // can't trace that range
356            return Err(EthApiError::HeaderNotFound(start.into()).into());
357        }
358        let end = to_block.unwrap_or(latest_block);
359        if end > latest_block {
360            return Err(EthApiError::HeaderNotFound(end.into()).into());
361        }
362
363        if start > end {
364            return Err(EthApiError::InvalidParams(
365                "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
366            )
367            .into())
368        }
369
370        // ensure that the range is not too large, since we need to fetch all blocks in the range
371        let distance = end.saturating_sub(start);
372        if distance > self.inner.eth_config.max_trace_filter_blocks {
373            return Err(EthApiError::InvalidParams(format!(
374                "Block range too large; currently limited to {} blocks",
375                self.inner.eth_config.max_trace_filter_blocks
376            ))
377            .into())
378        }
379
380        let mut all_traces = Vec::new();
381        let mut block_traces = Vec::with_capacity(self.inner.eth_config.max_tracing_requests);
382        for chunk_start in (start..=end).step_by(self.inner.eth_config.max_tracing_requests) {
383            let chunk_end = std::cmp::min(
384                chunk_start + self.inner.eth_config.max_tracing_requests as u64 - 1,
385                end,
386            );
387
388            // fetch all blocks in that chunk
389            let blocks = self
390                .eth_api()
391                .spawn_blocking_io(move |this| {
392                    Ok(this
393                        .provider()
394                        .recovered_block_range(chunk_start..=chunk_end)
395                        .map_err(Eth::Error::from_eth_err)?
396                        .into_iter()
397                        .map(Arc::new)
398                        .collect::<Vec<_>>())
399                })
400                .await?;
401
402            // trace all blocks
403            for block in &blocks {
404                let matcher = matcher.clone();
405                let traces = self.eth_api().trace_block_until(
406                    block.hash().into(),
407                    Some(block.clone()),
408                    None,
409                    TracingInspectorConfig::default_parity(),
410                    move |tx_info, mut ctx| {
411                        let mut traces = ctx
412                            .take_inspector()
413                            .into_parity_builder()
414                            .into_localized_transaction_traces(tx_info);
415                        traces.retain(|trace| matcher.matches(&trace.trace));
416                        Ok(Some(traces))
417                    },
418                );
419                block_traces.push(traces);
420            }
421
422            #[expect(clippy::iter_with_drain)]
423            let block_traces = futures::future::try_join_all(block_traces.drain(..)).await?;
424            all_traces.extend(block_traces.into_iter().flatten().flat_map(|traces| {
425                traces.into_iter().flatten().flat_map(|traces| traces.into_iter())
426            }));
427
428            // add reward traces for all blocks
429            for block in &blocks {
430                if let Some(base_block_reward) = self.calculate_base_block_reward(block.header())? {
431                    all_traces.extend(
432                        self.extract_reward_traces(
433                            block.header(),
434                            block.hash(),
435                            block.body().ommers(),
436                            base_block_reward,
437                        )
438                        .into_iter()
439                        .filter(|trace| matcher.matches(&trace.trace)),
440                    );
441                } else {
442                    // no block reward, means we're past the Paris hardfork and don't expect any
443                    // rewards because the blocks in ascending order
444                    break
445                }
446            }
447
448            // Skips the first `after` number of matching traces.
449            if let Some(cutoff) = after.map(|a| a as usize) &&
450                cutoff < all_traces.len()
451            {
452                all_traces.drain(..cutoff);
453                // we removed the first `after` traces
454                after = None;
455            }
456
457            // Return at most `count` traces after `after` has been consumed.
458            if after.is_none() &&
459                let Some(count) = count
460            {
461                let count = count as usize;
462                if count < all_traces.len() {
463                    all_traces.truncate(count);
464                    return Ok(all_traces)
465                }
466            }
467        }
468
469        // If `after` is greater than or equal to the number of matched traces, it returns an
470        // empty array.
471        if let Some(cutoff) = after.map(|a| a as usize) &&
472            cutoff >= all_traces.len()
473        {
474            return Ok(vec![])
475        }
476
477        Ok(all_traces)
478    }
479
480    /// Returns traces created at given block.
481    pub async fn trace_block(
482        &self,
483        block_id: BlockId,
484    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
485        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
486            return Err(EthApiError::HeaderNotFound(block_id).into());
487        };
488
489        let mut traces = self
490            .eth_api()
491            .trace_block_with(
492                block_id,
493                Some(block.clone()),
494                TracingInspectorConfig::default_parity(),
495                |tx_info, mut ctx| {
496                    let traces = ctx
497                        .take_inspector()
498                        .into_parity_builder()
499                        .into_localized_transaction_traces(tx_info);
500                    Ok(traces)
501                },
502            )
503            .await?
504            .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
505
506        if let Some(traces) = traces.as_mut() &&
507            let Some(base_block_reward) = self.calculate_base_block_reward(block.header())?
508        {
509            traces.extend(self.extract_reward_traces(
510                block.header(),
511                block.hash(),
512                block.body().ommers(),
513                base_block_reward,
514            ));
515        }
516
517        Ok(traces)
518    }
519
520    /// Replays all transactions in a block
521    pub async fn replay_block_transactions(
522        &self,
523        block_id: BlockId,
524        trace_types: HashSet<TraceType>,
525    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
526        self.eth_api()
527            .trace_block_with(
528                block_id,
529                None,
530                TracingInspectorConfig::from_parity_config(&trace_types),
531                move |tx_info, mut ctx| {
532                    let mut full_trace = ctx
533                        .take_inspector()
534                        .into_parity_builder()
535                        .into_trace_results(&ctx.result, &trace_types);
536
537                    // If statediffs were requested, populate them with the account balance and
538                    // nonce from pre-state
539                    if let Some(ref mut state_diff) = full_trace.state_diff {
540                        populate_state_diff(state_diff, &ctx.db, ctx.state.iter())
541                            .map_err(Eth::Error::from_eth_err)?;
542                    }
543
544                    let trace = TraceResultsWithTransactionHash {
545                        transaction_hash: tx_info.hash.expect("tx hash is set"),
546                        full_trace,
547                    };
548                    Ok(trace)
549                },
550            )
551            .await
552    }
553
554    /// Returns the opcodes of all transactions in the given block.
555    ///
556    /// This is the same as [`Self::trace_transaction_opcode_gas`] but for all transactions in a
557    /// block.
558    pub async fn trace_block_opcode_gas(
559        &self,
560        block_id: BlockId,
561    ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
562        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
563            return Err(EthApiError::HeaderNotFound(block_id).into());
564        };
565
566        let Some(transactions) = self
567            .eth_api()
568            .trace_block_inspector(
569                block_id,
570                Some(block.clone()),
571                OpcodeGasInspector::default,
572                move |tx_info, ctx| {
573                    let trace = TransactionOpcodeGas {
574                        transaction_hash: tx_info.hash.expect("tx hash is set"),
575                        opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
576                    };
577                    Ok(trace)
578                },
579            )
580            .await?
581        else {
582            return Ok(None);
583        };
584
585        Ok(Some(BlockOpcodeGas {
586            block_hash: block.hash(),
587            block_number: block.number(),
588            transactions,
589        }))
590    }
591
592    /// Returns all storage slots accessed during transaction execution along with their access
593    /// counts.
594    pub async fn trace_block_storage_access(
595        &self,
596        block_id: BlockId,
597    ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
598        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
599            return Err(EthApiError::HeaderNotFound(block_id).into());
600        };
601
602        let Some(transactions) = self
603            .eth_api()
604            .trace_block_inspector(
605                block_id,
606                Some(block.clone()),
607                StorageInspector::default,
608                move |tx_info, mut ctx| {
609                    let unique_loads = ctx.inspector.unique_loads();
610                    let warm_loads = ctx.inspector.warm_loads();
611                    let trace = TransactionStorageAccess {
612                        transaction_hash: tx_info.hash.expect("tx hash is set"),
613                        storage_access: ctx.take_inspector().into_accessed_slots(),
614                        unique_loads,
615                        warm_loads,
616                    };
617                    Ok(trace)
618                },
619            )
620            .await?
621        else {
622            return Ok(None);
623        };
624
625        Ok(Some(BlockStorageAccess {
626            block_hash: block.hash(),
627            block_number: block.number(),
628            transactions,
629        }))
630    }
631}
632
633#[async_trait]
634impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
635where
636    Eth: TraceExt + 'static,
637{
638    /// Executes the given call and returns a number of possible traces for it.
639    ///
640    /// Handler for `trace_call`
641    async fn trace_call(
642        &self,
643        call: RpcTxReq<Eth::NetworkTypes>,
644        trace_types: HashSet<TraceType>,
645        block_id: Option<BlockId>,
646        state_overrides: Option<StateOverride>,
647        block_overrides: Option<Box<BlockOverrides>>,
648    ) -> RpcResult<TraceResults> {
649        let _permit = self.acquire_trace_permit().await;
650        let request =
651            TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
652        Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
653    }
654
655    /// Handler for `trace_callMany`
656    async fn trace_call_many(
657        &self,
658        calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
659        block_id: Option<BlockId>,
660    ) -> RpcResult<Vec<TraceResults>> {
661        let _permit = self.acquire_trace_permit().await;
662        Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
663    }
664
665    /// Handler for `trace_rawTransaction`
666    async fn trace_raw_transaction(
667        &self,
668        data: Bytes,
669        trace_types: HashSet<TraceType>,
670        block_id: Option<BlockId>,
671    ) -> RpcResult<TraceResults> {
672        let _permit = self.acquire_trace_permit().await;
673        Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
674            .await
675            .map_err(Into::into)?)
676    }
677
678    /// Handler for `trace_replayBlockTransactions`
679    async fn replay_block_transactions(
680        &self,
681        block_id: BlockId,
682        trace_types: HashSet<TraceType>,
683    ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
684        let _permit = self.acquire_trace_permit().await;
685        Ok(Self::replay_block_transactions(self, block_id, trace_types)
686            .await
687            .map_err(Into::into)?)
688    }
689
690    /// Handler for `trace_replayTransaction`
691    async fn replay_transaction(
692        &self,
693        transaction: B256,
694        trace_types: HashSet<TraceType>,
695    ) -> RpcResult<TraceResults> {
696        let _permit = self.acquire_trace_permit().await;
697        Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
698    }
699
700    /// Handler for `trace_block`
701    async fn trace_block(
702        &self,
703        block_id: BlockId,
704    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
705        let _permit = self.acquire_trace_permit().await;
706        Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
707    }
708
709    /// Handler for `trace_filter`
710    ///
711    /// This is similar to `eth_getLogs` but for traces.
712    ///
713    /// # Limitations
714    /// This currently requires block filter fields, since reth does not have address indices yet.
715    async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
716        let _permit = self.inner.blocking_task_guard.clone().acquire_many_owned(2).await;
717        Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
718    }
719
720    /// Returns transaction trace at given index.
721    /// Handler for `trace_get`
722    async fn trace_get(
723        &self,
724        hash: B256,
725        indices: Vec<Index>,
726    ) -> RpcResult<Option<LocalizedTransactionTrace>> {
727        let _permit = self.acquire_trace_permit().await;
728        Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
729            .await
730            .map_err(Into::into)?)
731    }
732
733    /// Handler for `trace_transaction`
734    async fn trace_transaction(
735        &self,
736        hash: B256,
737    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
738        let _permit = self.acquire_trace_permit().await;
739        Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
740    }
741
742    /// Handler for `trace_transactionOpcodeGas`
743    async fn trace_transaction_opcode_gas(
744        &self,
745        tx_hash: B256,
746    ) -> RpcResult<Option<TransactionOpcodeGas>> {
747        let _permit = self.acquire_trace_permit().await;
748        Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
749    }
750
751    /// Handler for `trace_blockOpcodeGas`
752    async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
753        let _permit = self.acquire_trace_permit().await;
754        Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
755    }
756}
757
758impl<Eth> std::fmt::Debug for TraceApi<Eth> {
759    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
760        f.debug_struct("TraceApi").finish_non_exhaustive()
761    }
762}
763impl<Eth> Clone for TraceApi<Eth> {
764    fn clone(&self) -> Self {
765        Self { inner: Arc::clone(&self.inner) }
766    }
767}
768
769struct TraceApiInner<Eth> {
770    /// Access to commonly used code of the `eth` namespace
771    eth_api: Eth,
772    // restrict the number of concurrent calls to `trace_*`
773    blocking_task_guard: BlockingTaskGuard,
774    // eth config settings
775    eth_config: EthConfig,
776}
777
778/// Response type for storage tracing that contains all accessed storage slots
779/// for a transaction.
780#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
781#[serde(rename_all = "camelCase")]
782pub struct TransactionStorageAccess {
783    /// Hash of the transaction
784    pub transaction_hash: B256,
785    /// Tracks storage slots and access counter.
786    pub storage_access: HashMap<Address, HashMap<B256, u64>>,
787    /// Number of unique storage loads
788    pub unique_loads: u64,
789    /// Number of warm storage loads
790    pub warm_loads: u64,
791}
792
793/// Response type for storage tracing that contains all accessed storage slots
794#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
795#[serde(rename_all = "camelCase")]
796pub struct BlockStorageAccess {
797    /// The block hash
798    pub block_hash: BlockHash,
799    /// The block's number
800    pub block_number: u64,
801    /// All executed transactions in the block in the order they were executed
802    pub transactions: Vec<TransactionStorageAccess>,
803}
804
805/// Helper to construct a [`LocalizedTransactionTrace`] that describes a reward to the block
806/// beneficiary.
807fn reward_trace<H: BlockHeader>(
808    block_hash: BlockHash,
809    header: &H,
810    reward: RewardAction,
811) -> LocalizedTransactionTrace {
812    LocalizedTransactionTrace {
813        block_hash: Some(block_hash),
814        block_number: Some(header.number()),
815        transaction_hash: None,
816        transaction_position: None,
817        trace: TransactionTrace {
818            trace_address: vec![],
819            subtraces: 0,
820            action: Action::Reward(reward),
821            error: None,
822            result: None,
823        },
824    }
825}