Skip to content

Commit

Permalink
feat(test): add debug trace ext helpers (#4982)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse committed Nov 8, 2023
1 parent c1c6e98 commit 6e8ce6f
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 4 deletions.
104 changes: 101 additions & 3 deletions crates/rpc/rpc-testing-util/src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
//! Helpers for testing debug trace calls.

use reth_primitives::B256;
use reth_rpc_api::clients::DebugApiClient;
use futures::{Stream, StreamExt};
use jsonrpsee::core::Error as RpcError;
use reth_primitives::{BlockId, TxHash, B256};
use reth_rpc_api::{clients::DebugApiClient, EthApiClient};
use reth_rpc_types::trace::geth::{GethDebugTracerType, GethDebugTracingOptions};
use std::{
pin::Pin,
task::{Context, Poll},
};

const NOOP_TRACER: &str = include_str!("../assets/noop-tracer.js");
const JS_TRACER_TEMPLATE: &str = include_str!("../assets/tracer-template.js");

/// A result type for the `debug_trace_transaction` method that also captures the requested hash.
pub type TraceTransactionResult = Result<(serde_json::Value, TxHash), (RpcError, TxHash)>;

/// An extension trait for the Trace API.
#[async_trait::async_trait]
pub trait DebugApiExt {
Expand All @@ -19,10 +28,22 @@ pub trait DebugApiExt {
hash: B256,
opts: GethDebugTracingOptions,
) -> Result<serde_json::Value, jsonrpsee::core::Error>;

/// Trace all transactions in a block individually with the given tracing opts.
async fn debug_trace_transactions_in_block<B>(
&self,
block: B,
opts: GethDebugTracingOptions,
) -> Result<DebugTraceTransactionsStream<'_>, jsonrpsee::core::Error>
where
B: Into<BlockId> + Send;
}

#[async_trait::async_trait]
impl<T: DebugApiClient + Sync> DebugApiExt for T {
impl<T: DebugApiClient + Sync> DebugApiExt for T
where
T: EthApiClient,
{
type Provider = T;

async fn debug_trace_transaction_json(
Expand All @@ -35,6 +56,31 @@ impl<T: DebugApiClient + Sync> DebugApiExt for T {
params.insert(opts).unwrap();
self.request("debug_traceTransaction", params).await
}

async fn debug_trace_transactions_in_block<B>(
&self,
block: B,
opts: GethDebugTracingOptions,
) -> Result<DebugTraceTransactionsStream<'_>, jsonrpsee::core::Error>
where
B: Into<BlockId> + Send,
{
let block = match block.into() {
BlockId::Hash(hash) => self.block_by_hash(hash.block_hash, false).await,
BlockId::Number(tag) => self.block_by_number(tag, false).await,
}?
.ok_or_else(|| RpcError::Custom("block not found".to_string()))?;
let hashes = block.transactions.iter().map(|tx| (tx, opts.clone())).collect::<Vec<_>>();
let stream = futures::stream::iter(hashes.into_iter().map(move |(tx, opts)| async move {
match self.debug_trace_transaction_json(tx, opts).await {
Ok(result) => Ok((result, tx)),
Err(err) => Err((err, tx)),
}
}))
.buffered(10);

Ok(DebugTraceTransactionsStream { stream: Box::pin(stream) })
}
}

/// A helper type that can be used to build a javascript tracer.
Expand Down Expand Up @@ -146,6 +192,38 @@ impl From<JsTracerBuilder> for Option<GethDebugTracingOptions> {
}
}

/// A stream that yields the traces for the requested blocks.
#[must_use = "streams do nothing unless polled"]
pub struct DebugTraceTransactionsStream<'a> {
stream: Pin<Box<dyn Stream<Item = TraceTransactionResult> + 'a>>,
}

impl<'a> DebugTraceTransactionsStream<'a> {
/// Returns the next error result of the stream.
pub async fn next_err(&mut self) -> Option<(RpcError, TxHash)> {
loop {
match self.next().await? {
Ok(_) => continue,
Err(err) => return Some(err),
}
}
}
}

impl<'a> Stream for DebugTraceTransactionsStream<'a> {
type Item = TraceTransactionResult;

fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.stream.as_mut().poll_next(cx)
}
}

impl<'a> std::fmt::Debug for DebugTraceTransactionsStream<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DebugTraceTransactionsStream").finish_non_exhaustive()
}
}

/// A javascript tracer that does nothing
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
Expand All @@ -172,7 +250,9 @@ mod tests {
debug::{DebugApiExt, JsTracerBuilder, NoopJsTracer},
utils::parse_env_url,
};
use futures::StreamExt;
use jsonrpsee::http_client::HttpClientBuilder;
use reth_rpc_types::trace::geth::{CallConfig, GethDebugTracingOptions};

