Skip to main content

reth_rpc/
debug.rs

1use alloy_consensus::{transaction::TxHashRef, BlockHeader};
2use alloy_eips::{eip2718::Encodable2718, BlockId, BlockNumberOrTag};
3use alloy_evm::{env::BlockEnvironment, Evm};
4use alloy_genesis::ChainConfig;
5use alloy_primitives::{hex::decode, uint, Address, Bytes, B256, U256, U64};
6use alloy_rlp::{Decodable, Encodable};
7use alloy_rpc_types::BlockTransactionsKind;
8use alloy_rpc_types_debug::ExecutionWitness;
9use alloy_rpc_types_eth::{
10    state::EvmOverrides, Account, AccountInfo, BlockError, Bundle, Index, StateContext,
11};
12use alloy_rpc_types_trace::geth::{
13    ChainBlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
14    TraceResult,
15};
16use async_trait::async_trait;
17use futures::Stream;
18use jsonrpsee::{core::RpcResult, PendingSubscriptionSink, SubscriptionMessage};
19use parking_lot::RwLock;
20use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
21use reth_engine_primitives::ConsensusEngineEvent;
22use reth_errors::RethError;
23use reth_evm::{block::BlockExecutor, execute::Executor, ConfigureEvm, EvmEnvFor};
24use reth_primitives_traits::{
25    Block as BlockTrait, BlockBody, BlockTy, ReceiptWithBloom, RecoveredBlock,
26};
27use reth_revm::{db::State, witness::ExecutionWitnessRecord};
28use reth_rpc_api::DebugApiServer;
29use reth_rpc_convert::RpcTxReq;
30use reth_rpc_eth_api::{
31    helpers::{EthTransactions, TraceExt},
32    AsEthApiError, FromEthApiError, FromEvmError, RpcConvert, RpcNodeCore,
33};
34use reth_rpc_eth_types::{EthApiError, StateCacheDb};
35use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult};
36use reth_storage_api::{
37    BlockIdReader, BlockReaderIdExt, HashedPostStateProvider, HeaderProvider, ProviderBlock,
38    ReceiptProviderIdExt, StateProviderBox, StateProviderFactory, StateRootProvider,
39    StorageRootProvider, TransactionVariant,
40};
41use reth_tasks::{cancel::is_cancelled, pool::BlockingTaskGuard, Runtime};
42use reth_transaction_pool::TransactionPool;
43use reth_trie_common::{
44    root::storage_root_unsorted, updates::TrieUpdates, ExecutionWitnessMode, HashedPostState,
45    HashedStorage,
46};
47use revm::{database::states::bundle_state::BundleRetention, Database, DatabaseCommit};
48use revm_inspectors::tracing::{DebugInspector, TransactionContext};
49use serde::{Deserialize, Serialize};
50use std::{collections::VecDeque, sync::Arc};
51use tokio::sync::{AcquireError, OwnedSemaphorePermit};
52use tokio_stream::StreamExt;
53#[cfg(feature = "js-tracer")]
54use {
55    alloy_rpc_types_trace::geth::GethDebugTracerType,
56    revm_inspectors::tracing::js::JsInspectorInterrupt, std::time::Duration,
57};
58
59/// `debug` API implementation.
60///
61/// This type provides the functionality for handling `debug` related requests.
62pub struct DebugApi<Eth: RpcNodeCore> {
63    inner: Arc<DebugApiInner<Eth>>,
64}
65
66impl<Eth> DebugApi<Eth>
67where
68    Eth: RpcNodeCore,
69{
70    /// Create a new instance of the [`DebugApi`]
71    pub fn new(
72        eth_api: Eth,
73        blocking_task_guard: BlockingTaskGuard,
74        executor: &Runtime,
75        mut stream: impl Stream<Item = ConsensusEngineEvent<Eth::Primitives>> + Send + Unpin + 'static,
76    ) -> Self {
77        let bad_block_store = BadBlockStore::default();
78        let inner = Arc::new(DebugApiInner {
79            eth_api,
80            blocking_task_guard,
81            task_spawner: executor.clone(),
82            bad_block_store: bad_block_store.clone(),
83        });
84
85        // Spawn a task caching bad blocks
86        executor.spawn_task(async move {
87            while let Some(event) = stream.next().await {
88                if let ConsensusEngineEvent::InvalidBlock { block, error } = event &&
89                    let Ok(recovered) = RecoveredBlock::try_recover_sealed(*block)
90                {
91                    bad_block_store.insert(recovered, error);
92                }
93            }
94        });
95
96        Self { inner }
97    }
98
99    /// Access the underlying `Eth` API.
100    pub fn eth_api(&self) -> &Eth {
101        &self.inner.eth_api
102    }
103
104    /// Access the underlying provider.
105    pub fn provider(&self) -> &Eth::Provider {
106        self.inner.eth_api.provider()
107    }
108}
109
110// === impl DebugApi ===
111
112impl<Eth> DebugApi<Eth>
113where
114    Eth: TraceExt,
115{
116    /// Acquires a permit to execute a tracing call.
117    async fn acquire_trace_permit(&self) -> Result<OwnedSemaphorePermit, AcquireError> {
118        self.inner.blocking_task_guard.clone().acquire_owned().await
119    }
120
121    /// Trace the entire block asynchronously
122    async fn trace_block(
123        &self,
124        block: Arc<RecoveredBlock<ProviderBlock<Eth::Provider>>>,
125        evm_env: EvmEnvFor<Eth::Evm>,
126        opts: GethDebugTracingOptions,
127    ) -> Result<Vec<TraceResult>, Eth::Error> {
128        self.eth_api()
129            .spawn_with_state_at_block(block.parent_hash(), move |eth_api, mut db| {
130                let mut results = Vec::with_capacity(block.body().transactions().len());
131
132                eth_api.apply_pre_execution_changes(&block, &mut db)?;
133
134                let block_env = evm_env.block_env.clone();
135
136                let mut transactions = block.transactions_recovered().enumerate().peekable();
137                let inspector = DebugInspector::new(opts).map_err(Eth::Error::from_eth_err)?;
138                let mut evm =
139                    eth_api.evm_config().evm_with_env_and_inspector(&mut db, evm_env, inspector);
140                while let Some((index, tx)) = transactions.next() {
141                    if is_cancelled() {
142                        return Err(EthApiError::InternalEthError.into())
143                    }
144                    let tx_env = eth_api.evm_config().tx_env(tx);
145
146                    let res = evm.transact(tx_env.clone()).map_err(Eth::Error::from_evm_err)?;
147
148                    let (db, inspector, _) = evm.components_mut();
149                    let result = inspector
150                        .get_result(
151                            Some(TransactionContext {
152                                block_hash: Some(block.hash()),
153                                tx_hash: Some(*tx.tx_hash()),
154                                tx_index: Some(index),
155                            }),
156                            &tx_env,
157                            &block_env,
158                            &res,
159                            db,
160                        )
161                        .map_err(Eth::Error::from_eth_err)?;
162
163                    results.push(TraceResult::Success { result, tx_hash: Some(*tx.tx_hash()) });
164                    if transactions.peek().is_some() {
165                        inspector.fuse().map_err(Eth::Error::from_eth_err)?;
166                        // need to apply the state changes of this transaction before executing the
167                        // next transaction
168                        db.commit(res.state)
169                    }
170                }
171
172                Ok(results)
173            })
174            .await
175    }
176
177    /// Traces a block for `traceChain`, preserving transaction-level tracing failures in the
178    /// subscription result instead of failing the entire range.
179    async fn trace_chain_block(
180        &self,
181        block: Arc<RecoveredBlock<ProviderBlock<Eth::Provider>>>,
182        opts: GethDebugTracingOptions,
183    ) -> Result<Vec<Option<TraceResult>>, Eth::Error> {
184        let evm_env = self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
185
186        self.eth_api()
187            .spawn_with_state_at_block(block.parent_hash(), move |eth_api, mut db| {
188                let tx_count = block.body().transactions().len();
189                let mut results = Vec::with_capacity(tx_count);
190
191                eth_api.apply_pre_execution_changes(&block, &mut db)?;
192
193                let block_env = evm_env.block_env.clone();
194                let mut transactions = block.transactions_recovered().enumerate().peekable();
195                let inspector = match DebugInspector::new(opts) {
196                    Ok(inspector) => inspector,
197                    Err(err) => {
198                        if let Some((_, tx)) = transactions.peek() {
199                            results.push(Some(TraceResult::Error {
200                                error: err.to_string(),
201                                tx_hash: Some(*tx.tx_hash()),
202                            }));
203                        }
204                        results.resize(tx_count, None);
205                        return Ok(results)
206                    }
207                };
208                let mut evm =
209                    eth_api.evm_config().evm_with_env_and_inspector(&mut db, evm_env, inspector);
210
211                while let Some((index, tx)) = transactions.next() {
212                    let tx_hash = *tx.tx_hash();
213                    let tx_env = eth_api.evm_config().tx_env(tx);
214                    let res = match evm.transact(tx_env.clone()) {
215                        Ok(res) => res,
216                        Err(err) => {
217                            results.push(Some(TraceResult::Error {
218                                error: err.to_string(),
219                                tx_hash: Some(tx_hash),
220                            }));
221                            break
222                        }
223                    };
224
225                    let (db, inspector, _) = evm.components_mut();
226                    let result = match inspector.get_result(
227                        Some(TransactionContext {
228                            block_hash: Some(block.hash()),
229                            tx_hash: Some(tx_hash),
230                            tx_index: Some(index),
231                        }),
232                        &tx_env,
233                        &block_env,
234                        &res,
235                        db,
236                    ) {
237                        Ok(result) => result,
238                        Err(err) => {
239                            results.push(Some(TraceResult::Error {
240                                error: err.to_string(),
241                                tx_hash: Some(tx_hash),
242                            }));
243                            break
244                        }
245                    };
246
247                    results.push(Some(TraceResult::Success { result, tx_hash: Some(tx_hash) }));
248                    if let Some((_, next_tx)) = transactions.peek() {
249                        if let Err(err) = inspector.fuse() {
250                            results.push(Some(TraceResult::Error {
251                                error: err.to_string(),
252                                tx_hash: Some(*next_tx.tx_hash()),
253                            }));
254                            break
255                        }
256                        db.commit(res.state);
257                    }
258                }
259
260                results.resize(tx_count, None);
261                Ok(results)
262            })
263            .await
264    }
265
266    /// Replays the given block and returns the trace of each transaction.
267    ///
268    /// This expects a rlp encoded block
269    ///
270    /// Note, the parent of this block must be present, or it will fail.
271    pub async fn debug_trace_raw_block(
272        &self,
273        rlp_block: Bytes,
274        opts: GethDebugTracingOptions,
275    ) -> Result<Vec<TraceResult>, Eth::Error> {
276        let block: ProviderBlock<Eth::Provider> = Decodable::decode(&mut rlp_block.as_ref())
277            .map_err(BlockError::RlpDecodeRawBlock)
278            .map_err(Eth::Error::from_eth_err)?;
279
280        let evm_env = self
281            .eth_api()
282            .evm_config()
283            .evm_env(block.header())
284            .map_err(RethError::other)
285            .map_err(Eth::Error::from_eth_err)?;
286
287        // Depending on EIP-2 we need to recover the transactions differently
288        let senders =
289            if self.provider().chain_spec().is_homestead_active_at_block(block.header().number()) {
290                block.body().recover_signers()
291            } else {
292                block.body().recover_signers_unchecked()
293            }
294            .map_err(Eth::Error::from_eth_err)?;
295
296        self.trace_block(Arc::new(block.into_recovered_with_signers(senders)), evm_env, opts).await
297    }
298
299    /// Replays a block and returns the trace of each transaction.
300    pub async fn debug_trace_block(
301        &self,
302        block_id: BlockId,
303        opts: GethDebugTracingOptions,
304    ) -> Result<Vec<TraceResult>, Eth::Error> {
305        let block = self
306            .eth_api()
307            .recovered_block(block_id)
308            .await?
309            .ok_or(EthApiError::TracingBlockNotFound(block_id))?;
310        // Tracing requires the parent state, which does not exist for the genesis block.
311        if block.number() == 0 {
312            return Err(EthApiError::GenesisNotTraceable.into())
313        }
314        let evm_env = self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
315
316        self.trace_block(block, evm_env, opts).await
317    }
318
319    /// Trace the transaction according to the provided options.
320    ///
321    /// Ref: <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
322    pub async fn debug_trace_transaction(
323        &self,
324        tx_hash: B256,
325        opts: GethDebugTracingOptions,
326    ) -> Result<GethTrace, Eth::Error> {
327        let (transaction, block, bal) =
328            match self.eth_api().transaction_and_block_and_maybe_bal(tx_hash).await? {
329                None => return Err(EthApiError::TracingTransactionNotFound.into()),
330                Some(res) => res,
331            };
332
333        let interrupt = JsTraceInterrupt::new(&opts)?;
334        let tracer_interrupt = interrupt.clone();
335        let trace = self.eth_api().spawn_with_state_at_block(
336            block.parent_hash(),
337            move |eth_api, mut db| {
338                // configure env for the target transaction
339                let (tx, tx_info) = transaction.split();
340
341                // index should always be available because `transaction_and_block` only
342                // returns transactions included in a block
343                let index =
344                    tx_info.index.expect("transaction_and_block only returns block transactions")
345                        as usize;
346
347                let mut inspector = tracer_interrupt
348                    .attach(DebugInspector::new(opts).map_err(Eth::Error::from_eth_err)?);
349                let tx_env = eth_api.evm_config().tx_env(&tx);
350                let (res, evm_env) = eth_api.inspect_transaction_in_block(
351                    &block,
352                    &mut db,
353                    &mut inspector,
354                    index,
355                    tx_env.clone(),
356                    bal.as_deref(),
357                )?;
358
359                let trace = inspector
360                    .get_result(
361                        Some(TransactionContext {
362                            block_hash: Some(block.hash()),
363                            tx_index: Some(index),
364                            tx_hash: Some(*tx.tx_hash()),
365                        }),
366                        &tx_env,
367                        &evm_env.block_env,
368                        &res,
369                        &mut db,
370                    )
371                    .map_err(Eth::Error::from_eth_err)?;
372
373                Ok(trace)
374            },
375        );
376        interrupt.run(trace).await
377    }
378
379    /// The `debug_traceCall` method lets you run an `eth_call` within the context of the given
380    /// block execution using the final state of parent block as the base.
381    ///
382    /// If `tx_index` is provided in opts, the call will be traced at the state after executing
383    /// transactions up to the specified index within the block (0-indexed).
384    /// If not provided, then uses the post-state (default behavior).
385    ///
386    /// Differences compare to `eth_call`:
387    ///  - `debug_traceCall` executes with __enabled__ basefee check, `eth_call` does not: <https://github.com/paradigmxyz/reth/issues/6240>
388    pub async fn debug_trace_call(
389        &self,
390        call: RpcTxReq<Eth::NetworkTypes>,
391        block_id: Option<BlockId>,
392        opts: GethDebugTracingCallOptions,
393    ) -> Result<GethTrace, Eth::Error> {
394        let at = block_id.unwrap_or_default();
395        let GethDebugTracingCallOptions {
396            tracing_options,
397            state_overrides,
398            block_overrides,
399            tx_index,
400        } = opts;
401        let overrides = EvmOverrides::new(state_overrides, block_overrides.map(Box::new));
402
403        // Check if we need to replay transactions for a specific tx_index
404        if let Some(tx_idx) = tx_index {
405            return self
406                .debug_trace_call_at_tx_index(call, at, tx_idx as usize, tracing_options, overrides)
407                .await;
408        }
409
410        let interrupt = JsTraceInterrupt::new(&tracing_options)?;
411        let tracer_interrupt = interrupt.clone();
412        let this = self.clone();
413        let trace =
414            self.eth_api().spawn_with_call_at(call, at, overrides, move |db, evm_env, tx_env| {
415                let mut inspector = tracer_interrupt.attach(
416                    DebugInspector::new(tracing_options).map_err(Eth::Error::from_eth_err)?,
417                );
418                let res = this.eth_api().inspect(
419                    &mut *db,
420                    evm_env.clone(),
421                    tx_env.clone(),
422                    &mut inspector,
423                )?;
424                let trace = inspector
425                    .get_result(None, &tx_env, &evm_env.block_env, &res, db)
426                    .map_err(Eth::Error::from_eth_err)?;
427                Ok(trace)
428            });
429        interrupt.run(trace).await.map_err(|err| match err.as_err() {
430            Some(EthApiError::HeaderNotFound(id)) if *id == at => {
431                // Unknown blocks use -32000: https://github.com/ethereum/execution-apis/pull/855
432                EthApiError::TracingBlockNotFound(at).into()
433            }
434            _ => err,
435        })
436    }
437
438    /// Helper method to execute `debug_trace_call` at a specific transaction index within a block.
439    /// This replays transactions up to the specified index, then executes the trace call in that
440    /// state.
441    async fn debug_trace_call_at_tx_index(
442        &self,
443        call: RpcTxReq<Eth::NetworkTypes>,
444        block_id: BlockId,
445        tx_index: usize,
446        tracing_options: GethDebugTracingOptions,
447        overrides: EvmOverrides,
448    ) -> Result<GethTrace, Eth::Error> {
449        // Get the target block to check transaction count
450        let (block, bal) = self
451            .eth_api()
452            .recovered_block_and_maybe_bal(block_id)
453            .await?
454            .ok_or(EthApiError::TracingBlockNotFound(block_id))?;
455
456        if tx_index >= block.transaction_count() {
457            // tx_index out of bounds
458            return Err(EthApiError::InvalidParams(format!(
459                "tx_index {} out of bounds for block with {} transactions",
460                tx_index,
461                block.transaction_count()
462            ))
463            .into())
464        }
465
466        // state overrides commit changes to the database, which an attached BAL would take read
467        // precedence over, so only position via BAL if there are none
468        let bal = bal.filter(|_| !overrides.has_state());
469
470        let evm_env = self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
471
472        let interrupt = JsTraceInterrupt::new(&tracing_options)?;
473        let tracer_interrupt = interrupt.clone();
474        let trace = self.eth_api().spawn_with_state_at_block(
475            block.parent_hash(),
476            move |eth_api, mut db| {
477                // 1. position the state before the transaction at the index
478                eth_api.replay_block_until(&mut db, &block, tx_index, bal.as_deref())?;
479
480                // 2. now execute the trace call on this state
481                let (evm_env, tx_env) =
482                    eth_api.prepare_call_env(evm_env, call, &mut db, overrides)?;
483
484                let mut inspector = tracer_interrupt.attach(
485                    DebugInspector::new(tracing_options).map_err(Eth::Error::from_eth_err)?,
486                );
487                let res =
488                    eth_api.inspect(&mut db, evm_env.clone(), tx_env.clone(), &mut inspector)?;
489                let trace = inspector
490                    .get_result(None, &tx_env, &evm_env.block_env, &res, &mut db)
491                    .map_err(Eth::Error::from_eth_err)?;
492
493                Ok(trace)
494            },
495        );
496        interrupt.run(trace).await
497    }
498
499    /// The `debug_traceCallMany` method lets you run an `eth_callMany` within the context of the
500    /// given block execution using the first n transactions in the given block as base.
501    /// Each following bundle increments block number by 1 and block timestamp by 12 seconds
502    pub async fn debug_trace_call_many(
503        &self,
504        bundles: Vec<Bundle<RpcTxReq<Eth::NetworkTypes>>>,
505        state_context: Option<StateContext>,
506        opts: Option<GethDebugTracingCallOptions>,
507    ) -> Result<Vec<Vec<GethTrace>>, Eth::Error> {
508        if bundles.is_empty() {
509            return Err(EthApiError::InvalidParams(String::from("bundles are empty.")).into())
510        }
511
512        let StateContext { transaction_index, block_number } = state_context.unwrap_or_default();
513        let transaction_index = transaction_index.unwrap_or_default();
514
515        let target_block = block_number.unwrap_or_default();
516        let block = self
517            .eth_api()
518            .recovered_block(target_block)
519            .await?
520            .ok_or(EthApiError::HeaderNotFound(target_block))?;
521        let mut evm_env =
522            self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
523
524        let opts = opts.unwrap_or_default();
525        let GethDebugTracingCallOptions { tracing_options, mut state_overrides, .. } = opts;
526
527        // we're essentially replaying the transactions in the block here, hence we need the state
528        // that points to the beginning of the block, which is the state at the parent block
529        let mut at = block.parent_hash();
530        let mut replay_block_txs = true;
531
532        // if a transaction index is provided, we need to replay the transactions until the index
533        let num_txs =
534            transaction_index.index().unwrap_or_else(|| block.body().transactions().len());
535        // but if all transactions are to be replayed, we can use the state at the block itself
536        // this works with the exception of the PENDING block, because its state might not exist if
537        // built locally
538        if !target_block.is_pending() && num_txs == block.body().transactions().len() {
539            at = block.hash();
540            replay_block_txs = false;
541        }
542
543        self.eth_api()
544            .spawn_with_state_at_block(at, move |eth_api, mut db| {
545                // the outer vec for the bundles
546                let mut all_bundles = Vec::with_capacity(bundles.len());
547
548                if replay_block_txs {
549                    // only need to replay the transactions in the block if not all transactions are
550                    // to be replayed
551                    // Execute all transactions until index. No BAL positioning here: bundle
552                    // transactions commit state on top, and an attached BAL would take read
553                    // precedence over the committed changes
554                    eth_api.replay_block_until(&mut db, &block, num_txs, None)?;
555                }
556
557                // Trace all bundles
558                let mut bundles = bundles.into_iter().peekable();
559                let mut inspector = DebugInspector::new(tracing_options.clone())
560                    .map_err(Eth::Error::from_eth_err)?;
561                while let Some(bundle) = bundles.next() {
562                    let mut results = Vec::with_capacity(bundle.transactions.len());
563                    let Bundle { transactions, block_override } = bundle;
564
565                    let block_overrides = block_override.map(Box::new);
566
567                    let mut transactions = transactions.into_iter().peekable();
568                    while let Some(tx) = transactions.next() {
569                        if is_cancelled() {
570                            return Err(EthApiError::InternalEthError.into())
571                        }
572                        // apply state overrides only once, before the first transaction
573                        let state_overrides = state_overrides.take();
574                        let overrides = EvmOverrides::new(state_overrides, block_overrides.clone());
575
576                        let (evm_env, tx_env) =
577                            eth_api.prepare_call_env(evm_env.clone(), tx, &mut db, overrides)?;
578
579                        let res = eth_api.inspect(
580                            &mut db,
581                            evm_env.clone(),
582                            tx_env.clone(),
583                            &mut inspector,
584                        )?;
585                        let trace = inspector
586                            .get_result(None, &tx_env, &evm_env.block_env, &res, &mut db)
587                            .map_err(Eth::Error::from_eth_err)?;
588
589                        // If there is more transactions, commit the database
590                        // If there is no transactions, but more bundles, commit to the database too
591                        if transactions.peek().is_some() || bundles.peek().is_some() {
592                            inspector.fuse().map_err(Eth::Error::from_eth_err)?;
593                            db.commit(res.state);
594                        }
595                        results.push(trace);
596                    }
597                    // Increment block_env number and timestamp for the next bundle
598                    evm_env.block_env.inner_mut().number += uint!(1_U256);
599                    evm_env.block_env.inner_mut().timestamp += uint!(12_U256);
600
601                    all_bundles.push(results);
602                }
603                Ok(all_bundles)
604            })
605            .await
606    }
607
608    /// Generates an execution witness for the given block hash. see
609    /// [`Self::debug_execution_witness`] for more info.
610    pub async fn debug_execution_witness_by_block_hash(
611        &self,
612        hash: B256,
613        mode: Option<ExecutionWitnessMode>,
614    ) -> Result<ExecutionWitness, Eth::Error> {
615        let this = self.clone();
616        let block = this
617            .eth_api()
618            .recovered_block(hash.into())
619            .await?
620            .ok_or(EthApiError::HeaderNotFound(hash.into()))?;
621
622        self.debug_execution_witness_for_block(block, mode.unwrap_or_default()).await
623    }
624
625    /// The `debug_executionWitness` method allows for re-execution of a block with the purpose of
626    /// generating an execution witness. The witness comprises of a map of all hashed trie nodes to
627    /// their preimages that were required during the execution of the block, including during state
628    /// root recomputation.
629    pub async fn debug_execution_witness(
630        &self,
631        block_id: BlockId,
632        mode: Option<ExecutionWitnessMode>,
633    ) -> Result<ExecutionWitness, Eth::Error> {
634        let this = self.clone();
635        let block = this
636            .eth_api()
637            .recovered_block(block_id)
638            .await?
639            .ok_or(EthApiError::HeaderNotFound(block_id))?;
640
641        self.debug_execution_witness_for_block(block, mode.unwrap_or_default()).await
642    }
643
644    /// Generates an execution witness, using the given recovered block.
645    pub async fn debug_execution_witness_for_block(
646        &self,
647        block: Arc<RecoveredBlock<ProviderBlock<Eth::Provider>>>,
648        mode: ExecutionWitnessMode,
649    ) -> Result<ExecutionWitness, Eth::Error> {
650        let block_number = block.header().number();
651        self.eth_api()
652            .spawn_with_state_at_block(block.parent_hash(), move |eth_api, mut db| {
653                let block_executor = eth_api.evm_config().executor(&mut db);
654
655                let mut witness = None;
656                let _ = block_executor
657                    .execute_with_state_closure(&block, |statedb: &State<_>| {
658                        witness = Some(
659                            ExecutionWitnessRecord::new(statedb)
660                                .into_execution_witness::<StateProviderBox, _>(
661                                    &statedb.database.database,
662                                    eth_api.provider(),
663                                    block_number,
664                                    mode,
665                                ),
666                        );
667                    })
668                    .map_err(|err| EthApiError::Internal(err.into()))?;
669
670                Ok(witness
671                    .expect("state closure is called after successful execution")
672                    .map_err(EthApiError::from)?)
673            })
674            .await
675    }
676
677    /// Returns account information, including the storage root, after replaying the block through
678    /// the transaction at the given index.
679    pub async fn debug_account_at(
680        &self,
681        block_id: BlockId,
682        tx_index: Index,
683        address: Address,
684    ) -> Result<Option<Account>, Eth::Error> {
685        self.replay_block_until(block_id, tx_index, move |db| Self::account(db, address))
686            .await
687            .map(Option::flatten)
688    }
689
690    /// Returns account information after replaying the block through the transaction at the given
691    /// index.
692    pub async fn debug_account_info_at(
693        &self,
694        block_id: BlockId,
695        tx_index: Index,
696        address: Address,
697    ) -> Result<Option<AccountInfo>, Eth::Error> {
698        self.replay_block_until(block_id, tx_index, move |db| Self::account_info(db, address)).await
699    }
700
701    /// Replays a block through the transaction at the given index and calls `f` with the resulting
702    /// state.
703    async fn replay_block_until<F, R>(
704        &self,
705        block_id: BlockId,
706        tx_index: Index,
707        f: F,
708    ) -> Result<Option<R>, Eth::Error>
709    where
710        F: FnOnce(&mut StateCacheDb) -> Result<R, Eth::Error> + Send + 'static,
711        R: Send + 'static,
712    {
713        let block = self
714            .eth_api()
715            .recovered_block(block_id)
716            .await?
717            .ok_or(EthApiError::HeaderNotFound(block_id))?;
718        let tx_index = usize::from(tx_index);
719        let transaction_count = block.transaction_count();
720        if tx_index >= transaction_count {
721            return Err(EthApiError::InvalidParams(format!(
722                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
723            ))
724            .into())
725        }
726
727        self.eth_api()
728            .spawn_with_state_at_block(block.parent_hash(), move |eth_api, mut db| {
729                let mut executor = eth_api
730                    .evm_config()
731                    .executor_for_block(&mut db, block.sealed_block())
732                    .map_err(RethError::other)
733                    .map_err(Eth::Error::from_eth_err)?;
734                executor.apply_pre_execution_changes().map_err(Eth::Error::from_eth_err)?;
735
736                for tx in block.transactions_recovered().take(tx_index + 1) {
737                    if is_cancelled() {
738                        return Err(EthApiError::InternalEthError.into())
739                    }
740                    executor.execute_transaction(tx).map_err(Eth::Error::from_eth_err)?;
741                }
742                drop(executor);
743
744                f(&mut db)
745            })
746            .await
747            .map(Some)
748    }
749
750    /// Retrieves the account's balance, nonce, code hash, and storage root from the given state.
751    fn account(db: &mut StateCacheDb, address: Address) -> Result<Option<Account>, Eth::Error> {
752        let account = db.basic(address).map_err(Eth::Error::from_eth_err)?;
753        let Some(account) = account else { return Ok(None) };
754
755        let (hashed_storage, status) = db
756            .cache
757            .accounts
758            .get(&address)
759            .and_then(|account| {
760                account.account.as_ref().map(|plain_account| {
761                    (HashedStorage::from_plain_storage(&plain_account.storage), account.status)
762                })
763            })
764            .unwrap_or_default();
765        let storage_root = if status.was_destroyed() {
766            // Destruction makes every slot not present in the cache zero, so the cache contains the
767            // complete storage trie for the account's new incarnation.
768            storage_root_unsorted(
769                hashed_storage.storage.into_iter().filter(|(_, value)| !value.is_zero()),
770            )
771        } else {
772            db.database.storage_root(address, hashed_storage).map_err(Eth::Error::from_eth_err)?
773        };
774
775        Ok(Some(reth_primitives_traits::Account::from(account).into_trie_account(storage_root)))
776    }
777
778    /// Retrieves the account's balance, nonce, and code from the given state.
779    #[allow(clippy::needless_update)]
780    fn account_info<DB>(db: &mut DB, address: Address) -> Result<AccountInfo, Eth::Error>
781    where
782        DB: Database,
783        EthApiError: From<DB::Error>,
784    {
785        let account = db.basic(address).map_err(Eth::Error::from_eth_err)?.unwrap_or_default();
786        let code = if account.is_empty_code_hash() {
787            Default::default()
788        } else if let Some(code) = account.code {
789            code.original_bytes()
790        } else {
791            db.code_by_hash(account.code_hash).map_err(Eth::Error::from_eth_err)?.original_bytes()
792        };
793
794        Ok(AccountInfo {
795            balance: account.balance,
796            nonce: account.nonce,
797            code,
798            #[cfg(feature = "account-ext")]
799            extension: reth_primitives_traits::AccountExtension::from_shared(
800                account.extension.into_shared(),
801            ),
802            ..Default::default()
803        })
804    }
805
806    /// Returns the code associated with a given hash at the specified block ID. If no code is
807    /// found, it returns None. If no block ID is provided, it defaults to the latest block.
808    pub async fn debug_code_by_hash(
809        &self,
810        hash: B256,
811        block_id: Option<BlockId>,
812    ) -> Result<Option<Bytes>, Eth::Error> {
813        Ok(self
814            .provider()
815            .state_by_block_id(block_id.unwrap_or_default())
816            .map_err(Eth::Error::from_eth_err)?
817            .bytecode_by_hash(&hash)
818            .map_err(Eth::Error::from_eth_err)?
819            .map(|b| b.original_bytes()))
820    }
821
822    /// Returns the state root of the `HashedPostState` on top of the state for the given block with
823    /// trie updates.
824    async fn debug_state_root_with_updates(
825        &self,
826        hashed_state: HashedPostState,
827        block_id: Option<BlockId>,
828    ) -> Result<(B256, TrieUpdates), Eth::Error> {
829        self.inner
830            .eth_api
831            .spawn_blocking_io(move |this| {
832                let state = this
833                    .provider()
834                    .state_by_block_id(block_id.unwrap_or_default())
835                    .map_err(Eth::Error::from_eth_err)?;
836                state.state_root_with_updates(hashed_state).map_err(Eth::Error::from_eth_err)
837            })
838            .await
839    }
840
841    /// Executes a block and returns the state root after each transaction.
842    pub async fn intermediate_roots(&self, block_hash: B256) -> Result<Vec<B256>, Eth::Error> {
843        let block = self
844            .eth_api()
845            .recovered_block(block_hash.into())
846            .await?
847            .ok_or(EthApiError::HeaderNotFound(block_hash.into()))?;
848        let evm_env = self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
849
850        self.eth_api()
851            .spawn_with_state_at_block(block.parent_hash(), move |eth_api, mut db| {
852                // Enable transition tracking so that merge_transitions works
853                db.transition_state = Some(Default::default());
854
855                eth_api.apply_pre_execution_changes(&block, &mut db)?;
856
857                let mut roots = Vec::with_capacity(block.body().transactions().len());
858                let mut evm = eth_api.evm_config().evm_with_env(&mut db, evm_env);
859                for tx in block.transactions_recovered() {
860                    if is_cancelled() {
861                        return Err(EthApiError::InternalEthError.into())
862                    }
863                    let tx_env = eth_api.evm_config().tx_env(tx);
864                    evm.transact_commit(tx_env).map_err(Eth::Error::from_evm_err)?;
865
866                    let state = evm.db_mut();
867                    // Merge transitions into cumulative bundle_state
868                    state.merge_transitions(BundleRetention::PlainState);
869                    // Compute state root from the accumulated state changes
870                    let hashed_state = state
871                        .database
872                        .hashed_post_state(&state.bundle_state)
873                        .map_err(Eth::Error::from_eth_err)?;
874                    let root = state
875                        .database
876                        .state_root(hashed_state)
877                        .map_err(Eth::Error::from_eth_err)?;
878                    roots.push(root);
879                }
880
881                Ok(roots)
882            })
883            .await
884    }
885}
886
887#[async_trait]
888impl<Eth> DebugApiServer<RpcTxReq<Eth::NetworkTypes>> for DebugApi<Eth>
889where
890    Eth: EthTransactions + TraceExt,
891{
892    /// Handler for `debug_getRawHeader`
893    async fn raw_header(&self, block_id: BlockId) -> RpcResult<Bytes> {
894        let header = match block_id {
895            BlockId::Hash(hash) => self.provider().header(hash.into()).to_rpc_result()?,
896            BlockId::Number(number_or_tag) => {
897                let number = self
898                    .provider()
899                    .convert_block_number(number_or_tag)
900                    .to_rpc_result()?
901                    .ok_or(EthApiError::HeaderNotFound(block_id))?;
902                self.provider().header_by_number(number).to_rpc_result()?
903            }
904        }
905        .ok_or(EthApiError::HeaderNotFound(block_id))?;
906
907        let mut res = Vec::new();
908        header.encode(&mut res);
909        Ok(res.into())
910    }
911
912    /// Handler for `debug_getRawBlock`
913    async fn raw_block(&self, block_id: BlockId) -> RpcResult<Bytes> {
914        let block = self
915            .provider()
916            .block_by_id(block_id)
917            .to_rpc_result()?
918            .ok_or(EthApiError::HeaderNotFound(block_id))?;
919        let mut res = Vec::new();
920        block.encode(&mut res);
921        Ok(res.into())
922    }
923
924    /// Handler for `debug_getRawBlockAccessList`
925    async fn raw_block_access_list(&self, block_id: BlockId) -> RpcResult<Bytes> {
926        self.eth_api()
927            .get_raw_block_access_list(block_id)
928            .await
929            .map_err(Into::into)?
930            .ok_or_else(|| EthApiError::HeaderNotFound(block_id).into())
931    }
932
933    /// Handler for `debug_getRawTransaction`
934    ///
935    /// If this is a pooled EIP-4844 transaction, the blob sidecar is included.
936    ///
937    /// Returns the bytes of the transaction for the given hash.
938    async fn raw_transaction(&self, hash: B256) -> RpcResult<Option<Bytes>> {
939        self.eth_api().raw_transaction_by_hash(hash).await.map_err(Into::into)
940    }
941
942    /// Handler for `debug_getRawTransactions`
943    /// Returns the bytes of the transaction for the given hash.
944    async fn raw_transactions(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>> {
945        let block: RecoveredBlock<BlockTy<Eth::Primitives>> = self
946            .provider()
947            .block_with_senders_by_id(block_id, TransactionVariant::NoHash)
948            .to_rpc_result()?
949            .unwrap_or_default();
950        Ok(block.into_transactions_recovered().map(|tx| tx.encoded_2718().into()).collect())
951    }
952
953    /// Handler for `debug_getRawReceipts`
954    async fn raw_receipts(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>> {
955        Ok(self
956            .provider()
957            .receipts_by_block_id(block_id)
958            .to_rpc_result()?
959            .ok_or(EthApiError::HeaderNotFound(block_id))?
960            .into_iter()
961            .map(|receipt| ReceiptWithBloom::from(receipt).encoded_2718().into())
962            .collect())
963    }
964
965    /// Handler for `debug_getBadBlocks`
966    async fn bad_blocks(&self) -> RpcResult<Vec<serde_json::Value>> {
967        let entries = self.inner.bad_block_store.all();
968        let mut bad_blocks = Vec::with_capacity(entries.len());
969
970        #[derive(Serialize, Deserialize)]
971        struct BadBlockSerde<T> {
972            block: T,
973            hash: B256,
974            rlp: Bytes,
975            reason: String,
976        }
977
978        for entry in entries {
979            let rlp = Bytes::from(alloy_rlp::encode(entry.block.sealed_block()));
980            let hash = entry.block.hash();
981
982            let block = entry
983                .block
984                .clone_into_rpc_block(
985                    BlockTransactionsKind::Full,
986                    |tx, tx_info| self.eth_api().converter().fill(tx, tx_info),
987                    |header, block_size| {
988                        self.eth_api().converter().convert_header(header, Some(block_size))
989                    },
990                )
991                .map_err(|err| Eth::Error::from(err).into())?;
992
993            let bad_block =
994                serde_json::to_value(BadBlockSerde { block, hash, rlp, reason: entry.reason })
995                    .map_err(|err| EthApiError::other(internal_rpc_err(err.to_string())))?;
996
997            bad_blocks.push(bad_block);
998        }
999
1000        Ok(bad_blocks)
1001    }
1002
1003    /// Handler for `debug_clearTxpool`
1004    async fn debug_clear_txpool(&self) -> RpcResult<()> {
1005        let pool = self.eth_api().pool();
1006        let all_hashes = pool.all_transaction_hashes();
1007        let _ = pool.remove_transactions(all_hashes);
1008        Ok(())
1009    }
1010
1011    /// Handler for `debug_subscribe("traceChain", ...)`.
1012    async fn debug_subscribe(
1013        &self,
1014        pending: PendingSubscriptionSink,
1015        subscription: String,
1016        start_exclusive: BlockNumberOrTag,
1017        end_inclusive: BlockNumberOrTag,
1018        opts: Option<GethDebugTracingOptions>,
1019    ) -> jsonrpsee::core::SubscriptionResult {
1020        if subscription != "traceChain" {
1021            pending
1022                .reject(EthApiError::InvalidParams(format!(
1023                    "unsupported debug subscription: {subscription}"
1024                )))
1025                .await;
1026            return Ok(())
1027        }
1028
1029        let start_id = BlockId::Number(start_exclusive);
1030        let start = match self.eth_api().recovered_block(start_id).await {
1031            Ok(Some(block)) => block,
1032            Ok(None) => {
1033                pending.reject(EthApiError::TracingBlockNotFound(start_id)).await;
1034                return Ok(())
1035            }
1036            Err(err) => {
1037                pending.reject(err).await;
1038                return Ok(())
1039            }
1040        };
1041        let end_id = BlockId::Number(end_inclusive);
1042        let end = match self.eth_api().recovered_block(end_id).await {
1043            Ok(Some(block)) => block,
1044            Ok(None) => {
1045                pending.reject(EthApiError::TracingBlockNotFound(end_id)).await;
1046                return Ok(())
1047            }
1048            Err(err) => {
1049                pending.reject(err).await;
1050                return Ok(())
1051            }
1052        };
1053
1054        if start.number() >= end.number() {
1055            pending
1056                .reject(EthApiError::InvalidParams(format!(
1057                    "end block (#{}) needs to come after start block (#{})",
1058                    end.number(),
1059                    start.number()
1060                )))
1061                .await;
1062            return Ok(())
1063        }
1064
1065        let sink = pending.accept().await?;
1066        let this = self.clone();
1067        let task_spawner = self.inner.task_spawner.clone();
1068        task_spawner.spawn_task(async move {
1069            let end_number = end.number();
1070            let opts = opts.unwrap_or_default();
1071
1072            for number in (start.number() + 1)..=end_number {
1073                if sink.is_closed() {
1074                    break
1075                }
1076
1077                let block_id = BlockId::number(number);
1078                let block = match this.eth_api().recovered_block(block_id).await {
1079                    Ok(Some(block)) => block,
1080                    Ok(None) => {
1081                        tracing::warn!(target: "rpc::debug", %number, "Chain tracing block not found");
1082                        break
1083                    }
1084                    Err(err) => {
1085                        tracing::warn!(target: "rpc::debug", %number, %err, "Failed to load chain tracing block");
1086                        break
1087                    }
1088                };
1089                let permit = tokio::select! {
1090                    _ = sink.closed() => break,
1091                    permit = this.acquire_trace_permit() => match permit {
1092                        Ok(permit) => permit,
1093                        Err(err) => {
1094                            tracing::debug!(target: "rpc::debug", %err, "Failed to acquire trace permit");
1095                            break
1096                        }
1097                    }
1098                };
1099                if sink.is_closed() {
1100                    break
1101                }
1102                let traces = match this.trace_chain_block(block.clone(), opts.clone()).await {
1103                    Ok(traces) => traces,
1104                    Err(err) => {
1105                        tracing::warn!(target: "rpc::debug", %number, %err, "Failed to trace chain block");
1106                        break
1107                    }
1108                };
1109                drop(permit);
1110
1111                if traces.is_empty() && number != end_number {
1112                    continue
1113                }
1114                let result = ChainBlockTraceResult {
1115                    block: U256::from(number),
1116                    hash: block.hash(),
1117                    traces,
1118                };
1119                let message = match SubscriptionMessage::new(
1120                    sink.method_name(),
1121                    sink.subscription_id(),
1122                    &result,
1123                ) {
1124                    Ok(message) => message,
1125                    Err(err) => {
1126                        tracing::warn!(target: "rpc::debug", %number, %err, "Failed to serialize chain trace");
1127                        break
1128                    }
1129                };
1130                if sink.send(message).await.is_err() {
1131                    break
1132                }
1133            }
1134
1135            // Dropping jsonrpsee's final sink unregisters the subscription without notifying the
1136            // client that this finite stream completed. Retain it so the subscription remains
1137            // explicitly unsubscribable until the client unsubscribes or disconnects.
1138            sink.closed().await;
1139        });
1140
1141        Ok(())
1142    }
1143
1144    /// Handler for `debug_traceBlock`
1145    async fn debug_trace_block(
1146        &self,
1147        rlp_block: Bytes,
1148        opts: Option<GethDebugTracingOptions>,
1149    ) -> RpcResult<Vec<TraceResult>> {
1150        let _permit = self.acquire_trace_permit().await;
1151        Self::debug_trace_raw_block(self, rlp_block, opts.unwrap_or_default())
1152            .await
1153            .map_err(Into::into)
1154    }
1155
1156    /// Handler for `debug_traceBlockByHash`
1157    async fn debug_trace_block_by_hash(
1158        &self,
1159        block: B256,
1160        opts: Option<GethDebugTracingOptions>,
1161    ) -> RpcResult<Vec<TraceResult>> {
1162        let _permit = self.acquire_trace_permit().await;
1163        Self::debug_trace_block(self, block.into(), opts.unwrap_or_default())
1164            .await
1165            .map_err(Into::into)
1166    }
1167
1168    /// Handler for `debug_traceBlockByNumber`
1169    async fn debug_trace_block_by_number(
1170        &self,
1171        block: BlockNumberOrTag,
1172        opts: Option<GethDebugTracingOptions>,
1173    ) -> RpcResult<Vec<TraceResult>> {
1174        let _permit = self.acquire_trace_permit().await;
1175        Self::debug_trace_block(self, block.into(), opts.unwrap_or_default())
1176            .await
1177            .map_err(Into::into)
1178    }
1179
1180    /// Handler for `debug_traceTransaction`
1181    async fn debug_trace_transaction(
1182        &self,
1183        tx_hash: B256,
1184        opts: Option<GethDebugTracingOptions>,
1185    ) -> RpcResult<GethTrace> {
1186        let _permit = self.acquire_trace_permit().await;
1187        Self::debug_trace_transaction(self, tx_hash, opts.unwrap_or_default())
1188            .await
1189            .map_err(Into::into)
1190    }
1191
1192    /// Handler for `debug_traceCall`
1193    async fn debug_trace_call(
1194        &self,
1195        request: RpcTxReq<Eth::NetworkTypes>,
1196        block_id: Option<BlockId>,
1197        opts: Option<GethDebugTracingCallOptions>,
1198    ) -> RpcResult<GethTrace> {
1199        let _permit = self.acquire_trace_permit().await;
1200        Self::debug_trace_call(self, request, block_id, opts.unwrap_or_default())
1201            .await
1202            .map_err(Into::into)
1203    }
1204
1205    async fn debug_trace_call_many(
1206        &self,
1207        bundles: Vec<Bundle<RpcTxReq<Eth::NetworkTypes>>>,
1208        state_context: Option<StateContext>,
1209        opts: Option<GethDebugTracingCallOptions>,
1210    ) -> RpcResult<Vec<Vec<GethTrace>>> {
1211        let _permit = self.acquire_trace_permit().await;
1212        Self::debug_trace_call_many(self, bundles, state_context, opts).await.map_err(Into::into)
1213    }
1214
1215    /// Handler for `debug_executionWitness`
1216    async fn debug_execution_witness(
1217        &self,
1218        block: BlockId,
1219        mode: Option<ExecutionWitnessMode>,
1220    ) -> RpcResult<ExecutionWitness> {
1221        let _permit = self.acquire_trace_permit().await;
1222        Self::debug_execution_witness(self, block, mode).await.map_err(Into::into)
1223    }
1224
1225    /// Handler for `debug_executionWitnessByBlockHash`
1226    async fn debug_execution_witness_by_block_hash(
1227        &self,
1228        hash: B256,
1229        mode: Option<ExecutionWitnessMode>,
1230    ) -> RpcResult<ExecutionWitness> {
1231        let _permit = self.acquire_trace_permit().await;
1232        Self::debug_execution_witness_by_block_hash(self, hash, mode).await.map_err(Into::into)
1233    }
1234
1235    /// Handler for `debug_accountAt`
1236    async fn debug_account_at(
1237        &self,
1238        block_id: BlockId,
1239        tx_index: Index,
1240        address: Address,
1241    ) -> RpcResult<Option<Account>> {
1242        let _permit = self.acquire_trace_permit().await;
1243        Self::debug_account_at(self, block_id, tx_index, address).await.map_err(Into::into)
1244    }
1245
1246    /// Handler for `debug_accountInfoAt`
1247    async fn debug_account_info_at(
1248        &self,
1249        block_id: BlockId,
1250        tx_index: Index,
1251        address: Address,
1252    ) -> RpcResult<Option<AccountInfo>> {
1253        let _permit = self.acquire_trace_permit().await;
1254        Self::debug_account_info_at(self, block_id, tx_index, address).await.map_err(Into::into)
1255    }
1256
1257    async fn debug_account_range(
1258        &self,
1259        _block_number: BlockNumberOrTag,
1260        _start: Bytes,
1261        _max_results: u64,
1262        _nocode: bool,
1263        _nostorage: bool,
1264        _incompletes: bool,
1265    ) -> RpcResult<()> {
1266        Ok(())
1267    }
1268
1269    async fn debug_chaindb_compact(&self) -> RpcResult<()> {
1270        Ok(())
1271    }
1272
1273    async fn debug_chain_config(&self) -> RpcResult<ChainConfig> {
1274        Ok(crate::chain_config::chain_config(&*self.provider().chain_spec()))
1275    }
1276
1277    async fn debug_chaindb_property(&self, _property: String) -> RpcResult<()> {
1278        Ok(())
1279    }
1280
1281    async fn debug_code_by_hash(
1282        &self,
1283        hash: B256,
1284        block_id: Option<BlockId>,
1285    ) -> RpcResult<Option<Bytes>> {
1286        Self::debug_code_by_hash(self, hash, block_id).await.map_err(Into::into)
1287    }
1288
1289    async fn debug_db_ancient(&self, _kind: String, _number: u64) -> RpcResult<()> {
1290        Ok(())
1291    }
1292
1293    async fn debug_db_ancients(&self) -> RpcResult<()> {
1294        Ok(())
1295    }
1296
1297    /// `debug_db_get` - database key lookup
1298    ///
1299    /// Currently supported:
1300    /// * Contract bytecode associated with a code hash. The key format is: `<0x63><code_hash>`
1301    ///     * Prefix byte: 0x63 (required)
1302    ///     * Code hash: 32 bytes
1303    ///   Must be provided as either:
1304    ///     * Hex string: "0x63..." (66 hex characters after 0x)
1305    ///     * Raw byte string: raw byte string (33 bytes)
1306    ///   See Geth impl: <https://github.com/ethereum/go-ethereum/blob/737ffd1bf0cbee378d0111a5b17ae4724fb2216c/core/rawdb/schema.go#L120>
1307    async fn debug_db_get(&self, key: String) -> RpcResult<Option<Bytes>> {
1308        let key_bytes = if key.starts_with("0x") {
1309            decode(&key).map_err(|_| EthApiError::InvalidParams("Invalid hex key".to_string()))?
1310        } else {
1311            key.into_bytes()
1312        };
1313
1314        if key_bytes.len() != 33 {
1315            return Err(EthApiError::InvalidParams(format!(
1316                "Key must be 33 bytes, got {}",
1317                key_bytes.len()
1318            ))
1319            .into());
1320        }
1321        if key_bytes[0] != 0x63 {
1322            return Err(EthApiError::InvalidParams("Key prefix must be 0x63".to_string()).into());
1323        }
1324
1325        let code_hash = B256::from_slice(&key_bytes[1..33]);
1326
1327        // No block ID is provided, so it defaults to the latest block
1328        self.debug_code_by_hash(code_hash, None).await.map_err(Into::into)
1329    }
1330
1331    async fn debug_dump_block(&self, _number: BlockId) -> RpcResult<()> {
1332        Ok(())
1333    }
1334
1335    async fn debug_free_os_memory(&self) -> RpcResult<()> {
1336        Ok(())
1337    }
1338
1339    async fn debug_gc_stats(&self) -> RpcResult<()> {
1340        Ok(())
1341    }
1342
1343    async fn debug_get_accessible_state(
1344        &self,
1345        _from: BlockNumberOrTag,
1346        _to: BlockNumberOrTag,
1347    ) -> RpcResult<()> {
1348        Ok(())
1349    }
1350
1351    async fn debug_get_modified_accounts_by_hash(
1352        &self,
1353        _start_hash: B256,
1354        _end_hash: B256,
1355    ) -> RpcResult<()> {
1356        Ok(())
1357    }
1358
1359    async fn debug_get_modified_accounts_by_number(
1360        &self,
1361        _start_number: u64,
1362        _end_number: u64,
1363    ) -> RpcResult<()> {
1364        Ok(())
1365    }
1366
1367    async fn debug_intermediate_roots(
1368        &self,
1369        block_hash: B256,
1370        _opts: Option<GethDebugTracingCallOptions>,
1371    ) -> RpcResult<Vec<B256>> {
1372        let _permit = self.acquire_trace_permit().await;
1373        self.intermediate_roots(block_hash).await.map_err(Into::into)
1374    }
1375
1376    async fn debug_mem_stats(&self) -> RpcResult<()> {
1377        Ok(())
1378    }
1379
1380    async fn debug_preimage(&self, _hash: B256) -> RpcResult<()> {
1381        Ok(())
1382    }
1383
1384    async fn debug_print_block(&self, _number: u64) -> RpcResult<()> {
1385        Ok(())
1386    }
1387
1388    async fn debug_seed_hash(&self, _number: u64) -> RpcResult<B256> {
1389        Ok(Default::default())
1390    }
1391
1392    async fn debug_set_gc_percent(&self, _v: i32) -> RpcResult<()> {
1393        Ok(())
1394    }
1395
1396    async fn debug_set_head(&self, _number: U64) -> RpcResult<()> {
1397        Ok(())
1398    }
1399
1400    async fn debug_set_trie_flush_interval(&self, _interval: String) -> RpcResult<()> {
1401        Ok(())
1402    }
1403
1404    async fn debug_standard_trace_bad_block_to_file(
1405        &self,
1406        _block: BlockNumberOrTag,
1407        _opts: Option<GethDebugTracingCallOptions>,
1408    ) -> RpcResult<()> {
1409        Ok(())
1410    }
1411
1412    async fn debug_standard_trace_block_to_file(
1413        &self,
1414        _block: BlockNumberOrTag,
1415        _opts: Option<GethDebugTracingCallOptions>,
1416    ) -> RpcResult<()> {
1417        Ok(())
1418    }
1419
1420    async fn debug_state_root_with_updates(
1421        &self,
1422        hashed_state: HashedPostState,
1423        block_id: Option<BlockId>,
1424    ) -> RpcResult<(B256, TrieUpdates)> {
1425        Self::debug_state_root_with_updates(self, hashed_state, block_id).await.map_err(Into::into)
1426    }
1427
1428    async fn debug_storage_range_at(
1429        &self,
1430        _block_hash: B256,
1431        _tx_idx: usize,
1432        _contract_address: Address,
1433        _key_start: B256,
1434        _max_result: u64,
1435    ) -> RpcResult<()> {
1436        Ok(())
1437    }
1438
1439    async fn debug_trace_bad_block(
1440        &self,
1441        block_hash: B256,
1442        opts: Option<GethDebugTracingCallOptions>,
1443    ) -> RpcResult<Vec<TraceResult>> {
1444        let _permit = self.acquire_trace_permit().await;
1445        let entry = self
1446            .inner
1447            .bad_block_store
1448            .get(block_hash)
1449            .ok_or_else(|| internal_rpc_err("bad block not found in cache"))?;
1450
1451        let evm_env = self
1452            .eth_api()
1453            .evm_config()
1454            .evm_env(entry.block.header())
1455            .map_err(RethError::other)
1456            .to_rpc_result()?;
1457
1458        let opts = opts.map(|o| o.tracing_options).unwrap_or_default();
1459        self.trace_block(entry.block.clone(), evm_env, opts).await.map_err(Into::into)
1460    }
1461}
1462
1463impl<Eth: RpcNodeCore> std::fmt::Debug for DebugApi<Eth> {
1464    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1465        f.debug_struct("DebugApi").finish_non_exhaustive()
1466    }
1467}
1468
1469impl<Eth: RpcNodeCore> Clone for DebugApi<Eth> {
1470    fn clone(&self) -> Self {
1471        Self { inner: Arc::clone(&self.inner) }
1472    }
1473}
1474
1475struct DebugApiInner<Eth: RpcNodeCore> {
1476    /// The implementation of `eth` API
1477    eth_api: Eth,
1478    // restrict the number of concurrent calls to blocking calls
1479    blocking_task_guard: BlockingTaskGuard,
1480    /// Spawns long-running subscription tasks.
1481    task_spawner: Runtime,
1482    /// Cache for bad blocks.
1483    bad_block_store: BadBlockStore<BlockTy<Eth::Primitives>>,
1484}
1485
1486/// A bounded, deduplicating store of recently observed bad blocks.
1487#[derive(Clone, Debug)]
1488struct BadBlockStore<B: BlockTrait> {
1489    inner: Arc<RwLock<VecDeque<BadBlockEntry<B>>>>,
1490    limit: usize,
1491}
1492
1493/// A cached bad block paired with the reason it was rejected.
1494#[derive(Clone, Debug)]
1495struct BadBlockEntry<B: BlockTrait> {
1496    block: Arc<RecoveredBlock<B>>,
1497    reason: String,
1498}
1499
1500impl<B: BlockTrait> BadBlockStore<B> {
1501    /// Creates a new store with the given capacity.
1502    fn new(limit: usize) -> Self {
1503        Self { inner: Arc::new(RwLock::new(VecDeque::with_capacity(limit))), limit }
1504    }
1505
1506    /// Inserts a recovered block with its rejection reason, keeping only the most recent `limit`
1507    /// entries and deduplicating by block hash.
1508    fn insert(&self, block: RecoveredBlock<B>, reason: String) {
1509        let hash = block.hash();
1510        let mut guard = self.inner.write();
1511
1512        // skip if we already recorded this bad block , and keep original ordering
1513        if guard.iter().any(|entry| entry.block.hash() == hash) {
1514            return;
1515        }
1516        guard.push_back(BadBlockEntry { block: Arc::new(block), reason });
1517
1518        while guard.len() > self.limit {
1519            guard.pop_front();
1520        }
1521    }
1522
1523    /// Returns all cached bad block entries ordered from newest to oldest.
1524    fn all(&self) -> Vec<BadBlockEntry<B>> {
1525        let guard = self.inner.read();
1526        guard.iter().rev().cloned().collect()
1527    }
1528
1529    /// Returns the bad block entry with the given hash, if cached.
1530    fn get(&self, hash: B256) -> Option<BadBlockEntry<B>> {
1531        let guard = self.inner.read();
1532        guard.iter().find(|entry| entry.block.hash() == hash).cloned()
1533    }
1534}
1535
1536impl<B: BlockTrait> Default for BadBlockStore<B> {
1537    fn default() -> Self {
1538        Self::new(64)
1539    }
1540}
1541
1542/// Default timeout of a JS tracer, matching geth.
1543#[cfg(feature = "js-tracer")]
1544const DEFAULT_JS_TRACER_TIMEOUT: Duration = Duration::from_secs(5);
1545
1546/// Interrupts a JS tracer once its request is dropped or its timeout expires.
1547///
1548/// Built-in tracers can't be interrupted, so for them this does nothing.
1549#[derive(Clone, Debug, Default)]
1550struct JsTraceInterrupt {
1551    #[cfg(feature = "js-tracer")]
1552    js: Option<(JsInspectorInterrupt, Duration)>,
1553}
1554
1555impl JsTraceInterrupt {
1556    /// Creates an interrupt if `opts` selects a JS tracer, using its `timeout` or geth's default.
1557    #[cfg_attr(
1558        not(feature = "js-tracer"),
1559        expect(unused_variables, reason = "only JS tracers can be interrupted")
1560    )]
1561    fn new(opts: &GethDebugTracingOptions) -> Result<Self, EthApiError> {
1562        #[cfg(feature = "js-tracer")]
1563        if let Some(GethDebugTracerType::JsTracer(_)) = &opts.tracer {
1564            let timeout = match &opts.timeout {
1565                Some(timeout) => humantime::parse_duration(timeout).map_err(|err| {
1566                    EthApiError::InvalidParams(format!("invalid tracer timeout {timeout:?}: {err}"))
1567                })?,
1568                None => DEFAULT_JS_TRACER_TIMEOUT,
1569            };
1570            return Ok(Self { js: Some((JsInspectorInterrupt::new(), timeout)) })
1571        }
1572        Ok(Self::default())
1573    }
1574
1575    /// Lets this interrupt stop `inspector` if it's a JS tracer.
1576    #[cfg_attr(
1577        not(feature = "js-tracer"),
1578        expect(clippy::missing_const_for_fn, reason = "only JS tracers can be interrupted")
1579    )]
1580    fn attach(&self, inspector: DebugInspector) -> DebugInspector {
1581        #[cfg(feature = "js-tracer")]
1582        if let Some((interrupt, _)) = &self.js {
1583            return match inspector {
1584                DebugInspector::Js(js) => {
1585                    DebugInspector::Js(Box::new(js.with_interrupt(interrupt.clone())))
1586                }
1587                inspector => inspector,
1588            }
1589        }
1590        inspector
1591    }
1592
1593    /// Awaits `trace`, interrupting the tracer if the returned future is dropped or the timeout
1594    /// expires.
1595    ///
1596    /// The interrupt is armed before the future is first polled, so dropping it unpolled still
1597    /// stops a trace that is already running on a blocking task.
1598    #[cfg_attr(
1599        not(feature = "js-tracer"),
1600        expect(
1601            clippy::manual_async_fn,
1602            clippy::redundant_async_block,
1603            reason = "only JS tracers can be interrupted"
1604        )
1605    )]
1606    fn run<T, E: FromEthApiError>(
1607        &self,
1608        trace: impl Future<Output = Result<T, E>>,
1609    ) -> impl Future<Output = Result<T, E>> {
1610        #[cfg(feature = "js-tracer")]
1611        let js = self.js.as_ref().map(|(interrupt, timeout)| (interrupt.drop_guard(), *timeout));
1612        async move {
1613            #[cfg(feature = "js-tracer")]
1614            if let Some((_interrupt_on_drop, timeout)) = js {
1615                return tokio::time::timeout(timeout, trace)
1616                    .await
1617                    .map_err(|_| E::from_eth_err(EthApiError::ExecutionTimedOut(timeout)))?
1618            }
1619            trace.await
1620        }
1621    }
1622}
1623
1624#[cfg(test)]
1625mod tests {
1626    use super::*;
1627    use crate::{eth::helpers::types::EthRpcConverter, EthApi};
1628    use alloy_primitives::{keccak256, U256};
1629    use reth_chainspec::ChainSpec;
1630    use reth_db_api::{tables, transaction::DbTxMut};
1631    use reth_evm_ethereum::EthEvmConfig;
1632    use reth_network_api::noop::NoopNetwork;
1633    use reth_primitives_traits::StorageEntry;
1634    use reth_provider::test_utils::{create_test_provider_factory, NoopProvider};
1635    use reth_rpc_eth_api::EthApiServer;
1636    use reth_transaction_pool::test_utils::testing_pool;
1637    use revm::{
1638        database::{states::StorageSlot, AccountStatus, BundleAccount, BundleState},
1639        state::AccountInfo as RevmAccountInfo,
1640    };
1641
1642    #[tokio::test]
1643    async fn trace_call_out_of_range_block_error() {
1644        let eth_api = EthApi::<_, EthRpcConverter<ChainSpec>>::builder(
1645            NoopProvider::default(),
1646            testing_pool(),
1647            NoopNetwork::default(),
1648            EthEvmConfig::mainnet(),
1649        )
1650        .build();
1651        let debug_api = DebugApi::new(
1652            eth_api.clone(),
1653            BlockingTaskGuard::new(1),
1654            &Runtime::test(),
1655            futures::stream::empty(),
1656        );
1657        let block_id = BlockId::number(0xfffffffff);
1658        for tx_index in [None, Some(0)] {
1659            let mut opts: GethDebugTracingCallOptions =
1660                serde_json::from_value(serde_json::json!({ "tracer": "callTracer" })).unwrap();
1661            opts.tx_index = tx_index;
1662            let err = DebugApiServer::debug_trace_call(
1663                &debug_api,
1664                Default::default(),
1665                Some(block_id),
1666                Some(opts),
1667            )
1668            .await
1669            .unwrap_err();
1670            assert_eq!(err.code(), -32000);
1671            assert!(err.message().contains("not found"));
1672        }
1673
1674        let err = EthApiServer::call(&eth_api, Default::default(), Some(block_id), None, None)
1675            .await
1676            .unwrap_err();
1677        assert_eq!(err.code(), -32001);
1678    }
1679
1680    #[cfg(feature = "js-tracer")]
1681    fn js_tracer_opts(timeout: Option<&str>) -> GethDebugTracingOptions {
1682        GethDebugTracingOptions {
1683            tracer: Some(GethDebugTracerType::JsTracer(
1684                "{ fault: function() {}, result: function() { return null; } }".into(),
1685            )),
1686            timeout: timeout.map(Into::into),
1687            ..Default::default()
1688        }
1689    }
1690
1691    #[cfg(feature = "js-tracer")]
1692    #[test]
1693    fn js_trace_interrupt_stops_attached_js_tracer() {
1694        use revm::{
1695            context::TxEnv,
1696            context_interface::result::EVMError,
1697            database::{CacheDB, EmptyDB},
1698            primitives::TxKind,
1699            InspectEvm, MainBuilder, MainContext,
1700        };
1701
1702        let opts = js_tracer_opts(None);
1703        let interrupt = JsTraceInterrupt::new(&opts).unwrap();
1704        let inspector = interrupt.attach(DebugInspector::new(opts).unwrap());
1705        interrupt.js.as_ref().unwrap().0.interrupt();
1706
1707        let mut evm = revm::Context::mainnet()
1708            .with_db(CacheDB::<EmptyDB>::default())
1709            .build_mainnet_with_inspector(inspector);
1710        let result = evm.inspect_tx(TxEnv {
1711            gas_limit: 1_000_000,
1712            kind: TxKind::Call(Address::with_last_byte(1)),
1713            ..Default::default()
1714        });
1715        assert!(
1716            matches!(&result, Err(EVMError::Custom(msg)) if msg == "JavaScript tracing interrupted"),
1717            "{result:?}"
1718        );
1719    }
1720
1721    #[cfg(feature = "js-tracer")]
1722    #[tokio::test]
1723    async fn js_trace_interrupt_times_out_and_interrupts_tracer() {
1724        let interrupt = JsTraceInterrupt::new(&js_tracer_opts(Some("10ms"))).unwrap();
1725        let err =
1726            interrupt.run(std::future::pending::<Result<(), EthApiError>>()).await.unwrap_err();
1727        assert!(
1728            matches!(err, EthApiError::ExecutionTimedOut(timeout) if timeout == Duration::from_millis(10))
1729        );
1730        assert!(interrupt.js.unwrap().0.is_interrupted());
1731    }
1732
1733    #[cfg(feature = "js-tracer")]
1734    #[test]
1735    fn js_trace_interrupt_interrupts_tracer_when_dropped_unpolled() {
1736        let interrupt = JsTraceInterrupt::new(&js_tracer_opts(None)).unwrap();
1737        let (tracer_interrupt, _) = interrupt.js.clone().unwrap();
1738        let trace = interrupt.run(std::future::pending::<Result<(), EthApiError>>());
1739        assert!(!tracer_interrupt.is_interrupted());
1740        drop(trace);
1741        assert!(tracer_interrupt.is_interrupted());
1742    }
1743
1744    #[test]
1745    fn hashed_post_state_zeroes_destroyed_account_parent_storage() {
1746        let factory = create_test_provider_factory();
1747        let address = Address::with_last_byte(1);
1748        let old_slot = U256::ONE;
1749        let new_slot = U256::from(2);
1750        let old_value = U256::from(10);
1751        let new_value = U256::from(20);
1752        let hashed_address = keccak256(address);
1753        let hashed_old_slot = keccak256(B256::from(old_slot));
1754        let hashed_new_slot = keccak256(B256::from(new_slot));
1755
1756        let provider_rw = factory.provider_rw().unwrap();
1757        provider_rw
1758            .tx_ref()
1759            .put::<tables::HashedStorages>(
1760                hashed_address,
1761                StorageEntry { key: hashed_old_slot, value: old_value },
1762            )
1763            .unwrap();
1764        provider_rw.commit().unwrap();
1765
1766        let mut bundle_state = BundleState::default();
1767        bundle_state.state.insert(
1768            address,
1769            BundleAccount::new(
1770                Some(RevmAccountInfo::default()),
1771                Some(RevmAccountInfo::default()),
1772                std::iter::once((new_slot, StorageSlot::new_changed(U256::ZERO, new_value)))
1773                    .collect(),
1774                AccountStatus::DestroyedChanged,
1775            ),
1776        );
1777
1778        let provider = factory.latest().unwrap();
1779        let hashed_state = provider.hashed_post_state(&bundle_state).unwrap();
1780        let storage = &hashed_state.storages[&hashed_address];
1781
1782        assert_eq!(storage.storage[&hashed_old_slot], U256::ZERO);
1783        assert_eq!(storage.storage[&hashed_new_slot], new_value);
1784    }
1785}