1use crate::Provider;
3use alloy_json_rpc::RpcRecv;
4use alloy_network::Network;
5use alloy_primitives::{hex, Bytes, TxHash, B256};
6use alloy_rpc_types_debug::ExecutionWitness;
7use alloy_rpc_types_eth::{
8 BadBlock, BlockId, BlockNumberOrTag, Bundle, StateContext, TransactionRequest,
9};
10use alloy_rpc_types_trace::geth::{
11 BlockTraceResult, CallFrame, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
12 TraceResult,
13};
14use alloy_transport::TransportResult;
15
16#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
18#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
19pub trait DebugApi<N>: Send + Sync {
20 async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes>;
22
23 async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes>;
25
26 async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes>;
28
29 async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>>;
31
32 async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>>;
34
35 async fn debug_trace_chain(
38 &self,
39 start_exclusive: BlockNumberOrTag,
40 end_inclusive: BlockNumberOrTag,
41 ) -> TransportResult<Vec<BlockTraceResult>>;
42
43 async fn debug_trace_block(
52 &self,
53 rlp_block: &[u8],
54 trace_options: GethDebugTracingOptions,
55 ) -> TransportResult<Vec<TraceResult>>;
56
57 async fn debug_trace_transaction(
68 &self,
69 hash: TxHash,
70 trace_options: GethDebugTracingOptions,
71 ) -> TransportResult<GethTrace>;
72
73 async fn debug_trace_transaction_as<R>(
84 &self,
85 hash: TxHash,
86 trace_options: GethDebugTracingOptions,
87 ) -> TransportResult<R>
88 where
89 R: RpcRecv + serde::de::DeserializeOwned;
90
91 async fn debug_trace_transaction_js(
102 &self,
103 hash: TxHash,
104 trace_options: GethDebugTracingOptions,
105 ) -> TransportResult<serde_json::Value>;
106
107 async fn debug_trace_transaction_call(
118 &self,
119 hash: TxHash,
120 trace_options: GethDebugTracingOptions,
121 ) -> TransportResult<CallFrame>;
122
123 async fn debug_trace_call_as<R>(
134 &self,
135 tx: TransactionRequest,
136 block: BlockId,
137 trace_options: GethDebugTracingCallOptions,
138 ) -> TransportResult<R>
139 where
140 R: RpcRecv + serde::de::DeserializeOwned;
141
142 async fn debug_trace_call_js(
153 &self,
154 tx: TransactionRequest,
155 block: BlockId,
156 trace_options: GethDebugTracingCallOptions,
157 ) -> TransportResult<serde_json::Value>;
158
159 async fn debug_trace_call_callframe(
170 &self,
171 tx: TransactionRequest,
172 block: BlockId,
173 trace_options: GethDebugTracingCallOptions,
174 ) -> TransportResult<CallFrame>;
175
176 async fn debug_trace_block_by_hash(
187 &self,
188 block: B256,
189 trace_options: GethDebugTracingOptions,
190 ) -> TransportResult<Vec<TraceResult>>;
191
192 async fn debug_trace_block_by_number(
200 &self,
201 block: BlockNumberOrTag,
202 trace_options: GethDebugTracingOptions,
203 ) -> TransportResult<Vec<TraceResult>>;
204
205 async fn debug_trace_call(
218 &self,
219 tx: TransactionRequest,
220 block: BlockId,
221 trace_options: GethDebugTracingCallOptions,
222 ) -> TransportResult<GethTrace>;
223
224 async fn debug_trace_call_many(
232 &self,
233 bundles: Vec<Bundle>,
234 state_context: StateContext,
235 trace_options: GethDebugTracingCallOptions,
236 ) -> TransportResult<Vec<GethTrace>>;
237
238 async fn debug_execution_witness(
249 &self,
250 block: BlockNumberOrTag,
251 ) -> TransportResult<ExecutionWitness>;
252
253 async fn debug_code_by_hash(
261 &self,
262 hash: B256,
263 block: Option<BlockId>,
264 ) -> TransportResult<Option<Bytes>>;
265}
266
267#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
268#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
269impl<N, P> DebugApi<N> for P
270where
271 N: Network,
272 P: Provider<N>,
273{
274 async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes> {
275 self.client().request("debug_getRawHeader", (block,)).await
276 }
277
278 async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes> {
279 self.client().request("debug_getRawBlock", (block,)).await
280 }
281
282 async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes> {
283 self.client().request("debug_getRawTransaction", (hash,)).await
284 }
285
286 async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>> {
287 self.client().request("debug_getRawReceipts", (block,)).await
288 }
289
290 async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>> {
291 self.client().request_noparams("debug_getBadBlocks").await
292 }
293
294 async fn debug_trace_chain(
295 &self,
296 start_exclusive: BlockNumberOrTag,
297 end_inclusive: BlockNumberOrTag,
298 ) -> TransportResult<Vec<BlockTraceResult>> {
299 self.client().request("debug_traceChain", (start_exclusive, end_inclusive)).await
300 }
301
302 async fn debug_trace_block(
303 &self,
304 rlp_block: &[u8],
305 trace_options: GethDebugTracingOptions,
306 ) -> TransportResult<Vec<TraceResult>> {
307 let rlp_block = hex::encode_prefixed(rlp_block);
308 self.client().request("debug_traceBlock", (rlp_block, trace_options)).await
309 }
310
311 async fn debug_trace_transaction(
312 &self,
313 hash: TxHash,
314 trace_options: GethDebugTracingOptions,
315 ) -> TransportResult<GethTrace> {
316 self.client().request("debug_traceTransaction", (hash, trace_options)).await
317 }
318
319 async fn debug_trace_transaction_as<R>(
320 &self,
321 hash: TxHash,
322 trace_options: GethDebugTracingOptions,
323 ) -> TransportResult<R>
324 where
325 R: RpcRecv,
326 {
327 self.client().request("debug_traceTransaction", (hash, trace_options)).await
328 }
329
330 async fn debug_trace_transaction_js(
331 &self,
332 hash: TxHash,
333 trace_options: GethDebugTracingOptions,
334 ) -> TransportResult<serde_json::Value> {
335 self.debug_trace_transaction_as::<serde_json::Value>(hash, trace_options).await
336 }
337
338 async fn debug_trace_transaction_call(
339 &self,
340 hash: TxHash,
341 trace_options: GethDebugTracingOptions,
342 ) -> TransportResult<CallFrame> {
343 self.debug_trace_transaction_as::<CallFrame>(hash, trace_options).await
344 }
345
346 async fn debug_trace_call_as<R>(
347 &self,
348 tx: TransactionRequest,
349 block: BlockId,
350 trace_options: GethDebugTracingCallOptions,
351 ) -> TransportResult<R>
352 where
353 R: RpcRecv,
354 {
355 self.client().request("debug_traceCall", (tx, block, trace_options)).await
356 }
357
358 async fn debug_trace_call_js(
359 &self,
360 tx: TransactionRequest,
361 block: BlockId,
362 trace_options: GethDebugTracingCallOptions,
363 ) -> TransportResult<serde_json::Value> {
364 self.debug_trace_call_as::<serde_json::Value>(tx, block, trace_options).await
365 }
366
367 async fn debug_trace_call_callframe(
368 &self,
369 tx: TransactionRequest,
370 block: BlockId,
371 trace_options: GethDebugTracingCallOptions,
372 ) -> TransportResult<CallFrame> {
373 self.debug_trace_call_as::<CallFrame>(tx, block, trace_options).await
374 }
375
376 async fn debug_trace_block_by_hash(
377 &self,
378 block: B256,
379 trace_options: GethDebugTracingOptions,
380 ) -> TransportResult<Vec<TraceResult>> {
381 self.client().request("debug_traceBlockByHash", (block, trace_options)).await
382 }
383
384 async fn debug_trace_block_by_number(
385 &self,
386 block: BlockNumberOrTag,
387 trace_options: GethDebugTracingOptions,
388 ) -> TransportResult<Vec<TraceResult>> {
389 self.client().request("debug_traceBlockByNumber", (block, trace_options)).await
390 }
391
392 async fn debug_trace_call(
393 &self,
394 tx: TransactionRequest,
395 block: BlockId,
396 trace_options: GethDebugTracingCallOptions,
397 ) -> TransportResult<GethTrace> {
398 self.client().request("debug_traceCall", (tx, block, trace_options)).await
399 }
400
401 async fn debug_trace_call_many(
402 &self,
403 bundles: Vec<Bundle>,
404 state_context: StateContext,
405 trace_options: GethDebugTracingCallOptions,
406 ) -> TransportResult<Vec<GethTrace>> {
407 self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
408 }
409
410 async fn debug_execution_witness(
411 &self,
412 block: BlockNumberOrTag,
413 ) -> TransportResult<ExecutionWitness> {
414 self.client().request("debug_executionWitness", block).await
415 }
416
417 async fn debug_code_by_hash(
418 &self,
419 hash: B256,
420 block: Option<BlockId>,
421 ) -> TransportResult<Option<Bytes>> {
422 self.client().request("debug_codeByHash", (hash, block)).await
423 }
424}
425
426#[cfg(test)]
427mod test {
428 use super::*;
429 use crate::{ext::test::async_ci_only, ProviderBuilder, WalletProvider};
430 use alloy_network::TransactionBuilder;
431 use alloy_node_bindings::{utils::run_with_tempdir, Geth, Reth};
432 use alloy_primitives::{address, U256};
433
434 #[tokio::test]
435 async fn test_debug_trace_transaction() {
436 async_ci_only(|| async move {
437 let provider = ProviderBuilder::new().on_anvil_with_wallet();
438 let from = provider.default_signer_address();
439
440 let gas_price = provider.get_gas_price().await.unwrap();
441 let tx = TransactionRequest::default()
442 .from(from)
443 .to(address!("deadbeef00000000deadbeef00000000deadbeef"))
444 .value(U256::from(100))
445 .max_fee_per_gas(gas_price + 1)
446 .max_priority_fee_per_gas(gas_price + 1);
447 let pending = provider.send_transaction(tx).await.unwrap();
448 let receipt = pending.get_receipt().await.unwrap();
449
450 let hash = receipt.transaction_hash;
451 let trace_options = GethDebugTracingOptions::default();
452
453 let trace = provider.debug_trace_transaction(hash, trace_options).await.unwrap();
454
455 if let GethTrace::Default(trace) = trace {
456 assert_eq!(trace.gas, 21000)
457 }
458 })
459 .await;
460 }
461
462 #[tokio::test]
463 async fn test_debug_trace_call() {
464 async_ci_only(|| async move {
465 let provider = ProviderBuilder::new().on_anvil_with_wallet();
466 let from = provider.default_signer_address();
467 let gas_price = provider.get_gas_price().await.unwrap();
468 let tx = TransactionRequest::default()
469 .from(from)
470 .with_input("0xdeadbeef")
471 .max_fee_per_gas(gas_price + 1)
472 .max_priority_fee_per_gas(gas_price + 1);
473
474 let trace = provider
475 .debug_trace_call(
476 tx,
477 BlockNumberOrTag::Latest.into(),
478 GethDebugTracingCallOptions::default(),
479 )
480 .await
481 .unwrap();
482
483 if let GethTrace::Default(trace) = trace {
484 assert!(!trace.struct_logs.is_empty());
485 }
486 })
487 .await;
488 }
489
490 #[tokio::test]
491 async fn call_debug_get_raw_header() {
492 async_ci_only(|| async move {
493 run_with_tempdir("geth-test-", |temp_dir| async move {
494 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
495 let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
496
497 let rlp_header = provider
498 .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest))
499 .await
500 .expect("debug_getRawHeader call should succeed");
501
502 assert!(!rlp_header.is_empty());
503 })
504 .await;
505 })
506 .await;
507 }
508
509 #[tokio::test]
510 async fn call_debug_get_raw_block() {
511 async_ci_only(|| async move {
512 run_with_tempdir("geth-test-", |temp_dir| async move {
513 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
514 let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
515
516 let rlp_block = provider
517 .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest))
518 .await
519 .expect("debug_getRawBlock call should succeed");
520
521 assert!(!rlp_block.is_empty());
522 })
523 .await;
524 })
525 .await;
526 }
527
528 #[tokio::test]
529 async fn call_debug_get_raw_receipts() {
530 async_ci_only(|| async move {
531 run_with_tempdir("geth-test-", |temp_dir| async move {
532 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
533 let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
534
535 let result = provider
536 .debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest))
537 .await;
538 assert!(result.is_ok());
539 })
540 .await;
541 })
542 .await;
543 }
544
545 #[tokio::test]
546 async fn call_debug_get_bad_blocks() {
547 async_ci_only(|| async move {
548 run_with_tempdir("geth-test-", |temp_dir| async move {
549 let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
550 let provider = ProviderBuilder::new().on_http(geth.endpoint_url());
551
552 let result = provider.debug_get_bad_blocks().await;
553 assert!(result.is_ok());
554 })
555 .await;
556 })
557 .await;
558 }
559
560 #[tokio::test]
561 #[cfg_attr(windows, ignore = "no reth on windows")]
562 async fn debug_trace_call_many() {
563 async_ci_only(|| async move {
564 run_with_tempdir("reth-test-", |temp_dir| async move {
565 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
566 let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
567
568 let tx1 = TransactionRequest::default()
569 .with_from(address!("0000000000000000000000000000000000000123"))
570 .with_to(address!("0000000000000000000000000000000000000456"));
571
572 let tx2 = TransactionRequest::default()
573 .with_from(address!("0000000000000000000000000000000000000456"))
574 .with_to(address!("0000000000000000000000000000000000000789"));
575
576 let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }];
577 let state_context = StateContext::default();
578 let trace_options = GethDebugTracingCallOptions::default();
579 let result =
580 provider.debug_trace_call_many(bundles, state_context, trace_options).await;
581 assert!(result.is_ok());
582
583 let traces = result.unwrap();
584 assert_eq!(
585 serde_json::to_string_pretty(&traces).unwrap().trim(),
586 r#"
587[
588 [
589 {
590 "failed": false,
591 "gas": 21000,
592 "returnValue": "",
593 "structLogs": []
594 },
595 {
596 "failed": false,
597 "gas": 21000,
598 "returnValue": "",
599 "structLogs": []
600 }
601 ]
602]
603"#
604 .trim(),
605 );
606 })
607 .await;
608 })
609 .await;
610 }
611
612 }