Add filtering capability to parity_pendingTransactions
(issue 8269) (#10506)
* 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 <tomusdrw@users.noreply.github.com> * Use map_or instead of if-let Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * additional map_or replacement * change receiver type to Option<Option<Address>> * 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
This commit is contained in:
parent
7f02a08741
commit
3f61f2d8d9
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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;
|
||||
|
869
ethcore/src/miner/filter_options.rs
Normal file
869
ethcore/src/miner/filter_options.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<Address>,
|
||||
/// Contains the operator to filter the to value of the transaction
|
||||
pub to: FilterOperator<Option<Address>>,
|
||||
/// Contains the operator to filter the gas value of the transaction
|
||||
pub gas: FilterOperator<U256>,
|
||||
/// Contains the operator to filter the gas price value of the transaction
|
||||
pub gas_price: FilterOperator<U256>,
|
||||
/// Contains the operator to filter the transaction value
|
||||
pub value: FilterOperator<U256>,
|
||||
/// Contains the operator to filter the nonce value of the transaction
|
||||
pub nonce: FilterOperator<U256>,
|
||||
}
|
||||
|
||||
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<T> {
|
||||
O(FilterOperator<T>),
|
||||
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<T> {
|
||||
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<FilterOperator<T>, M::Error>;
|
||||
fn validate_to(&mut self) -> Result<FilterOperator<Option<Address>>, M::Error>;
|
||||
fn validate_value(&mut self) -> Result<FilterOperator<T>, 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<FilterOperator<T>, 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<FilterOperator<Option<Address>>, 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<FilterOperator<T>, 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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
||||
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<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct WrapperVisitor<T> {
|
||||
data: PhantomData<T>,
|
||||
};
|
||||
impl<'de, T: Deserialize<'de>> Visitor<'de> for WrapperVisitor<T> {
|
||||
type Value = Wrapper<T>;
|
||||
|
||||
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<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
||||
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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Gt
|
||||
let json = r#"
|
||||
{
|
||||
"from": {
|
||||
"gt": "0x5f3dffcf347944d3739b0805c934d86c8621997f"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Lt
|
||||
let json = r#"
|
||||
{
|
||||
"from": {
|
||||
"lt": "0x5f3dffcf347944d3739b0805c934d86c8621997f"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Action
|
||||
let json = r#"
|
||||
{
|
||||
"from": {
|
||||
"action": "contract_creation"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Unknown operator
|
||||
let json = r#"
|
||||
{
|
||||
"from": {
|
||||
"abc": "0x0"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Gt
|
||||
let json = r#"
|
||||
{
|
||||
"to": {
|
||||
"gt": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Lt
|
||||
let json = r#"
|
||||
{
|
||||
"to": {
|
||||
"lt": "0xe8b2d01ffa0a15736b2370b6e5064f9702c891b6"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(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::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Unknown operator
|
||||
let json = r#"
|
||||
{
|
||||
"to": {
|
||||
"abc": "0x0"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Action
|
||||
let json = r#"
|
||||
{
|
||||
"gas": {
|
||||
"action": "contract_creation"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Unknown operator
|
||||
let json = r#"
|
||||
{
|
||||
"gas": {
|
||||
"abc": "0x0"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Action
|
||||
let json = r#"
|
||||
{
|
||||
"gas_price": {
|
||||
"action": "contract_creation"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Unknown operator
|
||||
let json = r#"
|
||||
{
|
||||
"gas_price": {
|
||||
"abc": "0x0"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Action
|
||||
let json = r#"
|
||||
{
|
||||
"value": {
|
||||
"action": "contract_creation"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Unknown operator
|
||||
let json = r#"
|
||||
{
|
||||
"value": {
|
||||
"abc": "0x0"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(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::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Action
|
||||
let json = r#"
|
||||
{
|
||||
"nonce": {
|
||||
"action": "contract_creation"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
|
||||
// Unknown operator
|
||||
let json = r#"
|
||||
{
|
||||
"nonce": {
|
||||
"abc": "0x0"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res = serde_json::from_str::<FilterOptions>(json);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
@ -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<Arc<VerifiedTransaction>>
|
||||
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<C>(
|
||||
&self,
|
||||
chain: &C,
|
||||
max_len: usize,
|
||||
filter: Option<FilterOptions>,
|
||||
ordering: miner::PendingOrdering,
|
||||
) -> Vec<Arc<VerifiedTransaction>> 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<U256>, 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)
|
||||
|
@ -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<C>(&self, chain: &C, max_len: usize, ordering: PendingOrdering) -> Vec<Arc<VerifiedTransaction>>
|
||||
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<C>(&self, chain: &C, max_len: usize, filter: Option<FilterOptions>, ordering: PendingOrdering) -> Vec<Arc<VerifiedTransaction>>
|
||||
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<Arc<VerifiedTransaction>>;
|
||||
|
||||
|
@ -313,6 +313,14 @@ impl UnverifiedTransaction {
|
||||
self.r.is_zero() && self.s.is_zero()
|
||||
}
|
||||
|
||||
/// Returns transaction receiver, if any
|
||||
pub fn receiver(&self) -> Option<Address> {
|
||||
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);
|
||||
|
@ -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<usize>) -> Result<Vec<Transaction>> {
|
||||
fn pending_transactions(&self, limit: Option<usize>, _filter: Option<FilterOptions>) -> Result<Vec<Transaction>> {
|
||||
let txq = self.light_dispatch.transaction_queue.read();
|
||||
let chain_info = self.light_dispatch.client.chain_info();
|
||||
Ok(
|
||||
|
@ -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<C, M, U, S> Parity for ParityClient<C, M, U> where
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn pending_transactions(&self, limit: Option<usize>) -> Result<Vec<Transaction>> {
|
||||
let ready_transactions = self.miner.ready_transactions(
|
||||
fn pending_transactions(&self, limit: Option<usize>, filter: Option<FilterOptions>) -> Result<Vec<Transaction>> {
|
||||
let ready_transactions = self.miner.ready_transactions_filtered(
|
||||
&*self.client,
|
||||
limit.unwrap_or_else(usize::max_value),
|
||||
filter,
|
||||
miner::PendingOrdering::Priority,
|
||||
);
|
||||
|
||||
|
@ -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<C>(&self, _chain: &C, _max_len: usize, _filter: Option<FilterOptions>, _ordering: miner::PendingOrdering) -> Vec<Arc<VerifiedTransaction>> {
|
||||
self.queued_transactions()
|
||||
}
|
||||
|
||||
fn pending_transaction_hashes<C>(&self, _chain: &C) -> BTreeSet<H256> {
|
||||
self.queued_transactions().into_iter().map(|tx| tx.signed().hash()).collect()
|
||||
}
|
||||
|
@ -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<usize>) -> Result<Vec<Transaction>>;
|
||||
fn pending_transactions(&self, Option<usize>, Option<FilterOptions>) -> Result<Vec<Transaction>>;
|
||||
|
||||
/// Returns all transactions from transaction queue.
|
||||
///
|
||||
|
Loading…
Reference in New Issue
Block a user