alloy_provider/ext/
debug.rs

1//! This module extends the Ethereum JSON-RPC provider with the Debug namespace's RPC methods.
2use crate::Provider;
3use alloy_json_rpc::RpcRecv;
4use alloy_network::Network;
5use alloy_primitives::{hex, Bytes, TxHash, B256};
6use alloy_rpc_types_debug::ExecutionWitness;
7use alloy_rpc_types_eth::{
8    BadBlock, BlockId, BlockNumberOrTag, Bundle, StateContext, TransactionRequest,
9};
10use alloy_rpc_types_trace::geth::{
11    BlockTraceResult, CallFrame, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
12    TraceResult,
13};
14use alloy_transport::TransportResult;
15
16/// Debug namespace rpc interface that gives access to several non-standard RPC methods.
17#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
18#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
19pub trait DebugApi<N>: Send + Sync {
20    /// Returns an RLP-encoded header.
21    async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes>;
22
23    /// Retrieves and returns the RLP encoded block by number, hash or tag.
24    async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes>;
25
26    /// Returns an EIP-2718 binary-encoded transaction.
27    async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes>;
28
29    /// Returns an array of EIP-2718 binary-encoded receipts.
30    async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>>;
31
32    /// Returns an array of recent bad blocks that the client has seen on the network.
33    async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>>;
34
35    /// Returns the structured logs created during the execution of EVM between two blocks
36    /// (excluding start) as a JSON object.
37    async fn debug_trace_chain(
38        &self,
39        start_exclusive: BlockNumberOrTag,
40        end_inclusive: BlockNumberOrTag,
41    ) -> TransportResult<Vec<BlockTraceResult>>;
42
43    /// The debug_traceBlock method will return a full stack trace of all invoked opcodes of all
44    /// transaction that were included in this block.
45    ///
46    /// This expects an RLP-encoded block.
47    ///
48    /// # Note
49    ///
50    /// The parent of this block must be present, or it will fail.
51    async fn debug_trace_block(
52        &self,
53        rlp_block: &[u8],
54        trace_options: GethDebugTracingOptions,
55    ) -> TransportResult<Vec<TraceResult>>;
56
57    /// Reruns the transaction specified by the hash and returns the trace.
58    ///
59    /// It will replay any prior transactions to achieve the same state the transaction was executed
60    /// in.
61    ///
62    /// [GethDebugTracingOptions] can be used to specify the trace options.
63    ///
64    /// # Note
65    ///
66    /// Not all nodes support this call.
67    async fn debug_trace_transaction(
68        &self,
69        hash: TxHash,
70        trace_options: GethDebugTracingOptions,
71    ) -> TransportResult<GethTrace>;
72
73    /// Reruns the transaction specified by the hash and returns the trace in a specified format.
74    ///
75    /// This method allows for the trace to be returned as a type that implements `RpcRecv` and
76    /// `serde::de::DeserializeOwned`.
77    ///
78    /// [GethDebugTracingOptions] can be used to specify the trace options.
79    ///
80    /// # Note
81    ///
82    /// Not all nodes support this call.
83    async fn debug_trace_transaction_as<R>(
84        &self,
85        hash: TxHash,
86        trace_options: GethDebugTracingOptions,
87    ) -> TransportResult<R>
88    where
89        R: RpcRecv + serde::de::DeserializeOwned;
90
91    /// Reruns the transaction specified by the hash and returns the trace as a JSON object.
92    ///
93    /// This method provides the trace in a JSON format, which can be useful for further processing
94    /// or inspection.
95    ///
96    /// [GethDebugTracingOptions] can be used to specify the trace options.
97    ///
98    /// # Note
99    ///
100    /// Not all nodes support this call.
101    async fn debug_trace_transaction_js(
102        &self,
103        hash: TxHash,
104        trace_options: GethDebugTracingOptions,
105    ) -> TransportResult<serde_json::Value>;
106
107    /// Reruns the transaction specified by the hash and returns the trace as a call frame.
108    ///
109    /// This method provides the trace in the form of a `CallFrame`, which can be useful for
110    /// analyzing the call stack and execution details.
111    ///
112    /// [GethDebugTracingOptions] can be used to specify the trace options.
113    ///
114    /// # Note
115    ///
116    /// Not all nodes support this call.
117    async fn debug_trace_transaction_call(
118        &self,
119        hash: TxHash,
120        trace_options: GethDebugTracingOptions,
121    ) -> TransportResult<CallFrame>;
122
123    /// Reruns the transaction specified by the hash and returns the trace in a specified format.
124    ///
125    /// This method allows for the trace to be returned as a type that implements `RpcRecv` and
126    /// `serde::de::DeserializeOwned`.
127    ///
128    /// [GethDebugTracingOptions] can be used to specify the trace options.
129    ///
130    /// # Note
131    ///
132    /// Not all nodes support this call.
133    async fn debug_trace_call_as<R>(
134        &self,
135        tx: TransactionRequest,
136        block: BlockId,
137        trace_options: GethDebugTracingCallOptions,
138    ) -> TransportResult<R>
139    where
140        R: RpcRecv + serde::de::DeserializeOwned;
141
142    /// Reruns the transaction specified by the hash and returns the trace as a JSON object.
143    ///
144    /// This method provides the trace in a JSON format, which can be useful for further processing
145    /// or inspection.
146    ///
147    /// [GethDebugTracingOptions] can be used to specify the trace options.
148    ///
149    /// # Note
150    ///
151    /// Not all nodes support this call.
152    async fn debug_trace_call_js(
153        &self,
154        tx: TransactionRequest,
155        block: BlockId,
156        trace_options: GethDebugTracingCallOptions,
157    ) -> TransportResult<serde_json::Value>;
158
159    /// Reruns the transaction specified by the hash and returns the trace as a call frame.
160    ///
161    /// This method provides the trace in the form of a `CallFrame`, which can be useful for
162    /// analyzing the call stack and execution details.
163    ///
164    /// [GethDebugTracingOptions] can be used to specify the trace options.
165    ///
166    /// # Note
167    ///
168    /// Not all nodes support this call.
169    async fn debug_trace_call_callframe(
170        &self,
171        tx: TransactionRequest,
172        block: BlockId,
173        trace_options: GethDebugTracingCallOptions,
174    ) -> TransportResult<CallFrame>;
175
176    /// Return a full stack trace of all invoked opcodes of all transaction that were included in
177    /// this block.
178    ///
179    /// The parent of the block must be present or it will fail.
180    ///
181    /// [GethDebugTracingOptions] can be used to specify the trace options.
182    ///
183    /// # Note
184    ///
185    /// Not all nodes support this call.
186    async fn debug_trace_block_by_hash(
187        &self,
188        block: B256,
189        trace_options: GethDebugTracingOptions,
190    ) -> TransportResult<Vec<TraceResult>>;
191
192    /// Same as `debug_trace_block_by_hash` but block is specified by number.
193    ///
194    /// [GethDebugTracingOptions] can be used to specify the trace options.
195    ///
196    /// # Note
197    ///
198    /// Not all nodes support this call.
199    async fn debug_trace_block_by_number(
200        &self,
201        block: BlockNumberOrTag,
202        trace_options: GethDebugTracingOptions,
203    ) -> TransportResult<Vec<TraceResult>>;
204
205    /// Executes the given transaction without publishing it like `eth_call` and returns the trace
206    /// of the execution.
207    ///
208    /// The transaction will be executed in the context of the given block number or tag.
209    /// The state its run on is the state of the previous block.
210    ///
211    /// [GethDebugTracingOptions] can be used to specify the trace options.
212    ///
213    /// # Note
214    ///
215    ///
216    /// Not all nodes support this call.
217    async fn debug_trace_call(
218        &self,
219        tx: TransactionRequest,
220        block: BlockId,
221        trace_options: GethDebugTracingCallOptions,
222    ) -> TransportResult<GethTrace>;
223
224    /// Same as `debug_trace_call` but it used to run and trace multiple transactions at once.
225    ///
226    /// [GethDebugTracingOptions] can be used to specify the trace options.
227    ///
228    /// # Note
229    ///
230    /// Not all nodes support this call.
231    async fn debug_trace_call_many(
232        &self,
233        bundles: Vec<Bundle>,
234        state_context: StateContext,
235        trace_options: GethDebugTracingCallOptions,
236    ) -> TransportResult<Vec<GethTrace>>;
237
238    /// The `debug_executionWitness` method allows for re-execution of a block with the purpose of
239    /// generating an execution witness. The witness comprises of a map of all hashed trie nodes to
240    /// their preimages that were required during the execution of the block, including during
241    /// state root recomputation.
242    ///
243    /// The first argument is the block number or block hash.
244    ///
245    /// # Note
246    ///
247    /// Not all nodes support this call.
248    async fn debug_execution_witness(
249        &self,
250        block: BlockNumberOrTag,
251    ) -> TransportResult<ExecutionWitness>;
252
253    /// The `debug_codeByHash` method returns the code associated with a given hash at the specified
254    /// block. If no code is found, it returns None. If no block is provided, it defaults to the
255    /// latest block.
256    ///
257    /// # Note
258    ///
259    /// Not all nodes support this call.
260    async fn debug_code_by_hash(
261        &self,
262        hash: B256,
263        block: Option<BlockId>,
264    ) -> TransportResult<Option<Bytes>>;
265}
266
267#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
268#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
269impl<N, P> DebugApi<N> for P
270where
271    N: Network,
272    P: Provider<N>,
273{
274    async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes> {
275        self.client().request("debug_getRawHeader", (block,)).await
276    }
277
278    async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes> {
279        self.client().request("debug_getRawBlock", (block,)).await
280    }
281
282    async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes> {
283        self.client().request("debug_getRawTransaction", (hash,)).await
284    }
285
286    async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>> {
287        self.client().request("debug_getRawReceipts", (block,)).await
288    }
289
290    async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>> {
291        self.client().request_noparams("debug_getBadBlocks").await
292    }
293
294    async fn debug_trace_chain(
295        &self,
296        start_exclusive: BlockNumberOrTag,
297        end_inclusive: BlockNumberOrTag,
298    ) -> TransportResult<Vec<BlockTraceResult>> {
299        self.client().request("debug_traceChain", (start_exclusive, end_inclusive)).await
300    }
301
302    async fn debug_trace_block(
303        &self,
304        rlp_block: &[u8],
305        trace_options: GethDebugTracingOptions,
306    ) -> TransportResult<Vec<TraceResult>> {
307        let rlp_block = hex::encode_prefixed(rlp_block);
308        self.client().request("debug_traceBlock", (rlp_block, trace_options)).await
309    }
310
311    async fn debug_trace_transaction(
312        &self,
313        hash: TxHash,
314        trace_options: GethDebugTracingOptions,
315    ) -> TransportResult<GethTrace> {
316        self.client().request("debug_traceTransaction", (hash, trace_options)).await
317    }
318
319    async fn debug_trace_transaction_as<R>(
320        &self,
321        hash: TxHash,
322        trace_options: GethDebugTracingOptions,
323    ) -> TransportResult<R>
324    where
325        R: RpcRecv,
326    {
327        self.client().request("debug_traceTransaction", (hash, trace_options)).await
328    }
329
330    async fn debug_trace_transaction_js(
331        &self,
332        hash: TxHash,
333        trace_options: GethDebugTracingOptions,
334    ) -> TransportResult<serde_json::Value> {
335        self.debug_trace_transaction_as::<serde_json::Value>(hash, trace_options).await
336    }
337
338    async fn debug_trace_transaction_call(
339        &self,
340        hash: TxHash,
341        trace_options: GethDebugTracingOptions,
342    ) -> TransportResult<CallFrame> {
343        self.debug_trace_transaction_as::<CallFrame>(hash, trace_options).await
344    }
345
346    async fn debug_trace_call_as<R>(
347        &self,
348        tx: TransactionRequest,
349        block: BlockId,
350        trace_options: GethDebugTracingCallOptions,
351    ) -> TransportResult<R>
352    where
353        R: RpcRecv,
354    {
355        self.client().request("debug_traceCall", (tx, block, trace_options)).await
356    }
357
358    async fn debug_trace_call_js(
359        &self,
360        tx: TransactionRequest,
361        block: BlockId,
362        trace_options: GethDebugTracingCallOptions,
363    ) -> TransportResult<serde_json::Value> {
364        self.debug_trace_call_as::<serde_json::Value>(tx, block, trace_options).await
365    }
366
367    async fn debug_trace_call_callframe(
368        &self,
369        tx: TransactionRequest,
370        block: BlockId,
371        trace_options: GethDebugTracingCallOptions,
372    ) -> TransportResult<CallFrame> {
373        self.debug_trace_call_as::<CallFrame>(tx, block, trace_options).await
374    }
375
376    async fn debug_trace_block_by_hash(
377        &self,
378        block: B256,
379        trace_options: GethDebugTracingOptions,
380    ) -> TransportResult<Vec<TraceResult>> {
381        self.client().request("debug_traceBlockByHash", (block, trace_options)).await
382    }
383
384    async fn debug_trace_block_by_number(
385        &self,
386        block: BlockNumberOrTag,
387        trace_options: GethDebugTracingOptions,
388    ) -> TransportResult<Vec<TraceResult>> {
389        self.client().request("debug_traceBlockByNumber", (block, trace_options)).await
390    }
391
392    async fn debug_trace_call(
393        &self,
394        tx: TransactionRequest,
395        block: BlockId,
396        trace_options: GethDebugTracingCallOptions,
397    ) -> TransportResult<GethTrace> {
398        self.client().request("debug_traceCall", (tx, block, trace_options)).await
399    }
400
401    async fn debug_trace_call_many(
402        &self,
403        bundles: Vec<Bundle>,
404        state_context: StateContext,
405        trace_options: GethDebugTracingCallOptions,
406    ) -> TransportResult<Vec<GethTrace>> {
407        self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
408    }
409
410    async fn debug_execution_witness(
411        &self,
412        block: BlockNumberOrTag,
413    ) -> TransportResult<ExecutionWitness> {
414        self.client().request("debug_executionWitness", block).await
415    }
416
417    async fn debug_code_by_hash(
418        &self,
419        hash: B256,
420        block: Option<BlockId>,
421    ) -> TransportResult<Option<Bytes>> {
422        self.client().request("debug_codeByHash", (hash, block)).await
423    }
424}
425
426#[cfg(test)]
427mod test {
428    use super::*;
429    use crate::{ext::test::async_ci_only, ProviderBuilder, WalletProvider};
430    use alloy_network::TransactionBuilder;
431    use alloy_node_bindings::{utils::run_with_tempdir, Geth, Reth};
432    use alloy_primitives::{address, U256};
433
434    #[tokio::test]
435    async fn test_debug_trace_transaction() {
436        async_ci_only(|| async move {
437            let provider = ProviderBuilder::new().on_anvil_with_wallet();
438            let from = provider.default_signer_address();
439
440            let gas_price = provider.get_gas_price().await.unwrap();
441            let tx = TransactionRequest::default()
442                .from(from)
443                .to(address!("deadbeef00000000deadbeef00000000deadbeef"))
444                .value(U256::from(100))
445                .max_fee_per_gas(gas_price + 1)
446                .max_priority_fee_per_gas(gas_price + 1);
447            let pending = provider.send_transaction(tx).await.unwrap();
448            let receipt = pending.get_receipt().await.unwrap();
449
450            let hash = receipt.transaction_hash;
451            let trace_options = GethDebugTracingOptions::default();
452
453            let trace = provider.debug_trace_transaction(hash, trace_options).await.unwrap();
454
455            if let GethTrace::Default(trace) = trace {
456                assert_eq!(trace.gas, 21000)
457            }
458        })
459        .await;
460    }
461
462    #[tokio::test]
463    async fn test_debug_trace_call() {
464        async_ci_only(|| async move {
465            let provider = ProviderBuilder::new().on_anvil_with_wallet();
466            let from = provider.default_signer_address();
467            let gas_price = provider.get_gas_price().await.unwrap();
468            let tx = TransactionRequest::default()
469                .from(from)
470                .with_input("0xdeadbeef")
471                .max_fee_per_gas(gas_price + 1)
472                .max_priority_fee_per_gas(gas_price + 1);
473
474            let trace = provider
475                .debug_trace_call(
476                    tx,
477                    BlockNumberOrTag::Latest.into(),
478                    GethDebugTracingCallOptions::default(),
479                )
480                .await
481                .unwrap();
482
483            if let GethTrace::Default(trace) = trace {
484                assert!(!trace.struct_logs.is_empty());
485            }
486        })
487        .await;
488    }
489
490    #[tokio::test]
491    async fn call_debug_get_raw_header() {
492        async_ci_only(|| async move {
493            run_with_tempdir("geth-test-", |temp_dir| async move {
494                let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
495                let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
496
497                let rlp_header = provider
498                    .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest))
499                    .await
500                    .expect("debug_getRawHeader call should succeed");
501
502                assert!(!rlp_header.is_empty());
503            })
504            .await;
505        })
506        .await;
507    }
508
509    #[tokio::test]
510    async fn call_debug_get_raw_block() {
511        async_ci_only(|| async move {
512            run_with_tempdir("geth-test-", |temp_dir| async move {
513                let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
514                let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
515
516                let rlp_block = provider
517                    .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest))
518                    .await
519                    .expect("debug_getRawBlock call should succeed");
520
521                assert!(!rlp_block.is_empty());
522            })
523            .await;
524        })
525        .await;
526    }
527
528    #[tokio::test]
529    async fn call_debug_get_raw_receipts() {
530        async_ci_only(|| async move {
531            run_with_tempdir("geth-test-", |temp_dir| async move {
532                let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
533                let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
534
535                let result = provider
536                    .debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest))
537                    .await;
538                assert!(result.is_ok());
539            })
540            .await;
541        })
542        .await;
543    }
544
545    #[tokio::test]
546    async fn call_debug_get_bad_blocks() {
547        async_ci_only(|| async move {
548            run_with_tempdir("geth-test-", |temp_dir| async move {
549                let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
550                let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
551
552                let result = provider.debug_get_bad_blocks().await;
553                assert!(result.is_ok());
554            })
555            .await;
556        })
557        .await;
558    }
559
560    #[tokio::test]
561    #[cfg_attr(windows, ignore = "no reth on windows")]
562    async fn debug_trace_call_many() {
563        async_ci_only(|| async move {
564            run_with_tempdir("reth-test-", |temp_dir| async move {
565                let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
566                let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
567
568                let tx1 = TransactionRequest::default()
569                    .with_from(address!("0000000000000000000000000000000000000123"))
570                    .with_to(address!("0000000000000000000000000000000000000456"));
571
572                let tx2 = TransactionRequest::default()
573                    .with_from(address!("0000000000000000000000000000000000000456"))
574                    .with_to(address!("0000000000000000000000000000000000000789"));
575
576                let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }];
577                let state_context = StateContext::default();
578                let trace_options = GethDebugTracingCallOptions::default();
579                let result =
580                    provider.debug_trace_call_many(bundles, state_context, trace_options).await;
581                assert!(result.is_ok());
582
583                let traces = result.unwrap();
584                assert_eq!(
585                    serde_json::to_string_pretty(&traces).unwrap().trim(),
586                    r#"
587[
588  [
589    {
590      "failed": false,
591      "gas": 21000,
592      "returnValue": "",
593      "structLogs": []
594    },
595    {
596      "failed": false,
597      "gas": 21000,
598      "returnValue": "",
599      "structLogs": []
600    }
601  ]
602]
603"#
604                    .trim(),
605                );
606            })
607            .await;
608        })
609        .await;
610    }
611
612    // TODO: Enable for next reth release > v1.2.0
613    /*
614    #[tokio::test]
615    #[cfg_attr(windows, ignore = "no reth on windows")]
616    async fn test_debug_code_by_hash() {
617        async_ci_only(|| async move {
618            run_with_tempdir("reth-test-", |temp_dir| async move {
619                let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
620                let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
621
622                // Contract (mainnet): 0x4e59b44847b379578588920ca78fbf26c0b4956c
623                let code = provider.debug_code_by_hash(
624                    b256!("2fa86add0aed31f33a762c9d88e807c475bd51d0f52bd0955754b2608f7e4989"),
625                    None
626                ).await.unwrap().unwrap();
627                assert_eq!(code,
628                           Bytes::from_static(&hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\
629                           e03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3")));
630            }).await;
631        }).await;
632    }
633    */
634}