Skip to content

Commit

Permalink
Support topic wildcards and conditional indexed parameters (#182)
Browse files Browse the repository at this point in the history
* VariadicValue wildcard and nesting support

* Add FilteredParams type implementation

* Public re-export FilteredParams

* Use FilteredParams in EthApi

* Fix checker
  • Loading branch information
tgmichel authored Nov 6, 2020
1 parent 95aa393 commit 214f867
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 229 deletions.
225 changes: 223 additions & 2 deletions rpc/core/src/types/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ impl<'a, T> Deserialize<'a> for VariadicValue<T> where T: DeserializeOwned {

/// Filter Address
pub type FilterAddress = VariadicValue<H160>;
/// Topic
pub type Topic = VariadicValue<H256>;
/// Topic, supports `A` | `null` | `[A,B,C]` | `[A,[B,C]]` | [null,[B,C]] | [null,[null,C]]
pub type Topic = VariadicValue<Option<
VariadicValue<Option<H256>>
>>;
/// FlatTopic, simplifies the matching logic.
pub type FlatTopic = VariadicValue<Option<H256>>;

/// Filter
#[derive(Debug, PartialEq, Clone, Deserialize, Eq, Hash)]
Expand All @@ -69,6 +73,223 @@ pub struct Filter {
pub topics: Option<Topic>,
}

/// Helper for Filter matching.
/// Supports conditional indexed parameters and wildcards.
#[derive(Debug)]
pub struct FilteredParams {
pub filter: Option<Filter>,
flat_topics: Vec<FlatTopic>,
}

impl Default for FilteredParams {
fn default() -> Self {
FilteredParams {
filter: None,
flat_topics: Vec::new()
}
}
}

impl FilteredParams {
pub fn new(
f: Option<Filter>,
) -> Self {
if let Some(f) = f {
return FilteredParams {
filter: Some(f.clone()),
flat_topics: {
if let Some(t) = f.clone().topics {
Self::flatten(&t)
} else { Vec:: new() }
}
};
}
Self::default()
}
/// Cartesian product for VariadicValue conditional indexed parameters.
/// Executed once on struct instance.
/// i.e. `[A,[B,C]]` to `[[A,B],[A,C]]`.
fn flatten(topic: &Topic) -> Vec<FlatTopic> {
fn cartesian(lists: &Vec<Vec<Option<H256>>>) -> Vec<Vec<Option<H256>>> {
let mut res = vec![];
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![];
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<FlatTopic> = Vec::new();
match topic {
VariadicValue::Multiple(multi) => {
let mut foo: Vec<Vec<Option<H256>>> = Vec::new();
for v in multi {
foo.push({
if let Some(v) = v {
match v {
VariadicValue::Single(s) => {
vec![s.clone()]
},
VariadicValue::Multiple(s) => {
s.clone()
},
VariadicValue::Null => {
vec![None]
},
}
} else {
vec![None]
}
});
}
for permut in cartesian(&foo) {
out.push(FlatTopic::Multiple(permut));
}
},
VariadicValue::Single(single) => {
if let Some(single) = single {
out.push(single.clone());
}
},
VariadicValue::Null => {
out.push(FlatTopic::Null);
},
}
out
}

/// Replace None values - aka wildcards - for the log input value in that position.
pub fn replace(&self, log: &Log, topic: FlatTopic) -> Option<Vec<H256>> {
let mut out: Vec<H256> = Vec::new();
match topic {
VariadicValue::Single(value) => {
if let Some(value) = value {
out.push(value);
}
},
VariadicValue::Multiple(value) => {
for (k, v) in value.into_iter().enumerate() {
if let Some(v) = v {
out.push(v);
} else {
out.push(log.topics[k].clone());
}
}
},
_ => {}
};
if out.len() == 0 {
return None;
}
Some(out)
}

pub fn filter_block_range(
&self,
block_number: u64
) -> bool {
let mut out = true;
let filter = self.filter.clone().unwrap();
if let Some(from) = filter.from_block {
match from {
BlockNumber::Num(_) => {
if from.to_min_block_num().unwrap_or(0 as u64) > block_number {
out = false;
}
},
_ => {}
}
}
if let Some(to) = filter.to_block {
match to {
BlockNumber::Num(_) => {
if to.to_min_block_num().unwrap_or(0 as u64) < block_number {
out = false;
}
},
BlockNumber::Earliest => {
out = false;
},
_ => {}
}
}
out
}

pub fn filter_block_hash(
&self,
block_hash: H256
) -> bool {
if let Some(h) = self.filter.clone().unwrap().block_hash {
if h != block_hash { return false; }
}
true
}

pub fn filter_address(
&self,
log: &Log
) -> bool {
if let Some(input_address) = &self.filter.clone().unwrap().address {
match input_address {
VariadicValue::Single(x) => {
if log.address != *x { return false; }
},
VariadicValue::Multiple(x) => {
if !x.contains(&log.address) { return false; }
},
_ => { return true; }
}
}
true
}

pub fn filter_topics(
&self,
log: &Log
) -> bool {
let mut out: bool = true;
for topic in self.flat_topics.clone() {
match topic {
VariadicValue::Single(single) => {
if let Some(single) = single {
if !log.topics.starts_with(&vec![single]) {
out = false;
}
}
},
VariadicValue::Multiple(_) => {
let replaced: Option<Vec<H256>> = self.replace(log, topic);
if let Some(replaced) = replaced {
out = false;
if log.topics.starts_with(
&replaced[..]
) {
out = true;
}
}
},
_ => {
out = true;
}
}
}
out
}
}

/// Results of the filter_changes RPC.
#[derive(Debug, PartialEq)]
pub enum FilterChanges {
Expand Down
2 changes: 1 addition & 1 deletion rpc/core/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich};
pub use self::block_number::BlockNumber;
pub use self::call_request::CallRequest;
pub use self::filter::{Filter, FilterChanges, VariadicValue, FilterAddress, Topic};
pub use self::filter::{Filter, FilterChanges, VariadicValue, FilterAddress, Topic, FilteredParams};
pub use self::index::Index;
pub use self::log::Log;
pub use self::receipt::Receipt;
Expand Down
50 changes: 29 additions & 21 deletions rpc/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use sp_blockchain::{Error as BlockChainError, HeaderMetadata, HeaderBackend};
use sc_network::{NetworkService, ExHashT};
use frontier_rpc_core::{EthApi as EthApiT, NetApi as NetApiT};
use frontier_rpc_core::types::{
BlockNumber, Bytes, CallRequest, Filter, Index, Log, Receipt, RichBlock,
BlockNumber, Bytes, CallRequest, Filter, FilteredParams, Index, Log, Receipt, RichBlock,
SyncStatus, SyncInfo, Transaction, Work, Rich, Block, BlockTransactions, VariadicValue
};
use frontier_rpc_primitives::{EthereumRuntimeRPCApi, ConvertTransaction, TransactionStatus};
Expand Down Expand Up @@ -814,6 +814,7 @@ impl<B, C, P, CT, BE, H: ExHashT> EthApiT for EthApi<B, C, P, CT, BE, H> where
fn logs(&self, filter: Filter) -> Result<Vec<Log>> {
let mut blocks_and_statuses = Vec::new();
let mut ret = Vec::new();
let params = FilteredParams::new(Some(filter.clone()));

if let Some(hash) = filter.block_hash {
let id = match self.load_hash(hash)
Expand Down Expand Up @@ -872,42 +873,49 @@ impl<B, C, P, CT, BE, H: ExHashT> EthApiT for EthApi<B, C, P, CT, BE, H> where
let logs = status.logs.clone();
let mut transaction_log_index: u32 = 0;
let transaction_hash = status.transaction_hash;
for log in logs {
for ethereum_log in logs {
let mut log = Log {
address: ethereum_log.address.clone(),
topics: ethereum_log.topics.clone(),
data: Bytes(ethereum_log.data.clone()),
block_hash: None,
block_number: None,
transaction_hash: None,
transaction_index: None,
log_index: None,
transaction_log_index: None,
removed: false,
};
let mut add: bool = false;
if let (
Some(VariadicValue::Single(address)),
Some(VariadicValue::Multiple(topics))
Some(VariadicValue::Single(_)),
Some(VariadicValue::Multiple(_))
) = (
filter.address.clone(),
filter.topics.clone(),
) {
if address == log.address && log.topics.starts_with(&topics) {
if !params.filter_address(&log) && params.filter_topics(&log) {
add = true;
}
} else if let Some(VariadicValue::Single(address)) = filter.address {
if address == log.address {
} else if let Some(VariadicValue::Single(_)) = filter.address {
if !params.filter_address(&log) {
add = true;
}
} else if let Some(VariadicValue::Multiple(topics)) = &filter.topics {
if log.topics.starts_with(&topics) {
} else if let Some(VariadicValue::Multiple(_)) = &filter.topics {
if params.filter_topics(&log) {
add = true;
}
} else {
add = true;
}
if add {
ret.push(Log {
address: log.address.clone(),
topics: log.topics.clone(),
data: Bytes(log.data.clone()),
block_hash: Some(block_hash),
block_number: Some(block.header.number.clone()),
transaction_hash: Some(transaction_hash),
transaction_index: Some(U256::from(status.transaction_index)),
log_index: Some(U256::from(block_log_index)),
transaction_log_index: Some(U256::from(transaction_log_index)),
removed: false,
});
log.block_hash = Some(block_hash);
log.block_number = Some(block.header.number.clone());
log.transaction_hash = Some(transaction_hash);
log.transaction_index = Some(U256::from(status.transaction_index));
log.log_index = Some(U256::from(block_log_index));
log.transaction_log_index = Some(U256::from(transaction_log_index));
ret.push(log);
}
transaction_log_index += 1;
block_log_index += 1;
Expand Down
Loading

0 comments on commit 214f867

Please sign in to comment.