Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(test): add debug trace ext helpers #4982

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
Loading