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 #[cfg(feature = "pubsub")]
43 fn debug_subscribe_trace_chain(
44 &self,
45 start_exclusive: BlockNumberOrTag,
46 end_inclusive: BlockNumberOrTag,
47 trace_options: Option<GethDebugTracingOptions>,
48 ) -> crate::GetSubscription<
49 (&'static str, BlockNumberOrTag, BlockNumberOrTag, Option<GethDebugTracingOptions>),
50 alloy_rpc_types_trace::geth::ChainBlockTraceResult,
51 >;
52
53 async fn debug_trace_block(
62 &self,
63 rlp_block: &[u8],
64 trace_options: GethDebugTracingOptions,
65 ) -> TransportResult<Vec<TraceResult>>;
66
67 async fn debug_trace_transaction(
78 &self,
79 hash: TxHash,
80 trace_options: GethDebugTracingOptions,
81 ) -> TransportResult<GethTrace>;
82
83 async fn debug_trace_transaction_as<R>(
94 &self,
95 hash: TxHash,
96 trace_options: GethDebugTracingOptions,
97 ) -> TransportResult<R>
98 where
99 R: RpcRecv + serde::de::DeserializeOwned;
100
101 async fn debug_trace_transaction_js(
112 &self,
113 hash: TxHash,
114 trace_options: GethDebugTracingOptions,
115 ) -> TransportResult<serde_json::Value>;
116
117 async fn debug_trace_transaction_call(
128 &self,
129 hash: TxHash,
130 trace_options: GethDebugTracingOptions,
131 ) -> TransportResult<CallFrame>;
132
133 async fn debug_trace_call_as<R>(
144 &self,
145 tx: N::TransactionRequest,
146 block: BlockId,
147 trace_options: GethDebugTracingCallOptions,
148 ) -> TransportResult<R>
149 where
150 R: RpcRecv + serde::de::DeserializeOwned;
151
152 async fn debug_trace_call_js(
163 &self,
164 tx: N::TransactionRequest,
165 block: BlockId,
166 trace_options: GethDebugTracingCallOptions,
167 ) -> TransportResult<serde_json::Value>;
168
169 async fn debug_trace_call_callframe(
180 &self,
181 tx: N::TransactionRequest,
182 block: BlockId,
183 trace_options: GethDebugTracingCallOptions,
184 ) -> TransportResult<CallFrame>;
185
186 async fn debug_trace_call_prestate(
197 &self,
198 tx: N::TransactionRequest,
199 block: BlockId,
200 trace_options: GethDebugTracingCallOptions,
201 ) -> TransportResult<PreStateFrame>;
202
203 async fn debug_trace_block_by_hash(
214 &self,
215 block: B256,
216 trace_options: GethDebugTracingOptions,
217 ) -> TransportResult<Vec<TraceResult>>;
218
219 async fn debug_trace_block_by_number(
227 &self,
228 block: BlockNumberOrTag,
229 trace_options: GethDebugTracingOptions,
230 ) -> TransportResult<Vec<TraceResult>>;
231
232 async fn debug_trace_call(
245 &self,
246 tx: N::TransactionRequest,
247 block: BlockId,
248 trace_options: GethDebugTracingCallOptions,
249 ) -> TransportResult<GethTrace>;
250
251 async fn debug_trace_call_many(
259 &self,
260 bundles: Vec<Bundle>,
261 state_context: StateContext,
262 trace_options: GethDebugTracingCallOptions,
263 ) -> TransportResult<Vec<Vec<GethTrace>>>;
264
265 async fn debug_trace_call_many_as<R>(
276 &self,
277 bundles: Vec<Bundle>,
278 state_context: StateContext,
279 trace_options: GethDebugTracingCallOptions,
280 ) -> TransportResult<Vec<Vec<R>>>
281 where
282 R: RpcRecv + serde::de::DeserializeOwned;
283
284 async fn debug_trace_call_many_js(
295 &self,
296 bundles: Vec<Bundle>,
297 state_context: StateContext,
298 trace_options: GethDebugTracingCallOptions,
299 ) -> TransportResult<Vec<Vec<serde_json::Value>>>;
300
301 async fn debug_trace_call_many_callframe(
312 &self,
313 bundles: Vec<Bundle>,
314 state_context: StateContext,
315 trace_options: GethDebugTracingCallOptions,
316 ) -> TransportResult<Vec<Vec<CallFrame>>>;
317
318 async fn debug_trace_call_many_prestate(
329 &self,
330 bundles: Vec<Bundle>,
331 state_context: StateContext,
332 trace_options: GethDebugTracingCallOptions,
333 ) -> TransportResult<Vec<Vec<PreStateFrame>>>;
334
335 async fn debug_execution_witness(
347 &self,
348 block: BlockNumberOrTag,
349 ) -> TransportResult<ExecutionWitness>;
350
351 async fn debug_code_by_hash(
359 &self,
360 hash: B256,
361 block: Option<BlockId>,
362 ) -> TransportResult<Option<Bytes>>;
363
364 async fn debug_db_get(&self, key: &str) -> TransportResult<Bytes>;
378}
379
380#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
381#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
382impl<N, P> DebugApi<N> for P
383where
384 N: Network,
385 P: Provider<N>,
386{
387 async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes> {
388 self.client().request("debug_getRawHeader", (block,)).await
389 }
390
391 async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes> {
392 self.client().request("debug_getRawBlock", (block,)).await
393 }
394
395 async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes> {
396 self.client().request("debug_getRawTransaction", (hash,)).await
397 }
398
399 async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>> {
400 self.client().request("debug_getRawReceipts", (block,)).await
401 }
402
403 async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>> {
404 self.client().request_noparams("debug_getBadBlocks").await
405 }
406
407 async fn debug_trace_chain(
408 &self,
409 start_exclusive: BlockNumberOrTag,
410 end_inclusive: BlockNumberOrTag,
411 ) -> TransportResult<Vec<BlockTraceResult>> {
412 self.client().request("debug_traceChain", (start_exclusive, end_inclusive)).await
413 }
414
415 #[cfg(feature = "pubsub")]
416 fn debug_subscribe_trace_chain(
417 &self,
418 start_exclusive: BlockNumberOrTag,
419 end_inclusive: BlockNumberOrTag,
420 trace_options: Option<GethDebugTracingOptions>,
421 ) -> crate::GetSubscription<
422 (&'static str, BlockNumberOrTag, BlockNumberOrTag, Option<GethDebugTracingOptions>),
423 alloy_rpc_types_trace::geth::ChainBlockTraceResult,
424 > {
425 let mut call = self.client().request(
426 "debug_subscribe",
427 ("traceChain", start_exclusive, end_inclusive, trace_options),
428 );
429 call.set_is_subscription();
430 crate::GetSubscription::new(self.weak_client(), call)
431 }
432
433 async fn debug_trace_block(
434 &self,
435 rlp_block: &[u8],
436 trace_options: GethDebugTracingOptions,
437 ) -> TransportResult<Vec<TraceResult>> {
438 let rlp_block = hex::encode_prefixed(rlp_block);
439 self.client().request("debug_traceBlock", (rlp_block, trace_options)).await
440 }
441
442 async fn debug_trace_transaction(
443 &self,
444 hash: TxHash,
445 trace_options: GethDebugTracingOptions,
446 ) -> TransportResult<GethTrace> {
447 self.client().request("debug_traceTransaction", (hash, trace_options)).await
448 }
449
450 async fn debug_trace_transaction_as<R>(
451 &self,
452 hash: TxHash,
453 trace_options: GethDebugTracingOptions,
454 ) -> TransportResult<R>
455 where
456 R: RpcRecv,
457 {
458 self.client().request("debug_traceTransaction", (hash, trace_options)).await
459 }
460
461 async fn debug_trace_transaction_js(
462 &self,
463 hash: TxHash,
464 trace_options: GethDebugTracingOptions,
465 ) -> TransportResult<serde_json::Value> {
466 self.debug_trace_transaction_as::<serde_json::Value>(hash, trace_options).await
467 }
468
469 async fn debug_trace_transaction_call(
470 &self,
471 hash: TxHash,
472 trace_options: GethDebugTracingOptions,
473 ) -> TransportResult<CallFrame> {
474 self.debug_trace_transaction_as::<CallFrame>(hash, trace_options).await
475 }
476
477 async fn debug_trace_call_as<R>(
478 &self,
479 tx: N::TransactionRequest,
480 block: BlockId,
481 trace_options: GethDebugTracingCallOptions,
482 ) -> TransportResult<R>
483 where
484 R: RpcRecv,
485 {
486 self.client().request("debug_traceCall", (tx, block, trace_options)).await
487 }
488
489 async fn debug_trace_call_js(
490 &self,
491 tx: N::TransactionRequest,
492 block: BlockId,
493 trace_options: GethDebugTracingCallOptions,
494 ) -> TransportResult<serde_json::Value> {
495 self.debug_trace_call_as::<serde_json::Value>(tx, block, trace_options).await
496 }
497
498 async fn debug_trace_call_callframe(
499 &self,
500 tx: N::TransactionRequest,
501 block: BlockId,
502 trace_options: GethDebugTracingCallOptions,
503 ) -> TransportResult<CallFrame> {
504 self.debug_trace_call_as::<CallFrame>(tx, block, trace_options).await
505 }
506
507 async fn debug_trace_call_prestate(
508 &self,
509 tx: N::TransactionRequest,
510 block: BlockId,
511 trace_options: GethDebugTracingCallOptions,
512 ) -> TransportResult<PreStateFrame> {
513 self.debug_trace_call_as::<PreStateFrame>(tx, block, trace_options).await
514 }
515
516 async fn debug_trace_block_by_hash(
517 &self,
518 block: B256,
519 trace_options: GethDebugTracingOptions,
520 ) -> TransportResult<Vec<TraceResult>> {
521 self.client().request("debug_traceBlockByHash", (block, trace_options)).await
522 }
523
524 async fn debug_trace_block_by_number(
525 &self,
526 block: BlockNumberOrTag,
527 trace_options: GethDebugTracingOptions,
528 ) -> TransportResult<Vec<TraceResult>> {
529 self.client().request("debug_traceBlockByNumber", (block, trace_options)).await
530 }
531
532 async fn debug_trace_call(
533 &self,
534 tx: N::TransactionRequest,
535 block: BlockId,
536 trace_options: GethDebugTracingCallOptions,
537 ) -> TransportResult<GethTrace> {
538 self.client().request("debug_traceCall", (tx, block, trace_options)).await
539 }
540
541 async fn debug_trace_call_many(
542 &self,
543 bundles: Vec<Bundle>,
544 state_context: StateContext,
545 trace_options: GethDebugTracingCallOptions,
546 ) -> TransportResult<Vec<Vec<GethTrace>>> {
547 self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
548 }
549
550 async fn debug_trace_call_many_as<R>(
551 &self,
552 bundles: Vec<Bundle>,
553 state_context: StateContext,
554 trace_options: GethDebugTracingCallOptions,
555 ) -> TransportResult<Vec<Vec<R>>>
556 where
557 R: RpcRecv,
558 {
559 self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
560 }
561
562 async fn debug_trace_call_many_js(
563 &self,
564 bundles: Vec<Bundle>,
565 state_context: StateContext,
566 trace_options: GethDebugTracingCallOptions,
567 ) -> TransportResult<Vec<Vec<serde_json::Value>>> {
568 self.debug_trace_call_many_as::<serde_json::Value>(bundles, state_context, trace_options)
569 .await
570 }
571
572 async fn debug_trace_call_many_callframe(
573 &self,
574 bundles: Vec<Bundle>,
575 state_context: StateContext,
576 trace_options: GethDebugTracingCallOptions,
577 ) -> TransportResult<Vec<Vec<CallFrame>>> {
578 self.debug_trace_call_many_as::<CallFrame>(bundles, state_context, trace_options).await
579 }
580
581 async fn debug_trace_call_many_prestate(
582 &self,
583 bundles: Vec<Bundle>,
584 state_context: StateContext,
585 trace_options: GethDebugTracingCallOptions,
586 ) -> TransportResult<Vec<Vec<PreStateFrame>>> {
587 self.debug_trace_call_many_as::<PreStateFrame>(bundles, state_context, trace_options).await
588 }
589
590 async fn debug_execution_witness(
591 &self,
592 block: BlockNumberOrTag,
593 ) -> TransportResult<ExecutionWitness> {
594 self.client().request("debug_executionWitness", (block,)).await
595 }
596
597 async fn debug_code_by_hash(
598 &self,
599 hash: B256,
600 block: Option<BlockId>,
601 ) -> TransportResult<Option<Bytes>> {
602 self.client().request("debug_codeByHash", (hash, block)).await
603 }
604
605 async fn debug_db_get(&self, key: &str) -> TransportResult<Bytes> {
606 self.client().request("debug_dbGet", (key,)).await
607 }
608}
609
610#[cfg(test)]
611mod test {
612 use super::*;
613 use crate::{ext::test::async_ci_only, ProviderBuilder, WalletProvider};
614 use alloy_network::TransactionBuilder;
615 use alloy_node_bindings::{utils::run_with_tempdir, Geth, Reth};
616 use alloy_primitives::{address, U256};
617 use alloy_rpc_types_eth::TransactionRequest;
618
619 #[tokio::test]
620 async fn test_debug_trace_transaction() {
621 async_ci_only(|| async move {
622 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
623 let from = provider.default_signer_address();
624
625 let gas_price = provider.get_gas_price().await.unwrap();
626 let tx = TransactionRequest::default()
627 .from(from)
628 .to(address!("deadbeef00000000deadbeef00000000deadbeef"))
629 .value(U256::from(100))
630 .max_fee_per_gas(gas_price + 1)
631 .max_priority_fee_per_gas(gas_price + 1);
632 let pending = provider.send_transaction(tx).await.unwrap();
633 let receipt = pending.get_receipt().await.unwrap();
634
635 let hash = receipt.transaction_hash;
636 let trace_options = GethDebugTracingOptions::default();
637
638 let trace = provider.debug_trace_transaction(hash, trace_options).await.unwrap();
639
640 if let GethTrace::Default(trace) = trace {
641 assert_eq!(trace.gas, 21000)
642 }
643 })
644 .await;
645 }
646
647 #[tokio::test]
648 async fn test_debug_trace_call() {
649 async_ci_only(|| async move {
650 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
651 let from = provider.default_signer_address();
652 let gas_price = provider.get_gas_price().await.unwrap();
653 let tx = TransactionRequest::default()
654 .from(from)
655 .with_input("0xdeadbeef")
656 .max_fee_per_gas(gas_price + 1)
657 .max_priority_fee_per_gas(gas_price + 1);
658
659 let trace = provider
660 .debug_trace_call(
661 tx,
662 BlockNumberOrTag::Latest.into(),
663 GethDebugTracingCallOptions::default(),
664 )
665 .await
666 .unwrap();
667
668 if let GethTrace::Default(trace) = trace {
669 assert!(!trace.struct_logs.is_empty());
670 }
671 })
672 .await;
673 }
674
675 #[tokio::test]
676 async fn call_debug_get_raw_header() {
677 async_ci_only(|| async move {
678 run_with_tempdir("geth-test-", |temp_dir| async move {
679 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
680 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
681
682 let rlp_header = provider
683 .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest))
684 .await
685 .expect("debug_getRawHeader call should succeed");
686
687 assert!(!rlp_header.is_empty());
688 })
689 .await;
690 })
691 .await;
692 }
693
694 #[tokio::test]
695 async fn call_debug_get_raw_block() {
696 async_ci_only(|| async move {
697 run_with_tempdir("geth-test-", |temp_dir| async move {
698 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
699 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
700
701 let rlp_block = provider
702 .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest))
703 .await
704 .expect("debug_getRawBlock call should succeed");
705
706 assert!(!rlp_block.is_empty());
707 })
708 .await;
709 })
710 .await;
711 }
712
713 #[tokio::test]
714 async fn call_debug_get_raw_receipts() {
715 async_ci_only(|| async move {
716 run_with_tempdir("geth-test-", |temp_dir| async move {
717 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
718 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
719
720 let result = provider
721 .debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest))
722 .await;
723 assert!(result.is_ok());
724 })
725 .await;
726 })
727 .await;
728 }
729
730 #[tokio::test]
731 async fn call_debug_get_bad_blocks() {
732 async_ci_only(|| async move {
733 run_with_tempdir("geth-test-", |temp_dir| async move {
734 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
735 let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
736
737 let result = provider.debug_get_bad_blocks().await;
738 assert!(result.is_ok());
739 })
740 .await;
741 })
742 .await;
743 }
744
745 #[tokio::test]
746 #[cfg_attr(windows, ignore = "no reth on windows")]
747 async fn debug_trace_call_many() {
748 async_ci_only(|| async move {
749 run_with_tempdir("reth-test-", |temp_dir| async move {
750 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
751 let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
752
753 let tx1 = TransactionRequest::default()
754 .with_from(address!("0000000000000000000000000000000000000123"))
755 .with_to(address!("0000000000000000000000000000000000000456"));
756
757 let tx2 = TransactionRequest::default()
758 .with_from(address!("0000000000000000000000000000000000000456"))
759 .with_to(address!("0000000000000000000000000000000000000789"));
760
761 let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }];
762 let state_context = StateContext::default();
763 let trace_options = GethDebugTracingCallOptions::default();
764 let result =
765 provider.debug_trace_call_many(bundles, state_context, trace_options).await;
766 assert!(result.is_ok());
767
768 let traces = result.unwrap();
769 assert_eq!(
770 serde_json::to_string_pretty(&traces).unwrap().trim(),
771 r#"
772[
773 [
774 {
775 "failed": false,
776 "gas": 21000,
777 "returnValue": "0x",
778 "structLogs": []
779 },
780 {
781 "failed": false,
782 "gas": 21000,
783 "returnValue": "0x",
784 "structLogs": []
785 }
786 ]
787]
788"#
789 .trim(),
790 );
791 })
792 .await;
793 })
794 .await;
795 }
796
797 #[tokio::test]
798 #[cfg_attr(windows, ignore = "no reth on windows")]
799 async fn test_debug_code_by_hash() {
800 use alloy_primitives::b256;
801
802 async_ci_only(|| async move {
803 run_with_tempdir("reth-test-", |temp_dir| async move {
804 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
805 let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
806
807 let empty_code_hash =
810 b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
811 let empty_code = provider.debug_code_by_hash(empty_code_hash, None).await.unwrap();
812 if let Some(code) = empty_code {
814 assert!(
815 code.is_empty() || code == Bytes::from_static(&[]),
816 "Empty code hash should return empty bytes"
817 );
818 }
819
820 let non_existent_hash =
822 b256!("0000000000000000000000000000000000000000000000000000000000000001");
823 let no_code = provider.debug_code_by_hash(non_existent_hash, None).await.unwrap();
824 assert!(no_code.is_none(), "Non-existent hash should return None");
825
826 let another_hash =
829 b256!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
830 let result = provider.debug_code_by_hash(another_hash, None).await;
831 assert!(result.is_ok(), "API call should not error even for random hashes");
832 })
833 .await;
834 })
835 .await;
836 }
837}