From 3f61f2d8d9632d85838833da665d16ff6fdcb804 Mon Sep 17 00:00:00 2001 From: Fabio Lama <42901763+lamafab@users.noreply.github.com> Date: Fri, 28 Jun 2019 08:27:59 +0000 Subject: [PATCH] Add filtering capability to `parity_pendingTransactions` (issue 8269) (#10506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expand parameters for pending_transactions() * move ready_transactions content into filtered method * apply filter based on tx_hash, sender or receiver * call filtered transactions from RPC interface * attempt at testing... * replace parameters with _ on light client * addes some comments * removed uncompleted tests in miner.rs * attempt at testing, needs more work... * Formatting for ready_transactions_filtered Co-Authored-By: Tomasz Drwięga * Use map_or instead of if-let Co-Authored-By: Tomasz Drwięga * additional map_or replacement * change receiver type to Option> * remove faulty MiningService tests, test RPC later * remove tx hash from pending transaction filtering * as_unsigned() method for SignedTransaction * implement Deserialize for FilterOptions * implement Validate for MapAccess type * additional formatting * directly name cover in pattern matching * test valid vull deserialization * test valid sender operators * test valid receiver operators * test valid gas operators * test valid gas price operators * test valid value operators * test valid nonce operators * additional tsets for defaults, unknown filter types, unknown operators and some renames * move filter_options to ethcore to avoid package cycling * adjusted function/method parameters for FilterOptions * implement filter for sender and receiver * implement filter for gas * implement filter for gas price, tx value and nonce * improve filtering implementation; use common function, use combinators * improved documentation for FilterOptions * small documentation adjustments * remove warnings * replace FilterOperator::ContractCreation with FilterOperator::Eq(None) * implement validate_receiver * make small changes like renames, preamble * cleanup code according to suggestions, add docs * small improvements like formatting and newline --- Cargo.lock | 1 + ethcore/Cargo.toml | 1 + ethcore/src/lib.rs | 2 + ethcore/src/miner/filter_options.rs | 869 +++++++++++++++++++ ethcore/src/miner/miner.rs | 78 ++ ethcore/src/miner/mod.rs | 11 +- ethcore/types/src/transaction/transaction.rs | 8 + rpc/src/v1/impls/light/parity.rs | 3 +- rpc/src/v1/impls/parity.rs | 7 +- rpc/src/v1/tests/helpers/miner_service.rs | 6 +- rpc/src/v1/traits/parity.rs | 3 +- 11 files changed, 982 insertions(+), 7 deletions(-) create mode 100644 ethcore/src/miner/filter_options.rs diff --git a/Cargo.lock b/Cargo.lock index 2e6fefa5b..eeb01529c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,6 +924,7 @@ dependencies = [ "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "stats 0.1.0", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "time-utils 0.1.0", diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index b35bad357..9296f10b1 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -82,6 +82,7 @@ fetch = { path = "../util/fetch" } kvdb-rocksdb = "0.1.3" parity-runtime = { path = "../util/runtime" } rlp_compress = { path = "../util/rlp-compress" } +serde_json = "1.0" tempdir = "0.3" trie-standardmap = "0.12.4" diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index a8f7f002e..254b6bed7 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -122,6 +122,8 @@ extern crate blooms_db; extern crate env_logger; #[cfg(test)] extern crate rlp_compress; +#[cfg(test)] +extern crate serde_json; #[macro_use] extern crate ethabi_derive; diff --git a/ethcore/src/miner/filter_options.rs b/ethcore/src/miner/filter_options.rs new file mode 100644 index 000000000..db0e67378 --- /dev/null +++ b/ethcore/src/miner/filter_options.rs @@ -0,0 +1,869 @@ +// Copyright 2015-2019 Parity Technologies (UK) Ltd. +// This file is part of Parity Ethereum. + +// Parity Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Ethereum. If not, see . + +use ethereum_types::{Address, U256}; +use serde::de::{Deserialize, Deserializer, Error, MapAccess, Visitor}; +use std::fmt; +use std::marker::PhantomData; + +/// This structure provides filtering options for the pending transactions RPC API call +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FilterOptions { + /// Contains the operator to filter the from value of the transaction + pub from: FilterOperator
, + /// Contains the operator to filter the to value of the transaction + pub to: FilterOperator>, + /// Contains the operator to filter the gas value of the transaction + pub gas: FilterOperator, + /// Contains the operator to filter the gas price value of the transaction + pub gas_price: FilterOperator, + /// Contains the operator to filter the transaction value + pub value: FilterOperator, + /// Contains the operator to filter the nonce value of the transaction + pub nonce: FilterOperator, +} + +impl Default for FilterOptions { + fn default() -> Self { + FilterOptions { + from: FilterOperator::Any, + to: FilterOperator::Any, + gas: FilterOperator::Any, + gas_price: FilterOperator::Any, + value: FilterOperator::Any, + nonce: FilterOperator::Any, + } + } +} + +/// The highly generic use of implementing Deserialize for FilterOperator +/// will result in a compiler error if the type FilterOperator::Eq(None) +/// gets returned explicitly. Therefore this Wrapper will be used for +/// deserialization, directly identifying the contract creation. +enum Wrapper { + O(FilterOperator), + CC, // Contract Creation +} + +/// Available operators for filtering options. +/// The `from` filter only accepts Any and Eq(Address) +/// The `to` filter only accepts Any, Eq(Address) and Eq(None) for contract creation. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum FilterOperator { + Any, + Eq(T), + GreaterThan(T), + LessThan(T), +} + +/// Since there are multiple operators which are not supported equally by all filters, +/// this trait will validate each of those operators. The corresponding method is called +/// inside the `Deserialize` -> `Visitor` implementation for FilterOperator. In case new +/// operators get introduced, a whitelist instead of a blacklist is used. +/// +/// The `from` filter validates with `validate_from` +/// The `to` filter validates with `validate_from` +/// All other filters such as gas and price validate with `validate_value` +trait Validate<'de, T, M: MapAccess<'de>> { + fn validate_from(&mut self) -> Result, M::Error>; + fn validate_to(&mut self) -> Result>, M::Error>; + fn validate_value(&mut self) -> Result, M::Error>; +} + +impl<'de, T, M> Validate<'de, T, M> for M + where T: Deserialize<'de>, M: MapAccess<'de> +{ + fn validate_from(&mut self) -> Result, M::Error> { + use self::Wrapper as W; + use self::FilterOperator::*; + let wrapper = self.next_value()?; + match wrapper { + W::O(val) => { + match val { + Any | Eq(_) => Ok(val), + _ => { + Err(M::Error::custom( + "the `from` filter only supports the `eq` operator", + )) + } + } + }, + W::CC => { + Err(M::Error::custom( + "the `from` filter only supports the `eq` operator", + )) + } + } + } + fn validate_to(&mut self) -> Result>, M::Error> { + use self::Wrapper as W; + use self::FilterOperator::*; + let wrapper = self.next_value()?; + match wrapper { + W::O(val) => { + match val { + Any => Ok(Any), + Eq(address) => Ok(Eq(Some(address))), + _ => { + Err(M::Error::custom( + "the `to` filter only supports the `eq` or `action` operator", + )) + } + } + }, + W::CC => Ok(FilterOperator::Eq(None)), + } + } + fn validate_value(&mut self) -> Result, M::Error> { + use self::Wrapper as W; + let wrapper = self.next_value()?; + match wrapper { + W::O(val) => Ok(val), + W::CC => { + Err(M::Error::custom( + "the operator `action` is only supported by the `to` filter", + )) + } + } + } +} + +impl<'de> Deserialize<'de> for FilterOptions { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FilterOptionsVisitor; + impl<'de> Visitor<'de> for FilterOptionsVisitor { + type Value = FilterOptions; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + // "This Visitor expects to receive ..." + formatter.write_str("a map with one valid filter such as `from`, `to`, `gas`, `gas_price`, `value` or `nonce`") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut filter = FilterOptions::default(); + while let Some(key) = map.next_key()? { + match key { + "from" => { + filter.from = map.validate_from()?; + }, + "to" => { + // Compiler cannot infer type, so set one (nothing specific for this method) + filter.to = Validate::<(), _>::validate_to(&mut map)?; + }, + "gas" => { + filter.gas = map.validate_value()?; + }, + "gas_price" => { + filter.gas_price = map.validate_value()?; + }, + "value" => { + filter.value = map.validate_value()?; + }, + "nonce" => { + filter.nonce = map.validate_value()?; + }, + unknown => { + return Err(M::Error::unknown_field( + unknown, + &["from", "to", "gas", "gas_price", "value", "nonce"], + )) + } + } + } + + Ok(filter) + } + } + + impl<'de, T: Deserialize<'de>> Deserialize<'de> for Wrapper { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct WrapperVisitor { + data: PhantomData, + }; + impl<'de, T: Deserialize<'de>> Visitor<'de> for WrapperVisitor { + type Value = Wrapper; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + // "This Visitor expects to receive ..." + formatter.write_str( + "a map with one valid operator such as `eq`, `gt` or `lt`. \ + The to filter can also contain `action`", + ) + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + use self::Wrapper as W; + let mut counter = 0; + let mut f_op = Wrapper::O(FilterOperator::Any); + + while let Some(key) = map.next_key()? { + match key { + "eq" => f_op = W::O(FilterOperator::Eq(map.next_value()?)), + "gt" => f_op = W::O(FilterOperator::GreaterThan(map.next_value()?)), + "lt" => f_op = W::O(FilterOperator::LessThan(map.next_value()?)), + "action" => { + match map.next_value()? { + "contract_creation" => { + f_op = W::CC; + }, + _ => { + return Err(M::Error::custom( + "`action` only supports the value `contract_creation`", + )) + } + } + } + unknown => { + // skip mentioning `action` since it's a special/rare + // case and might confuse the usage with other filters. + return Err(M::Error::unknown_field(unknown, &["eq", "gt", "lt"])); + } + } + + counter += 1; + } + + // Good practices ensured: only one operator per filter field is allowed. + // In case there is more than just one operator, this method must still process + // all of them, otherwise serde returns an error mentioning a trailing comma issue + // (even on valid JSON), which is misleading to the user of this software. + if counter > 1 { + return Err(M::Error::custom( + "only one operator per filter type allowed", + )); + } + + Ok(f_op) + } + } + + deserializer.deserialize_map(WrapperVisitor { data: PhantomData }) + } + } + + deserializer.deserialize_map(FilterOptionsVisitor) + } +} + +#[cfg(test)] +mod tests { + use ethereum_types::{Address, U256}; + use serde_json; + use super::*; + use std::str::FromStr; + + #[test] + fn valid_defaults() { + let default = FilterOptions::default(); + assert_eq!(default.from, FilterOperator::Any); + assert_eq!(default.to, FilterOperator::Any); + assert_eq!(default.gas, FilterOperator::Any); + assert_eq!(default.gas_price, FilterOperator::Any); + assert_eq!(default.value, FilterOperator::Any); + assert_eq!(default.nonce, FilterOperator::Any); + + let json = r#"{}"#; + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, default); + } + + #[test] + fn valid_full_deserialization() { + let json = r#" + { + "from": { + "eq": "0x5f3dffcf347944d3739b0805c934d86c8621997f" + }, + "to": { + "eq": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6" + }, + "gas": { + "eq": "0x493e0" + }, + "gas_price": { + "eq": "0x12a05f200" + }, + "value": { + "eq": "0x0" + }, + "nonce": { + "eq": "0x577" + } + } + "#; + + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + from: FilterOperator::Eq(Address::from_str("5f3dffcf347944d3739b0805c934d86c8621997f").unwrap()), + to: FilterOperator::Eq(Some(Address::from_str("e8b2d01ffa0a15736b2370b6e5064f9702c891b6").unwrap())), + gas: FilterOperator::Eq(U256::from(300_000)), + gas_price: FilterOperator::Eq(U256::from(5_000_000_000 as i64)), + value: FilterOperator::Eq(U256::from(0)), + nonce: FilterOperator::Eq(U256::from(1399)), + }) + } + + #[test] + fn invalid_full_deserialization() { + // Invalid filter type `zyx` + let json = r#" + { + "from": { + "eq": "0x5f3dffcf347944d3739b0805c934d86c8621997f" + }, + "to": { + "eq": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6" + }, + "zyx": { + "eq": "0x493e0" + }, + "gas_price": { + "eq": "0x12a05f200" + }, + "value": { + "eq": "0x0" + }, + "nonce": { + "eq": "0x577" + } + } + "#; + + let res = serde_json::from_str::(json); + assert!(res.is_err()) + } + + #[test] + fn valid_from_operators() { + // Only one valid operator for from + let json = r#" + { + "from": { + "eq": "0x5f3dffcf347944d3739b0805c934d86c8621997f" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + from: FilterOperator::Eq(Address::from_str("5f3dffcf347944d3739b0805c934d86c8621997f").unwrap()), + ..default + }); + } + + #[test] + fn invalid_from_operators() { + // Multiple operators are invalid + let json = r#" + { + "from": { + "eq": "0x5f3dffcf347944d3739b0805c934d86c8621997f", + "lt": "0x407d73d8a49eeb85d32cf465507dd71d507100c1" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Gt + let json = r#" + { + "from": { + "gt": "0x5f3dffcf347944d3739b0805c934d86c8621997f" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Lt + let json = r#" + { + "from": { + "lt": "0x5f3dffcf347944d3739b0805c934d86c8621997f" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Action + let json = r#" + { + "from": { + "action": "contract_creation" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Unknown operator + let json = r#" + { + "from": { + "abc": "0x0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + } + + #[test] + fn valid_to_operators() { + // Only two valid operator for to + // Eq + let json = r#" + { + "to": { + "eq": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + to: FilterOperator::Eq(Some(Address::from_str("e8b2d01ffa0a15736b2370b6e5064f9702c891b6").unwrap())), + ..default.clone() + }); + + // Action + let json = r#" + { + "to": { + "action": "contract_creation" + } + } + "#; + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + to: FilterOperator::Eq(None), + ..default + }); + } + + #[test] + fn invalid_to_operators() { + // Multiple operators are invalid + let json = r#" + { + "to": { + "eq": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6", + "action": "contract_creation" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Gt + let json = r#" + { + "to": { + "gt": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Lt + let json = r#" + { + "to": { + "lt": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Action (invalid value, must be "contract_creation") + let json = r#" + { + "to": { + "action": "some_invalid_value" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Unknown operator + let json = r#" + { + "to": { + "abc": "0x0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + } + + #[test] + fn valid_gas_operators() { + // Eq + let json = r#" + { + "gas": { + "eq": "0x493e0" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + gas: FilterOperator::Eq(U256::from(300_000)), + ..default.clone() + }); + + // Gt + let json = r#" + { + "gas": { + "gt": "0x493e0" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + gas: FilterOperator::GreaterThan(U256::from(300_000)), + ..default.clone() + }); + + // Lt + let json = r#" + { + "gas": { + "lt": "0x493e0" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + gas: FilterOperator::LessThan(U256::from(300_000)), + ..default + }); + } + + #[test] + fn invalid_gas_operators() { + // Multiple operators are invalid + let json = r#" + { + "gas": { + "eq": "0x493e0", + "lt": "0x493e0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Action + let json = r#" + { + "gas": { + "action": "contract_creation" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Unknown operator + let json = r#" + { + "gas": { + "abc": "0x0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + } + + #[test] + fn valid_gas_price_operators() { + // Eq + let json = r#" + { + "gas_price": { + "eq": "0x12a05f200" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + gas_price: FilterOperator::Eq(U256::from(5_000_000_000 as i64)), + ..default.clone() + }); + + // Gt + let json = r#" + { + "gas_price": { + "gt": "0x12a05f200" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + gas_price: FilterOperator::GreaterThan(U256::from(5_000_000_000 as i64)), + ..default.clone() + }); + + // Lt + let json = r#" + { + "gas_price": { + "lt": "0x12a05f200" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + gas_price: FilterOperator::LessThan(U256::from(5_000_000_000 as i64)), + ..default + }); + } + + #[test] + fn invalid_gas_price_operators() { + // Multiple operators are invalid + let json = r#" + { + "gas_price": { + "eq": "0x12a05f200", + "lt": "0x12a05f200" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Action + let json = r#" + { + "gas_price": { + "action": "contract_creation" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Unknown operator + let json = r#" + { + "gas_price": { + "abc": "0x0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + } + + #[test] + fn valid_value_operators() { + // Eq + let json = r#" + { + "value": { + "eq": "0x0" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + value: FilterOperator::Eq(U256::from(0)), + ..default.clone() + }); + + // Gt + let json = r#" + { + "value": { + "gt": "0x0" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + value: FilterOperator::GreaterThan(U256::from(0)), + ..default.clone() + }); + + // Lt + let json = r#" + { + "value": { + "lt": "0x0" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + value: FilterOperator::LessThan(U256::from(0)), + ..default + }); + } + + #[test] + fn invalid_value_operators() { + // Multiple operators are invalid + let json = r#" + { + "value": { + "eq": "0x0", + "lt": "0x0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Action + let json = r#" + { + "value": { + "action": "contract_creation" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Unknown operator + let json = r#" + { + "value": { + "abc": "0x0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + } + + #[test] + fn valid_nonce_operators() { + // Eq + let json = r#" + { + "nonce": { + "eq": "0x577" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + nonce: FilterOperator::Eq(U256::from(1399)), + ..default.clone() + }); + + // Gt + let json = r#" + { + "nonce": { + "gt": "0x577" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + nonce: FilterOperator::GreaterThan(U256::from(1399)), + ..default.clone() + }); + + // Lt + let json = r#" + { + "nonce": { + "lt": "0x577" + } + } + "#; + let default = FilterOptions::default(); + let res = serde_json::from_str::(json).unwrap(); + assert_eq!(res, FilterOptions { + nonce: FilterOperator::LessThan(U256::from(1399)), + ..default + }); + } + + #[test] + fn invalid_nonce_operators() { + // Multiple operators are invalid + let json = r#" + { + "nonce": { + "eq": "0x577", + "lt": "0x577" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Action + let json = r#" + { + "nonce": { + "action": "contract_creation" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + + // Unknown operator + let json = r#" + { + "nonce": { + "abc": "0x0" + } + } + "#; + let res = serde_json::from_str::(json); + assert!(res.is_err()); + } +} diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 2cba11b65..c35423d74 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -31,6 +31,7 @@ use ethcore_miner::work_notify::NotifyWork; use ethereum_types::{H256, U256, Address}; use futures::sync::mpsc; use io::IoChannel; +use miner::filter_options::{FilterOptions, FilterOperator}; use miner::pool_client::{PoolClient, CachedNonceClient, NonceCache}; use miner; use parking_lot::{Mutex, RwLock}; @@ -1050,6 +1051,19 @@ impl miner::MinerService for Miner { -> Vec> where C: ChainInfo + Nonce + Sync, + { + // No special filtering options applied (neither tx_hash, receiver or sender) + self.ready_transactions_filtered(chain, max_len, None, ordering) + } + + fn ready_transactions_filtered( + &self, + chain: &C, + max_len: usize, + filter: Option, + ordering: miner::PendingOrdering, + ) -> Vec> where + C: ChainInfo + Nonce + Sync, { let chain_info = chain.chain_info(); @@ -1071,12 +1085,76 @@ impl miner::MinerService for Miner { ) }; + use miner::filter_options::FilterOperator::*; let from_pending = || { self.map_existing_pending_block(|sealing| { + // This filter is used for gas, gas price, value and nonce. + // Sender and receiver have their custom matches, since those + // allow/disallow different operators. + fn match_common_filter(operator: &FilterOperator, tx_value: &U256) -> bool { + match operator { + Eq(value) => tx_value == value, + GreaterThan(value) => tx_value > value, + LessThan(value) => tx_value < value, + // Will always occure on `Any`, other operators + // get handled during deserialization + _ => true, + } + } + sealing.transactions .iter() .map(|signed| pool::VerifiedTransaction::from_pending_block_transaction(signed.clone())) .map(Arc::new) + // Filter by sender + .filter(|tx| { + filter.as_ref().map_or(true, |filter| { + let sender = tx.signed().sender(); + match filter.from { + Eq(value) => sender == value, + // Will always occure on `Any`, other operators + // get handled during deserialization + _ => true, + } + }) + }) + // Filter by receiver + .filter(|tx| { + filter.as_ref().map_or(true, |filter| { + let receiver = (*tx.signed()).receiver(); + match filter.to { + // Could apply to `Some(Address)` or `None` (for contract creation) + Eq(value) => receiver == value, + // Will always occure on `Any`, other operators + // get handled during deserialization + _ => true, + } + }) + }) + // Filter by gas + .filter(|tx| { + filter.as_ref().map_or(true, |filter| { + match_common_filter(&filter.gas, &(*tx.signed()).gas) + }) + }) + // Filter by gas price + .filter(|tx| { + filter.as_ref().map_or(true, |filter| { + match_common_filter(&filter.gas_price, &(*tx.signed()).gas_price) + }) + }) + // Filter by tx value + .filter(|tx| { + filter.as_ref().map_or(true, |filter| { + match_common_filter(&filter.value, &(*tx.signed()).value) + }) + }) + // Filter by nonce + .filter(|tx| { + filter.as_ref().map_or(true, |filter| { + match_common_filter(&filter.nonce, &(*tx.signed()).nonce) + }) + }) .take(max_len) .collect() }, chain_info.best_block_number) diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index fd7ab9651..c6397fccc 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -20,12 +20,13 @@ //! Keeps track of transactions and currently sealed pending block. mod miner; - +mod filter_options; pub mod pool_client; #[cfg(feature = "stratum")] pub mod stratum; pub use self::miner::{Miner, MinerOptions, Penalization, PendingSet, AuthoringParams, Author}; +pub use self::filter_options::FilterOptions; pub use ethcore_miner::local_accounts::LocalAccounts; pub use ethcore_miner::pool::PendingOrdering; @@ -184,6 +185,14 @@ pub trait MinerService : Send + Sync { fn ready_transactions(&self, chain: &C, max_len: usize, ordering: PendingOrdering) -> Vec> where C: ChainInfo + Nonce + Sync; + /// Get a list of all ready transactions either ordered by priority or unordered (cheaper), optionally filtered by hash, sender or receiver. + /// + /// Depending on the settings may look in transaction pool or only in pending block. + /// If you don't need a full set of transactions, you can add `max_len` and create only a limited set of + /// transactions. + fn ready_transactions_filtered(&self, chain: &C, max_len: usize, filter: Option, ordering: PendingOrdering) -> Vec> + where C: ChainInfo + Nonce + Sync; + /// Get a list of all transactions in the pool (some of them might not be ready for inclusion yet). fn queued_transactions(&self) -> Vec>; diff --git a/ethcore/types/src/transaction/transaction.rs b/ethcore/types/src/transaction/transaction.rs index 12e0d7125..dbd84b0fd 100644 --- a/ethcore/types/src/transaction/transaction.rs +++ b/ethcore/types/src/transaction/transaction.rs @@ -313,6 +313,14 @@ impl UnverifiedTransaction { self.r.is_zero() && self.s.is_zero() } + /// Returns transaction receiver, if any + pub fn receiver(&self) -> Option
{ + match self.unsigned.action { + Action::Create => None, + Action::Call(receiver) => Some(receiver), + } + } + /// Append object with a signature into RLP stream fn rlp_append_sealed_transaction(&self, s: &mut RlpStream) { s.begin_list(9); diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index f3dea5485..06ad7b43b 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -26,6 +26,7 @@ use ethstore::random_phrase; use sync::{LightSyncInfo, LightSyncProvider, LightNetworkDispatcher, ManageNetwork}; use updater::VersionInfo as UpdaterVersionInfo; use ethereum_types::{H64, H160, H256, H512, U64, U256}; +use ethcore::miner::FilterOptions; use ethcore_logger::RotatingLogger; use jsonrpc_core::{Result, BoxFuture}; @@ -217,7 +218,7 @@ where .map(Into::into) } - fn pending_transactions(&self, limit: Option) -> Result> { + fn pending_transactions(&self, limit: Option, _filter: Option) -> Result> { let txq = self.light_dispatch.transaction_queue.read(); let chain_info = self.light_dispatch.client.chain_info(); Ok( diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 0430acb75..1d5556f43 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -22,7 +22,7 @@ use std::collections::BTreeMap; use crypto::DEFAULT_MAC; use ethereum_types::{Address, H64, H160, H256, H512, U64, U256}; use ethcore::client::{BlockChainClient, StateClient, Call}; -use ethcore::miner::{self, MinerService}; +use ethcore::miner::{self, MinerService, FilterOptions}; use ethcore::snapshot::{SnapshotService, RestorationStatus}; use ethcore::state::StateInfo; use ethcore_logger::RotatingLogger; @@ -245,10 +245,11 @@ impl Parity for ParityClient where .map(Into::into) } - fn pending_transactions(&self, limit: Option) -> Result> { - let ready_transactions = self.miner.ready_transactions( + fn pending_transactions(&self, limit: Option, filter: Option) -> Result> { + let ready_transactions = self.miner.ready_transactions_filtered( &*self.client, limit.unwrap_or_else(usize::max_value), + filter, miner::PendingOrdering::Priority, ); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 3d7552456..694dd9662 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -24,7 +24,7 @@ use ethcore::block::SealedBlock; use ethcore::client::{Nonce, PrepareOpenBlock, StateClient, EngineInfo}; use ethcore::engines::{Engine, signer::EngineSigner}; use ethcore::error::Error; -use ethcore::miner::{self, MinerService, AuthoringParams}; +use ethcore::miner::{self, MinerService, AuthoringParams, FilterOptions}; use ethereum_types::{H256, U256, Address}; use miner::pool::local_transactions::Status as LocalTransactionStatus; use miner::pool::{verifier, VerifiedTransaction, QueueStatus}; @@ -222,6 +222,10 @@ impl MinerService for TestMinerService { self.queued_transactions() } + fn ready_transactions_filtered(&self, _chain: &C, _max_len: usize, _filter: Option, _ordering: miner::PendingOrdering) -> Vec> { + self.queued_transactions() + } + fn pending_transaction_hashes(&self, _chain: &C) -> BTreeSet { self.queued_transactions().into_iter().map(|tx| tx.signed().hash()).collect() } diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index a89e13171..050c9bd07 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -19,6 +19,7 @@ use std::collections::BTreeMap; use ethereum_types::{H64, H160, H256, H512, U64, U256}; +use ethcore::miner::FilterOptions; use jsonrpc_core::{BoxFuture, Result}; use jsonrpc_derive::rpc; use v1::types::{ @@ -125,7 +126,7 @@ pub trait Parity { /// Returns all pending transactions from transaction queue. #[rpc(name = "parity_pendingTransactions")] - fn pending_transactions(&self, Option) -> Result>; + fn pending_transactions(&self, Option, Option) -> Result>; /// Returns all transactions from transaction queue. ///