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