Skip to main content

reth_rpc/
debug.rs

1use alloy_consensus::{transaction::TxHashRef, BlockHeader};
2use alloy_eip7928::BlockAccessList;
3use alloy_eips::{eip2718::Encodable2718, BlockId, BlockNumberOrTag};
4use alloy_evm::env::BlockEnvironment;
5use alloy_genesis::ChainConfig;
6use alloy_primitives::{hex::decode, uint, Address, Bytes, B256, U64};
7use alloy_rlp::{Decodable, Encodable};
8use alloy_rpc_types::BlockTransactionsKind;
9use alloy_rpc_types_debug::ExecutionWitness;
10use alloy_rpc_types_eth::{state::EvmOverrides, BlockError, Bundle, StateContext};
11use alloy_rpc_types_trace::geth::{
12    BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult,
13};
14use async_trait::async_trait;
15use futures::Stream;
16use jsonrpsee::core::RpcResult;
17use parking_lot::RwLock;
18use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
19use reth_engine_primitives::ConsensusEngineEvent;
20use reth_errors::RethError;
21use reth_evm::{execute::Executor, ConfigureEvm, EvmEnvFor};
22use reth_primitives_traits::{
23    Block as BlockTrait, BlockBody, BlockTy, ReceiptWithBloom, RecoveredBlock,
24};
25use reth_revm::{db::State, witness::ExecutionWitnessRecord};
26use reth_rpc_api::DebugApiServer;
27use reth_rpc_convert::RpcTxReq;
28use reth_rpc_eth_api::{
29    helpers::{EthTransactions, TraceExt},
30    FromEthApiError, RpcConvert, RpcNodeCore,
31};
32use reth_rpc_eth_types::EthApiError;
33use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult};
34use reth_storage_api::{
35    BlockIdReader, BlockReaderIdExt, HeaderProvider, ProviderBlock, ReceiptProviderIdExt,
36    StateProofProvider, StateProviderFactory, StateRootProvider, TransactionVariant,
37};
38use reth_tasks::{pool::BlockingTaskGuard, Runtime};
39use reth_trie_common::{updates::TrieUpdates, HashedPostState};
40use revm::DatabaseCommit;
41use revm_inspectors::tracing::{DebugInspector, TransactionContext};
42use serde::{Deserialize, Serialize};
43use std::{collections::VecDeque, sync::Arc};
44use tokio::sync::{AcquireError, OwnedSemaphorePermit};
45use tokio_stream::StreamExt;
46
47/// `debug` API implementation.
48///
49/// This type provides the functionality for handling `debug` related requests.
50pub struct DebugApi<Eth: RpcNodeCore> {
51    inner: Arc<DebugApiInner<Eth>>,
52}
53
54impl<Eth> DebugApi<Eth>
55where
56    Eth: RpcNodeCore,
57{
58    /// Create a new instance of the [`DebugApi`]
59    pub fn new(
60        eth_api: Eth,
61        blocking_task_guard: BlockingTaskGuard,
62        executor: &Runtime,
63        mut stream: impl Stream<Item = ConsensusEngineEvent<Eth::Primitives>> + Send + Unpin + 'static,
64    ) -> Self {
65        let bad_block_store = BadBlockStore::default();
66        let inner = Arc::new(DebugApiInner {
67            eth_api,
68            blocking_task_guard,
69            bad_block_store: bad_block_store.clone(),
70        });
71
72        // Spawn a task caching bad blocks
73        executor.spawn_task(async move {
74            while let Some(event) = stream.next().await {
75                if let ConsensusEngineEvent::InvalidBlock(block) = event &&
76                    let Ok(recovered) =
77                        RecoveredBlock::try_recover_sealed(block.as_ref().clone())
78                {
79                    bad_block_store.insert(recovered);
80                }
81            }
82        });
83
84        Self { inner }
85    }
86
87    /// Access the underlying `Eth` API.
88    pub fn eth_api(&self) -> &Eth {
89        &self.inner.eth_api
90    }
91
92    /// Access the underlying provider.
93    pub fn provider(&self) -> &Eth::Provider {
94        self.inner.eth_api.provider()
95    }
96}
97
98// === impl DebugApi ===
99
100impl<Eth> DebugApi<Eth>
101where
102    Eth: TraceExt,
103{
104    /// Acquires a permit to execute a tracing call.
105    async fn acquire_trace_permit(&self) -> Result<OwnedSemaphorePermit, AcquireError> {
106        self.inner.blocking_task_guard.clone().acquire_owned().await
107    }
108
109    /// Trace the entire block asynchronously
110    async fn trace_block(
111        &self,
112        block: Arc<RecoveredBlock<ProviderBlock<Eth::Provider>>>,
113        evm_env: EvmEnvFor<Eth::Evm>,
114        opts: GethDebugTracingOptions,
115    ) -> Result<Vec<TraceResult>, Eth::Error> {
116        self.eth_api()
117            .spawn_with_state_at_block(block.parent_hash(), move |eth_api, mut db| {
118                let mut results = Vec::with_capacity(block.body().transactions().len());
119
120                eth_api.apply_pre_execution_changes(&block, &mut db)?;
121
122                let mut transactions = block.transactions_recovered().enumerate().peekable();
123                let mut inspector = DebugInspector::new(opts).map_err(Eth::Error::from_eth_err)?;
124                while let Some((index, tx)) = transactions.next() {
125                    let tx_hash = *tx.tx_hash();
126                    let tx_env = eth_api.evm_config().tx_env(tx);
127
128                    let res = eth_api.inspect(
129                        &mut db,
130                        evm_env.clone(),
131                        tx_env.clone(),
132                        &mut inspector,
133                    )?;
134                    let result = inspector
135                        .get_result(
136                            Some(TransactionContext {
137                                block_hash: Some(block.hash()),
138                                tx_hash: Some(tx_hash),
139                                tx_index: Some(index),
140                            }),
141                            &tx_env,
142                            &evm_env.block_env,
143                            &res,
144                            &mut db,
145                        )
146                        .map_err(Eth::Error::from_eth_err)?;
147
148                    results.push(TraceResult::Success { result, tx_hash: Some(tx_hash) });
149                    if transactions.peek().is_some() {
150                        inspector.fuse().map_err(Eth::Error::from_eth_err)?;
151                        // need to apply the state changes of this transaction before executing the
152                        // next transaction
153                        db.commit(res.state)
154                    }
155                }
156
157                Ok(results)
158            })
159            .await
160    }
161
162    /// Replays the given block and returns the trace of each transaction.
163    ///
164    /// This expects a rlp encoded block
165    ///
166    /// Note, the parent of this block must be present, or it will fail.
167    pub async fn debug_trace_raw_block(
168        &self,
169        rlp_block: Bytes,
170        opts: GethDebugTracingOptions,
171    ) -> Result<Vec<TraceResult>, Eth::Error> {
172        let block: ProviderBlock<Eth::Provider> = Decodable::decode(&mut rlp_block.as_ref())
173            .map_err(BlockError::RlpDecodeRawBlock)
174            .map_err(Eth::Error::from_eth_err)?;
175
176        let evm_env = self
177            .eth_api()
178            .evm_config()
179            .evm_env(block.header())
180            .map_err(RethError::other)
181            .map_err(Eth::Error::from_eth_err)?;
182
183        // Depending on EIP-2 we need to recover the transactions differently
184        let senders =
185            if self.provider().chain_spec().is_homestead_active_at_block(block.header().number()) {
186                block.body().recover_signers()
187            } else {
188                block.body().recover_signers_unchecked()
189            }
190            .map_err(Eth::Error::from_eth_err)?;
191
192        self.trace_block(Arc::new(block.into_recovered_with_signers(senders)), evm_env, opts).await
193    }
194
195    /// Replays a block and returns the trace of each transaction.
196    pub async fn debug_trace_block(
197        &self,
198        block_id: BlockId,
199        opts: GethDebugTracingOptions,
200    ) -> Result<Vec<TraceResult>, Eth::Error> {
201        let block_hash = self
202            .provider()
203            .block_hash_for_id(block_id)
204            .map_err(Eth::Error::from_eth_err)?
205            .ok_or(EthApiError::HeaderNotFound(block_id))?;
206
207        let block = self
208            .eth_api()
209            .recovered_block(block_hash.into())
210            .await?
211            .ok_or(EthApiError::HeaderNotFound(block_id))?;
212        let evm_env = self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
213
214        self.trace_block(block, evm_env, opts).await
215    }
216
217    /// Trace the transaction according to the provided options.
218    ///
219    /// Ref: <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
220    pub async fn debug_trace_transaction(
221        &self,
222        tx_hash: B256,
223        opts: GethDebugTracingOptions,
224    ) -> Result<GethTrace, Eth::Error> {
225        let (transaction, block) = match self.eth_api().transaction_and_block(tx_hash).await? {
226            None => return Err(EthApiError::TransactionNotFound.into()),
227            Some(res) => res,
228        };
229        let evm_env = self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
230
231        // we need to get the state of the parent block because we're essentially replaying the
232        // block the transaction is included in
233        let state_at: BlockId = block.parent_hash().into();
234        let block_hash = block.hash();
235
236        self.eth_api()
237            .spawn_with_state_at_block(state_at, move |eth_api, mut db| {
238                let block_txs = block.transactions_recovered();
239
240                // configure env for the target transaction
241                let tx = transaction.into_recovered();
242
243                eth_api.apply_pre_execution_changes(&block, &mut db)?;
244
245                // replay all transactions prior to the targeted transaction
246                let index = eth_api.replay_transactions_until(
247                    &mut db,
248                    evm_env.clone(),
249                    block_txs,
250                    *tx.tx_hash(),
251                )?;
252
253                let tx_env = eth_api.evm_config().tx_env(&tx);
254
255                let mut inspector = DebugInspector::new(opts).map_err(Eth::Error::from_eth_err)?;
256                let res =
257                    eth_api.inspect(&mut db, evm_env.clone(), tx_env.clone(), &mut inspector)?;
258                let trace = inspector
259                    .get_result(
260                        Some(TransactionContext {
261                            block_hash: Some(block_hash),
262                            tx_index: Some(index),
263                            tx_hash: Some(*tx.tx_hash()),
264                        }),
265                        &tx_env,
266                        &evm_env.block_env,
267                        &res,
268                        &mut db,
269                    )
270                    .map_err(Eth::Error::from_eth_err)?;
271
272                Ok(trace)
273            })
274            .await
275    }
276
277    /// The `debug_traceCall` method lets you run an `eth_call` within the context of the given
278    /// block execution using the final state of parent block as the base.
279    ///
280    /// If `tx_index` is provided in opts, the call will be traced at the state after executing
281    /// transactions up to the specified index within the block (0-indexed).
282    /// If not provided, then uses the post-state (default behavior).
283    ///
284    /// Differences compare to `eth_call`:
285    ///  - `debug_traceCall` executes with __enabled__ basefee check, `eth_call` does not: <https://github.com/paradigmxyz/reth/issues/6240>
286    pub async fn debug_trace_call(
287        &self,
288        call: RpcTxReq<Eth::NetworkTypes>,
289        block_id: Option<BlockId>,
290        opts: GethDebugTracingCallOptions,
291    ) -> Result<GethTrace, Eth::Error> {
292        let at = block_id.unwrap_or_default();
293        let GethDebugTracingCallOptions {
294            tracing_options,
295            state_overrides,
296            block_overrides,
297            tx_index,
298        } = opts;
299        let overrides = EvmOverrides::new(state_overrides, block_overrides.map(Box::new));
300
301        // Check if we need to replay transactions for a specific tx_index
302        if let Some(tx_idx) = tx_index {
303            return self
304                .debug_trace_call_at_tx_index(call, at, tx_idx as usize, tracing_options, overrides)
305                .await;
306        }
307
308        let this = self.clone();
309        self.eth_api()
310            .spawn_with_call_at(call, at, overrides, move |db, evm_env, tx_env| {
311                let mut inspector =
312                    DebugInspector::new(tracing_options).map_err(Eth::Error::from_eth_err)?;
313                let res = this.eth_api().inspect(
314                    &mut *db,
315                    evm_env.clone(),
316                    tx_env.clone(),
317                    &mut inspector,
318                )?;
319                let trace = inspector
320                    .get_result(None, &tx_env, &evm_env.block_env, &res, db)
321                    .map_err(Eth::Error::from_eth_err)?;
322                Ok(trace)
323            })
324            .await
325    }
326
327    /// Helper method to execute `debug_trace_call` at a specific transaction index within a block.
328    /// This replays transactions up to the specified index, then executes the trace call in that
329    /// state.
330    async fn debug_trace_call_at_tx_index(
331        &self,
332        call: RpcTxReq<Eth::NetworkTypes>,
333        block_id: BlockId,
334        tx_index: usize,
335        tracing_options: GethDebugTracingOptions,
336        overrides: EvmOverrides,
337    ) -> Result<GethTrace, Eth::Error> {
338        // Get the target block to check transaction count
339        let block = self
340            .eth_api()
341            .recovered_block(block_id)
342            .await?
343            .ok_or(EthApiError::HeaderNotFound(block_id))?;
344
345        if tx_index >= block.transaction_count() {
346            // tx_index out of bounds
347            return Err(EthApiError::InvalidParams(format!(
348                "tx_index {} out of bounds for block with {} transactions",
349                tx_index,
350                block.transaction_count()
351            ))
352            .into())
353        }
354
355        let evm_env = self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
356
357        // execute after the parent block, replaying `tx_index` transactions
358        let state_at = block.parent_hash();
359
360        self.eth_api()
361            .spawn_with_state_at_block(state_at, move |eth_api, mut db| {
362                // 1. apply pre-execution changes
363                eth_api.apply_pre_execution_changes(&block, &mut db)?;
364
365                // 2. replay the required number of transactions
366                eth_api.replay_transactions_until(
367                    &mut db,
368                    evm_env.clone(),
369                    block.transactions_recovered(),
370                    *block.body().transactions()[tx_index].tx_hash(),
371                )?;
372
373                // 3. now execute the trace call on this state
374                let (evm_env, tx_env) =
375                    eth_api.prepare_call_env(evm_env, call, &mut db, overrides)?;
376
377                let mut inspector =
378                    DebugInspector::new(tracing_options).map_err(Eth::Error::from_eth_err)?;
379                let res =
380                    eth_api.inspect(&mut db, evm_env.clone(), tx_env.clone(), &mut inspector)?;
381                let trace = inspector
382                    .get_result(None, &tx_env, &evm_env.block_env, &res, &mut db)
383                    .map_err(Eth::Error::from_eth_err)?;
384
385                Ok(trace)
386            })
387            .await
388    }
389
390    /// The `debug_traceCallMany` method lets you run an `eth_callMany` within the context of the
391    /// given block execution using the first n transactions in the given block as base.
392    /// Each following bundle increments block number by 1 and block timestamp by 12 seconds
393    pub async fn debug_trace_call_many(
394        &self,
395        bundles: Vec<Bundle<RpcTxReq<Eth::NetworkTypes>>>,
396        state_context: Option<StateContext>,
397        opts: Option<GethDebugTracingCallOptions>,
398    ) -> Result<Vec<Vec<GethTrace>>, Eth::Error> {
399        if bundles.is_empty() {
400            return Err(EthApiError::InvalidParams(String::from("bundles are empty.")).into())
401        }
402
403        let StateContext { transaction_index, block_number } = state_context.unwrap_or_default();
404        let transaction_index = transaction_index.unwrap_or_default();
405
406        let target_block = block_number.unwrap_or_default();
407        let block = self
408            .eth_api()
409            .recovered_block(target_block)
410            .await?
411            .ok_or(EthApiError::HeaderNotFound(target_block))?;
412        let mut evm_env =
413            self.eth_api().evm_env_for_header(block.sealed_block().sealed_header())?;
414
415        let opts = opts.unwrap_or_default();
416        let GethDebugTracingCallOptions { tracing_options, mut state_overrides, .. } = opts;
417
418        // we're essentially replaying the transactions in the block here, hence we need the state
419        // that points to the beginning of the block, which is the state at the parent block
420        let mut at = block.parent_hash();
421        let mut replay_block_txs = true;
422
423        // if a transaction index is provided, we need to replay the transactions until the index
424        let num_txs =
425            transaction_index.index().unwrap_or_else(|| block.body().transactions().len());
426        // but if all transactions are to be replayed, we can use the state at the block itself
427        // this works with the exception of the PENDING block, because its state might not exist if
428        // built locally
429        if !target_block.is_pending() && num_txs == block.body().transactions().len() {
430            at = block.hash();
431            replay_block_txs = false;
432        }
433
434        self.eth_api()
435            .spawn_with_state_at_block(at, move |eth_api, mut db| {
436                // the outer vec for the bundles
437                let mut all_bundles = Vec::with_capacity(bundles.len());
438
439                if replay_block_txs {
440                    // only need to replay the transactions in the block if not all transactions are
441                    // to be replayed
442                    eth_api.apply_pre_execution_changes(&block, &mut db)?;
443
444                    let transactions = block.transactions_recovered().take(num_txs);
445
446                    // Execute all transactions until index
447                    for tx in transactions {
448                        let tx_env = eth_api.evm_config().tx_env(tx);
449                        let res = eth_api.transact(&mut db, evm_env.clone(), tx_env)?;
450                        db.commit(res.state);
451                    }
452                }
453
454                // Trace all bundles
455                let mut bundles = bundles.into_iter().peekable();
456                let mut inspector = DebugInspector::new(tracing_options.clone())
457                    .map_err(Eth::Error::from_eth_err)?;
458                while let Some(bundle) = bundles.next() {
459                    let mut results = Vec::with_capacity(bundle.transactions.len());
460                    let Bundle { transactions, block_override } = bundle;
461
462                    let block_overrides = block_override.map(Box::new);
463
464                    let mut transactions = transactions.into_iter().peekable();
465                    while let Some(tx) = transactions.next() {
466                        // apply state overrides only once, before the first transaction
467                        let state_overrides = state_overrides.take();
468                        let overrides = EvmOverrides::new(state_overrides, block_overrides.clone());
469
470                        let (evm_env, tx_env) =
471                            eth_api.prepare_call_env(evm_env.clone(), tx, &mut db, overrides)?;
472
473                        let res = eth_api.inspect(
474                            &mut db,
475                            evm_env.clone(),
476                            tx_env.clone(),
477                            &mut inspector,
478                        )?;
479                        let trace = inspector
480                            .get_result(None, &tx_env, &evm_env.block_env, &res, &mut db)
481                            .map_err(Eth::Error::from_eth_err)?;
482
483                        // If there is more transactions, commit the database
484                        // If there is no transactions, but more bundles, commit to the database too
485                        if transactions.peek().is_some() || bundles.peek().is_some() {
486                            inspector.fuse().map_err(Eth::Error::from_eth_err)?;
487                            db.commit(res.state);
488                        }
489                        results.push(trace);
490                    }
491                    // Increment block_env number and timestamp for the next bundle
492                    evm_env.block_env.inner_mut().number += uint!(1_U256);
493                    evm_env.block_env.inner_mut().timestamp += uint!(12_U256);
494
495                    all_bundles.push(results);
496                }
497                Ok(all_bundles)
498            })
499            .await
500    }
501
502    /// Generates an execution witness for the given block hash. see
503    /// [`Self::debug_execution_witness`] for more info.
504    pub async fn debug_execution_witness_by_block_hash(
505        &self,
506        hash: B256,
507    ) -> Result<ExecutionWitness, Eth::Error> {
508        let this = self.clone();
509        let block = this
510            .eth_api()
511            .recovered_block(hash.into())
512            .await?
513            .ok_or(EthApiError::HeaderNotFound(hash.into()))?;
514
515        self.debug_execution_witness_for_block(block).await
516    }
517
518    /// The `debug_executionWitness` method allows for re-execution of a block with the purpose of
519    /// generating an execution witness. The witness comprises of a map of all hashed trie nodes to
520    /// their preimages that were required during the execution of the block, including during state
521    /// root recomputation.
522    pub async fn debug_execution_witness(
523        &self,
524        block_id: BlockNumberOrTag,
525    ) -> Result<ExecutionWitness, Eth::Error> {
526        let this = self.clone();
527        let block = this
528            .eth_api()
529            .recovered_block(block_id.into())
530            .await?
531            .ok_or(EthApiError::HeaderNotFound(block_id.into()))?;
532
533        self.debug_execution_witness_for_block(block).await
534    }
535
536    /// Generates an execution witness, using the given recovered block.
537    pub async fn debug_execution_witness_for_block(
538        &self,
539        block: Arc<RecoveredBlock<ProviderBlock<Eth::Provider>>>,
540    ) -> Result<ExecutionWitness, Eth::Error> {
541        let block_number = block.header().number();
542
543        let (mut exec_witness, lowest_block_number) = self
544            .eth_api()
545            .spawn_with_state_at_block(block.parent_hash(), move |eth_api, mut db| {
546                let block_executor = eth_api.evm_config().executor(&mut db);
547
548                let mut witness_record = ExecutionWitnessRecord::default();
549
550                let _ = block_executor
551                    .execute_with_state_closure(&block, |statedb: &State<_>| {
552                        witness_record.record_executed_state(statedb);
553                    })
554                    .map_err(|err| EthApiError::Internal(err.into()))?;
555
556                let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } =
557                    witness_record;
558
559                let state = db
560                    .database
561                    .0
562                    .witness(Default::default(), hashed_state)
563                    .map_err(EthApiError::from)?;
564                Ok((
565                    ExecutionWitness { state, codes, keys, ..Default::default() },
566                    lowest_block_number,
567                ))
568            })
569            .await?;
570
571        let smallest = match lowest_block_number {
572            Some(smallest) => smallest,
573            None => {
574                // Return only the parent header, if there were no calls to the
575                // BLOCKHASH opcode.
576                block_number.saturating_sub(1)
577            }
578        };
579
580        let range = smallest..block_number;
581        exec_witness.headers = self
582            .provider()
583            .headers_range(range)
584            .map_err(EthApiError::from)?
585            .into_iter()
586            .map(|header| {
587                let mut serialized_header = Vec::new();
588                header.encode(&mut serialized_header);
589                serialized_header.into()
590            })
591            .collect();
592
593        Ok(exec_witness)
594    }
595
596    /// Returns the code associated with a given hash at the specified block ID. If no code is
597    /// found, it returns None. If no block ID is provided, it defaults to the latest block.
598    pub async fn debug_code_by_hash(
599        &self,
600        hash: B256,
601        block_id: Option<BlockId>,
602    ) -> Result<Option<Bytes>, Eth::Error> {
603        Ok(self
604            .provider()
605            .state_by_block_id(block_id.unwrap_or_default())
606            .map_err(Eth::Error::from_eth_err)?
607            .bytecode_by_hash(&hash)
608            .map_err(Eth::Error::from_eth_err)?
609            .map(|b| b.original_bytes()))
610    }
611
612    /// Returns the state root of the `HashedPostState` on top of the state for the given block with
613    /// trie updates.
614    async fn debug_state_root_with_updates(
615        &self,
616        hashed_state: HashedPostState,
617        block_id: Option<BlockId>,
618    ) -> Result<(B256, TrieUpdates), Eth::Error> {
619        self.inner
620            .eth_api
621            .spawn_blocking_io(move |this| {
622                let state = this
623                    .provider()
624                    .state_by_block_id(block_id.unwrap_or_default())
625                    .map_err(Eth::Error::from_eth_err)?;
626                state.state_root_with_updates(hashed_state).map_err(Eth::Error::from_eth_err)
627            })
628            .await
629    }
630}
631
632#[async_trait]
633impl<Eth> DebugApiServer<RpcTxReq<Eth::NetworkTypes>> for DebugApi<Eth>
634where
635    Eth: EthTransactions + TraceExt,
636{
637    /// Handler for `debug_getRawHeader`
638    async fn raw_header(&self, block_id: BlockId) -> RpcResult<Bytes> {
639        let header = match block_id {
640            BlockId::Hash(hash) => self.provider().header(hash.into()).to_rpc_result()?,
641            BlockId::Number(number_or_tag) => {
642                let number = self
643                    .provider()
644                    .convert_block_number(number_or_tag)
645                    .to_rpc_result()?
646                    .ok_or_else(|| {
647                    internal_rpc_err("Pending block not supported".to_string())
648                })?;
649                self.provider().header_by_number(number).to_rpc_result()?
650            }
651        };
652
653        let mut res = Vec::new();
654        if let Some(header) = header {
655            header.encode(&mut res);
656        }
657
658        Ok(res.into())
659    }
660
661    /// Handler for `debug_getRawBlock`
662    async fn raw_block(&self, block_id: BlockId) -> RpcResult<Bytes> {
663        let block = self
664            .provider()
665            .block_by_id(block_id)
666            .to_rpc_result()?
667            .ok_or(EthApiError::HeaderNotFound(block_id))?;
668        let mut res = Vec::new();
669        block.encode(&mut res);
670        Ok(res.into())
671    }
672
673    /// Handler for `debug_getRawTransaction`
674    ///
675    /// If this is a pooled EIP-4844 transaction, the blob sidecar is included.
676    ///
677    /// Returns the bytes of the transaction for the given hash.
678    async fn raw_transaction(&self, hash: B256) -> RpcResult<Option<Bytes>> {
679        self.eth_api().raw_transaction_by_hash(hash).await.map_err(Into::into)
680    }
681
682    /// Handler for `debug_getRawTransactions`
683    /// Returns the bytes of the transaction for the given hash.
684    async fn raw_transactions(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>> {
685        let block: RecoveredBlock<BlockTy<Eth::Primitives>> = self
686            .provider()
687            .block_with_senders_by_id(block_id, TransactionVariant::NoHash)
688            .to_rpc_result()?
689            .unwrap_or_default();
690        Ok(block.into_transactions_recovered().map(|tx| tx.encoded_2718().into()).collect())
691    }
692
693    /// Handler for `debug_getRawReceipts`
694    async fn raw_receipts(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>> {
695        Ok(self
696            .provider()
697            .receipts_by_block_id(block_id)
698            .to_rpc_result()?
699            .unwrap_or_default()
700            .into_iter()
701            .map(|receipt| ReceiptWithBloom::from(receipt).encoded_2718().into())
702            .collect())
703    }
704
705    /// Handler for `debug_getBadBlocks`
706    async fn bad_blocks(&self) -> RpcResult<Vec<serde_json::Value>> {
707        let blocks = self.inner.bad_block_store.all();
708        let mut bad_blocks = Vec::with_capacity(blocks.len());
709
710        #[derive(Serialize, Deserialize)]
711        struct BadBlockSerde<T> {
712            block: T,
713            hash: B256,
714            rlp: Bytes,
715        }
716
717        for block in blocks {
718            let rlp = alloy_rlp::encode(block.sealed_block()).into();
719            let hash = block.hash();
720
721            let block = block
722                .clone_into_rpc_block(
723                    BlockTransactionsKind::Full,
724                    |tx, tx_info| self.eth_api().converter().fill(tx, tx_info),
725                    |header, size| self.eth_api().converter().convert_header(header, size),
726                )
727                .map_err(|err| Eth::Error::from(err).into())?;
728
729            let bad_block = serde_json::to_value(BadBlockSerde { block, hash, rlp })
730                .map_err(|err| EthApiError::other(internal_rpc_err(err.to_string())))?;
731
732            bad_blocks.push(bad_block);
733        }
734
735        Ok(bad_blocks)
736    }
737
738    /// Handler for `debug_traceChain`
739    async fn debug_trace_chain(
740        &self,
741        _start_exclusive: BlockNumberOrTag,
742        _end_inclusive: BlockNumberOrTag,
743    ) -> RpcResult<Vec<BlockTraceResult>> {
744        Err(internal_rpc_err("unimplemented"))
745    }
746
747    /// Handler for `debug_traceBlock`
748    async fn debug_trace_block(
749        &self,
750        rlp_block: Bytes,
751        opts: Option<GethDebugTracingOptions>,
752    ) -> RpcResult<Vec<TraceResult>> {
753        let _permit = self.acquire_trace_permit().await;
754        Self::debug_trace_raw_block(self, rlp_block, opts.unwrap_or_default())
755            .await
756            .map_err(Into::into)
757    }
758
759    /// Handler for `debug_traceBlockByHash`
760    async fn debug_trace_block_by_hash(
761        &self,
762        block: B256,
763        opts: Option<GethDebugTracingOptions>,
764    ) -> RpcResult<Vec<TraceResult>> {
765        let _permit = self.acquire_trace_permit().await;
766        Self::debug_trace_block(self, block.into(), opts.unwrap_or_default())
767            .await
768            .map_err(Into::into)
769    }
770
771    /// Handler for `debug_traceBlockByNumber`
772    async fn debug_trace_block_by_number(
773        &self,
774        block: BlockNumberOrTag,
775        opts: Option<GethDebugTracingOptions>,
776    ) -> RpcResult<Vec<TraceResult>> {
777        let _permit = self.acquire_trace_permit().await;
778        Self::debug_trace_block(self, block.into(), opts.unwrap_or_default())
779            .await
780            .map_err(Into::into)
781    }
782
783    /// Handler for `debug_traceTransaction`
784    async fn debug_trace_transaction(
785        &self,
786        tx_hash: B256,
787        opts: Option<GethDebugTracingOptions>,
788    ) -> RpcResult<GethTrace> {
789        let _permit = self.acquire_trace_permit().await;
790        Self::debug_trace_transaction(self, tx_hash, opts.unwrap_or_default())
791            .await
792            .map_err(Into::into)
793    }
794
795    /// Handler for `debug_traceCall`
796    async fn debug_trace_call(
797        &self,
798        request: RpcTxReq<Eth::NetworkTypes>,
799        block_id: Option<BlockId>,
800        opts: Option<GethDebugTracingCallOptions>,
801    ) -> RpcResult<GethTrace> {
802        let _permit = self.acquire_trace_permit().await;
803        Self::debug_trace_call(self, request, block_id, opts.unwrap_or_default())
804            .await
805            .map_err(Into::into)
806    }
807
808    async fn debug_trace_call_many(
809        &self,
810        bundles: Vec<Bundle<RpcTxReq<Eth::NetworkTypes>>>,
811        state_context: Option<StateContext>,
812        opts: Option<GethDebugTracingCallOptions>,
813    ) -> RpcResult<Vec<Vec<GethTrace>>> {
814        let _permit = self.acquire_trace_permit().await;
815        Self::debug_trace_call_many(self, bundles, state_context, opts).await.map_err(Into::into)
816    }
817
818    /// Handler for `debug_executionWitness`
819    async fn debug_execution_witness(
820        &self,
821        block: BlockNumberOrTag,
822    ) -> RpcResult<ExecutionWitness> {
823        let _permit = self.acquire_trace_permit().await;
824        Self::debug_execution_witness(self, block).await.map_err(Into::into)
825    }
826
827    /// Handler for `debug_executionWitnessByBlockHash`
828    async fn debug_execution_witness_by_block_hash(
829        &self,
830        hash: B256,
831    ) -> RpcResult<ExecutionWitness> {
832        let _permit = self.acquire_trace_permit().await;
833        Self::debug_execution_witness_by_block_hash(self, hash).await.map_err(Into::into)
834    }
835
836    async fn debug_get_block_access_list(&self, _block_id: BlockId) -> RpcResult<BlockAccessList> {
837        Err(internal_rpc_err("unimplemented"))
838    }
839
840    async fn debug_backtrace_at(&self, _location: &str) -> RpcResult<()> {
841        Ok(())
842    }
843
844    async fn debug_account_range(
845        &self,
846        _block_number: BlockNumberOrTag,
847        _start: Bytes,
848        _max_results: u64,
849        _nocode: bool,
850        _nostorage: bool,
851        _incompletes: bool,
852    ) -> RpcResult<()> {
853        Ok(())
854    }
855
856    async fn debug_block_profile(&self, _file: String, _seconds: u64) -> RpcResult<()> {
857        Ok(())
858    }
859
860    async fn debug_chaindb_compact(&self) -> RpcResult<()> {
861        Ok(())
862    }
863
864    async fn debug_chain_config(&self) -> RpcResult<ChainConfig> {
865        Ok(self.provider().chain_spec().genesis().config.clone())
866    }
867
868    async fn debug_chaindb_property(&self, _property: String) -> RpcResult<()> {
869        Ok(())
870    }
871
872    async fn debug_code_by_hash(
873        &self,
874        hash: B256,
875        block_id: Option<BlockId>,
876    ) -> RpcResult<Option<Bytes>> {
877        Self::debug_code_by_hash(self, hash, block_id).await.map_err(Into::into)
878    }
879
880    async fn debug_cpu_profile(&self, _file: String, _seconds: u64) -> RpcResult<()> {
881        Ok(())
882    }
883
884    async fn debug_db_ancient(&self, _kind: String, _number: u64) -> RpcResult<()> {
885        Ok(())
886    }
887
888    async fn debug_db_ancients(&self) -> RpcResult<()> {
889        Ok(())
890    }
891
892    /// `debug_db_get` - database key lookup
893    ///
894    /// Currently supported:
895    /// * Contract bytecode associated with a code hash. The key format is: `<0x63><code_hash>`
896    ///     * Prefix byte: 0x63 (required)
897    ///     * Code hash: 32 bytes
898    ///   Must be provided as either:
899    ///     * Hex string: "0x63..." (66 hex characters after 0x)
900    ///     * Raw byte string: raw byte string (33 bytes)
901    ///   See Geth impl: <https://github.com/ethereum/go-ethereum/blob/737ffd1bf0cbee378d0111a5b17ae4724fb2216c/core/rawdb/schema.go#L120>
902    async fn debug_db_get(&self, key: String) -> RpcResult<Option<Bytes>> {
903        let key_bytes = if key.starts_with("0x") {
904            decode(&key).map_err(|_| EthApiError::InvalidParams("Invalid hex key".to_string()))?
905        } else {
906            key.into_bytes()
907        };
908
909        if key_bytes.len() != 33 {
910            return Err(EthApiError::InvalidParams(format!(
911                "Key must be 33 bytes, got {}",
912                key_bytes.len()
913            ))
914            .into());
915        }
916        if key_bytes[0] != 0x63 {
917            return Err(EthApiError::InvalidParams("Key prefix must be 0x63".to_string()).into());
918        }
919
920        let code_hash = B256::from_slice(&key_bytes[1..33]);
921
922        // No block ID is provided, so it defaults to the latest block
923        self.debug_code_by_hash(code_hash, None).await.map_err(Into::into)
924    }
925
926    async fn debug_dump_block(&self, _number: BlockId) -> RpcResult<()> {
927        Ok(())
928    }
929
930    async fn debug_free_os_memory(&self) -> RpcResult<()> {
931        Ok(())
932    }
933
934    async fn debug_freeze_client(&self, _node: String) -> RpcResult<()> {
935        Ok(())
936    }
937
938    async fn debug_gc_stats(&self) -> RpcResult<()> {
939        Ok(())
940    }
941
942    async fn debug_get_accessible_state(
943        &self,
944        _from: BlockNumberOrTag,
945        _to: BlockNumberOrTag,
946    ) -> RpcResult<()> {
947        Ok(())
948    }
949
950    async fn debug_get_modified_accounts_by_hash(
951        &self,
952        _start_hash: B256,
953        _end_hash: B256,
954    ) -> RpcResult<()> {
955        Ok(())
956    }
957
958    async fn debug_get_modified_accounts_by_number(
959        &self,
960        _start_number: u64,
961        _end_number: u64,
962    ) -> RpcResult<()> {
963        Ok(())
964    }
965
966    async fn debug_go_trace(&self, _file: String, _seconds: u64) -> RpcResult<()> {
967        Ok(())
968    }
969
970    async fn debug_intermediate_roots(
971        &self,
972        _block_hash: B256,
973        _opts: Option<GethDebugTracingCallOptions>,
974    ) -> RpcResult<()> {
975        Ok(())
976    }
977
978    async fn debug_mem_stats(&self) -> RpcResult<()> {
979        Ok(())
980    }
981
982    async fn debug_mutex_profile(&self, _file: String, _nsec: u64) -> RpcResult<()> {
983        Ok(())
984    }
985
986    async fn debug_preimage(&self, _hash: B256) -> RpcResult<()> {
987        Ok(())
988    }
989
990    async fn debug_print_block(&self, _number: u64) -> RpcResult<()> {
991        Ok(())
992    }
993
994    async fn debug_seed_hash(&self, _number: u64) -> RpcResult<B256> {
995        Ok(Default::default())
996    }
997
998    async fn debug_set_block_profile_rate(&self, _rate: u64) -> RpcResult<()> {
999        Ok(())
1000    }
1001
1002    async fn debug_set_gc_percent(&self, _v: i32) -> RpcResult<()> {
1003        Ok(())
1004    }
1005
1006    async fn debug_set_head(&self, _number: U64) -> RpcResult<()> {
1007        Ok(())
1008    }
1009
1010    async fn debug_set_mutex_profile_fraction(&self, _rate: i32) -> RpcResult<()> {
1011        Ok(())
1012    }
1013
1014    async fn debug_set_trie_flush_interval(&self, _interval: String) -> RpcResult<()> {
1015        Ok(())
1016    }
1017
1018    async fn debug_stacks(&self) -> RpcResult<()> {
1019        Ok(())
1020    }
1021
1022    async fn debug_standard_trace_bad_block_to_file(
1023        &self,
1024        _block: BlockNumberOrTag,
1025        _opts: Option<GethDebugTracingCallOptions>,
1026    ) -> RpcResult<()> {
1027        Ok(())
1028    }
1029
1030    async fn debug_standard_trace_block_to_file(
1031        &self,
1032        _block: BlockNumberOrTag,
1033        _opts: Option<GethDebugTracingCallOptions>,
1034    ) -> RpcResult<()> {
1035        Ok(())
1036    }
1037
1038    async fn debug_start_cpu_profile(&self, _file: String) -> RpcResult<()> {
1039        Ok(())
1040    }
1041
1042    async fn debug_start_go_trace(&self, _file: String) -> RpcResult<()> {
1043        Ok(())
1044    }
1045
1046    async fn debug_state_root_with_updates(
1047        &self,
1048        hashed_state: HashedPostState,
1049        block_id: Option<BlockId>,
1050    ) -> RpcResult<(B256, TrieUpdates)> {
1051        Self::debug_state_root_with_updates(self, hashed_state, block_id).await.map_err(Into::into)
1052    }
1053
1054    async fn debug_stop_cpu_profile(&self) -> RpcResult<()> {
1055        Ok(())
1056    }
1057
1058    async fn debug_stop_go_trace(&self) -> RpcResult<()> {
1059        Ok(())
1060    }
1061
1062    async fn debug_storage_range_at(
1063        &self,
1064        _block_hash: B256,
1065        _tx_idx: usize,
1066        _contract_address: Address,
1067        _key_start: B256,
1068        _max_result: u64,
1069    ) -> RpcResult<()> {
1070        Ok(())
1071    }
1072
1073    async fn debug_trace_bad_block(
1074        &self,
1075        block_hash: B256,
1076        opts: Option<GethDebugTracingCallOptions>,
1077    ) -> RpcResult<Vec<TraceResult>> {
1078        let _permit = self.acquire_trace_permit().await;
1079        let block = self
1080            .inner
1081            .bad_block_store
1082            .get(block_hash)
1083            .ok_or_else(|| internal_rpc_err("bad block not found in cache"))?;
1084
1085        let evm_env = self
1086            .eth_api()
1087            .evm_config()
1088            .evm_env(block.header())
1089            .map_err(RethError::other)
1090            .to_rpc_result()?;
1091
1092        let opts = opts.map(|o| o.tracing_options).unwrap_or_default();
1093        self.trace_block(block, evm_env, opts).await.map_err(Into::into)
1094    }
1095
1096    async fn debug_verbosity(&self, level: usize) -> RpcResult<()> {
1097        reth_tracing::set_log_verbosity(level).map_err(internal_rpc_err)
1098    }
1099
1100    async fn debug_vmodule(&self, pattern: String) -> RpcResult<()> {
1101        reth_tracing::set_log_vmodule(&pattern).map_err(internal_rpc_err)
1102    }
1103
1104    async fn debug_write_block_profile(&self, _file: String) -> RpcResult<()> {
1105        Ok(())
1106    }
1107
1108    async fn debug_write_mem_profile(&self, _file: String) -> RpcResult<()> {
1109        Ok(())
1110    }
1111
1112    async fn debug_write_mutex_profile(&self, _file: String) -> RpcResult<()> {
1113        Ok(())
1114    }
1115}
1116
1117impl<Eth: RpcNodeCore> std::fmt::Debug for DebugApi<Eth> {
1118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1119        f.debug_struct("DebugApi").finish_non_exhaustive()
1120    }
1121}
1122
1123impl<Eth: RpcNodeCore> Clone for DebugApi<Eth> {
1124    fn clone(&self) -> Self {
1125        Self { inner: Arc::clone(&self.inner) }
1126    }
1127}
1128
1129struct DebugApiInner<Eth: RpcNodeCore> {
1130    /// The implementation of `eth` API
1131    eth_api: Eth,
1132    // restrict the number of concurrent calls to blocking calls
1133    blocking_task_guard: BlockingTaskGuard,
1134    /// Cache for bad blocks.
1135    bad_block_store: BadBlockStore<BlockTy<Eth::Primitives>>,
1136}
1137
1138/// A bounded, deduplicating store of recently observed bad blocks.
1139#[derive(Clone, Debug)]
1140struct BadBlockStore<B: BlockTrait> {
1141    inner: Arc<RwLock<VecDeque<Arc<RecoveredBlock<B>>>>>,
1142    limit: usize,
1143}
1144
1145impl<B: BlockTrait> BadBlockStore<B> {
1146    /// Creates a new store with the given capacity.
1147    fn new(limit: usize) -> Self {
1148        Self { inner: Arc::new(RwLock::new(VecDeque::with_capacity(limit))), limit }
1149    }
1150
1151    /// Inserts a recovered block, keeping only the most recent `limit` entries and deduplicating
1152    /// by block hash.
1153    fn insert(&self, block: RecoveredBlock<B>) {
1154        let hash = block.hash();
1155        let mut guard = self.inner.write();
1156
1157        // skip if we already recorded this bad block , and keep original ordering
1158        if guard.iter().any(|b| b.hash() == hash) {
1159            return;
1160        }
1161        guard.push_back(Arc::new(block));
1162
1163        while guard.len() > self.limit {
1164            guard.pop_front();
1165        }
1166    }
1167
1168    /// Returns all cached bad blocks ordered from newest to oldest.
1169    fn all(&self) -> Vec<Arc<RecoveredBlock<B>>> {
1170        let guard = self.inner.read();
1171        guard.iter().rev().cloned().collect()
1172    }
1173
1174    /// Returns the bad block with the given hash, if cached.
1175    fn get(&self, hash: B256) -> Option<Arc<RecoveredBlock<B>>> {
1176        let guard = self.inner.read();
1177        guard.iter().find(|b| b.hash() == hash).cloned()
1178    }
1179}
1180
1181impl<B: BlockTrait> Default for BadBlockStore<B> {
1182    fn default() -> Self {
1183        Self::new(64)
1184    }
1185}