use crate::Provider;
use alloy_network::Network;
use alloy_primitives::{hex, Bytes, TxHash, B256};
use alloy_rpc_types_eth::{
BadBlock, BlockId, BlockNumberOrTag, Bundle, StateContext, TransactionRequest,
};
use alloy_rpc_types_trace::geth::{
BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult,
};
use alloy_transport::{Transport, TransportResult};
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait DebugApi<N, T>: 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>>;
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_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: 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<GethTrace>>;
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl<N, T, P> DebugApi<N, T> for P
where
N: Network,
T: Transport + Clone,
P: Provider<T, 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
}
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_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: 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<GethTrace>> {
self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).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};
#[tokio::test]
async fn test_debug_trace_transaction() {
async_ci_only(|| async move {
let provider = ProviderBuilder::new().with_recommended_fillers().on_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().on_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().on_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().on_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().on_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().on_http(geth.endpoint_url());
let result = provider.debug_get_bad_blocks().await;
assert!(result.is_ok());
})
.await;
})
.await;
}
#[tokio::test]
#[cfg(not(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().with_recommended_fillers().on_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": "",
"structLogs": []
},
{
"failed": false,
"gas": 21000,
"returnValue": "",
"structLogs": []
}
]
]
"#
.trim(),
);
})
.await;
})
.await;
}
}