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.
///