1use 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 PreStateFrame, TraceResult,
11};
12use alloy_transport::TransportResult;
13
14#[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 async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes>;
20
21 async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes>;
23
24 async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes>;
26
27 async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>>;
29
30 async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>>;
32
33 async fn debug_trace_chain(
36 &self,
37 start_exclusive: BlockNumberOrTag,
38 end_inclusive: BlockNumberOrTag,
39 ) -> TransportResult<Vec<BlockTraceResult>>;
40
41 async fn debug_trace_block(
50 &self,
51 rlp_block: &[u8],
52 trace_options: GethDebugTracingOptions,
53 ) -> TransportResult<Vec<TraceResult>>;
54
55 async fn debug_trace_transaction(
66 &self,
67 hash: TxHash,
68 trace_options: GethDebugTracingOptions,
69 ) -> TransportResult<GethTrace>;
70
71 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 async fn debug_trace_transaction_js(
100 &self,
101 hash: TxHash,
102 trace_options: GethDebugTracingOptions,
103 ) -> TransportResult<serde_json::Value>;
104
105 async fn debug_trace_transaction_call(
116 &self,
117 hash: TxHash,
118 trace_options: GethDebugTracingOptions,
119 ) -> TransportResult<CallFrame>;
120
121 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 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 async fn debug_trace_call_callframe(
168 &self,
169 tx: N::TransactionRequest,
170 block: BlockId,
171 trace_options: GethDebugTracingCallOptions,
172 ) -> TransportResult<CallFrame>;
173
174 async fn debug_trace_call_prestate(
185 &self,
186 tx: N::TransactionRequest,
187 block: BlockId,
188 trace_options: GethDebugTracingCallOptions,
189 ) -> TransportResult<PreStateFrame>;
190
191 async fn debug_trace_block_by_hash(
202 &self,
203 block: B256,
204 trace_options: GethDebugTracingOptions,
205 ) -> TransportResult<Vec<TraceResult>>;
206
207 async fn debug_trace_block_by_number(
215 &self,
216 block: BlockNumberOrTag,
217 trace_options: GethDebugTracingOptions,
218 ) -> TransportResult<Vec<TraceResult>>;
219
220 async fn debug_trace_call(
233 &self,
234 tx: N::TransactionRequest,
235 block: BlockId,
236 trace_options: GethDebugTracingCallOptions,
237 ) -> TransportResult<GethTrace>;
238
239 async fn debug_trace_call_many(
247 &self,
248 bundles: Vec<Bundle>,
249 state_context: StateContext,
250 trace_options: GethDebugTracingCallOptions,
251 ) -> TransportResult<Vec<Vec<GethTrace>>>;
252
253 async fn debug_trace_call_many_as<R>(
264 &self,
265 bundles: Vec<Bundle>,
266 state_context: StateContext,
267 trace_options: GethDebugTracingCallOptions,
268 ) -> TransportResult<Vec<Vec<R>>>
269 where
270 R: RpcRecv + serde::de::DeserializeOwned;
271
272 async fn debug_trace_call_many_js(
283 &self,
284 bundles: Vec<Bundle>,
285 state_context: StateContext,
286 trace_options: GethDebugTracingCallOptions,
287 ) -> TransportResult<Vec<Vec<serde_json::Value>>>;
288
289 async fn debug_trace_call_many_callframe(
300 &self,
301 bundles: Vec<Bundle>,
302 state_context: StateContext,
303 trace_options: GethDebugTracingCallOptions,
304 ) -> TransportResult<Vec<Vec<CallFrame>>>;
305
306 async fn debug_trace_call_many_prestate(
317 &self,
318 bundles: Vec<Bundle>,
319 state_context: StateContext,
320 trace_options: GethDebugTracingCallOptions,
321 ) -> TransportResult<Vec<Vec<PreStateFrame>>>;
322
323 async fn debug_execution_witness(
334 &self,
335 block: BlockNumberOrTag,
336 ) -> TransportResult<ExecutionWitness>;
337
338 async fn debug_code_by_hash(
346 &self,
347 hash: B256,
348 block: Option<BlockId>,
349 ) -> TransportResult<Option<Bytes>>;
350
351 async fn debug_db_get(&self, key: &str) -> TransportResult<Bytes>;
365}
366
367#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
368#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
369impl<N, P> DebugApi<N> for P
370where
371 N: Network,
372 P: Provider<N>,
373{
374 async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes> {
375 self.client().request("debug_getRawHeader", (block,)).await
376 }
377
378 async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes> {
379 self.client().request("debug_getRawBlock", (block,)).await
380 }
381
382 async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes> {
383 self.client().request("debug_getRawTransaction", (hash,)).await
384 }
385
386 async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>> {
387 self.client().request("debug_getRawReceipts", (block,)).await
388 }
389
390 async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>> {
391 self.client().request_noparams("debug_getBadBlocks").await
392 }
393
394 async fn debug_trace_chain(
395 &self,
396 start_exclusive: BlockNumberOrTag,
397 end_inclusive: BlockNumberOrTag,
398 ) -> TransportResult<Vec<BlockTraceResult>> {
399 self.client().request("debug_traceChain", (start_exclusive, end_inclusive)).await
400 }
401
402 async fn debug_trace_block(
403 &self,
404 rlp_block: &[u8],
405 trace_options: GethDebugTracingOptions,
406 ) -> TransportResult<Vec<TraceResult>> {
407 let rlp_block = hex::encode_prefixed(rlp_block);
408 self.client().request("debug_traceBlock", (rlp_block, trace_options)).await
409 }
410
411 async fn debug_trace_transaction(
412 &self,
413 hash: TxHash,
414 trace_options: GethDebugTracingOptions,
415 ) -> TransportResult<GethTrace> {
416 self.client().request("debug_traceTransaction", (hash, trace_options)).await
417 }
418
419 async fn debug_trace_transaction_as<R>(
420 &self,
421 hash: TxHash,
422 trace_options: GethDebugTracingOptions,
423 ) -> TransportResult<R>
424 where
425 R: RpcRecv,
426 {
427 self.client().request("debug_traceTransaction", (hash, trace_options)).await
428 }
429
430 async fn debug_trace_transaction_js(
431 &self,
432 hash: TxHash,
433 trace_options: GethDebugTracingOptions,
434 ) -> TransportResult<serde_json::Value> {
435 self.debug_trace_transaction_as::<serde_json::Value>(hash, trace_options).await
436 }
437
438 async fn debug_trace_transaction_call(
439 &self,
440 hash: TxHash,
441 trace_options: GethDebugTracingOptions,
442 ) -> TransportResult<CallFrame> {
443 self.debug_trace_transaction_as::<CallFrame>(hash, trace_options).await
444 }
445
446 async fn debug_trace_call_as<R>(
447 &self,
448 tx: N::TransactionRequest,
449 block: BlockId,
450 trace_options: GethDebugTracingCallOptions,
451 ) -> TransportResult<R>
452 where
453 R: RpcRecv,
454 {
455 self.client().request("debug_traceCall", (tx, block, trace_options)).await
456 }
457
458 async fn debug_trace_call_js(
459 &self,
460 tx: N::TransactionRequest,
461 block: BlockId,
462 trace_options: GethDebugTracingCallOptions,
463 ) -> TransportResult<serde_json::Value> {
464 self.debug_trace_call_as::<serde_json::Value>(tx, block, trace_options).await
465 }
466
467 async fn debug_trace_call_callframe(
468 &self,
469 tx: N::TransactionRequest,
470 block: BlockId,
471 trace_options: GethDebugTracingCallOptions,
472 ) -> TransportResult<CallFrame> {
473 self.debug_trace_call_as::<CallFrame>(tx, block, trace_options).await
474 }
475
476 async fn debug_trace_call_prestate(
477 &self,
478 tx: N::TransactionRequest,
479 block: BlockId,
480 trace_options: GethDebugTracingCallOptions,
481 ) -> TransportResult<PreStateFrame> {
482 self.debug_trace_call_as::<PreStateFrame>(tx, block, trace_options).await
483 }
484
485 async fn debug_trace_block_by_hash(
486 &self,
487 block: B256,
488 trace_options: GethDebugTracingOptions,
489 ) -> TransportResult<Vec<TraceResult>> {
490 self.client().request("debug_traceBlockByHash", (block, trace_options)).await
491 }
492
493 async fn debug_trace_block_by_number(
494 &self,
495 block: BlockNumberOrTag,
496 trace_options: GethDebugTracingOptions,
497 ) -> TransportResult<Vec<TraceResult>> {
498 self.client().request("debug_traceBlockByNumber", (block, trace_options)).await
499 }
500
501 async fn debug_trace_call(
502 &self,
503 tx: N::TransactionRequest,
504 block: BlockId,
505 trace_options: GethDebugTracingCallOptions,
506 ) -> TransportResult<GethTrace> {
507 self.client().request("debug_traceCall", (tx, block, trace_options)).await
508 }
509
510 async fn debug_trace_call_many(
511 &self,
512 bundles: Vec<Bundle>,
513 state_context: StateContext,
514 trace_options: GethDebugTracingCallOptions,
515 ) -> TransportResult<Vec<Vec<GethTrace>>> {
516 self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
517 }
518
519 async fn debug_trace_call_many_as<R>(
520 &self,
521 bundles: Vec<Bundle>,
522 state_context: StateContext,
523 trace_options: GethDebugTracingCallOptions,
524 ) -> TransportResult<Vec<Vec<R>>>
525 where
526 R: RpcRecv,
527 {
528 self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
529 }
530
531 async fn debug_trace_call_many_js(
532 &self,
533 bundles: Vec<Bundle>,
534 state_context: StateContext,
535 trace_options: GethDebugTracingCallOptions,
536 ) -> TransportResult<Vec<Vec<serde_json::Value>>> {
537 self.debug_trace_call_many_as::<serde_json::Value>(bundles, state_context, trace_options)
538 .await
539 }
540
541 async fn debug_trace_call_many_callframe(
542 &self,
543 bundles: Vec<Bundle>,
544 state_context: StateContext,
545 trace_options: GethDebugTracingCallOptions,
546 ) -> TransportResult<Vec<Vec<CallFrame>>> {
547 self.debug_trace_call_many_as::<CallFrame>(bundles, state_context, trace_options).await
548 }
549
550 async fn debug_trace_call_many_prestate(
551 &self,
552 bundles: Vec<Bundle>,
553 state_context: StateContext,
554 trace_options: GethDebugTracingCallOptions,
555 ) -> TransportResult<Vec<Vec<PreStateFrame>>> {
556 self.debug_trace_call_many_as::<PreStateFrame>(bundles, state_context, trace_options).await
557 }
558
559 async fn debug_execution_witness(
560 &self,
561 block: BlockNumberOrTag,
562 ) -> TransportResult<ExecutionWitness> {
563 self.client().request("debug_executionWitness", (block,)).await
564 }
565
566 async fn debug_code_by_hash(
567 &self,
568 hash: B256,
569 block: Option<BlockId>,
570 ) -> TransportResult<Option<Bytes>> {
571 self.client().request("debug_codeByHash", (hash, block)).await
572 }
573
574 async fn debug_db_get(&self, key: &str) -> TransportResult<Bytes> {
575 self.client().request("debug_dbGet", (key,)).await
576 }
577}
578
579#[cfg(test)]
580mod test {
581 use super::*;
582 use crate::{ext::test::async_ci_only, ProviderBuilder, WalletProvider};
583 use alloy_network::TransactionBuilder;
584 use alloy_node_bindings::{utils::run_with_tempdir, Geth, Reth};
585 use alloy_primitives::{address, U256};
586 use alloy_rpc_types_eth::TransactionRequest;
587
588 #[tokio::test]
589 async fn test_debug_trace_transaction() {
590 async_ci_only(|| async move {
591 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
592 let from = provider.default_signer_address();
593
594 let gas_price = provider.get_gas_price().await.unwrap();
595 let tx = TransactionRequest::default()
596 .from(from)
597 .to(address!("deadbeef00000000deadbeef00000000deadbeef"))
598 .value(U256::from(100))
599 .max_fee_per_gas(gas_price + 1)
600 .max_priority_fee_per_gas(gas_price + 1);
601 let pending = provider.send_transaction(tx).await.unwrap();
602 let receipt = pending.get_receipt().await.unwrap();
603
604 let hash = receipt.transaction_hash;
605 let trace_options = GethDebugTracingOptions::default();
606
607 let trace = provider.debug_trace_transaction(hash, trace_options).await.unwrap();
608
609 if let GethTrace::Default(trace) = trace {
610 assert_eq!(trace.gas, 21000)
611 }
612 })
613 .await;
614 }
615
616 #[tokio::test]
617 async fn test_debug_trace_call() {
618 async_ci_only(|| async move {
619 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
620 let from = provider.default_signer_address();
621 let gas_price = provider.get_gas_price().await.unwrap();
622 let tx = TransactionRequest::default()
623 .from(from)
624 .with_input("0xdeadbeef")
625 .max_fee_per_gas(gas_price + 1)
626 .max_priority_fee_per_gas(gas_price + 1);
627
628 let trace = provider
629 .debug_trace_call(
630 tx,
631 BlockNumberOrTag::Latest.into(),
632 GethDebugTracingCallOptions::default(),
633 )
634 .await
635 .unwrap();
636
637 if let GethTrace::Default(trace) = trace {
638 assert!(!trace.struct_logs.is_empty());
639 }
640 })
641 .await;
642 }
643
644 #[tokio::test]
645 async fn call_debug_get_raw_header() {
646 async_ci_only(|| async move {
647 run_with_tempdir("geth-test-", |temp_dir| async move {
648 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
649 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
650
651 let rlp_header = provider
652 .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest))
653 .await
654 .expect("debug_getRawHeader call should succeed");
655
656 assert!(!rlp_header.is_empty());
657 })
658 .await;
659 })
660 .await;
661 }
662
663 #[tokio::test]
664 async fn call_debug_get_raw_block() {
665 async_ci_only(|| async move {
666 run_with_tempdir("geth-test-", |temp_dir| async move {
667 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
668 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
669
670 let rlp_block = provider
671 .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest))
672 .await
673 .expect("debug_getRawBlock call should succeed");
674
675 assert!(!rlp_block.is_empty());
676 })
677 .await;
678 })
679 .await;
680 }
681
682 #[tokio::test]
683 async fn call_debug_get_raw_receipts() {
684 async_ci_only(|| async move {
685 run_with_tempdir("geth-test-", |temp_dir| async move {
686 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
687 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
688
689 let result = provider
690 .debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest))
691 .await;
692 assert!(result.is_ok());
693 })
694 .await;
695 })
696 .await;
697 }
698
699 #[tokio::test]
700 async fn call_debug_get_bad_blocks() {
701 async_ci_only(|| async move {
702 run_with_tempdir("geth-test-", |temp_dir| async move {
703 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
704 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
705
706 let result = provider.debug_get_bad_blocks().await;
707 assert!(result.is_ok());
708 })
709 .await;
710 })
711 .await;
712 }
713
714 #[tokio::test]
715 #[cfg_attr(windows, ignore = "no reth on windows")]
716 async fn debug_trace_call_many() {
717 async_ci_only(|| async move {
718 run_with_tempdir("reth-test-", |temp_dir| async move {
719 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
720 let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
721
722 let tx1 = TransactionRequest::default()
723 .with_from(address!("0000000000000000000000000000000000000123"))
724 .with_to(address!("0000000000000000000000000000000000000456"));
725
726 let tx2 = TransactionRequest::default()
727 .with_from(address!("0000000000000000000000000000000000000456"))
728 .with_to(address!("0000000000000000000000000000000000000789"));
729
730 let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }];
731 let state_context = StateContext::default();
732 let trace_options = GethDebugTracingCallOptions::default();
733 let result =
734 provider.debug_trace_call_many(bundles, state_context, trace_options).await;
735 assert!(result.is_ok());
736
737 let traces = result.unwrap();
738 assert_eq!(
739 serde_json::to_string_pretty(&traces).unwrap().trim(),
740 r#"
741[
742 [
743 {
744 "failed": false,
745 "gas": 21000,
746 "returnValue": "0x",
747 "structLogs": []
748 },
749 {
750 "failed": false,
751 "gas": 21000,
752 "returnValue": "0x",
753 "structLogs": []
754 }
755 ]
756]
757"#
758 .trim(),
759 );
760 })
761 .await;
762 })
763 .await;
764 }
765
766 #[tokio::test]
767 #[cfg_attr(windows, ignore = "no reth on windows")]
768 async fn test_debug_code_by_hash() {
769 use alloy_primitives::b256;
770
771 async_ci_only(|| async move {
772 run_with_tempdir("reth-test-", |temp_dir| async move {
773 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
774 let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
775
776 let empty_code_hash =
779 b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
780 let empty_code = provider.debug_code_by_hash(empty_code_hash, None).await.unwrap();
781 if let Some(code) = empty_code {
783 assert!(
784 code.is_empty() || code == Bytes::from_static(&[]),
785 "Empty code hash should return empty bytes"
786 );
787 }
788
789 let non_existent_hash =
791 b256!("0000000000000000000000000000000000000000000000000000000000000001");
792 let no_code = provider.debug_code_by_hash(non_existent_hash, None).await.unwrap();
793 assert!(no_code.is_none(), "Non-existent hash should return None");
794
795 let another_hash =
798 b256!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
799 let result = provider.debug_code_by_hash(another_hash, None).await;
800 assert!(result.is_ok(), "API call should not error even for random hashes");
801 })
802 .await;
803 })
804 .await;
805 }
806}