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));
}
}