From 99f2bfe77de81f53aa9be2f0a1d51b0fab7119db Mon Sep 17 00:00:00 2001 From: David Kulman <77690992+Kuly14@users.noreply.github.com> Date: Mon, 20 Feb 2023 22:27:33 +0800 Subject: [PATCH] feat: rpc Filter type (#1389) --- Cargo.lock | 1 + crates/primitives/Cargo.toml | 1 + crates/primitives/src/bloom.rs | 193 ++++- crates/primitives/src/filter.rs | 1217 +++++++++++++++++++++++++++++++ crates/primitives/src/lib.rs | 1 + 5 files changed, 1409 insertions(+), 4 deletions(-) create mode 100644 crates/primitives/src/filter.rs diff --git a/Cargo.lock b/Cargo.lock index b174a1c7f3..b2e23a26f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4692,6 +4692,7 @@ dependencies = [ "bytes", "crc", "criterion", + "crunchy", "derive_more", "ethers-core", "fixed-hash", diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index beea3fefb4..df03f8b5e3 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -23,6 +23,7 @@ revm-primitives = { version="1.0.0", features = ["serde"] } ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } parity-scale-codec = { version = "3.2.1", features = ["derive", "bytes"] } tiny-keccak = { version = "2.0", features = ["keccak"] } +crunchy = { version = "0.2.2", default-features = false, features = ["limit_256"] } # Bloom fixed-hash = { version = "0.8", default-features = false, features = [ diff --git a/crates/primitives/src/bloom.rs b/crates/primitives/src/bloom.rs index be929af310..884f386d38 100644 --- a/crates/primitives/src/bloom.rs +++ b/crates/primitives/src/bloom.rs @@ -1,16 +1,201 @@ //! Bloom type. +#![allow(missing_docs)] use crate::{impl_fixed_hash_type, keccak256, Log}; use bytes::Buf; +use core::{mem, ops}; +use crunchy::unroll; use derive_more::{AsRef, Deref}; -use fixed_hash::construct_fixed_hash; +use fixed_hash::*; use impl_serde::impl_fixed_hash_serde; use reth_codecs::{impl_hash_compact, Compact}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper, RlpMaxEncodedLen}; +use tiny_keccak::{Hasher, Keccak}; /// Length of bloom filter used for Ethereum. -pub const BLOOM_BYTE_LENGTH: usize = 256; +pub const BLOOM_BITS: u32 = 3; +pub const BLOOM_SIZE: usize = 256; -impl_fixed_hash_type!((Bloom, BLOOM_BYTE_LENGTH)); +impl_fixed_hash_type!((Bloom, BLOOM_SIZE)); + +/// Returns log2. +fn log2(x: usize) -> u32 { + if x <= 1 { + return 0 + } + + let n = x.leading_zeros(); + mem::size_of::() as u32 * 8 - n +} + +#[derive(Debug)] +pub enum Input<'a> { + Raw(&'a [u8]), + Hash(&'a [u8; 32]), +} + +enum Hash<'a> { + Ref(&'a [u8; 32]), + Owned([u8; 32]), +} + +impl<'a> From> for Hash<'a> { + fn from(input: Input<'a>) -> Self { + match input { + Input::Raw(raw) => { + let mut out = [0u8; 32]; + let mut keccak256 = Keccak::v256(); + keccak256.update(raw); + keccak256.finalize(&mut out); + Hash::Owned(out) + } + Input::Hash(hash) => Hash::Ref(hash), + } + } +} + +impl<'a> ops::Index for Hash<'a> { + type Output = u8; + + fn index(&self, index: usize) -> &u8 { + match *self { + Hash::Ref(r) => &r[index], + Hash::Owned(ref hash) => &hash[index], + } + } +} + +impl<'a> Hash<'a> { + fn len(&self) -> usize { + match *self { + Hash::Ref(r) => r.len(), + Hash::Owned(ref hash) => hash.len(), + } + } +} + +// impl<'a> PartialEq> for Bloom { +// fn eq(&self, other: &BloomRef<'a>) -> bool { +// let s_ref: &[u8] = &self.0; +// let o_ref: &[u8] = other.0; +// s_ref.eq(o_ref) +// } +// } + +impl<'a> From> for Bloom { + fn from(input: Input<'a>) -> Bloom { + let mut bloom = Bloom::default(); + bloom.accrue(input); + bloom + } +} + +impl Bloom { + pub fn contains_bloom<'a, B>(&self, bloom: B) -> bool + where + BloomRef<'a>: From, + { + let bloom_ref: BloomRef<'_> = bloom.into(); + // workaround for https://github.com/rust-lang/rust/issues/43644 + self.contains_bloom_ref(bloom_ref) + } + + fn contains_bloom_ref(&self, bloom: BloomRef<'_>) -> bool { + let self_ref: BloomRef<'_> = self.into(); + self_ref.contains_bloom(bloom) + } + + pub fn accrue(&mut self, input: Input<'_>) { + let p = BLOOM_BITS; + + let m = self.0.len(); + let bloom_bits = m * 8; + let mask = bloom_bits - 1; + let bloom_bytes = (log2(bloom_bits) + 7) / 8; + + let hash: Hash<'_> = input.into(); + + // must be a power of 2 + assert_eq!(m & (m - 1), 0); + // out of range + assert!(p * bloom_bytes <= hash.len() as u32); + + let mut ptr = 0; + + assert_eq!(BLOOM_BITS, 3); + unroll! { + for i in 0..3 { + let _ = i; + let mut index = 0_usize; + for _ in 0..bloom_bytes { + index = (index << 8) | hash[ptr] as usize; + ptr += 1; + } + index &= mask; + self.0[m - 1 - index / 8] |= 1 << (index % 8); + } + } + } + + // pub fn accrue_bloom<'a, B>(&mut self, bloom: B) + // where + // BloomRef<'a>: From, + // { + // let bloom_ref: BloomRef<'_> = bloom.into(); + // assert_eq!(self.0.len(), BLOOM_SIZE); + // assert_eq!(bloom_ref.0.len(), BLOOM_SIZE); + // for i in 0..BLOOM_SIZE { + // self.0[i] |= bloom_ref.0[i]; + // } + // } + + // pub fn data(&self) -> &[u8; BLOOM_SIZE] { + // &self.0 + // } +} + +#[derive(Clone, Copy, Debug)] +pub struct BloomRef<'a>(&'a [u8; BLOOM_SIZE]); + +impl<'a> BloomRef<'a> { + #[allow(clippy::trivially_copy_pass_by_ref)] + // pub fn is_empty(&self) -> bool { + // self.0.iter().all(|x| *x == 0) + // } + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn contains_bloom<'b, B>(&self, bloom: B) -> bool + where + BloomRef<'b>: From, + { + let bloom_ref: BloomRef<'_> = bloom.into(); + assert_eq!(self.0.len(), BLOOM_SIZE); + assert_eq!(bloom_ref.0.len(), BLOOM_SIZE); + for i in 0..BLOOM_SIZE { + let a = self.0[i]; + let b = bloom_ref.0[i]; + if (a & b) != b { + return false + } + } + true + } + + // #[allow(clippy::trivially_copy_pass_by_ref)] + // pub fn data(&self) -> &'a [u8; BLOOM_SIZE] { + // self.0 + // } +} + +// impl<'a> From<&'a [u8; BLOOM_SIZE]> for BloomRef<'a> { +// fn from(data: &'a [u8; BLOOM_SIZE]) -> Self { +// BloomRef(data) +// } +// } + +impl<'a> From<&'a Bloom> for BloomRef<'a> { + fn from(bloom: &'a Bloom) -> Self { + BloomRef(&bloom.0) + } +} // See Section 4.3.1 "Transaction Receipt" of the Yellow Paper fn m3_2048(bloom: &mut Bloom, x: &[u8]) { @@ -18,7 +203,7 @@ fn m3_2048(bloom: &mut Bloom, x: &[u8]) { let h: &[u8; 32] = hash.as_ref(); for i in [0, 2, 4] { let bit = (h[i + 1] as usize + ((h[i] as usize) << 8)) & 0x7FF; - bloom.0[BLOOM_BYTE_LENGTH - 1 - bit / 8] |= 1 << (bit % 8); + bloom.0[BLOOM_SIZE - 1 - bit / 8] |= 1 << (bit % 8); } } diff --git a/crates/primitives/src/filter.rs b/crates/primitives/src/filter.rs new file mode 100644 index 0000000000..796ac7bd6d --- /dev/null +++ b/crates/primitives/src/filter.rs @@ -0,0 +1,1217 @@ +#![allow(missing_docs)] +use crate::{ + bloom::{Bloom, Input}, + keccak256, + rpc::BlockNumber, + Address, Log, H160, H256, U64, +}; +use ethers_core::types::U256; +use std::ops::{Range, RangeFrom, RangeTo}; + +use serde::{ + de::{DeserializeOwned, MapAccess, Visitor}, + ser::SerializeStruct, + Deserialize, Deserializer, Serialize, Serializer, +}; + +pub type BloomFilter = Vec>; + +/// A single topic +pub type Topic = ValueOrArray>; + +/// Represents the target range of blocks for the filter +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum FilterBlockOption { + Range { from_block: Option, to_block: Option }, + AtBlockHash(H256), +} + +impl FilterBlockOption { + pub fn get_to_block(&self) -> Option<&BlockNumber> { + match self { + FilterBlockOption::Range { to_block, .. } => to_block.as_ref(), + FilterBlockOption::AtBlockHash(_) => None, + } + } + + pub fn get_from_block(&self) -> Option<&BlockNumber> { + match self { + FilterBlockOption::Range { from_block, .. } => from_block.as_ref(), + FilterBlockOption::AtBlockHash(_) => None, + } + } +} + +impl From for FilterBlockOption { + fn from(block: BlockNumber) -> Self { + let block = Some(block); + FilterBlockOption::Range { from_block: block, to_block: block } + } +} + +impl From for FilterBlockOption { + fn from(block: U64) -> Self { + BlockNumber::from(block).into() + } +} + +impl From for FilterBlockOption { + fn from(block: u64) -> Self { + BlockNumber::from(block).into() + } +} + +impl> From> for FilterBlockOption { + fn from(r: Range) -> Self { + let from_block = Some(r.start.into()); + let to_block = Some(r.end.into()); + FilterBlockOption::Range { from_block, to_block } + } +} + +impl> From> for FilterBlockOption { + fn from(r: RangeTo) -> Self { + let to_block = Some(r.end.into()); + FilterBlockOption::Range { from_block: Some(BlockNumber::Earliest), to_block } + } +} + +impl> From> for FilterBlockOption { + fn from(r: RangeFrom) -> Self { + let from_block = Some(r.start.into()); + FilterBlockOption::Range { from_block, to_block: Some(BlockNumber::Latest) } + } +} + +impl From for FilterBlockOption { + fn from(hash: H256) -> Self { + FilterBlockOption::AtBlockHash(hash) + } +} + +impl Default for FilterBlockOption { + fn default() -> Self { + FilterBlockOption::Range { from_block: None, to_block: None } + } +} + +impl FilterBlockOption { + #[must_use] + pub fn set_from_block(&self, block: BlockNumber) -> Self { + let to_block = + if let FilterBlockOption::Range { to_block, .. } = self { *to_block } else { None }; + + FilterBlockOption::Range { from_block: Some(block), to_block } + } + + #[must_use] + pub fn set_to_block(&self, block: BlockNumber) -> Self { + let from_block = + if let FilterBlockOption::Range { from_block, .. } = self { *from_block } else { None }; + + FilterBlockOption::Range { from_block, to_block: Some(block) } + } + + #[must_use] + pub fn set_hash(&self, hash: H256) -> Self { + FilterBlockOption::AtBlockHash(hash) + } +} + +/// Filter for +#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)] +pub struct Filter { + /// Filter block options, specifying on which blocks the filter should + /// match. + // https://eips.ethereum.org/EIPS/eip-234 + pub block_option: FilterBlockOption, + + /// Address + pub address: Option>, + + /// Topics + // TODO: We could improve the low level API here by using ethabi's RawTopicFilter + // and/or TopicFilter + pub topics: [Option; 4], +} + +impl Filter { + pub fn new() -> Self { + Self::default() + } + + /// Sets the inner filter object + /// + /// *NOTE:* ranges are always inclusive + /// + /// # Examples + /// + /// Match only a specific block + /// + /// ```rust + /// # use reth_primitives::filter::Filter; + /// # fn main() { + /// let filter = Filter::new().select(69u64); + /// # } + /// ``` + /// This is the same as `Filter::new().from_block(1337u64).to_block(1337u64)` + /// + /// Match the latest block only + /// + /// ```rust + /// # use reth_primitives::{filter::Filter, rpc::BlockNumber}; + /// # fn main() { + /// let filter = Filter::new().select(BlockNumber::Latest); + /// # } + /// ``` + /// + /// Match a block by its hash + /// + /// ```rust + /// # use reth_primitives::{filter::Filter, H256}; + /// # fn main() { + /// let filter = Filter::new().select(H256::zero()); + /// # } + /// ``` + /// This is the same as `at_block_hash` + /// + /// Match a range of blocks + /// + /// ```rust + /// # use reth_primitives::filter::Filter; + /// # fn main() { + /// let filter = Filter::new().select(0u64..100u64); + /// # } + /// ``` + /// + /// Match all blocks in range `(1337..BlockNumber::Latest)` + /// + /// ```rust + /// # use reth_primitives::filter::Filter; + /// # fn main() { + /// let filter = Filter::new().select(1337u64..); + /// # } + /// ``` + /// + /// Match all blocks in range `(BlockNumber::Earliest..1337)` + /// + /// ```rust + /// # use reth_primitives::filter::Filter; + /// # fn main() { + /// let filter = Filter::new().select(..1337u64); + /// # } + /// ``` + #[must_use] + pub fn select(mut self, filter: impl Into) -> Self { + self.block_option = filter.into(); + self + } + + #[allow(clippy::wrong_self_convention)] + #[must_use] + pub fn from_block>(mut self, block: T) -> Self { + self.block_option = self.block_option.set_from_block(block.into()); + self + } + + #[allow(clippy::wrong_self_convention)] + #[must_use] + pub fn to_block>(mut self, block: T) -> Self { + self.block_option = self.block_option.set_to_block(block.into()); + self + } + + #[allow(clippy::wrong_self_convention)] + #[must_use] + pub fn at_block_hash>(mut self, hash: T) -> Self { + self.block_option = self.block_option.set_hash(hash.into()); + self + } + /// Sets the inner filter object + /// + /// *NOTE:* ranges are always inclusive + /// + /// # Examples + /// + /// Match only a specific address `("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF")` + /// + /// ```rust + /// # use reth_primitives::{Address, filter::Filter}; + /// # fn main() { + /// let filter = Filter::new().address("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::
().unwrap()); + /// # } + /// ``` + /// + /// Match all addresses in array `(vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF", + /// "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"])` + /// + /// ```rust + /// # use reth_primitives::{filter::Filter, Address}; + /// # fn main() { + /// let addresses = vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::
().unwrap(),"0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".parse::
().unwrap()]; + /// let filter = Filter::new().address(addresses); + /// # } + /// ``` + #[must_use] + pub fn address>>(mut self, address: T) -> Self { + self.address = Some(address.into()); + self + } + + /// Given the event signature in string form, it hashes it and adds it to the topics to monitor + #[must_use] + pub fn event(self, event_name: &str) -> Self { + let hash = keccak256(event_name.as_bytes()); + self.topic0(hash) + } + + /// Hashes all event signatures and sets them as array to topic0 + #[must_use] + pub fn events(self, events: impl IntoIterator>) -> Self { + let events = events.into_iter().map(|e| keccak256(e.as_ref())).collect::>(); + self.topic0(events) + } + + /// Sets topic0 (the event name for non-anonymous events) + #[must_use] + pub fn topic0>(mut self, topic: T) -> Self { + self.topics[0] = Some(topic.into()); + self + } + + /// Sets the 1st indexed topic + #[must_use] + pub fn topic1>(mut self, topic: T) -> Self { + self.topics[1] = Some(topic.into()); + self + } + + /// Sets the 2nd indexed topic + #[must_use] + pub fn topic2>(mut self, topic: T) -> Self { + self.topics[2] = Some(topic.into()); + self + } + + /// Sets the 3rd indexed topic + #[must_use] + pub fn topic3>(mut self, topic: T) -> Self { + self.topics[3] = Some(topic.into()); + self + } + + pub fn is_paginatable(&self) -> bool { + self.get_from_block().is_some() + } + + /// Returns the numeric value of the `toBlock` field + pub fn get_to_block(&self) -> Option { + self.block_option.get_to_block().and_then(|b| b.as_number()) + } + + /// Returns the numeric value of the `fromBlock` field + pub fn get_from_block(&self) -> Option { + self.block_option.get_from_block().and_then(|b| b.as_number()) + } + + /// Returns the numeric value of the `fromBlock` field + pub fn get_block_hash(&self) -> Option { + match self.block_option { + FilterBlockOption::AtBlockHash(hash) => Some(hash), + FilterBlockOption::Range { .. } => None, + } + } + + /// Flattens the topics using the cartesian product + fn flatten(&self) -> Vec>> { + fn cartesian(lists: &[Vec>]) -> Vec>> { + let mut res = Vec::new(); + let mut list_iter = lists.iter(); + if let Some(first_list) = list_iter.next() { + for &i in first_list { + res.push(vec![i]); + } + } + for l in list_iter { + let mut tmp = Vec::new(); + for r in res { + for &el in l { + let mut tmp_el = r.clone(); + tmp_el.push(el); + tmp.push(tmp_el); + } + } + res = tmp; + } + res + } + let mut out = Vec::new(); + let mut tmp = Vec::new(); + for v in self.topics.iter() { + let v = if let Some(v) = v { + match v { + ValueOrArray::Value(s) => { + vec![*s] + } + ValueOrArray::Array(s) => s.clone(), + } + } else { + vec![None] + }; + tmp.push(v); + } + for v in cartesian(&tmp) { + out.push(ValueOrArray::Array(v)); + } + out + } + + /// Returns an iterator over all existing topics + pub fn topics(&self) -> impl Iterator + '_ { + self.topics.iter().flatten() + } + + /// Returns true if at least one topic is set + pub fn has_topics(&self) -> bool { + self.topics.iter().any(|t| t.is_some()) + } +} + +impl Serialize for Filter { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("Filter", 5)?; + match self.block_option { + FilterBlockOption::Range { from_block, to_block } => { + if let Some(ref from_block) = from_block { + s.serialize_field("fromBlock", from_block)?; + } + + if let Some(ref to_block) = to_block { + s.serialize_field("toBlock", to_block)?; + } + } + + FilterBlockOption::AtBlockHash(ref h) => s.serialize_field("blockHash", h)?, + } + + if let Some(ref address) = self.address { + s.serialize_field("address", address)?; + } + + let mut filtered_topics = Vec::new(); + for i in 0..4 { + if self.topics[i].is_some() { + filtered_topics.push(&self.topics[i]); + } else { + // TODO: This can be optimized + if self.topics[i + 1..].iter().any(|x| x.is_some()) { + filtered_topics.push(&None); + } + } + } + s.serialize_field("topics", &filtered_topics)?; + + s.end() + } +} + +impl<'de> Deserialize<'de> for Filter { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FilterVisitor; + + impl<'de> Visitor<'de> for FilterVisitor { + type Value = Filter; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("Filter object") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut from_block: Option> = None; + let mut to_block: Option> = None; + let mut block_hash: Option> = None; + let mut address: Option>> = None; + let mut topics: Option>>> = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "fromBlock" => { + if from_block.is_some() { + return Err(serde::de::Error::duplicate_field("fromBlock")) + } + if block_hash.is_some() { + return Err(serde::de::Error::custom( + "fromBlock not allowed with blockHash", + )) + } + from_block = Some(map.next_value()?) + } + "toBlock" => { + if to_block.is_some() { + return Err(serde::de::Error::duplicate_field("toBlock")) + } + if block_hash.is_some() { + return Err(serde::de::Error::custom( + "toBlock not allowed with blockHash", + )) + } + to_block = Some(map.next_value()?) + } + "blockHash" => { + if block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockHash")) + } + if from_block.is_some() || to_block.is_some() { + return Err(serde::de::Error::custom( + "fromBlock,toBlock not allowed with blockHash", + )) + } + block_hash = Some(map.next_value()?) + } + "address" => { + if address.is_some() { + return Err(serde::de::Error::duplicate_field("address")) + } + address = Some(map.next_value()?) + } + "topics" => { + if topics.is_some() { + return Err(serde::de::Error::duplicate_field("topics")) + } + topics = Some(map.next_value()?) + } + + key => { + return Err(serde::de::Error::unknown_field( + key, + &["fromBlock", "toBlock", "address", "topics", "blockHash"], + )) + } + } + } + + let from_block = from_block.unwrap_or_default(); + let to_block = to_block.unwrap_or_default(); + let block_hash = block_hash.unwrap_or_default(); + let address = address.unwrap_or_default(); + let topics_vec = topics.flatten().unwrap_or_default(); + + // maximum allowed filter len + if topics_vec.len() > 4 { + return Err(serde::de::Error::custom("exceeded maximum topics len")) + } + let mut topics: [Option; 4] = [None, None, None, None]; + for (idx, topic) in topics_vec.into_iter().enumerate() { + topics[idx] = topic; + } + + let block_option = if let Some(block_hash) = block_hash { + FilterBlockOption::AtBlockHash(block_hash) + } else { + FilterBlockOption::Range { from_block, to_block } + }; + + Ok(Filter { block_option, address, topics }) + } + } + + deserializer.deserialize_any(FilterVisitor) + } +} + +/// Union type for representing a single value or a vector of values inside a filter +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum ValueOrArray { + /// A single value + Value(T), + /// A vector of values + Array(Vec), +} + +impl From for ValueOrArray { + fn from(src: H160) -> Self { + ValueOrArray::Value(src) + } +} + +impl From> for ValueOrArray { + fn from(src: Vec) -> Self { + ValueOrArray::Array(src) + } +} + +impl From for Topic { + fn from(src: H256) -> Self { + ValueOrArray::Value(Some(src)) + } +} + +impl From> for ValueOrArray { + fn from(src: Vec) -> Self { + ValueOrArray::Array(src) + } +} + +impl From> for Topic { + fn from(src: ValueOrArray) -> Self { + match src { + ValueOrArray::Value(val) => ValueOrArray::Value(Some(val)), + ValueOrArray::Array(arr) => arr.into(), + } + } +} + +impl> From> for Topic { + fn from(src: Vec) -> Self { + ValueOrArray::Array(src.into_iter().map(Into::into).map(Some).collect()) + } +} + +impl From
for Topic { + fn from(src: Address) -> Self { + let mut bytes = [0; 32]; + bytes[12..32].copy_from_slice(src.as_bytes()); + ValueOrArray::Value(Some(H256::from(bytes))) + } +} + +impl From for Topic { + fn from(src: U256) -> Self { + let mut bytes = [0; 32]; + src.to_big_endian(&mut bytes); + ValueOrArray::Value(Some(H256::from(bytes))) + } +} + +impl Serialize for ValueOrArray +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + ValueOrArray::Value(inner) => inner.serialize(serializer), + ValueOrArray::Array(inner) => inner.serialize(serializer), + } + } +} + +impl<'a, T> Deserialize<'a> for ValueOrArray +where + T: DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'a>, + { + let value = serde_json::Value::deserialize(deserializer)?; + + if value.is_null() { + return Ok(ValueOrArray::Array(Vec::new())) + } + + #[derive(Deserialize)] + #[serde(untagged)] + enum Variadic { + Value(T), + Array(Vec), + } + + match serde_json::from_value::>(value).map_err(|err| { + serde::de::Error::custom(format!("Invalid variadic value or array type: {err}")) + })? { + Variadic::Value(val) => Ok(ValueOrArray::Value(val)), + Variadic::Array(arr) => Ok(ValueOrArray::Array(arr)), + } + } +} + +/// Support for matching [Filter]s +#[derive(Debug, Default)] +pub struct FilteredParams { + pub filter: Option, + pub flat_topics: Vec>>, +} + +impl FilteredParams { + pub fn new(filter: Option) -> Self { + if let Some(filter) = filter { + let flat_topics = filter.flatten(); + FilteredParams { filter: Some(filter), flat_topics } + } else { + Default::default() + } + } + + /// Returns the [BloomFilter] for the given address + pub fn address_filter(address: &Option>) -> BloomFilter { + address.as_ref().map(address_to_bloom_filter).unwrap_or_default() + } + + /// Returns the [BloomFilter] for the given topics + pub fn topics_filter(topics: &Option>>>) -> Vec { + let mut output = Vec::new(); + if let Some(topics) = topics { + output.extend(topics.iter().map(topics_to_bloom_filter)); + } + output + } + + /// Returns `true` if the bloom matches the topics + pub fn matches_topics(bloom: Bloom, topic_filters: &[BloomFilter]) -> bool { + if topic_filters.is_empty() { + return true + } + + // returns true if a filter matches + for filter in topic_filters.iter() { + let mut is_match = false; + for maybe_bloom in filter { + is_match = maybe_bloom.as_ref().map(|b| bloom.contains_bloom(b)).unwrap_or(true); + if !is_match { + break + } + } + if is_match { + return true + } + } + false + } + + /// Returns `true` if the bloom contains the address + pub fn matches_address(bloom: Bloom, address_filter: &BloomFilter) -> bool { + if address_filter.is_empty() { + return true + } else { + for maybe_bloom in address_filter { + if maybe_bloom.as_ref().map(|b| bloom.contains_bloom(b)).unwrap_or(true) { + return true + } + } + } + false + } + + /// Replace None values - aka wildcards - for the log input value in that position. + pub fn replace(&self, log: &Log, topic: Topic) -> Option> { + let mut out: Vec = Vec::new(); + match topic { + ValueOrArray::Value(value) => { + if let Some(value) = value { + out.push(value); + } + } + ValueOrArray::Array(value) => { + for (k, v) in value.into_iter().enumerate() { + if let Some(v) = v { + out.push(v); + } else { + out.push(log.topics[k]); + } + } + } + }; + if out.is_empty() { + return None + } + Some(out) + } + + pub fn filter_block_range(&self, block_number: u64) -> bool { + if self.filter.is_none() { + return true + } + let filter = self.filter.as_ref().unwrap(); + let mut res = true; + + if let Some(BlockNumber::Number(num)) = filter.block_option.get_from_block() { + if num.as_u64() > block_number { + res = false; + } + } + + if let Some(to) = filter.block_option.get_to_block() { + match to { + BlockNumber::Number(num) => { + if num.as_u64() < block_number { + res = false; + } + } + BlockNumber::Earliest => { + res = false; + } + _ => {} + } + } + res + } + + pub fn filter_block_hash(&self, block_hash: H256) -> bool { + if let Some(h) = self.filter.as_ref().and_then(|f| f.get_block_hash()) { + if h != block_hash { + return false + } + } + true + } + + pub fn filter_address(&self, log: &Log) -> bool { + if let Some(input_address) = &self.filter.as_ref().and_then(|f| f.address.clone()) { + match input_address { + ValueOrArray::Value(x) => { + if log.address != *x { + return false + } + } + ValueOrArray::Array(x) => { + if x.is_empty() { + return true + } + if !x.contains(&log.address) { + return false + } + } + } + } + true + } + + pub fn filter_topics(&self, log: &Log) -> bool { + let mut out: bool = true; + for topic in self.flat_topics.iter().cloned() { + match topic { + ValueOrArray::Value(single) => { + if let Some(single) = single { + if !log.topics.starts_with(&[single]) { + out = false; + } + } + } + ValueOrArray::Array(multi) => { + if multi.is_empty() { + out = true; + continue + } + // Shrink the topics until the last item is Some. + let mut new_multi = multi; + while new_multi.iter().last().unwrap_or(&Some(H256::default())).is_none() { + new_multi.pop(); + } + // We can discard right away any logs with lesser topics than the filter. + if new_multi.len() > log.topics.len() { + out = false; + break + } + let replaced: Option> = + self.replace(log, ValueOrArray::Array(new_multi)); + if let Some(replaced) = replaced { + out = false; + if log.topics.starts_with(&replaced[..]) { + out = true; + break + } + } + } + } + } + out + } +} + +fn topics_to_bloom_filter(topics: &ValueOrArray>) -> BloomFilter { + let mut blooms = BloomFilter::new(); + match topics { + ValueOrArray::Value(topic) => { + if let Some(topic) = topic { + let bloom: Bloom = Input::Raw(topic.as_ref()).into(); + blooms.push(Some(bloom)); + } else { + blooms.push(None); + } + } + ValueOrArray::Array(topics) => { + if topics.is_empty() { + blooms.push(None); + } else { + for topic in topics.iter() { + if let Some(topic) = topic { + let bloom: Bloom = Input::Raw(topic.as_ref()).into(); + blooms.push(Some(bloom)); + } else { + blooms.push(None); + } + } + } + } + } + blooms +} + +fn address_to_bloom_filter(address: &ValueOrArray
) -> BloomFilter { + let mut blooms = BloomFilter::new(); + match address { + ValueOrArray::Value(address) => { + let bloom: Bloom = Input::Raw(address.as_ref()).into(); + blooms.push(Some(bloom)) + } + ValueOrArray::Array(addresses) => { + if addresses.is_empty() { + blooms.push(None); + } else { + for address in addresses.iter() { + let bloom: Bloom = Input::Raw(address.as_ref()).into(); + blooms.push(Some(bloom)); + } + } + } + } + blooms +} + +#[cfg(test)] +mod tests { + use super::*; + use ethers_core::utils::serialize; + use serde_json::json; + + #[test] + fn can_serde_value_or_array() { + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + struct Item { + value: ValueOrArray, + } + + let item = Item { value: ValueOrArray::Value(U256::one()) }; + let json = serde_json::to_value(item.clone()).unwrap(); + let deserialized: Item = serde_json::from_value(json).unwrap(); + assert_eq!(item, deserialized); + + let item = Item { value: ValueOrArray::Array(vec![U256::one(), U256::zero()]) }; + let json = serde_json::to_value(item.clone()).unwrap(); + let deserialized: Item = serde_json::from_value(json).unwrap(); + assert_eq!(item, deserialized); + } + + #[test] + fn filter_serialization_test() { + let t1 = "9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::
().unwrap(); + let t2 = H256::from([0; 32]); + let t3 = U256::from(123); + + let t1_padded = H256::from(t1); + let t3_padded = H256::from({ + let mut x = [0; 32]; + x[31] = 123; + x + }); + + let event = "ValueChanged(address,string,string)"; + let t0 = H256::from(keccak256(event.as_bytes())); + let addr: Address = "f817796F60D268A36a57b8D2dF1B97B14C0D0E1d".parse().unwrap(); + let filter = Filter::new(); + + let ser = serialize(&filter); + assert_eq!(ser, json!({ "topics": [] })); + + let filter = filter.address(ValueOrArray::Value(addr)); + + let ser = serialize(&filter); + assert_eq!(ser, json!({"address" : addr, "topics": []})); + + let filter = filter.event(event); + + // 0 + let ser = serialize(&filter); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0]})); + + // 1 + let ser = serialize(&filter.clone().topic1(t1)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded]})); + + // 2 + let ser = serialize(&filter.clone().topic2(t2)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2]})); + + // 3 + let ser = serialize(&filter.clone().topic3(t3)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, null, t3_padded]})); + + // 1 & 2 + let ser = serialize(&filter.clone().topic1(t1).topic2(t2)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2]})); + + // 1 & 3 + let ser = serialize(&filter.clone().topic1(t1).topic3(t3)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, null, t3_padded]})); + + // 2 & 3 + let ser = serialize(&filter.clone().topic2(t2).topic3(t3)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2, t3_padded]})); + + // 1 & 2 & 3 + let ser = serialize(&filter.topic1(t1).topic2(t2).topic3(t3)); + assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2, t3_padded]})); + } + + fn build_bloom(address: Address, topic1: H256, topic2: H256) -> Bloom { + let mut block_bloom = Bloom::default(); + block_bloom.accrue(Input::Raw(&address[..])); + block_bloom.accrue(Input::Raw(&topic1[..])); + block_bloom.accrue(Input::Raw(&topic2[..])); + block_bloom + } + + fn topic_filter( + topic1: H256, + topic2: H256, + topic3: H256, + ) -> (Filter, Option>>>) { + let filter = Filter { + block_option: Default::default(), + address: None, + topics: [ + Some(ValueOrArray::Value(Some(topic1))), + Some(ValueOrArray::Array(vec![Some(topic2), Some(topic3)])), + None, + None, + ], + }; + let filtered_params = FilteredParams::new(Some(filter.clone())); + + (filter, Some(filtered_params.flat_topics)) + } + + #[test] + fn can_detect_different_topics() { + let topic1 = H256::random(); + let topic2 = H256::random(); + let topic3 = H256::random(); + + let (_, topics) = topic_filter(topic1, topic2, topic3); + let topics_bloom = FilteredParams::topics_filter(&topics); + assert!(!FilteredParams::matches_topics( + build_bloom(Address::random(), H256::random(), H256::random()), + &topics_bloom + )); + } + + #[test] + fn can_match_topic() { + let topic1 = H256::random(); + let topic2 = H256::random(); + let topic3 = H256::random(); + + let (_, topics) = topic_filter(topic1, topic2, topic3); + let _topics_bloom = FilteredParams::topics_filter(&topics); + + let topics_bloom = FilteredParams::topics_filter(&topics); + assert!(FilteredParams::matches_topics( + build_bloom(Address::random(), topic1, topic2), + &topics_bloom + )); + } + + #[test] + fn can_match_empty_topics() { + let filter = + Filter { block_option: Default::default(), address: None, topics: Default::default() }; + + let filtered_params = FilteredParams::new(Some(filter)); + let topics = Some(filtered_params.flat_topics); + + let topics_bloom = FilteredParams::topics_filter(&topics); + assert!(FilteredParams::matches_topics( + build_bloom(Address::random(), H256::random(), H256::random()), + &topics_bloom + )); + } + + #[test] + fn can_match_address_and_topics() { + let rng_address = Address::random(); + let topic1 = H256::random(); + let topic2 = H256::random(); + let topic3 = H256::random(); + + let filter = Filter { + block_option: Default::default(), + address: Some(ValueOrArray::Value(rng_address)), + topics: [ + Some(ValueOrArray::Value(Some(topic1))), + Some(ValueOrArray::Array(vec![Some(topic2), Some(topic3)])), + None, + None, + ], + }; + let filtered_params = FilteredParams::new(Some(filter.clone())); + let topics = Some(filtered_params.flat_topics); + let address_filter = FilteredParams::address_filter(&filter.address); + let topics_filter = FilteredParams::topics_filter(&topics); + assert!( + FilteredParams::matches_address( + build_bloom(rng_address, topic1, topic2), + &address_filter + ) && FilteredParams::matches_topics( + build_bloom(rng_address, topic1, topic2), + &topics_filter + ) + ); + } + + #[test] + fn can_match_topics_wildcard() { + let topic1 = H256::random(); + let topic2 = H256::random(); + let topic3 = H256::random(); + + let filter = Filter { + block_option: Default::default(), + address: None, + topics: [None, Some(ValueOrArray::Array(vec![Some(topic2), Some(topic3)])), None, None], + }; + let filtered_params = FilteredParams::new(Some(filter)); + let topics = Some(filtered_params.flat_topics); + let topics_bloom = FilteredParams::topics_filter(&topics); + assert!(FilteredParams::matches_topics( + build_bloom(Address::random(), topic1, topic2), + &topics_bloom + )); + } + + #[test] + fn can_match_topics_wildcard_mismatch() { + let filter = Filter { + block_option: Default::default(), + address: None, + topics: [ + None, + Some(ValueOrArray::Array(vec![Some(H256::random()), Some(H256::random())])), + None, + None, + ], + }; + let filtered_params = FilteredParams::new(Some(filter)); + let topics_input = Some(filtered_params.flat_topics); + let topics_bloom = FilteredParams::topics_filter(&topics_input); + assert!(!FilteredParams::matches_topics( + build_bloom(Address::random(), H256::random(), H256::random()), + &topics_bloom + )); + } + + #[test] + fn can_match_address_filter() { + let rng_address = Address::random(); + let filter = Filter { + block_option: Default::default(), + address: Some(ValueOrArray::Value(rng_address)), + topics: Default::default(), + }; + let address_bloom = FilteredParams::address_filter(&filter.address); + assert!(FilteredParams::matches_address( + build_bloom(rng_address, H256::random(), H256::random(),), + &address_bloom + )); + } + + #[test] + fn can_detect_different_address() { + let bloom_address = Address::random(); + let rng_address = Address::random(); + let filter = Filter { + block_option: Default::default(), + address: Some(ValueOrArray::Value(rng_address)), + topics: Default::default(), + }; + let address_bloom = FilteredParams::address_filter(&filter.address); + assert!(!FilteredParams::matches_address( + build_bloom(bloom_address, H256::random(), H256::random(),), + &address_bloom + )); + } + + #[test] + fn can_convert_to_ethers_filter() { + let json = json!( + { + "fromBlock": "0x429d3b", + "toBlock": "0x429d3b", + "address": "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75", + "0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078" + ] + } + ); + + let filter: Filter = serde_json::from_value(json).unwrap(); + assert_eq!( + filter, + Filter { + block_option: FilterBlockOption::Range { + from_block: Some(4365627u64.into()), + to_block: Some(4365627u64.into()), + }, + address: Some(ValueOrArray::Value( + "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907".parse().unwrap() + )), + topics: [ + Some(ValueOrArray::Value(Some( + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + .parse() + .unwrap(), + ))), + Some(ValueOrArray::Value(Some( + "0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75" + .parse() + .unwrap(), + ))), + Some(ValueOrArray::Value(Some( + "0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078" + .parse() + .unwrap(), + ))), + None, + ], + } + ); + } + + #[test] + fn can_convert_to_ethers_filter_with_null_fields() { + let json = json!( + { + "fromBlock": "0x429d3b", + "toBlock": "0x429d3b", + "address": null, + "topics": null + } + ); + + let filter: Filter = serde_json::from_value(json).unwrap(); + assert_eq!( + filter, + Filter { + block_option: FilterBlockOption::Range { + from_block: Some(4365627u64.into()), + to_block: Some(4365627u64.into()), + }, + address: None, + topics: [None, None, None, None,], + } + ); + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 340aec66d9..5a53052812 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -16,6 +16,7 @@ pub mod bloom; mod chain; pub mod constants; mod error; +pub mod filter; mod forkid; mod genesis; mod hardfork;