mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: support trace_filter (#4818)
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -7680,9 +7680,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.7.2"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8faa444297615a4e020acb64146b0603c9c395c03a97c17fd9028816d3b4d63e"
|
checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@ -66,7 +66,9 @@ pub trait TraceApi {
|
|||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>>;
|
) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>>;
|
||||||
|
|
||||||
/// Returns traces matching given filter
|
/// Returns traces matching given filter.
|
||||||
|
///
|
||||||
|
/// This is similar to `eth_getLogs` but for traces.
|
||||||
#[method(name = "filter")]
|
#[method(name = "filter")]
|
||||||
async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>>;
|
async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>>;
|
||||||
|
|
||||||
|
|||||||
@ -161,10 +161,11 @@ where
|
|||||||
{
|
{
|
||||||
let block_id = BlockId::Number(BlockNumberOrTag::default());
|
let block_id = BlockId::Number(BlockNumberOrTag::default());
|
||||||
let trace_filter = TraceFilter {
|
let trace_filter = TraceFilter {
|
||||||
from_block: None,
|
from_block: Default::default(),
|
||||||
to_block: None,
|
to_block: Default::default(),
|
||||||
from_address: None,
|
from_address: Default::default(),
|
||||||
to_address: None,
|
to_address: Default::default(),
|
||||||
|
mode: Default::default(),
|
||||||
after: None,
|
after: None,
|
||||||
count: None,
|
count: None,
|
||||||
};
|
};
|
||||||
@ -182,9 +183,7 @@ where
|
|||||||
TraceApiClient::trace_block(client, block_id).await.unwrap();
|
TraceApiClient::trace_block(client, block_id).await.unwrap();
|
||||||
TraceApiClient::replay_block_transactions(client, block_id, HashSet::default()).await.unwrap();
|
TraceApiClient::replay_block_transactions(client, block_id, HashSet::default()).await.unwrap();
|
||||||
|
|
||||||
assert!(is_unimplemented(
|
TraceApiClient::trace_filter(client, trace_filter).await.unwrap();
|
||||||
TraceApiClient::trace_filter(client, trace_filter).await.err().unwrap()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn test_basic_web3_calls<C>(client: &C)
|
async fn test_basic_web3_calls<C>(client: &C)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
//! `trace_filter` types and support
|
//! `trace_filter` types and support
|
||||||
use reth_primitives::{serde_helper::num::u64_hex_or_decimal_opt, Address};
|
use reth_primitives::{serde_helper::num::u64_hex_or_decimal_opt, Address};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
/// Trace filter.
|
/// Trace filter.
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@ -14,15 +15,66 @@ pub struct TraceFilter {
|
|||||||
#[serde(with = "u64_hex_or_decimal_opt")]
|
#[serde(with = "u64_hex_or_decimal_opt")]
|
||||||
pub to_block: Option<u64>,
|
pub to_block: Option<u64>,
|
||||||
/// From address
|
/// From address
|
||||||
pub from_address: Option<Vec<Address>>,
|
#[serde(default)]
|
||||||
|
pub from_address: Vec<Address>,
|
||||||
/// To address
|
/// To address
|
||||||
pub to_address: Option<Vec<Address>>,
|
#[serde(default)]
|
||||||
|
pub to_address: Vec<Address>,
|
||||||
|
/// How to apply `from_address` and `to_address` filters.
|
||||||
|
#[serde(default)]
|
||||||
|
pub mode: TraceFilterMode,
|
||||||
/// Output offset
|
/// Output offset
|
||||||
pub after: Option<u64>,
|
pub after: Option<u64>,
|
||||||
/// Output amount
|
/// Output amount
|
||||||
pub count: Option<u64>,
|
pub count: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === impl TraceFilter ===
|
||||||
|
|
||||||
|
impl TraceFilter {
|
||||||
|
/// Returns a `TraceFilterMatcher` for this filter.
|
||||||
|
pub fn matcher(&self) -> TraceFilterMatcher {
|
||||||
|
let from_addresses = self.from_address.iter().cloned().collect();
|
||||||
|
let to_addresses = self.to_address.iter().cloned().collect();
|
||||||
|
TraceFilterMatcher { mode: self.mode, from_addresses, to_addresses }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How to apply `from_address` and `to_address` filters.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum TraceFilterMode {
|
||||||
|
/// Return traces for transactions with matching `from` OR `to` addresses.
|
||||||
|
#[default]
|
||||||
|
Union,
|
||||||
|
/// Only return traces for transactions with matching `from` _and_ `to` addresses.
|
||||||
|
Intersection,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper type for matching `from` and `to` addresses.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct TraceFilterMatcher {
|
||||||
|
mode: TraceFilterMode,
|
||||||
|
from_addresses: HashSet<Address>,
|
||||||
|
to_addresses: HashSet<Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TraceFilterMatcher {
|
||||||
|
/// Returns `true` if the given `from` and `to` addresses match this filter.
|
||||||
|
pub fn matches(&self, from: Address, to: Option<Address>) -> bool {
|
||||||
|
match self.mode {
|
||||||
|
TraceFilterMode::Union => {
|
||||||
|
self.from_addresses.contains(&from) ||
|
||||||
|
to.map_or(false, |to| self.to_addresses.contains(&to))
|
||||||
|
}
|
||||||
|
TraceFilterMode::Intersection => {
|
||||||
|
self.from_addresses.contains(&from) &&
|
||||||
|
to.map_or(false, |to| self.to_addresses.contains(&to))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@ -5,7 +5,6 @@ use crate::{
|
|||||||
utils::recover_raw_transaction,
|
utils::recover_raw_transaction,
|
||||||
EthTransactions,
|
EthTransactions,
|
||||||
},
|
},
|
||||||
result::internal_rpc_err,
|
|
||||||
TracingCallGuard,
|
TracingCallGuard,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -249,6 +248,86 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all transaction traces that match the given filter.
|
||||||
|
///
|
||||||
|
/// This is similar to [Self::trace_block] but only returns traces for transactions that match
|
||||||
|
/// the filter.
|
||||||
|
pub async fn trace_filter(
|
||||||
|
&self,
|
||||||
|
filter: TraceFilter,
|
||||||
|
) -> EthResult<Vec<LocalizedTransactionTrace>> {
|
||||||
|
let matcher = filter.matcher();
|
||||||
|
let TraceFilter { from_block, to_block, after: _after, count: _count, .. } = filter;
|
||||||
|
let start = from_block.unwrap_or(0);
|
||||||
|
let end = if let Some(to_block) = to_block {
|
||||||
|
to_block
|
||||||
|
} else {
|
||||||
|
self.provider().best_block_number()?
|
||||||
|
};
|
||||||
|
|
||||||
|
// ensure that the range is not too large, since we need to fetch all blocks in the range
|
||||||
|
let distance = end.saturating_sub(start);
|
||||||
|
if distance > 100 {
|
||||||
|
return Err(EthApiError::InvalidParams(
|
||||||
|
"Block range too large; currently limited to 100 blocks".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch all blocks in that range
|
||||||
|
let blocks = self.provider().block_range(start..=end)?;
|
||||||
|
|
||||||
|
// find relevant blocks to trace
|
||||||
|
let mut target_blocks = Vec::new();
|
||||||
|
for block in blocks {
|
||||||
|
let mut transaction_indices = HashSet::new();
|
||||||
|
for (tx_idx, tx) in block.body.iter().enumerate() {
|
||||||
|
let from = tx.recover_signer().ok_or(BlockError::InvalidSignature)?;
|
||||||
|
let to = tx.to();
|
||||||
|
if matcher.matches(from, to) {
|
||||||
|
transaction_indices.insert(tx_idx as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !transaction_indices.is_empty() {
|
||||||
|
target_blocks.push((block.number, transaction_indices));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this could be optimized to only trace the block until the highest matching index in
|
||||||
|
// that block
|
||||||
|
|
||||||
|
// trace all relevant blocks
|
||||||
|
let mut block_traces = Vec::with_capacity(target_blocks.len());
|
||||||
|
for (num, indices) in target_blocks {
|
||||||
|
let traces = self.trace_block_with(
|
||||||
|
num.into(),
|
||||||
|
TracingInspectorConfig::default_parity(),
|
||||||
|
move |tx_info, inspector, res, _, _| {
|
||||||
|
if let Some(idx) = tx_info.index {
|
||||||
|
if !indices.contains(&idx) {
|
||||||
|
// only record traces for relevant transactions
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let traces = inspector
|
||||||
|
.with_transaction_gas_used(res.gas_used())
|
||||||
|
.into_parity_builder()
|
||||||
|
.into_localized_transaction_traces(tx_info);
|
||||||
|
Ok(Some(traces))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
block_traces.push(traces);
|
||||||
|
}
|
||||||
|
|
||||||
|
let block_traces = futures::future::try_join_all(block_traces).await?;
|
||||||
|
let all_traces = block_traces
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|traces| traces.into_iter().flatten().flat_map(|traces| traces.into_iter()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(all_traces)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all traces for the given transaction hash
|
/// Returns all traces for the given transaction hash
|
||||||
pub async fn trace_transaction(
|
pub async fn trace_transaction(
|
||||||
&self,
|
&self,
|
||||||
@ -532,8 +611,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for `trace_filter`
|
/// Handler for `trace_filter`
|
||||||
async fn trace_filter(&self, _filter: TraceFilter) -> Result<Vec<LocalizedTransactionTrace>> {
|
///
|
||||||
Err(internal_rpc_err("unimplemented"))
|
/// This is similar to `eth_getLogs` but for traces.
|
||||||
|
///
|
||||||
|
/// # Limitations
|
||||||
|
/// This currently requires block filter fields, since reth does not have address indices yet.
|
||||||
|
async fn trace_filter(&self, filter: TraceFilter) -> Result<Vec<LocalizedTransactionTrace>> {
|
||||||
|
Ok(TraceApi::trace_filter(self, filter).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns transaction trace at given index.
|
/// Returns transaction trace at given index.
|
||||||
|
|||||||
Reference in New Issue
Block a user