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