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_evm::ConfigureEvm;
23use reth_primitives_traits::{BlockBody, BlockHeader};
24use reth_rpc_api::TraceApiServer;
25use reth_rpc_convert::RpcTxReq;
26use reth_rpc_eth_api::{
27    helpers::{Call, LoadPendingBlock, LoadTransaction, Trace, TraceExt},
28    FromEthApiError, RpcNodeCore,
29};
30use reth_rpc_eth_types::{error::EthApiError, utils::recover_raw_transaction, EthConfig};
31use reth_storage_api::{BlockNumReader, BlockReader};
32use reth_tasks::pool::BlockingTaskGuard;
33use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
34use revm::DatabaseCommit;
35use revm_inspectors::{
36    opcode::OpcodeGasInspector,
37    storage::StorageInspector,
38    tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig},
39};
40use serde::{Deserialize, Serialize};
41use std::sync::Arc;
42use tokio::sync::{AcquireError, OwnedSemaphorePermit};
43
44/// Maximum number of `trace_filter` blocks replayed concurrently.
45const TRACE_FILTER_BLOCK_BUFFER_SIZE: usize = 4;
46/// Number of blocks fetched per provider range read in `trace_filter`.
47const TRACE_FILTER_FETCH_CHUNK_SIZE: usize = 16;
48
49/// `trace` API implementation.
50///
51/// This type provides the functionality for handling `trace` related requests.
52pub struct TraceApi<Eth> {
53    inner: Arc<TraceApiInner<Eth>>,
54}
55
56// === impl TraceApi ===
57
58impl<Eth> TraceApi<Eth> {
59    /// Create a new instance of the [`TraceApi`]
60    pub fn new(
61        eth_api: Eth,
62        blocking_task_guard: BlockingTaskGuard,
63        eth_config: EthConfig,
64    ) -> Self {
65        let inner = Arc::new(TraceApiInner { eth_api, blocking_task_guard, eth_config });
66        Self { inner }
67    }
68
69    /// Acquires a permit to execute a tracing call.
70    async fn acquire_trace_permit(
71        &self,
72    ) -> std::result::Result<OwnedSemaphorePermit, AcquireError> {
73        self.inner.blocking_task_guard.clone().acquire_owned().await
74    }
75
76    /// Access the underlying `Eth` API.
77    pub fn eth_api(&self) -> &Eth {
78        &self.inner.eth_api
79    }
80}
81
82impl<Eth: RpcNodeCore> TraceApi<Eth> {
83    /// Access the underlying provider.
84    pub fn provider(&self) -> &Eth::Provider {
85        self.inner.eth_api.provider()
86    }
87}
88
89// === impl TraceApi === //
90
91impl<Eth> TraceApi<Eth>
92where
93    // tracing methods do _not_ read from mempool, hence no `LoadBlock` trait
94    // bound
95    Eth: Trace + Call + LoadPendingBlock + LoadTransaction + 'static,
96{
97    /// Executes the given call and returns a number of possible traces for it.
98    pub async fn trace_call(
99        &self,
100        trace_request: TraceCallRequest<RpcTxReq<Eth::NetworkTypes>>,
101    ) -> Result<TraceResults, Eth::Error> {
102        let at = trace_request.block_id.unwrap_or_default();
103        let config = TracingInspectorConfig::from_parity_config(&trace_request.trace_types);
104        let overrides =
105            EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides);
106        let mut inspector = TracingInspector::new(config);
107        let this = self.clone();
108        self.eth_api()
109            .spawn_with_call_at(trace_request.call, at, overrides, move |db, evm_env, tx_env| {
110                let res = this.eth_api().inspect(&mut *db, evm_env, tx_env, &mut inspector)?;
111                let trace_res = inspector
112                    .into_parity_builder()
113                    .into_trace_results_with_state(&res, &trace_request.trace_types, &db)
114                    .map_err(Eth::Error::from_eth_err)?;
115                Ok(trace_res)
116            })
117            .await
118    }
119
120    /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces.
121    pub async fn trace_raw_transaction(
122        &self,
123        tx: Bytes,
124        trace_types: HashSet<TraceType>,
125        block_id: Option<BlockId>,
126    ) -> Result<TraceResults, Eth::Error> {
127        let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
128            .map(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
129
130        let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
131        let tx_env = self.eth_api().evm_config().tx_env(tx);
132
133        let config = TracingInspectorConfig::from_parity_config(&trace_types);
134
135        self.eth_api()
136            .spawn_trace_at_with_state(evm_env, tx_env, config, at, move |inspector, res, db| {
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.into());
374        }
375
376        if start > end {
377            return Err(EthApiError::InvalidParams(
378                "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
379            )
380            .into())
381        }
382
383        // ensure that the range is not too large, since every block in the range may be replayed
384        let distance = end.saturating_sub(start);
385        if distance > self.inner.eth_config.max_trace_filter_blocks {
386            return Err(EthApiError::InvalidParams(format!(
387                "Block range too large; currently limited to {} blocks",
388                self.inner.eth_config.max_trace_filter_blocks
389            ))
390            .into())
391        }
392
393        let mut all_traces = Vec::new();
394        let block_buffer_size =
395            self.inner.eth_config.max_tracing_requests.clamp(1, TRACE_FILTER_BLOCK_BUFFER_SIZE);
396        let mut include_reward_traces = true;
397
398        for chunk_start in (start..=end).step_by(TRACE_FILTER_FETCH_CHUNK_SIZE) {
399            let chunk_end = (chunk_start + TRACE_FILTER_FETCH_CHUNK_SIZE as u64 - 1).min(end);
400
401            let blocks = self
402                .eth_api()
403                .spawn_blocking_io(move |this| {
404                    let blocks = this
405                        .provider()
406                        .recovered_block_range(chunk_start..=chunk_end)
407                        .map_err(Eth::Error::from_eth_err)?;
408
409                    Ok(blocks.into_iter().map(Arc::new).collect::<Vec<_>>())
410                })
411                .await?;
412
413            let mut block_replays = futures::stream::iter(blocks)
414                .map(|block| {
415                    let this = self.clone();
416                    let matcher = matcher.clone();
417
418                    let block_hash = block.hash();
419
420                    async move {
421                        let permit = this.acquire_trace_permit().await;
422                        let traces = this
423                            .eth_api()
424                            .trace_block_until(
425                                block_hash.into(),
426                                Some(block.clone()),
427                                None,
428                                TracingInspectorConfig::default_parity(),
429                                move |tx_info, mut ctx| {
430                                    // Keep the block replay permit inside the spawned replay task.
431                                    let _block_replay_permit = &permit;
432                                    let mut traces = ctx
433                                        .take_inspector()
434                                        .into_parity_builder()
435                                        .into_localized_transaction_traces(tx_info);
436                                    traces.retain(|trace| matcher.matches(&trace.trace));
437                                    Ok(Some(traces))
438                                },
439                            )
440                            .await?;
441
442                        Ok::<_, Eth::Error>((block, traces))
443                    }
444                })
445                .buffered(block_buffer_size);
446
447            while let Some(block_replay) = block_replays.next().await {
448                let (block, traces) = block_replay?;
449                let reward_traces = if include_reward_traces {
450                    if let Some(base_block_reward) =
451                        self.calculate_base_block_reward(block.header())?
452                    {
453                        self.extract_reward_traces(
454                            block.header(),
455                            block.hash(),
456                            block.body().ommers(),
457                            base_block_reward,
458                        )
459                        .into_iter()
460                        .filter(|trace| matcher.matches(&trace.trace))
461                        .collect::<Vec<_>>()
462                    } else {
463                        // Blocks are processed in ascending order, so once a historical range
464                        // reaches post-Paris blocks, later blocks in the range have no rewards.
465                        include_reward_traces = false;
466                        Vec::new()
467                    }
468                } else {
469                    Vec::new()
470                };
471
472                if let Some(traces) = traces {
473                    all_traces.extend(traces.into_iter().flatten().flatten());
474                }
475                all_traces.extend(reward_traces);
476
477                if let Some(traces) =
478                    apply_trace_filter_pagination(&mut all_traces, &mut after, count)
479                {
480                    return Ok(traces)
481                }
482            }
483        }
484
485        // If `after` is greater than or equal to the number of matched traces, it returns an
486        // empty array.
487        if let Some(cutoff) = after.map(|a| a as usize) &&
488            cutoff >= all_traces.len()
489        {
490            return Ok(vec![])
491        }
492
493        Ok(all_traces)
494    }
495
496    /// Returns traces created at given block.
497    pub async fn trace_block(
498        &self,
499        block_id: BlockId,
500    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
501        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
502            return Err(EthApiError::HeaderNotFound(block_id).into());
503        };
504
505        let mut traces = self
506            .eth_api()
507            .trace_block_with(
508                block_id,
509                Some(block.clone()),
510                TracingInspectorConfig::default_parity(),
511                |tx_info, mut ctx| {
512                    let traces = ctx
513                        .take_inspector()
514                        .into_parity_builder()
515                        .into_localized_transaction_traces(tx_info);
516                    Ok(traces)
517                },
518            )
519            .await?
520            .map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
521
522        if let Some(traces) = traces.as_mut() &&
523            let Some(base_block_reward) = self.calculate_base_block_reward(block.header())?
524        {
525            traces.extend(self.extract_reward_traces(
526                block.header(),
527                block.hash(),
528                block.body().ommers(),
529                base_block_reward,
530            ));
531        }
532
533        Ok(traces)
534    }
535
536    /// Replays all transactions in a block
537    pub async fn replay_block_transactions(
538        &self,
539        block_id: BlockId,
540        trace_types: HashSet<TraceType>,
541    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
542        self.eth_api()
543            .trace_block_with(
544                block_id,
545                None,
546                TracingInspectorConfig::from_parity_config(&trace_types),
547                move |tx_info, mut ctx| {
548                    let mut full_trace = ctx
549                        .take_inspector()
550                        .into_parity_builder()
551                        .into_trace_results(&ctx.result, &trace_types);
552
553                    // If statediffs were requested, populate them with the account balance and
554                    // nonce from pre-state
555                    if let Some(ref mut state_diff) = full_trace.state_diff {
556                        populate_state_diff(state_diff, &ctx.db, ctx.state.iter())
557                            .map_err(Eth::Error::from_eth_err)?;
558                    }
559
560                    let trace = TraceResultsWithTransactionHash {
561                        transaction_hash: tx_info.hash.expect("tx hash is set"),
562                        full_trace,
563                    };
564                    Ok(trace)
565                },
566            )
567            .await
568    }
569
570    /// Returns the opcodes of all transactions in the given block.
571    ///
572    /// This is the same as [`Self::trace_transaction_opcode_gas`] but for all transactions in a
573    /// block.
574    pub async fn trace_block_opcode_gas(
575        &self,
576        block_id: BlockId,
577    ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
578        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
579            return Err(EthApiError::HeaderNotFound(block_id).into());
580        };
581
582        let Some(transactions) = self
583            .eth_api()
584            .trace_block_inspector(
585                block_id,
586                Some(block.clone()),
587                OpcodeGasInspector::default,
588                move |tx_info, ctx| {
589                    let trace = TransactionOpcodeGas {
590                        transaction_hash: tx_info.hash.expect("tx hash is set"),
591                        opcode_gas: ctx.inspector.opcode_gas_iter().collect(),
592                    };
593                    Ok(trace)
594                },
595            )
596            .await?
597        else {
598            return Ok(None);
599        };
600
601        Ok(Some(BlockOpcodeGas {
602            block_hash: block.hash(),
603            block_number: block.number(),
604            transactions,
605        }))
606    }
607
608    /// Returns all storage slots accessed during transaction execution along with their access
609    /// counts.
610    pub async fn trace_block_storage_access(
611        &self,
612        block_id: BlockId,
613    ) -> Result<Option<BlockStorageAccess>, Eth::Error> {
614        let Some(block) = self.eth_api().recovered_block(block_id).await? else {
615            return Err(EthApiError::HeaderNotFound(block_id).into());
616        };
617
618        let Some(transactions) = self
619            .eth_api()
620            .trace_block_inspector(
621                block_id,
622                Some(block.clone()),
623                StorageInspector::default,
624                move |tx_info, mut ctx| {
625                    let unique_loads = ctx.inspector.unique_loads();
626                    let warm_loads = ctx.inspector.warm_loads();
627                    let trace = TransactionStorageAccess {
628                        transaction_hash: tx_info.hash.expect("tx hash is set"),
629                        storage_access: ctx.take_inspector().into_accessed_slots(),
630                        unique_loads,
631                        warm_loads,
632                    };
633                    Ok(trace)
634                },
635            )
636            .await?
637        else {
638            return Ok(None);
639        };
640
641        Ok(Some(BlockStorageAccess {
642            block_hash: block.hash(),
643            block_number: block.number(),
644            transactions,
645        }))
646    }
647}
648
649fn apply_trace_filter_pagination(
650    all_traces: &mut Vec<LocalizedTransactionTrace>,
651    after: &mut Option<u64>,
652    count: Option<u64>,
653) -> Option<Vec<LocalizedTransactionTrace>> {
654    // Skips the first `after` number of matching traces.
655    if let Some(cutoff) = after.map(|a| a as usize) &&
656        cutoff < all_traces.len()
657    {
658        all_traces.drain(..cutoff);
659        // we removed the first `after` traces
660        *after = None;
661    }
662
663    // Return at most `count` traces after `after` has been consumed.
664    if after.is_none() &&
665        let Some(count) = count
666    {
667        let count = count as usize;
668        if count < all_traces.len() {
669            all_traces.truncate(count);
670            return Some(std::mem::take(all_traces))
671        }
672    }
673
674    None
675}
676
677#[async_trait]
678impl<Eth> TraceApiServer<RpcTxReq<Eth::NetworkTypes>> for TraceApi<Eth>
679where
680    Eth: TraceExt + 'static,
681{
682    /// Executes the given call and returns a number of possible traces for it.
683    ///
684    /// Handler for `trace_call`
685    async fn trace_call(
686        &self,
687        call: RpcTxReq<Eth::NetworkTypes>,
688        trace_types: HashSet<TraceType>,
689        block_id: Option<BlockId>,
690        state_overrides: Option<StateOverride>,
691        block_overrides: Option<Box<BlockOverrides>>,
692    ) -> RpcResult<TraceResults> {
693        let _permit = self.acquire_trace_permit().await;
694        let request =
695            TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
696        Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
697    }
698
699    /// Handler for `trace_callMany`
700    async fn trace_call_many(
701        &self,
702        calls: Vec<(RpcTxReq<Eth::NetworkTypes>, HashSet<TraceType>)>,
703        block_id: Option<BlockId>,
704    ) -> RpcResult<Vec<TraceResults>> {
705        let _permit = self.acquire_trace_permit().await;
706        Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
707    }
708
709    /// Handler for `trace_rawTransaction`
710    async fn trace_raw_transaction(
711        &self,
712        data: Bytes,
713        trace_types: HashSet<TraceType>,
714        block_id: Option<BlockId>,
715    ) -> RpcResult<TraceResults> {
716        let _permit = self.acquire_trace_permit().await;
717        Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
718            .await
719            .map_err(Into::into)?)
720    }
721
722    /// Handler for `trace_replayBlockTransactions`
723    async fn replay_block_transactions(
724        &self,
725        block_id: BlockId,
726        trace_types: HashSet<TraceType>,
727    ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
728        let _permit = self.acquire_trace_permit().await;
729        Ok(Self::replay_block_transactions(self, block_id, trace_types)
730            .await
731            .map_err(Into::into)?)
732    }
733
734    /// Handler for `trace_replayTransaction`
735    async fn replay_transaction(
736        &self,
737        transaction: B256,
738        trace_types: HashSet<TraceType>,
739    ) -> RpcResult<TraceResults> {
740        let _permit = self.acquire_trace_permit().await;
741        Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
742    }
743
744    /// Handler for `trace_block`
745    async fn trace_block(
746        &self,
747        block_id: BlockId,
748    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
749        let _permit = self.acquire_trace_permit().await;
750        Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
751    }
752
753    /// Handler for `trace_filter`
754    ///
755    /// This is similar to `eth_getLogs` but for traces.
756    ///
757    /// # Limitations
758    /// This currently requires block filter fields, since reth does not have address indices yet.
759    async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
760        Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
761    }
762
763    /// Returns transaction trace at given index.
764    /// Handler for `trace_get`
765    async fn trace_get(
766        &self,
767        hash: B256,
768        indices: Vec<Index>,
769    ) -> RpcResult<Option<LocalizedTransactionTrace>> {
770        let _permit = self.acquire_trace_permit().await;
771        Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
772            .await
773            .map_err(Into::into)?)
774    }
775
776    /// Handler for `trace_transaction`
777    async fn trace_transaction(
778        &self,
779        hash: B256,
780    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
781        let _permit = self.acquire_trace_permit().await;
782        Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
783    }
784
785    /// Handler for `trace_transactionOpcodeGas`
786    async fn trace_transaction_opcode_gas(
787        &self,
788        tx_hash: B256,
789    ) -> RpcResult<Option<TransactionOpcodeGas>> {
790        let _permit = self.acquire_trace_permit().await;
791        Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
792    }
793
794    /// Handler for `trace_blockOpcodeGas`
795    async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
796        let _permit = self.acquire_trace_permit().await;
797        Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
798    }
799}
800
801impl<Eth> std::fmt::Debug for TraceApi<Eth> {
802    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
803        f.debug_struct("TraceApi").finish_non_exhaustive()
804    }
805}
806impl<Eth> Clone for TraceApi<Eth> {
807    fn clone(&self) -> Self {
808        Self { inner: Arc::clone(&self.inner) }
809    }
810}
811
812struct TraceApiInner<Eth> {
813    /// Access to commonly used code of the `eth` namespace
814    eth_api: Eth,
815    // restrict the number of concurrent calls to `trace_*`
816    blocking_task_guard: BlockingTaskGuard,
817    // eth config settings
818    eth_config: EthConfig,
819}
820
821/// Response type for storage tracing that contains all accessed storage slots
822/// for a transaction.
823#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
824#[serde(rename_all = "camelCase")]
825pub struct TransactionStorageAccess {
826    /// Hash of the transaction
827    pub transaction_hash: B256,
828    /// Tracks storage slots and access counter.
829    pub storage_access: HashMap<Address, HashMap<B256, u64>>,
830    /// Number of unique storage loads
831    pub unique_loads: u64,
832    /// Number of warm storage loads
833    pub warm_loads: u64,
834}
835
836/// Response type for storage tracing that contains all accessed storage slots
837#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
838#[serde(rename_all = "camelCase")]
839pub struct BlockStorageAccess {
840    /// The block hash
841    pub block_hash: BlockHash,
842    /// The block's number
843    pub block_number: u64,
844    /// All executed transactions in the block in the order they were executed
845    pub transactions: Vec<TransactionStorageAccess>,
846}
847
848/// Helper to construct a [`LocalizedTransactionTrace`] that describes a reward to the block
849/// beneficiary.
850fn reward_trace<H: BlockHeader>(
851    block_hash: BlockHash,
852    header: &H,
853    reward: RewardAction,
854) -> LocalizedTransactionTrace {
855    LocalizedTransactionTrace {
856        block_hash: Some(block_hash),
857        block_number: Some(header.number()),
858        transaction_hash: None,
859        transaction_position: None,
860        trace: TransactionTrace {
861            trace_address: vec![],
862            subtraces: 0,
863            action: Action::Reward(reward),
864            error: None,
865            result: None,
866        },
867    }
868}
869
870#[cfg(test)]
871mod tests {
872    use super::*;
873
874    fn localized_transaction_trace(
875        block_number: u64,
876        transaction_position: u64,
877    ) -> LocalizedTransactionTrace {
878        LocalizedTransactionTrace {
879            block_hash: Some(B256::ZERO),
880            block_number: Some(block_number),
881            transaction_hash: Some(B256::ZERO),
882            transaction_position: Some(transaction_position),
883            trace: TransactionTrace::default(),
884        }
885    }
886
887    fn localized_reward_trace(block_number: u64) -> LocalizedTransactionTrace {
888        LocalizedTransactionTrace {
889            block_hash: Some(B256::ZERO),
890            block_number: Some(block_number),
891            transaction_hash: None,
892            transaction_position: None,
893            trace: TransactionTrace {
894                trace_address: vec![],
895                subtraces: 0,
896                action: Action::Reward(RewardAction {
897                    author: Address::ZERO,
898                    reward_type: RewardType::Block,
899                    value: U256::ZERO,
900                }),
901                error: None,
902                result: None,
903            },
904        }
905    }
906
907    fn trace_order(traces: &[LocalizedTransactionTrace]) -> Vec<(u64, Option<u64>, bool)> {
908        traces
909            .iter()
910            .map(|trace| {
911                (
912                    trace.block_number.unwrap(),
913                    trace.transaction_position,
914                    trace.trace.action.is_reward(),
915                )
916            })
917            .collect()
918    }
919
920    #[test]
921    fn trace_filter_paginates_after_per_block_reward_order() {
922        let mut all_traces = vec![
923            localized_transaction_trace(1, 0),
924            localized_reward_trace(1),
925            localized_transaction_trace(2, 0),
926            localized_reward_trace(2),
927        ];
928
929        let mut after = Some(1);
930        let paginated =
931            apply_trace_filter_pagination(&mut all_traces, &mut after, Some(1)).unwrap();
932
933        assert_eq!(trace_order(&paginated), vec![(1, None, true)]);
934    }
935}