Skip to main content

reth_rpc/
debug.rs

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