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 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_block_by_hash(
185 &self,
186 block: B256,
187 trace_options: GethDebugTracingOptions,
188 ) -> TransportResult<Vec<TraceResult>>;
189
190 async fn debug_trace_block_by_number(
198 &self,
199 block: BlockNumberOrTag,
200 trace_options: GethDebugTracingOptions,
201 ) -> TransportResult<Vec<TraceResult>>;
202
203 async fn debug_trace_call(
216 &self,
217 tx: N::TransactionRequest,
218 block: BlockId,
219 trace_options: GethDebugTracingCallOptions,
220 ) -> TransportResult<GethTrace>;
221
222 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 async fn debug_execution_witness(
247 &self,
248 block: BlockNumberOrTag,
249 ) -> TransportResult<ExecutionWitness>;
250
251 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 }