mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(test): add debug trace ext helpers (#4982)
This commit is contained in:
@ -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 {
|
||||
@ -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(
|
||||
@ -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.
|
||||
@ -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]
|
||||
@ -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";
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
@ -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)]
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user