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