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