use crate::Provider;
use alloy_json_rpc::RpcRecv;
use alloy_network::{Ethereum, Network};
use alloy_primitives::{hex, Bytes, TxHash, B256};
use alloy_rpc_types_debug::ExecutionWitness;
use alloy_rpc_types_eth::{BadBlock, BlockId, BlockNumberOrTag, Bundle, StateContext};
use alloy_rpc_types_trace::geth::{
BlockTraceResult, CallFrame, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
PreStateFrame, TraceResult,
};
use alloy_transport::TransportResult;
#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
pub trait DebugApi<N: Network = Ethereum>: Send + Sync {
async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes>;
async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes>;
async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes>;
async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>>;
async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>>;
async fn debug_trace_chain(
&self,
start_exclusive: BlockNumberOrTag,
end_inclusive: BlockNumberOrTag,
) -> TransportResult<Vec<BlockTraceResult>>;
#[cfg(feature = "pubsub")]
fn debug_subscribe_trace_chain(
&self,
start_exclusive: BlockNumberOrTag,
end_inclusive: BlockNumberOrTag,
trace_options: Option<GethDebugTracingOptions>,
) -> crate::GetSubscription<
(&'static str, BlockNumberOrTag, BlockNumberOrTag, Option<GethDebugTracingOptions>),
alloy_rpc_types_trace::geth::ChainBlockTraceResult,
>;
async fn debug_trace_block(
&self,
rlp_block: &[u8],
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>>;
async fn debug_trace_transaction(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<GethTrace>;
async fn debug_trace_transaction_as<R>(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<R>
where
R: RpcRecv + serde::de::DeserializeOwned;
async fn debug_trace_transaction_js(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<serde_json::Value>;
async fn debug_trace_transaction_call(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<CallFrame>;
async fn debug_trace_call_as<R>(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<R>
where
R: RpcRecv + serde::de::DeserializeOwned;
async fn debug_trace_call_js(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<serde_json::Value>;
async fn debug_trace_call_callframe(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<CallFrame>;
async fn debug_trace_call_prestate(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<PreStateFrame>;
async fn debug_trace_block_by_hash(
&self,
block: B256,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>>;
async fn debug_trace_block_by_number(
&self,
block: BlockNumberOrTag,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>>;
async fn debug_trace_call(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<GethTrace>;
async fn debug_trace_call_many(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<GethTrace>>>;
async fn debug_trace_call_many_as<R>(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<R>>>
where
R: RpcRecv + serde::de::DeserializeOwned;
async fn debug_trace_call_many_js(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<serde_json::Value>>>;
async fn debug_trace_call_many_callframe(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<CallFrame>>>;
async fn debug_trace_call_many_prestate(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<PreStateFrame>>>;
async fn debug_execution_witness(
&self,
block: BlockNumberOrTag,
) -> TransportResult<ExecutionWitness>;
async fn debug_code_by_hash(
&self,
hash: B256,
block: Option<BlockId>,
) -> TransportResult<Option<Bytes>>;
async fn debug_db_get(&self, key: &str) -> TransportResult<Bytes>;
}
#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
impl<N, P> DebugApi<N> for P
where
N: Network,
P: Provider<N>,
{
async fn debug_get_raw_header(&self, block: BlockId) -> TransportResult<Bytes> {
self.client().request("debug_getRawHeader", (block,)).await
}
async fn debug_get_raw_block(&self, block: BlockId) -> TransportResult<Bytes> {
self.client().request("debug_getRawBlock", (block,)).await
}
async fn debug_get_raw_transaction(&self, hash: TxHash) -> TransportResult<Bytes> {
self.client().request("debug_getRawTransaction", (hash,)).await
}
async fn debug_get_raw_receipts(&self, block: BlockId) -> TransportResult<Vec<Bytes>> {
self.client().request("debug_getRawReceipts", (block,)).await
}
async fn debug_get_bad_blocks(&self) -> TransportResult<Vec<BadBlock>> {
self.client().request_noparams("debug_getBadBlocks").await
}
async fn debug_trace_chain(
&self,
start_exclusive: BlockNumberOrTag,
end_inclusive: BlockNumberOrTag,
) -> TransportResult<Vec<BlockTraceResult>> {
self.client().request("debug_traceChain", (start_exclusive, end_inclusive)).await
}
#[cfg(feature = "pubsub")]
fn debug_subscribe_trace_chain(
&self,
start_exclusive: BlockNumberOrTag,
end_inclusive: BlockNumberOrTag,
trace_options: Option<GethDebugTracingOptions>,
) -> crate::GetSubscription<
(&'static str, BlockNumberOrTag, BlockNumberOrTag, Option<GethDebugTracingOptions>),
alloy_rpc_types_trace::geth::ChainBlockTraceResult,
> {
let mut call = self.client().request(
"debug_subscribe",
("traceChain", start_exclusive, end_inclusive, trace_options),
);
call.set_is_subscription();
crate::GetSubscription::new(self.weak_client(), call)
}
async fn debug_trace_block(
&self,
rlp_block: &[u8],
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
let rlp_block = hex::encode_prefixed(rlp_block);
self.client().request("debug_traceBlock", (rlp_block, trace_options)).await
}
async fn debug_trace_transaction(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<GethTrace> {
self.client().request("debug_traceTransaction", (hash, trace_options)).await
}
async fn debug_trace_transaction_as<R>(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<R>
where
R: RpcRecv,
{
self.client().request("debug_traceTransaction", (hash, trace_options)).await
}
async fn debug_trace_transaction_js(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<serde_json::Value> {
self.debug_trace_transaction_as::<serde_json::Value>(hash, trace_options).await
}
async fn debug_trace_transaction_call(
&self,
hash: TxHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<CallFrame> {
self.debug_trace_transaction_as::<CallFrame>(hash, trace_options).await
}
async fn debug_trace_call_as<R>(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<R>
where
R: RpcRecv,
{
self.client().request("debug_traceCall", (tx, block, trace_options)).await
}
async fn debug_trace_call_js(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<serde_json::Value> {
self.debug_trace_call_as::<serde_json::Value>(tx, block, trace_options).await
}
async fn debug_trace_call_callframe(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<CallFrame> {
self.debug_trace_call_as::<CallFrame>(tx, block, trace_options).await
}
async fn debug_trace_call_prestate(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<PreStateFrame> {
self.debug_trace_call_as::<PreStateFrame>(tx, block, trace_options).await
}
async fn debug_trace_block_by_hash(
&self,
block: B256,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self.client().request("debug_traceBlockByHash", (block, trace_options)).await
}
async fn debug_trace_block_by_number(
&self,
block: BlockNumberOrTag,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self.client().request("debug_traceBlockByNumber", (block, trace_options)).await
}
async fn debug_trace_call(
&self,
tx: N::TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<GethTrace> {
self.client().request("debug_traceCall", (tx, block, trace_options)).await
}
async fn debug_trace_call_many(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<GethTrace>>> {
self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
}
async fn debug_trace_call_many_as<R>(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<R>>>
where
R: RpcRecv,
{
self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await
}
async fn debug_trace_call_many_js(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<serde_json::Value>>> {
self.debug_trace_call_many_as::<serde_json::Value>(bundles, state_context, trace_options)
.await
}
async fn debug_trace_call_many_callframe(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<CallFrame>>> {
self.debug_trace_call_many_as::<CallFrame>(bundles, state_context, trace_options).await
}
async fn debug_trace_call_many_prestate(
&self,
bundles: Vec<Bundle>,
state_context: StateContext,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<Vec<Vec<PreStateFrame>>> {
self.debug_trace_call_many_as::<PreStateFrame>(bundles, state_context, trace_options).await
}
async fn debug_execution_witness(
&self,
block: BlockNumberOrTag,
) -> TransportResult<ExecutionWitness> {
self.client().request("debug_executionWitness", (block,)).await
}
async fn debug_code_by_hash(
&self,
hash: B256,
block: Option<BlockId>,
) -> TransportResult<Option<Bytes>> {
self.client().request("debug_codeByHash", (hash, block)).await
}
async fn debug_db_get(&self, key: &str) -> TransportResult<Bytes> {
self.client().request("debug_dbGet", (key,)).await
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{ext::test::async_ci_only, ProviderBuilder, WalletProvider};
use alloy_network::TransactionBuilder;
use alloy_node_bindings::{utils::run_with_tempdir, Geth, Reth};
use alloy_primitives::{address, U256};
use alloy_rpc_types_eth::TransactionRequest;
#[tokio::test]
async fn test_debug_trace_transaction() {
async_ci_only(|| async move {
let provider = ProviderBuilder::new().connect_anvil_with_wallet();
let from = provider.default_signer_address();
let gas_price = provider.get_gas_price().await.unwrap();
let tx = TransactionRequest::default()
.from(from)
.to(address!("deadbeef00000000deadbeef00000000deadbeef"))
.value(U256::from(100))
.max_fee_per_gas(gas_price + 1)
.max_priority_fee_per_gas(gas_price + 1);
let pending = provider.send_transaction(tx).await.unwrap();
let receipt = pending.get_receipt().await.unwrap();
let hash = receipt.transaction_hash;
let trace_options = GethDebugTracingOptions::default();
let trace = provider.debug_trace_transaction(hash, trace_options).await.unwrap();
if let GethTrace::Default(trace) = trace {
assert_eq!(trace.gas, 21000)
}
})
.await;
}
#[tokio::test]
async fn test_debug_trace_call() {
async_ci_only(|| async move {
let provider = ProviderBuilder::new().connect_anvil_with_wallet();
let from = provider.default_signer_address();
let gas_price = provider.get_gas_price().await.unwrap();
let tx = TransactionRequest::default()
.from(from)
.with_input("0xdeadbeef")
.max_fee_per_gas(gas_price + 1)
.max_priority_fee_per_gas(gas_price + 1);
let trace = provider
.debug_trace_call(
tx,
BlockNumberOrTag::Latest.into(),
GethDebugTracingCallOptions::default(),
)
.await
.unwrap();
if let GethTrace::Default(trace) = trace {
assert!(!trace.struct_logs.is_empty());
}
})
.await;
}
#[tokio::test]
async fn call_debug_get_raw_header() {
async_ci_only(|| async move {
run_with_tempdir("geth-test-", |temp_dir| async move {
let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
let rlp_header = provider
.debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest))
.await
.expect("debug_getRawHeader call should succeed");
assert!(!rlp_header.is_empty());
})
.await;
})
.await;
}
#[tokio::test]
async fn call_debug_get_raw_block() {
async_ci_only(|| async move {
run_with_tempdir("geth-test-", |temp_dir| async move {
let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
let rlp_block = provider
.debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest))
.await
.expect("debug_getRawBlock call should succeed");
assert!(!rlp_block.is_empty());
})
.await;
})
.await;
}
#[tokio::test]
async fn call_debug_get_raw_receipts() {
async_ci_only(|| async move {
run_with_tempdir("geth-test-", |temp_dir| async move {
let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
let result = provider
.debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest))
.await;
assert!(result.is_ok());
})
.await;
})
.await;
}
#[tokio::test]
async fn call_debug_get_bad_blocks() {
async_ci_only(|| async move {
run_with_tempdir("geth-test-", |temp_dir| async move {
let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
let result = provider.debug_get_bad_blocks().await;
assert!(result.is_ok());
})
.await;
})
.await;
}
#[tokio::test]
#[cfg_attr(windows, ignore = "no reth on windows")]
async fn debug_trace_call_many() {
async_ci_only(|| async move {
run_with_tempdir("reth-test-", |temp_dir| async move {
let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
let tx1 = TransactionRequest::default()
.with_from(address!("0000000000000000000000000000000000000123"))
.with_to(address!("0000000000000000000000000000000000000456"));
let tx2 = TransactionRequest::default()
.with_from(address!("0000000000000000000000000000000000000456"))
.with_to(address!("0000000000000000000000000000000000000789"));
let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }];
let state_context = StateContext::default();
let trace_options = GethDebugTracingCallOptions::default();
let result =
provider.debug_trace_call_many(bundles, state_context, trace_options).await;
assert!(result.is_ok());
let traces = result.unwrap();
assert_eq!(
serde_json::to_string_pretty(&traces).unwrap().trim(),
r#"
[
[
{
"failed": false,
"gas": 21000,
"returnValue": "0x",
"structLogs": []
},
{
"failed": false,
"gas": 21000,
"returnValue": "0x",
"structLogs": []
}
]
]
"#
.trim(),
);
})
.await;
})
.await;
}
#[tokio::test]
#[cfg_attr(windows, ignore = "no reth on windows")]
async fn test_debug_code_by_hash() {
use alloy_primitives::b256;
async_ci_only(|| async move {
run_with_tempdir("reth-test-", |temp_dir| async move {
let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
let empty_code_hash =
b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
let empty_code = provider.debug_code_by_hash(empty_code_hash, None).await.unwrap();
if let Some(code) = empty_code {
assert!(
code.is_empty() || code == Bytes::from_static(&[]),
"Empty code hash should return empty bytes"
);
}
let non_existent_hash =
b256!("0000000000000000000000000000000000000000000000000000000000000001");
let no_code = provider.debug_code_by_hash(non_existent_hash, None).await.unwrap();
assert!(no_code.is_none(), "Non-existent hash should return None");
let another_hash =
b256!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
let result = provider.debug_code_by_hash(another_hash, None).await;
assert!(result.is_ok(), "API call should not error even for random hashes");
})
.await;
})
.await;
}
}