diff --git a/Cargo.lock b/Cargo.lock index 8aa1c92c5d14..8c818c5c0238 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7678,9 +7678,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8faa444297615a4e020acb64146b0603c9c395c03a97c17fd9028816d3b4d63e" +checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef" dependencies = [ "displaydoc", "serde", diff --git a/crates/rpc/rpc-types/src/eth/trace/filter.rs b/crates/rpc/rpc-types/src/eth/trace/filter.rs index 3121335daeb9..0c9eac10f669 100644 --- a/crates/rpc/rpc-types/src/eth/trace/filter.rs +++ b/crates/rpc/rpc-types/src/eth/trace/filter.rs @@ -1,6 +1,7 @@ //! `trace_filter` types and support use reth_primitives::{serde_helper::num::u64_hex_or_decimal_opt, Address}; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; /// Trace filter. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -14,15 +15,66 @@ pub struct TraceFilter { #[serde(with = "u64_hex_or_decimal_opt")] pub to_block: Option, /// From address - pub from_address: Option>, + #[serde(default)] + pub from_address: Vec
, /// To address - pub to_address: Option>, + #[serde(default)] + pub to_address: Vec
, + /// How to apply `from_address` and `to_address` filters. + #[serde(default)] + pub mode: TraceFilterMode, /// Output offset pub after: Option, /// Output amount pub count: Option, } +// === 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
, + to_addresses: HashSet
, +} + +impl TraceFilterMatcher { + /// Returns `true` if the given `from` and `to` addresses match this filter. + pub fn matches(&self, from: Address, to: Option
) -> 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)] mod tests { use super::*; diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index c3b3e7212c8a..f38fc0e57e76 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -5,7 +5,6 @@ use crate::{ utils::recover_raw_transaction, EthTransactions, }, - result::internal_rpc_err, TracingCallGuard, }; use async_trait::async_trait; @@ -249,12 +248,16 @@ where } } - /// Returns all traces for the given transaction hash + /// 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> { - let TraceFilter { from_block, to_block, from_address, to_address, after, count } = filter; + 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 @@ -265,15 +268,64 @@ where // 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()) - ) + 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)); + } } - self.provider().block_ran + // 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(); - todo!() + Ok(all_traces) } /// Returns all traces for the given transaction hash