// random tx <https://sepolia.etherscan.io/tx/0x5525c63a805df2b83c113ebcc8c7672a3b290673c4e81335b410cd9ebc64e085>
const TX_1: &str = "0x5525c63a805df2b83c113ebcc8c7672a3b290673c4e81335b410cd9ebc64e085";
Expand Down Expand Up @@ -200,4 +280,22 @@ mod tests {
.unwrap();
assert_eq!(res, serde_json::Value::Object(Default::default()));
}

#[tokio::test]
#[ignore]
async fn can_debug_trace_block_transactions() {
let block = 11_117_104u64;
let url = parse_env_url("RETH_RPC_TEST_NODE_URL").unwrap();
let client = HttpClientBuilder::default().build(url).unwrap();

let opts =
GethDebugTracingOptions::default().call_config(CallConfig::default().only_top_call());

let mut stream = client.debug_trace_transactions_in_block(block, opts).await.unwrap();
while let Some(res) = stream.next().await {
if let Err((err, tx)) = res {
println!("failed to trace {:?} {}", tx, err);
}
}
}
}
2 changes: 1 addition & 1 deletion crates/rpc/rpc-testing-util/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::{
use jsonrpsee::core::Error as RpcError;
use reth_rpc_types::trace::parity::LocalizedTransactionTrace;

/// A result type for the `trace_block` method that also
/// A result type for the `trace_block` method that also captures the requested block.
pub type TraceBlockResult = Result<(Vec<LocalizedTransactionTrace>, BlockId), (RpcError, BlockId)>;

/// An extension trait for the Trace API.
Expand Down
73 changes: 73 additions & 0 deletions crates/rpc/rpc-types/src/eth/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,46 @@ impl BlockTransactions {
pub fn is_uncle(&self) -> bool {
matches!(self, Self::Uncle)
}

/// Returns an iterator over the transaction hashes.
pub fn iter(&self) -> BlockTransactionsHashIterator<'_> {
BlockTransactionsHashIterator::new(self)
}
}

/// An Iterator over the transaction hashes of a block.
#[derive(Debug, Clone)]
pub struct BlockTransactionsHashIterator<'a> {
txs: &'a BlockTransactions,
idx: usize,
}

impl<'a> BlockTransactionsHashIterator<'a> {
fn new(txs: &'a BlockTransactions) -> Self {
Self { txs, idx: 0 }
}
}

impl<'a> Iterator for BlockTransactionsHashIterator<'a> {
type Item = B256;

fn next(&mut self) -> Option<Self::Item> {
match self.txs {
BlockTransactions::Full(txs) => {
let tx = txs.get(self.idx);
self.idx += 1;
tx.map(|tx| tx.hash)
}
BlockTransactions::Hashes(txs) => {
let tx = txs.get(self.idx).copied();
self.idx += 1;
tx
}
BlockTransactions::Uncle => None,
}
}
}

/// Determines how the `transactions` field of [Block] should be filled.
///
/// This essentially represents the `full:bool` argument in RPC calls that determine whether the
Expand Down Expand Up @@ -218,6 +257,7 @@ impl From<Header> for RichHeader {
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Rich<T> {
/// Standard value.
#[serde(flatten)]
pub inner: T,
/// Additional fields that should be serialized into the `Block` object
#[serde(flatten)]
Expand Down Expand Up @@ -401,4 +441,37 @@ mod tests {
let s = r#"{"blockNumber": "0xe39dd0"}"#;
let _overrides = serde_json::from_str::<BlockOverrides>(s).unwrap();
}

#[test]
fn serde_rich_block() {
let s = r#"{
"hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
"parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"miner": "0x829bd824b016326a401d083b33d092293333a830",
"stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
"transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
"receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
"logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
"difficulty": "0xc40faff9c737d",
"number": "0xa9a230",
"gasLimit": "0xbe5a66",
"gasUsed": "0xbe0fcc",
"timestamp": "0x5f93b749",
"extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
"mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
"nonce": "0x4722f2acd35abe0f",
"totalDifficulty": "0x3dc957fd8167fb2684a",
"uncles": [],
"transactions": [
"0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916"
],
"size": "0xaeb6"
}"#;

let block = serde_json::from_str::<RichBlock>(s).unwrap();
let serialized = serde_json::to_string(&block).unwrap();
let block2 = serde_json::from_str::<RichBlock>(&serialized).unwrap();
assert_eq!(block, block2);
}
}

0 comments on commit 6e8ce6f

Please sign in to comment.