diff --git a/crates/rpc-types-trace/src/filter.rs b/crates/rpc-types-trace/src/filter.rs index d0fae8e2412..3e17294ad69 100644 --- a/crates/rpc-types-trace/src/filter.rs +++ b/crates/rpc-types-trace/src/filter.rs @@ -1,4 +1,8 @@ //! `trace_filter` types and support +use crate::parity::{ + Action, CallAction, CreateAction, CreateOutput, RewardAction, SelfdestructAction, TraceOutput, + TransactionTrace, +}; use alloy_primitives::Address; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -93,31 +97,96 @@ pub enum TraceFilterMode { Intersection, } -/// Helper type for matching `from` and `to` addresses. Empty sets match all addresses. +/// Address filter. +/// This is a set of addresses to match against. +/// An empty set matches all addresses. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct AddressFilter(pub HashSet
); + +impl FromIterator
for AddressFilter { + fn from_iter>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl From> for AddressFilter { + fn from(addrs: Vec
) -> Self { + Self::from_iter(addrs) + } +} + +impl AddressFilter { + /// Returns `true` if the given address is in the filter or the filter address set is empty. + pub fn matches(&self, addr: &Address) -> bool { + self.matches_all() || self.0.contains(addr) + } + + /// Returns `true` if the address set is empty. + pub fn matches_all(&self) -> bool { + self.0.is_empty() + } +} + +/// `TraceFilterMatcher` is a filter used for matching `TransactionTrace` based on +/// it's action and result(if available). It allows filtering traces by their mode, from address +/// set, and to address set, and empty address set means match all addresses. #[derive(Clone, Debug, PartialEq, Eq)] pub struct TraceFilterMatcher { mode: TraceFilterMode, - from_addresses: HashSet
, - to_addresses: HashSet
, + from_addresses: AddressFilter, + to_addresses: AddressFilter, } 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.from_addresses.is_empty(), self.to_addresses.is_empty()) { - (true, true) => true, - (false, true) => self.from_addresses.contains(&from), - (true, false) => to.map_or(false, |to_addr| self.to_addresses.contains(&to_addr)), - (false, false) => match self.mode { - TraceFilterMode::Union => { - self.from_addresses.contains(&from) - || to.map_or(false, |to_addr| self.to_addresses.contains(&to_addr)) - } - TraceFilterMode::Intersection => { - self.from_addresses.contains(&from) - && to.map_or(false, |to_addr| self.to_addresses.contains(&to_addr)) - } - }, + /// Returns `true` if the given `TransactionTrace` matches this filter. + /// + /// # Arguments + /// + /// - `trace`: A reference to a `TransactionTrace` to be evaluated against the filter. + /// + /// # Returns + /// + /// - `true` if the transaction trace matches the filter criteria; otherwise, `false`. + /// + /// # Behavior + /// + /// The function evaluates whether the `trace` matches based on its action type: + /// - `Call`: Matches if either the `from` or `to` addresses in the call action match the + /// filter's address criteria. + /// - `Create`: Matches if the `from` address in action matches, and the result's address (if + /// available) matches the filter's address criteria. + /// - `Selfdestruct`: Matches if the `address` and `refund_address` matches the filter's address + /// criteria. + /// - `Reward`: Matches if the `author` address matches the filter's `to_addresses` criteria. + /// + /// The overall result depends on the filter mode: + /// - `Union` mode: The trace matches if either the `from` or `to` address matches. + /// - `Intersection` mode: The trace matches only if both the `from` and `to` addresses match. + pub fn matches(&self, trace: &TransactionTrace) -> bool { + let (from_matches, to_matches) = match trace.action { + Action::Call(CallAction { from, to, .. }) => { + (self.from_addresses.matches(&from), self.to_addresses.matches(&to)) + } + Action::Create(CreateAction { from, .. }) => ( + self.from_addresses.matches(&from), + match trace.result { + Some(TraceOutput::Create(CreateOutput { address: to, .. })) => { + self.to_addresses.matches(&to) + } + _ => self.to_addresses.matches_all(), + }, + ), + Action::Selfdestruct(SelfdestructAction { address, refund_address, .. }) => { + (self.from_addresses.matches(&address), self.to_addresses.matches(&refund_address)) + } + Action::Reward(RewardAction { author, .. }) => { + (self.from_addresses.matches_all(), self.to_addresses.matches(&author)) + } + }; + + match self.mode { + TraceFilterMode::Union => from_matches || to_matches, + TraceFilterMode::Intersection => from_matches && to_matches, } } } @@ -125,6 +194,7 @@ impl TraceFilterMatcher { #[cfg(test)] mod tests { use super::*; + use alloy_primitives::{Bytes, U256}; use serde_json::json; #[test] @@ -137,88 +207,191 @@ mod tests { #[test] fn test_filter_matcher_addresses_unspecified() { - let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); - let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); - let filter_json = json!({ - "fromBlock": "0x3", - "toBlock": "0x5", - }); - let filter: TraceFilter = - serde_json::from_value(filter_json).expect("Failed to parse filter"); - let matcher = filter.matcher(); - assert!(matcher.matches(test_addr_d8, None)); - assert!(matcher.matches(test_addr_16, None)); - assert!(matcher.matches(test_addr_d8, Some(test_addr_16))); - assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); - } + let filter_json = json!({ "fromBlock": "0x3", "toBlock": "0x5" }); + let matcher = serde_json::from_value::(filter_json).unwrap().matcher(); + let s = r#"{ + "action": { + "from": "0x66e29f0b6b1b07071f2fde4345d512386cb66f5f", + "callType": "call", + "gas": "0x10bfc", + "input": "0x", + "to": "0x160f5f00288e9e1cc8655b327e081566e580a71d", + "value": "0x244b" + }, + "error": "Reverted", + "result": { + "gasUsed": "0x9daf", + "output": "0x" + }, + "subtraces": 3, + "traceAddress": [], + "type": "call" + }"#; + let trace = serde_json::from_str::(s).unwrap(); - #[test] - fn test_filter_matcher_from_address() { - let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); - let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); - let filter_json = json!({ - "fromBlock": "0x3", - "toBlock": "0x5", - "fromAddress": [test_addr_d8] - }); - let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); - let matcher = filter.matcher(); - assert!(matcher.matches(test_addr_d8, None)); - assert!(!matcher.matches(test_addr_16, None)); - assert!(matcher.matches(test_addr_d8, Some(test_addr_16))); - assert!(!matcher.matches(test_addr_16, Some(test_addr_d8))); + assert!(matcher.matches(&trace)); } #[test] - fn test_filter_matcher_to_address() { - let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); - let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); - let filter_json = json!({ - "fromBlock": "0x3", - "toBlock": "0x5", - "toAddress": [test_addr_d8], - }); - let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); - let matcher = filter.matcher(); - assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); - assert!(!matcher.matches(test_addr_16, None)); - assert!(!matcher.matches(test_addr_d8, Some(test_addr_16))); - } + fn test_filter_matcher() { + let addr0 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); + let addr1 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); + let addr2 = "0x160f5f00288e9e1cc8655b327e081566e580a71f".parse().unwrap(); - #[test] - fn test_filter_matcher_both_addresses_union() { - let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); - let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); - let filter_json = json!({ - "fromBlock": "0x3", - "toBlock": "0x5", - "fromAddress": [test_addr_16], - "toAddress": [test_addr_d8], - }); - let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); - let matcher = filter.matcher(); - assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); - assert!(matcher.matches(test_addr_16, None)); - assert!(matcher.matches(test_addr_d8, Some(test_addr_d8))); - assert!(!matcher.matches(test_addr_d8, Some(test_addr_16))); - } + let m0 = TraceFilterMatcher { + mode: TraceFilterMode::Union, + from_addresses: Default::default(), + to_addresses: Default::default(), + }; - #[test] - fn test_filter_matcher_both_addresses_intersection() { - let test_addr_d8 = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap(); - let test_addr_16 = "0x160f5f00288e9e1cc8655b327e081566e580a71d".parse().unwrap(); - let filter_json = json!({ - "fromBlock": "0x3", - "toBlock": "0x5", - "fromAddress": [test_addr_16], - "toAddress": [test_addr_d8], - "mode": "intersection", - }); - let filter: TraceFilter = serde_json::from_value(filter_json).unwrap(); - let matcher = filter.matcher(); - assert!(matcher.matches(test_addr_16, Some(test_addr_d8))); - assert!(!matcher.matches(test_addr_16, None)); - assert!(!matcher.matches(test_addr_d8, Some(test_addr_d8))); - assert!(!matcher.matches(test_addr_d8, Some(test_addr_16))); + let m1 = TraceFilterMatcher { + mode: TraceFilterMode::Union, + from_addresses: AddressFilter::from(vec![addr0]), + to_addresses: Default::default(), + }; + + let m2 = TraceFilterMatcher { + mode: TraceFilterMode::Union, + from_addresses: AddressFilter::from(vec![]), + to_addresses: AddressFilter::from(vec![addr1]), + }; + + let m3 = TraceFilterMatcher { + mode: TraceFilterMode::Union, + from_addresses: AddressFilter::from(vec![addr0]), + to_addresses: AddressFilter::from(vec![addr1]), + }; + + let m4 = TraceFilterMatcher { + mode: TraceFilterMode::Intersection, + from_addresses: Default::default(), + to_addresses: Default::default(), + }; + + let m5 = TraceFilterMatcher { + mode: TraceFilterMode::Intersection, + from_addresses: AddressFilter::from(vec![addr0]), + to_addresses: Default::default(), + }; + + let m6 = TraceFilterMatcher { + mode: TraceFilterMode::Intersection, + from_addresses: Default::default(), + to_addresses: AddressFilter::from(vec![addr1]), + }; + + let m7 = TraceFilterMatcher { + mode: TraceFilterMode::Intersection, + from_addresses: AddressFilter::from(vec![addr0]), + to_addresses: AddressFilter::from(vec![addr1]), + }; + + // normal call 0 + let trace = TransactionTrace { + action: Action::Call(CallAction { from: addr0, to: addr1, ..Default::default() }), + ..Default::default() + }; + assert!(m0.matches(&trace)); + assert!(m1.matches(&trace)); + assert!(m2.matches(&trace)); + assert!(m3.matches(&trace)); + assert!(m4.matches(&trace)); + assert!(m5.matches(&trace)); + assert!(m6.matches(&trace)); + assert!(m7.matches(&trace)); + + // normal call 1 + let trace = TransactionTrace { + action: Action::Call(CallAction { from: addr0, to: addr2, ..Default::default() }), + ..Default::default() + }; + assert!(m0.matches(&trace)); + assert!(m1.matches(&trace)); + assert!(m2.matches(&trace)); + assert!(m3.matches(&trace)); + assert!(m4.matches(&trace)); + assert!(m5.matches(&trace)); + assert!(!m6.matches(&trace)); + assert!(!m7.matches(&trace)); + + // create success + let trace = TransactionTrace { + action: Action::Create(CreateAction { + from: addr0, + gas: 10240, + init: Bytes::new(), + value: U256::from(0), + }), + result: Some(TraceOutput::Create(CreateOutput { + address: addr1, + code: Bytes::new(), + gas_used: 1025, + })), + ..Default::default() + }; + assert!(m0.matches(&trace)); + assert!(m1.matches(&trace)); + assert!(m2.matches(&trace)); + assert!(m3.matches(&trace)); + assert!(m4.matches(&trace)); + assert!(m5.matches(&trace)); + assert!(m6.matches(&trace)); + assert!(m7.matches(&trace)); + + // create failure + let trace = TransactionTrace { + action: Action::Create(CreateAction { + from: addr0, + gas: 100, + init: Bytes::new(), + value: U256::from(0), + }), + error: Some("out of gas".into()), + ..Default::default() + }; + assert!(m0.matches(&trace)); + assert!(m1.matches(&trace)); + assert!(m2.matches(&trace)); + assert!(m3.matches(&trace)); + assert!(m4.matches(&trace)); + assert!(m5.matches(&trace)); + assert!(!m6.matches(&trace)); + assert!(!m7.matches(&trace)); + + // selfdestruct + let trace = TransactionTrace { + action: Action::Selfdestruct(SelfdestructAction { + address: addr0, + refund_address: addr1, + balance: U256::from(0), + }), + ..Default::default() + }; + assert!(m0.matches(&trace)); + assert!(m1.matches(&trace)); + assert!(m2.matches(&trace)); + assert!(m3.matches(&trace)); + assert!(m4.matches(&trace)); + assert!(m5.matches(&trace)); + assert!(m6.matches(&trace)); + assert!(m7.matches(&trace)); + + // reward + let trace = TransactionTrace { + action: Action::Reward(RewardAction { + author: addr0, + reward_type: crate::parity::RewardType::Block, + value: U256::from(0), + }), + ..Default::default() + }; + assert!(m0.matches(&trace)); + assert!(m1.matches(&trace)); + assert!(m2.matches(&trace)); + assert!(!m3.matches(&trace)); + assert!(m4.matches(&trace)); + assert!(!m5.matches(&trace)); + assert!(!m6.matches(&trace)); + assert!(!m7.matches(&trace)); } }