From d02e16fb707a169c3a7c130c542ea73747140795 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Tue, 23 Jul 2024 00:31:34 +0800 Subject: [PATCH 1/6] feat(trace): filter with TransactionTrace Signed-off-by: jsvisa --- crates/rpc-types-trace/src/filter.rs | 68 ++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/crates/rpc-types-trace/src/filter.rs b/crates/rpc-types-trace/src/filter.rs index d0fae8e2412..dbdc862f774 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,59 @@ pub enum TraceFilterMode { Intersection, } +/// 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(HashSet
); + +impl AddressFilter { + /// Returns `true` if the given address is in the filter. + pub fn matches(&self, addr: &Address) -> bool { + self.0.is_empty() || self.0.contains(addr) + } + + /// Returns `true` if the filter is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + /// Helper type for matching `from` and `to` addresses. Empty sets 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. + 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.is_empty(), + }, + ), + Action::Reward(RewardAction { author, .. }) => { + (self.from_addresses.matches(&author), self.to_addresses.is_empty()) + } + Action::Selfdestruct(SelfdestructAction { address, refund_address, .. }) => { + (self.from_addresses.matches(&address), self.to_addresses.matches(&refund_address)) + } + }; + + match self.mode { + TraceFilterMode::Union => from_matches || to_matches, + TraceFilterMode::Intersection => from_matches && to_matches, } } } From 7f4ca13bab61d1f937564f945b09bd512f855d11 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Tue, 23 Jul 2024 01:23:59 +0800 Subject: [PATCH 2/6] wip: add test case Signed-off-by: jsvisa --- crates/rpc-types-trace/src/filter.rs | 208 +++++++++++++++++---------- 1 file changed, 133 insertions(+), 75 deletions(-) diff --git a/crates/rpc-types-trace/src/filter.rs b/crates/rpc-types-trace/src/filter.rs index dbdc862f774..a93843f3623 100644 --- a/crates/rpc-types-trace/src/filter.rs +++ b/crates/rpc-types-trace/src/filter.rs @@ -104,6 +104,11 @@ pub enum TraceFilterMode { pub struct AddressFilter(HashSet
); impl AddressFilter { + /// Creates a new `AddressFilter` from a list of addresses. + pub fn new(addresses: Vec
) -> Self { + Self(addresses.into_iter().collect()) + } + /// Returns `true` if the given address is in the filter. pub fn matches(&self, addr: &Address) -> bool { self.0.is_empty() || self.0.contains(addr) @@ -139,12 +144,12 @@ impl TraceFilterMatcher { _ => self.to_addresses.is_empty(), }, ), - Action::Reward(RewardAction { author, .. }) => { - (self.from_addresses.matches(&author), self.to_addresses.is_empty()) - } 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(&author), self.to_addresses.is_empty()) + } }; match self.mode { @@ -157,7 +162,10 @@ impl TraceFilterMatcher { #[cfg(test)] mod tests { use super::*; + use crate::parity::RewardType; + use alloy_primitives::{Address, Bytes, TxHash, B256, U256, U64}; use serde_json::json; + use std::str::FromStr; #[test] fn test_parse_filter() { @@ -167,90 +175,140 @@ mod tests { assert_eq!(filter.to_block, Some(5)); } + fn address(addr: &str) -> Address { + addr.parse().unwrap() + } + + fn create_trace(action: Action, result: Option) -> TransactionTrace { + TransactionTrace { action, result, subtraces: 1, trace_address: vec![], error: None } + } + #[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))); + fn test_matches_call_action_union_mode() { + let matcher = TraceFilterMatcher { + mode: TraceFilterMode::Union, + from_addresses: AddressFilter::new(vec![address( + "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", + )]), + to_addresses: AddressFilter::new(vec![address( + "0x4f4495243837681061c4743b74b3eedf548d56a5", + )]), + }; + + let trace = create_trace( + Action::Call(CallAction { + from: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), + call_type: "call".to_string(), + gas: U256::from_str("0x4a0d00").unwrap(), + input: vec![0x12], + to: address("0x4f4495243837681061c4743b74b3eedf548d56a5"), + value: U256::zero(), + }), + Some(TraceOutput::Call(CallOutput { + gas_used: U256::from_str("0x17d337").unwrap(), + output: Bytes::new(), + })), + ); + + assert!(matcher.matches(&trace)); } #[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))); + fn test_matches_create_action_intersection_mode() { + let matcher = TraceFilterMatcher { + mode: TraceFilterMode::Intersection, + from_addresses: AddressFilter::new(vec![address( + "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", + )]), + to_addresses: AddressFilter::new(vec![address( + "0x4f4495243837681061c4743b74b3eedf548d56a5", + )]), + }; + + let trace = create_trace( + Action::Create(CreateAction { + from: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), + gas: U256::from_str("0x4a0d00").unwrap(), + init: vec![], + value: U256::zero(), + }), + Some(TraceOutput::Create(CreateOutput { + code: Bytes::new(), + address: address("0x4f4495243837681061c4743b74b3eedf548d56a5"), + gas_used: 0, + })), + ); + + 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_matches_reward_action_no_matches() { + let matcher = TraceFilterMatcher { + mode: TraceFilterMode::Union, + from_addresses: AddressFilter::new(vec![address( + "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", + )]), + to_addresses: AddressFilter::new(vec![address( + "0x4f4495243837681061c4743b74b3eedf548d56a5", + )]), + }; + + let trace = create_trace( + Action::Reward(RewardAction { + reward_type: RewardType::Block, + author: address("0x1234567890123456789012345678901234567890"), + value: U256::from_str("0x1").unwrap(), + }), + None, + ); + + assert!(!matcher.matches(&trace)); } #[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))); + fn test_matches_selfdestruct_action_partial_match() { + let matcher = TraceFilterMatcher { + mode: TraceFilterMode::Union, + from_addresses: AddressFilter::new(vec![address( + "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", + )]), + to_addresses: AddressFilter::new(vec![]), + }; + + let trace = create_trace( + Action::Selfdestruct(SelfdestructAction { + address: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), + refund_address: address("0x4f4495243837681061c4743b74b3eedf548d56a5"), + balance: U256::from_str("0x1").unwrap(), + }), + None, + ); + + assert!(matcher.matches(&trace)); } #[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))); + fn test_matches_selfdestruct_action_intersection_no_match() { + let matcher = TraceFilterMatcher { + mode: TraceFilterMode::Intersection, + from_addresses: AddressFilter::new(vec![address( + "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", + )]), + to_addresses: AddressFilter::new(vec![address( + "0x4f4495243837681061c4743b74b3eedf548d56a5", + )]), + }; + + let trace = create_trace( + Action::Selfdestruct(SelfdestructAction { + address: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), + refund_address: address("0x1234567890123456789012345678901234567890"), + balance: U256::from_str("0x1").unwrap(), + }), + None, + ); + + assert!(!matcher.matches(&trace)); } } From e8c214362ad2874ee1acb3b6e3d4d93fab311789 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Tue, 23 Jul 2024 06:21:17 +0800 Subject: [PATCH 3/6] Revert "wip: add test case" This reverts commit 7f4ca13bab61d1f937564f945b09bd512f855d11. Signed-off-by: jsvisa --- crates/rpc-types-trace/src/filter.rs | 208 ++++++++++----------------- 1 file changed, 75 insertions(+), 133 deletions(-) diff --git a/crates/rpc-types-trace/src/filter.rs b/crates/rpc-types-trace/src/filter.rs index a93843f3623..28f83ad824c 100644 --- a/crates/rpc-types-trace/src/filter.rs +++ b/crates/rpc-types-trace/src/filter.rs @@ -104,18 +104,13 @@ pub enum TraceFilterMode { pub struct AddressFilter(HashSet
); impl AddressFilter { - /// Creates a new `AddressFilter` from a list of addresses. - pub fn new(addresses: Vec
) -> Self { - Self(addresses.into_iter().collect()) - } - /// Returns `true` if the given address is in the filter. pub fn matches(&self, addr: &Address) -> bool { self.0.is_empty() || self.0.contains(addr) } /// Returns `true` if the filter is empty. - pub fn is_empty(&self) -> bool { + pub fn matches_all(&self) -> bool { self.0.is_empty() } } @@ -141,14 +136,14 @@ impl TraceFilterMatcher { Some(TraceOutput::Create(CreateOutput { address: to, .. })) => { self.to_addresses.matches(&to) } - _ => self.to_addresses.is_empty(), + _ => 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(&author), self.to_addresses.is_empty()) + (self.from_addresses.matches_all(), self.to_addresses.matches(&author)) } }; @@ -162,10 +157,7 @@ impl TraceFilterMatcher { #[cfg(test)] mod tests { use super::*; - use crate::parity::RewardType; - use alloy_primitives::{Address, Bytes, TxHash, B256, U256, U64}; use serde_json::json; - use std::str::FromStr; #[test] fn test_parse_filter() { @@ -175,140 +167,90 @@ mod tests { assert_eq!(filter.to_block, Some(5)); } - fn address(addr: &str) -> Address { - addr.parse().unwrap() - } - - fn create_trace(action: Action, result: Option) -> TransactionTrace { - TransactionTrace { action, result, subtraces: 1, trace_address: vec![], error: None } - } - #[test] - fn test_matches_call_action_union_mode() { - let matcher = TraceFilterMatcher { - mode: TraceFilterMode::Union, - from_addresses: AddressFilter::new(vec![address( - "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", - )]), - to_addresses: AddressFilter::new(vec![address( - "0x4f4495243837681061c4743b74b3eedf548d56a5", - )]), - }; - - let trace = create_trace( - Action::Call(CallAction { - from: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), - call_type: "call".to_string(), - gas: U256::from_str("0x4a0d00").unwrap(), - input: vec![0x12], - to: address("0x4f4495243837681061c4743b74b3eedf548d56a5"), - value: U256::zero(), - }), - Some(TraceOutput::Call(CallOutput { - gas_used: U256::from_str("0x17d337").unwrap(), - output: Bytes::new(), - })), - ); - - assert!(matcher.matches(&trace)); + 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))); } #[test] - fn test_matches_create_action_intersection_mode() { - let matcher = TraceFilterMatcher { - mode: TraceFilterMode::Intersection, - from_addresses: AddressFilter::new(vec![address( - "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", - )]), - to_addresses: AddressFilter::new(vec![address( - "0x4f4495243837681061c4743b74b3eedf548d56a5", - )]), - }; - - let trace = create_trace( - Action::Create(CreateAction { - from: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), - gas: U256::from_str("0x4a0d00").unwrap(), - init: vec![], - value: U256::zero(), - }), - Some(TraceOutput::Create(CreateOutput { - code: Bytes::new(), - address: address("0x4f4495243837681061c4743b74b3eedf548d56a5"), - gas_used: 0, - })), - ); - - assert!(matcher.matches(&trace)); + 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))); } #[test] - fn test_matches_reward_action_no_matches() { - let matcher = TraceFilterMatcher { - mode: TraceFilterMode::Union, - from_addresses: AddressFilter::new(vec![address( - "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", - )]), - to_addresses: AddressFilter::new(vec![address( - "0x4f4495243837681061c4743b74b3eedf548d56a5", - )]), - }; - - let trace = create_trace( - Action::Reward(RewardAction { - reward_type: RewardType::Block, - author: address("0x1234567890123456789012345678901234567890"), - value: U256::from_str("0x1").unwrap(), - }), - None, - ); - - assert!(!matcher.matches(&trace)); + 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))); } #[test] - fn test_matches_selfdestruct_action_partial_match() { - let matcher = TraceFilterMatcher { - mode: TraceFilterMode::Union, - from_addresses: AddressFilter::new(vec![address( - "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", - )]), - to_addresses: AddressFilter::new(vec![]), - }; - - let trace = create_trace( - Action::Selfdestruct(SelfdestructAction { - address: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), - refund_address: address("0x4f4495243837681061c4743b74b3eedf548d56a5"), - balance: U256::from_str("0x1").unwrap(), - }), - None, - ); - - assert!(matcher.matches(&trace)); + 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))); } #[test] - fn test_matches_selfdestruct_action_intersection_no_match() { - let matcher = TraceFilterMatcher { - mode: TraceFilterMode::Intersection, - from_addresses: AddressFilter::new(vec![address( - "0xc77820eef59629fc8d88154977bc8de8a1b2f4ae", - )]), - to_addresses: AddressFilter::new(vec![address( - "0x4f4495243837681061c4743b74b3eedf548d56a5", - )]), - }; - - let trace = create_trace( - Action::Selfdestruct(SelfdestructAction { - address: address("0xc77820eef59629fc8d88154977bc8de8a1b2f4ae"), - refund_address: address("0x1234567890123456789012345678901234567890"), - balance: U256::from_str("0x1").unwrap(), - }), - None, - ); - - assert!(!matcher.matches(&trace)); + 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))); } } From 14d53a9e27f0749d1c0b4c98967dc180ebbd32c6 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Tue, 23 Jul 2024 08:51:54 +0800 Subject: [PATCH 4/6] add test case Signed-off-by: jsvisa --- crates/rpc-types-trace/src/filter.rs | 270 +++++++++++++++++++-------- 1 file changed, 193 insertions(+), 77 deletions(-) diff --git a/crates/rpc-types-trace/src/filter.rs b/crates/rpc-types-trace/src/filter.rs index 28f83ad824c..ff5a5051241 100644 --- a/crates/rpc-types-trace/src/filter.rs +++ b/crates/rpc-types-trace/src/filter.rs @@ -103,6 +103,18 @@ pub enum TraceFilterMode { #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct AddressFilter(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. pub fn matches(&self, addr: &Address) -> bool { @@ -157,6 +169,7 @@ impl TraceFilterMatcher { #[cfg(test)] mod tests { use super::*; + use alloy_primitives::{Bytes, U256}; use serde_json::json; #[test] @@ -169,88 +182,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)); } } From 5b1c80210031b1a39aac58c8b07438735dd727dc Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 23 Jul 2024 22:58:16 +0800 Subject: [PATCH 5/6] Update crates/rpc-types-trace/src/filter.rs Co-authored-by: Matthias Seitz --- crates/rpc-types-trace/src/filter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc-types-trace/src/filter.rs b/crates/rpc-types-trace/src/filter.rs index ff5a5051241..0396135f3f2 100644 --- a/crates/rpc-types-trace/src/filter.rs +++ b/crates/rpc-types-trace/src/filter.rs @@ -101,7 +101,7 @@ pub enum TraceFilterMode { /// This is a set of addresses to match against. /// An empty set matches all addresses. #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct AddressFilter(HashSet
); +pub struct AddressFilter(pub HashSet
); impl FromIterator
for AddressFilter { fn from_iter>(iter: I) -> Self { From 11a3bd5ae58f0b1f8bf6306e6bbc1ce2b9ac44b4 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Tue, 23 Jul 2024 23:40:11 +0800 Subject: [PATCH 6/6] add more docs Signed-off-by: jsvisa --- crates/rpc-types-trace/src/filter.rs | 33 ++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/rpc-types-trace/src/filter.rs b/crates/rpc-types-trace/src/filter.rs index 0396135f3f2..3e17294ad69 100644 --- a/crates/rpc-types-trace/src/filter.rs +++ b/crates/rpc-types-trace/src/filter.rs @@ -116,18 +116,20 @@ impl From> for AddressFilter { } impl AddressFilter { - /// Returns `true` if the given address is in the filter. + /// 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.0.is_empty() || self.0.contains(addr) + self.matches_all() || self.0.contains(addr) } - /// Returns `true` if the filter is empty. + /// Returns `true` if the address set is empty. pub fn matches_all(&self) -> bool { self.0.is_empty() } } -/// Helper type for matching `from` and `to` addresses. Empty sets match all addresses. +/// `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, @@ -137,6 +139,29 @@ pub struct TraceFilterMatcher { impl TraceFilterMatcher { /// 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, .. }) => {