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::{EthChainSpec, EthereumHardfork, MAINNET, SEPOLIA};
19use reth_evm::ConfigureEvm;
20use reth_primitives_traits::{BlockBody, BlockHeader};
21use reth_provider::{BlockNumReader, BlockReader, ChainSpecProvider};
22use reth_revm::{database::StateProviderDatabase, db::CacheDB};
23use reth_rpc_api::TraceApiServer;
24use reth_rpc_eth_api::{helpers::TraceExt, FromEthApiError, RpcNodeCore};
25use reth_rpc_eth_types::{error::EthApiError, utils::recover_raw_transaction, EthConfig};
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        let end = if let Some(to_block) = to_block {
250            to_block
251        } else {
252            self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?
253        };
254
255        if start > end {
256            return Err(EthApiError::InvalidParams(
257                "invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
258            )
259            .into())
260        }
261
262        // ensure that the range is not too large, since we need to fetch all blocks in the range
263        let distance = end.saturating_sub(start);
264        if distance > self.inner.eth_config.max_trace_filter_blocks {
265            return Err(EthApiError::InvalidParams(
266                "Block range too large; currently limited to 100 blocks".to_string(),
267            )
268            .into())
269        }
270
271        // fetch all blocks in that range
272        let blocks = self
273            .provider()
274            .recovered_block_range(start..=end)
275            .map_err(Eth::Error::from_eth_err)?
276            .into_iter()
277            .map(Arc::new)
278            .collect::<Vec<_>>();
279
280        // trace all blocks
281        let mut block_traces = Vec::with_capacity(blocks.len());
282        for block in &blocks {
283            let matcher = matcher.clone();
284            let traces = self.eth_api().trace_block_until(
285                block.hash().into(),
286                Some(block.clone()),
287                None,
288                TracingInspectorConfig::default_parity(),
289                move |tx_info, inspector, _, _, _| {
290                    let mut traces =
291                        inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
292                    traces.retain(|trace| matcher.matches(&trace.trace));
293                    Ok(Some(traces))
294                },
295            );
296            block_traces.push(traces);
297        }
298
299        let block_traces = futures::future::try_join_all(block_traces).await?;
300        let mut all_traces = block_traces
301            .into_iter()
302            .flatten()
303            .flat_map(|traces| traces.into_iter().flatten().flat_map(|traces| traces.into_iter()))
304            .collect::<Vec<_>>();
305
306        // add reward traces for all blocks
307        for block in &blocks {
308            if let Some(base_block_reward) = self.calculate_base_block_reward(block.header())? {
309                all_traces.extend(
310                    self.extract_reward_traces(
311                        block.header(),
312                        block.body().ommers(),
313                        base_block_reward,
314                    )
315                    .into_iter()
316                    .filter(|trace| matcher.matches(&trace.trace)),
317                );
318            } else {
319                // no block reward, means we're past the Paris hardfork and don't expect any rewards
320                // because the blocks in ascending order
321                break
322            }
323        }
324
325        // Skips the first `after` number of matching traces.
326        // If `after` is greater than or equal to the number of matched traces, it returns an empty
327        // array.
328        if let Some(after) = after.map(|a| a as usize) {
329            if after < all_traces.len() {
330                all_traces.drain(..after);
331            } else {
332                return Ok(vec![])
333            }
334        }
335
336        // Return at most `count` of traces
337        if let Some(count) = count {
338            let count = count as usize;
339            if count < all_traces.len() {
340                all_traces.truncate(count);
341            }
342        };
343
344        Ok(all_traces)
345    }
346
347    /// Returns all traces for the given transaction hash
348    pub async fn trace_transaction(
349        &self,
350        hash: B256,
351    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
352        self.eth_api()
353            .spawn_trace_transaction_in_block(
354                hash,
355                TracingInspectorConfig::default_parity(),
356                move |tx_info, inspector, _, _| {
357                    let traces =
358                        inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
359                    Ok(traces)
360                },
361            )
362            .await
363    }
364
365    /// Returns traces created at given block.
366    pub async fn trace_block(
367        &self,
368        block_id: BlockId,
369    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
370        let traces = self.eth_api().trace_block_with(
371            block_id,
372            None,
373            TracingInspectorConfig::default_parity(),
374            |tx_info, inspector, _, _, _| {
375                let traces =
376                    inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
377                Ok(traces)
378            },
379        );
380
381        let block = self.eth_api().recovered_block(block_id);
382        let (maybe_traces, maybe_block) = futures::try_join!(traces, block)?;
383
384        let mut maybe_traces =
385            maybe_traces.map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
386
387        if let (Some(block), Some(traces)) = (maybe_block, maybe_traces.as_mut()) {
388            if let Some(base_block_reward) = self.calculate_base_block_reward(block.header())? {
389                traces.extend(self.extract_reward_traces(
390                    block.header(),
391                    block.body().ommers(),
392                    base_block_reward,
393                ));
394            }
395        }
396
397        Ok(maybe_traces)
398    }
399
400    /// Replays all transactions in a block
401    pub async fn replay_block_transactions(
402        &self,
403        block_id: BlockId,
404        trace_types: HashSet<TraceType>,
405    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
406        self.eth_api()
407            .trace_block_with(
408                block_id,
409                None,
410                TracingInspectorConfig::from_parity_config(&trace_types),
411                move |tx_info, inspector, res, state, db| {
412                    let mut full_trace =
413                        inspector.into_parity_builder().into_trace_results(&res, &trace_types);
414
415                    // If statediffs were requested, populate them with the account balance and
416                    // nonce from pre-state
417                    if let Some(ref mut state_diff) = full_trace.state_diff {
418                        populate_state_diff(state_diff, db, state.iter())
419                            .map_err(Eth::Error::from_eth_err)?;
420                    }
421
422                    let trace = TraceResultsWithTransactionHash {
423                        transaction_hash: tx_info.hash.expect("tx hash is set"),
424                        full_trace,
425                    };
426                    Ok(trace)
427                },
428            )
429            .await
430    }
431
432    /// Returns all opcodes with their count and combined gas usage for the given transaction in no
433    /// particular order.
434    pub async fn trace_transaction_opcode_gas(
435        &self,
436        tx_hash: B256,
437    ) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
438        self.eth_api()
439            .spawn_trace_transaction_in_block_with_inspector(
440                tx_hash,
441                OpcodeGasInspector::default(),
442                move |_tx_info, inspector, _res, _| {
443                    let trace = TransactionOpcodeGas {
444                        transaction_hash: tx_hash,
445                        opcode_gas: inspector.opcode_gas_iter().collect(),
446                    };
447                    Ok(trace)
448                },
449            )
450            .await
451    }
452
453    /// Returns the opcodes of all transactions in the given block.
454    ///
455    /// This is the same as [`Self::trace_transaction_opcode_gas`] but for all transactions in a
456    /// block.
457    pub async fn trace_block_opcode_gas(
458        &self,
459        block_id: BlockId,
460    ) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
461        let res = self
462            .eth_api()
463            .trace_block_inspector(
464                block_id,
465                None,
466                OpcodeGasInspector::default,
467                move |tx_info, inspector, _res, _, _| {
468                    let trace = TransactionOpcodeGas {
469                        transaction_hash: tx_info.hash.expect("tx hash is set"),
470                        opcode_gas: inspector.opcode_gas_iter().collect(),
471                    };
472                    Ok(trace)
473                },
474            )
475            .await?;
476
477        let Some(transactions) = res else { return Ok(None) };
478
479        let Some(block) = self.eth_api().recovered_block(block_id).await? else { return Ok(None) };
480
481        Ok(Some(BlockOpcodeGas {
482            block_hash: block.hash(),
483            block_number: block.number(),
484            transactions,
485        }))
486    }
487
488    /// Calculates the base block reward for the given block:
489    ///
490    /// - if Paris hardfork is activated, no block rewards are given
491    /// - if Paris hardfork is not activated, calculate block rewards with block number only
492    /// - if Paris hardfork is unknown, calculate block rewards with block number and ttd
493    fn calculate_base_block_reward<H: BlockHeader>(
494        &self,
495        header: &H,
496    ) -> Result<Option<u128>, Eth::Error> {
497        let chain_spec = self.provider().chain_spec();
498        let is_paris_activated = if chain_spec.chain() == MAINNET.chain() {
499            Some(header.number()) >= EthereumHardfork::Paris.mainnet_activation_block()
500        } else if chain_spec.chain() == SEPOLIA.chain() {
501            Some(header.number()) >= EthereumHardfork::Paris.sepolia_activation_block()
502        } else {
503            true
504        };
505
506        if is_paris_activated {
507            return Ok(None)
508        }
509
510        Ok(Some(base_block_reward_pre_merge(&chain_spec, header.number())))
511    }
512
513    /// Extracts the reward traces for the given block:
514    ///  - block reward
515    ///  - uncle rewards
516    fn extract_reward_traces<H: BlockHeader>(
517        &self,
518        header: &H,
519        ommers: Option<&[H]>,
520        base_block_reward: u128,
521    ) -> Vec<LocalizedTransactionTrace> {
522        let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
523        let mut traces = Vec::with_capacity(ommers_cnt + 1);
524
525        let block_reward = block_reward(base_block_reward, ommers_cnt);
526        traces.push(reward_trace(
527            header,
528            RewardAction {
529                author: header.beneficiary(),
530                reward_type: RewardType::Block,
531                value: U256::from(block_reward),
532            },
533        ));
534
535        let Some(ommers) = ommers else { return traces };
536
537        for uncle in ommers {
538            let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
539            traces.push(reward_trace(
540                header,
541                RewardAction {
542                    author: uncle.beneficiary(),
543                    reward_type: RewardType::Uncle,
544                    value: U256::from(uncle_reward),
545                },
546            ));
547        }
548        traces
549    }
550}
551
552#[async_trait]
553impl<Eth> TraceApiServer for TraceApi<Eth>
554where
555    Eth: TraceExt + 'static,
556{
557    /// Executes the given call and returns a number of possible traces for it.
558    ///
559    /// Handler for `trace_call`
560    async fn trace_call(
561        &self,
562        call: TransactionRequest,
563        trace_types: HashSet<TraceType>,
564        block_id: Option<BlockId>,
565        state_overrides: Option<StateOverride>,
566        block_overrides: Option<Box<BlockOverrides>>,
567    ) -> RpcResult<TraceResults> {
568        let _permit = self.acquire_trace_permit().await;
569        let request =
570            TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
571        Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
572    }
573
574    /// Handler for `trace_callMany`
575    async fn trace_call_many(
576        &self,
577        calls: Vec<(TransactionRequest, HashSet<TraceType>)>,
578        block_id: Option<BlockId>,
579    ) -> RpcResult<Vec<TraceResults>> {
580        let _permit = self.acquire_trace_permit().await;
581        Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
582    }
583
584    /// Handler for `trace_rawTransaction`
585    async fn trace_raw_transaction(
586        &self,
587        data: Bytes,
588        trace_types: HashSet<TraceType>,
589        block_id: Option<BlockId>,
590    ) -> RpcResult<TraceResults> {
591        let _permit = self.acquire_trace_permit().await;
592        Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
593            .await
594            .map_err(Into::into)?)
595    }
596
597    /// Handler for `trace_replayBlockTransactions`
598    async fn replay_block_transactions(
599        &self,
600        block_id: BlockId,
601        trace_types: HashSet<TraceType>,
602    ) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
603        let _permit = self.acquire_trace_permit().await;
604        Ok(Self::replay_block_transactions(self, block_id, trace_types)
605            .await
606            .map_err(Into::into)?)
607    }
608
609    /// Handler for `trace_replayTransaction`
610    async fn replay_transaction(
611        &self,
612        transaction: B256,
613        trace_types: HashSet<TraceType>,
614    ) -> RpcResult<TraceResults> {
615        let _permit = self.acquire_trace_permit().await;
616        Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
617    }
618
619    /// Handler for `trace_block`
620    async fn trace_block(
621        &self,
622        block_id: BlockId,
623    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
624        let _permit = self.acquire_trace_permit().await;
625        Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
626    }
627
628    /// Handler for `trace_filter`
629    ///
630    /// This is similar to `eth_getLogs` but for traces.
631    ///
632    /// # Limitations
633    /// This currently requires block filter fields, since reth does not have address indices yet.
634    async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
635        Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
636    }
637
638    /// Returns transaction trace at given index.
639    /// Handler for `trace_get`
640    async fn trace_get(
641        &self,
642        hash: B256,
643        indices: Vec<Index>,
644    ) -> RpcResult<Option<LocalizedTransactionTrace>> {
645        let _permit = self.acquire_trace_permit().await;
646        Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
647            .await
648            .map_err(Into::into)?)
649    }
650
651    /// Handler for `trace_transaction`
652    async fn trace_transaction(
653        &self,
654        hash: B256,
655    ) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
656        let _permit = self.acquire_trace_permit().await;
657        Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
658    }
659
660    /// Handler for `trace_transactionOpcodeGas`
661    async fn trace_transaction_opcode_gas(
662        &self,
663        tx_hash: B256,
664    ) -> RpcResult<Option<TransactionOpcodeGas>> {
665        let _permit = self.acquire_trace_permit().await;
666        Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
667    }
668
669    /// Handler for `trace_blockOpcodeGas`
670    async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
671        let _permit = self.acquire_trace_permit().await;
672        Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
673    }
674}
675
676impl<Eth> std::fmt::Debug for TraceApi<Eth> {
677    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678        f.debug_struct("TraceApi").finish_non_exhaustive()
679    }
680}
681impl<Eth> Clone for TraceApi<Eth> {
682    fn clone(&self) -> Self {
683        Self { inner: Arc::clone(&self.inner) }
684    }
685}
686
687struct TraceApiInner<Eth> {
688    /// Access to commonly used code of the `eth` namespace
689    eth_api: Eth,
690    // restrict the number of concurrent calls to `trace_*`
691    blocking_task_guard: BlockingTaskGuard,
692    // eth config settings
693    eth_config: EthConfig,
694}
695
696/// Helper to construct a [`LocalizedTransactionTrace`] that describes a reward to the block
697/// beneficiary.
698fn reward_trace<H: BlockHeader>(header: &H, reward: RewardAction) -> LocalizedTransactionTrace {
699    LocalizedTransactionTrace {
700        block_hash: Some(header.hash_slow()),
701        block_number: Some(header.number()),
702        transaction_hash: None,
703        transaction_position: None,
704        trace: TransactionTrace {
705            trace_address: vec![],
706            subtraces: 0,
707            action: Action::Reward(reward),
708            error: None,
709            result: None,
710        },
711    }
712}