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