From c467c7d1e20ffbb4c8e8788fb9ba715da5abf06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 3 Jan 2018 15:57:42 +0100 Subject: [PATCH 01/77] Implementation of Verifier, Scoring and Ready. --- Cargo.lock | 2 + ethcore/transaction/src/transaction.rs | 2 +- miner/Cargo.toml | 2 + miner/src/lib.rs | 4 + miner/src/pool/client.rs | 54 +++++++ miner/src/pool/mod.rs | 68 +++++++++ miner/src/pool/ready.rs | 75 ++++++++++ miner/src/pool/scoring.rs | 87 +++++++++++ miner/src/pool/verifier.rs | 200 +++++++++++++++++++++++++ transaction-pool/src/pool.rs | 2 + 10 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 miner/src/pool/client.rs create mode 100644 miner/src/pool/mod.rs create mode 100644 miner/src/pool/ready.rs create mode 100644 miner/src/pool/scoring.rs create mode 100644 miner/src/pool/verifier.rs diff --git a/Cargo.lock b/Cargo.lock index f3bcf667086..57a8d499d1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,6 +601,7 @@ name = "ethcore-miner" version = "1.9.0" dependencies = [ "common-types 0.1.0", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.9.0", "ethcore-transaction 0.1.0", "ethereum-types 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -615,6 +616,7 @@ dependencies = [ "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "table 0.1.0", + "transaction-pool 1.9.0", "transient-hashmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/ethcore/transaction/src/transaction.rs b/ethcore/transaction/src/transaction.rs index f206c549a7c..e3d7fcde8f9 100644 --- a/ethcore/transaction/src/transaction.rs +++ b/ethcore/transaction/src/transaction.rs @@ -360,7 +360,7 @@ impl UnverifiedTransaction { } } - /// Get the hash of this header (keccak of the RLP). + /// Get the hash of this transaction (keccak of the RLP). pub fn hash(&self) -> H256 { self.hash } diff --git a/miner/Cargo.toml b/miner/Cargo.toml index d255e54a187..873361f3d5a 100644 --- a/miner/Cargo.toml +++ b/miner/Cargo.toml @@ -11,6 +11,7 @@ authors = ["Parity Technologies "] hyper = { git = "https://github.com/paritytech/hyper", default-features = false } common-types = { path = "../ethcore/types" } +error-chain = "0.11" ethash = { path = "../ethash" } ethcore-transaction = { path = "../ethcore/transaction" } ethereum-types = "0.1" @@ -24,4 +25,5 @@ native-contracts = { path = "../ethcore/native_contracts" } parking_lot = "0.5" rustc-hex = "1.0" table = { path = "../util/table" } +transaction-pool = { path = "../transaction-pool" } transient-hashmap = "0.4" diff --git a/miner/src/lib.rs b/miner/src/lib.rs index fe6e5fe2144..1bb5943c961 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -29,10 +29,13 @@ extern crate linked_hash_map; extern crate native_contracts; extern crate parking_lot; extern crate table; +extern crate transaction_pool as txpool; extern crate transient_hashmap; #[macro_use] extern crate log; +#[macro_use] +extern crate error_chain; #[cfg(test)] extern crate rustc_hex; @@ -42,6 +45,7 @@ extern crate ethkey; pub mod banning_queue; pub mod external; pub mod local_transactions; +pub mod pool; pub mod service_transaction_checker; pub mod transaction_queue; pub mod work_notify; diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs new file mode 100644 index 00000000000..eba66d5372e --- /dev/null +++ b/miner/src/pool/client.rs @@ -0,0 +1,54 @@ +//! Transaction Pool state client. +//! +//! `Client` encapsulates all external data required for the verifaction and readiness. +//! It includes any Ethereum state parts required for checking the transaction and +//! any consensus-required structure of the transaction. + +use std::fmt; + +use ethereum_types::{U256, H256, H160 as Address}; +use transaction; + +/// Account Details +#[derive(Debug)] +pub struct AccountDetails { + /// Current account nonce + pub nonce: U256, + /// Current account balance + pub balance: U256, + /// Is this account a local account? + pub is_local: bool, +} + +/// Transaction type +#[derive(Debug, PartialEq)] +pub enum TransactionType { + /// Regular transaction + Regular, + /// Service transaction (allowed by a contract) + Service, + /// Denied transaction (disallowed by a contract) + Denied, +} + +/// State client. +pub trait Client: fmt::Debug { + /// Is transaction with given hash already in the blockchain? + fn transaction_already_included(&self, hash: &H256) -> bool; + + /// Structurarily verify given transaction. + fn verify_transaction(&self, tx: transaction::UnverifiedTransaction) + -> Result; + + /// Fetch account details for given sender. + fn account_details(&self, address: &Address) -> AccountDetails; + + /// Fetch only account nonce for given sender. + fn account_nonce(&self, address: &Address) -> U256; + + /// Estimate minimal gas requirurement for given transaction. + fn required_gas(&self, tx: &transaction::SignedTransaction) -> U256; + + /// Classify transaction (check if transaction is filtered by some contracts). + fn transaction_type(&self, tx: &transaction::SignedTransaction) -> TransactionType; +} diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs new file mode 100644 index 00000000000..88c6440ca2b --- /dev/null +++ b/miner/src/pool/mod.rs @@ -0,0 +1,68 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +//! Transaction Pool + +use ethereum_types::{H256, H160 as Address}; +use heapsize::HeapSizeOf; +use transaction; +use txpool; + +pub mod client; +pub mod ready; +pub mod scoring; +pub mod verifier; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub(crate) enum Priority { + Local, + Retracted, + Regular, +} + +/// Verified transaction stored in the pool. +#[derive(Debug)] +pub struct VerifiedTransaction { + transaction: transaction::PendingTransaction, + hash: H256, + sender: Address, + priority: Priority, + insertion_id: usize, +} + +impl VerifiedTransaction { + pub(crate) fn priority(&self) -> Priority { + self.priority + } +} + +impl txpool::VerifiedTransaction for VerifiedTransaction { + fn hash(&self) -> &H256 { + &self.hash + } + + fn mem_usage(&self) -> usize { + self.transaction.heap_size_of_children() + } + + fn sender(&self) -> &Address { + &self.sender + } + + fn insertion_id(&self) -> u64 { + self.insertion_id as u64 + } +} diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs new file mode 100644 index 00000000000..65a6de2a055 --- /dev/null +++ b/miner/src/pool/ready.rs @@ -0,0 +1,75 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +//! Transaction Readiness indicator +//! +//! Transaction readiness is responsible for indicating if +//! particular transaction can be included in the block. +//! +//! Regular transactions are ready iff the current state nonce +//! (obtained from `Client`) equals to the transaction nonce. +//! +//! Let's define `S = state nonce`. Transactions are processed +//! in order, so we first include transaction with nonce `S`, +//! but then we are able to include the one with `S + 1` nonce. +//! So bear in mind that transactions can be included in chains +//! and their readiness is dependent on previous transactions from +//! the same sender. +//! +//! There are three possible outcomes: +//! - The transaction is old (stalled; state nonce < transaction nonce) +//! - The transaction is ready (current; state nonce == transaction nonce) +//! - The transaction is not ready yet (future; state nonce > transaction nonce) +//! +//! NOTE The transactions are always checked for readines in order they are stored within the queue. +//! First `Readiness::Future` response also causes all subsequent transactions from the same sender +//! to be marked as `Future`. + +use std::cmp; +use std::collections::HashMap; + +use ethereum_types::{U256, H160 as Address}; +use txpool::{self, VerifiedTransaction as IVerifiedTransaction}; + +use super::client::Client; +use super::VerifiedTransaction; + +/// Checks readiness of transactions by comparing the nonce to state nonce. +#[derive(Debug)] +pub struct ClientReadiness { + nonces: HashMap, + state: C, +} + +impl txpool::Ready for ClientReadiness { + fn is_ready(&mut self, tx: &VerifiedTransaction) -> txpool::Readiness { + let sender = tx.sender(); + let state = &self.state; + // TODO [ToDr] Handle null-sender transactions + let state_nonce = || state.account_nonce(sender); + let nonce = self.nonces.entry(*sender).or_insert_with(state_nonce); + match tx.transaction.nonce.cmp(nonce) { + cmp::Ordering::Greater => txpool::Readiness::Future, + cmp::Ordering::Less => txpool::Readiness::Stalled, + cmp::Ordering::Equal => { + *nonce = *nonce + 1.into(); + txpool::Readiness::Ready + }, + } + } +} + + diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs new file mode 100644 index 00000000000..1f42f0e06c2 --- /dev/null +++ b/miner/src/pool/scoring.rs @@ -0,0 +1,87 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +//! Transaction Scoring and Ordering +//! +//! Ethereum transactions from the same sender are ordered by `nonce`. +//! Low nonces need to be included first. If there are two transactions from the same sender +//! and with the same `nonce` only one of them can be included. +//! We choose the one with higher gas price, but also require that gas price increment +//! is high enough to prevent attacking miners by requiring them to reshuffle/reexecute +//! the queue too often. +//! +//! Transactions between senders are prioritized using `gas price`. Higher `gas price` +//! yields more profits for miners. Additionally we prioritize transactions that originate +//! from our local node (own transactions). + +use std::cmp; +use std::sync::Arc; + +use ethereum_types::U256; +use txpool; +use super::VerifiedTransaction; + +/// Transaction with the same (sender, nonce) can be replaced only if +/// `new_gas_price > old_gas_price + old_gas_price >> SHIFT` +const GAS_PRICE_BUMP_SHIFT: usize = 3; // 2 = 25%, 3 = 12.5%, 4 = 6.25% + + +/// Simple, gas-price based scoring for transactions. +/// +/// TODO [ToDr] Consider including: +/// - Penalization +/// - Score of first transaction = Max/Avg(gas price of all transactions) +#[derive(Debug)] +pub struct GasPrice; + +impl txpool::Scoring for GasPrice { + type Score = U256; + + fn compare(&self, old: &VerifiedTransaction, other: &VerifiedTransaction) -> cmp::Ordering { + // TODO [ToDr] Handle null-sender transactions + old.transaction.nonce.cmp(&other.transaction.nonce) + } + + fn choose(&self, old: &VerifiedTransaction, new: &VerifiedTransaction) -> txpool::scoring::Choice { + let old_gp = old.transaction.gas_price; + let new_gp = new.transaction.gas_price; + + let min_required_gp = old_gp + (old_gp >> GAS_PRICE_BUMP_SHIFT); + // TODO [ToDr] Handle null-sender transactions + match min_required_gp.cmp(&new_gp) { + cmp::Ordering::Greater => txpool::scoring::Choice::RejectNew, + _ => txpool::scoring::Choice::ReplaceOld, + } + } + + fn update_scores(&self, txs: &[Arc], scores: &mut [U256], _change: txpool::scoring::Change) { + // TODO [ToDr] Optimize + for i in 0..txs.len() { + scores[i] = txs[i].transaction.gas_price; + let boost = match txs[i].priority() { + super::Priority::Local => 10, + super::Priority::Retracted => 5, + super::Priority::Regular => 0, + }; + // TODO [ToDr] overflow? + scores[i] = scores[i] + scores[i] >> boost; + } + } + + fn should_replace(&self, old: &VerifiedTransaction, new: &VerifiedTransaction) -> bool { + self.choose(old, new) == txpool::scoring::Choice::ReplaceOld + } +} diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs new file mode 100644 index 00000000000..e5c30c4bef4 --- /dev/null +++ b/miner/src/pool/verifier.rs @@ -0,0 +1,200 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +//! Transaction Verifier +//! +//! Responsible for verifying a transaction before importing to the pool. +//! Should make sure that the transaction is structuraly valid. +//! +//! May have some overlap with `Readiness` since we don't want to keep around +//! stalled transactions. + +use std::cmp; +use std::sync::atomic::{self, AtomicUsize}; + +use ethereum_types::{U256, H256}; +use transaction; +use txpool; + +use super::client::{Client, TransactionType}; +use super::VerifiedTransaction; + +/// Verification options. +#[derive(Debug)] +pub struct Options { + /// Minimal allowed gas price. + pub minimal_gas_price: U256, + /// Current block gas limit. + pub block_gas_limit: U256, + /// Maximal gas limit for a single transaction. + pub tx_gas_limit: U256, +} + +/// Transaction verifier. +/// +/// Verification can be run in parallel for all incoming transactions. +#[derive(Debug)] +pub struct Verifier { + client: T, + options: Options, + id: AtomicUsize, +} + +/// Transaction to verify. +pub enum Transaction { + /// Fresh, never verified transaction. + /// + /// We need to do full verification of such transactions + Unverified(transaction::UnverifiedTransaction), + /// Locally signed or retracted transaction. + /// + /// We can skip consistency verifications and just verify readiness. + Pending(transaction::PendingTransaction), +} + +impl Transaction { + fn hash(&self) -> H256 { + match *self { + Transaction::Unverified(ref tx) => tx.hash(), + Transaction::Pending(ref tx) => tx.transaction.hash(), + } + } +} + +impl txpool::Verifier for Verifier { + type Error = transaction::Error; + type VerifiedTransaction = VerifiedTransaction; + + fn verify_transaction(&self, tx: Transaction) -> Result { + let hash = tx.hash(); + + if self.client.transaction_already_included(&hash) { + trace!(target: "txqueue", "Rejected tx {:?}: already in the blockchain", hash); + bail!(transaction::Error::AlreadyImported) + } + + let was_unverified = if let Transaction::Unverified(_) = tx { true } else { false }; + let (tx, condition) = match tx { + Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { + Ok(signed) => (signed, None), + Err(err) => { + debug!(target: "txqueue", "Rejected tx {:?}: invalid signature: {:?}", hash, err); + bail!(err) + }, + }, + Transaction::Pending(tx) => (tx.transaction, tx.condition), + }; + + let gas_limit = cmp::min(self.options.tx_gas_limit, self.options.block_gas_limit); + if tx.gas > gas_limit { + debug!( + target: "txqueue", + "Dropping transaction above gas limit: {:?} ({} > min({}, {}))", + hash, + tx.gas, + self.options.block_gas_limit, + self.options.tx_gas_limit, + ); + bail!(transaction::Error::GasLimitExceeded { limit: gas_limit, got: tx.gas }); + } + + let minimal_gas = self.client.required_gas(&tx); + if tx.gas < minimal_gas { + trace!(target: "txqueue", + "Dropping transaction with insufficient gas: {:?} ({} > {})", + tx.hash(), + tx.gas, + minimal_gas, + ); + + bail!(transaction::Error::InsufficientGas { + minimal: minimal_gas, + got: tx.gas, + }) + } + + let transaction_type = self.client.transaction_type(&tx); + if let TransactionType::Denied = transaction_type { + debug!(target: "txqueue", "Rejected tx {:?}: denied by contract.", hash); + bail!(transaction::Error::NotAllowed) + } + + let sender = tx.sender(); + let account_details = self.client.account_details(&sender); + + if tx.gas_price < self.options.minimal_gas_price { + if let TransactionType::Service = transaction_type { + trace!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); + } else if account_details.is_local { + trace!(target: "txqueue", "Local tx {:?} below minimal gas price accepted", hash); + } else { + debug!( + target: "txqueue", + "Rejected tx {:?}: below minimal gas price threshold (gp: {} < {})", + hash, + tx.gas_price, + self.options.minimal_gas_price, + ); + bail!(transaction::Error::InsufficientGasPrice { + minimal: self.options.minimal_gas_price, + got: tx.gas_price, + }); + } + } + + let cost = tx.value + tx.gas_price * tx.gas; + if account_details.balance < cost { + debug!( + target: "txqueue", + "Rejected tx {:?}: not enough balance: ({} < {})", + hash, + account_details.balance, + cost, + ); + bail!(transaction::Error::InsufficientBalance { + cost: cost, + balance: account_details.balance, + }); + } + + if tx.nonce < account_details.nonce { + debug!( + target: "txqueue", + "Rejected tx {:?}: old nonce ({} < {})", + hash, + tx.nonce, + account_details.nonce, + ); + bail!(transaction::Error::AlreadyImported); + } + + let priority = match (account_details.is_local, was_unverified) { + (true, _) => super::Priority::Local, + (false, true) => super::Priority::Regular, + (false, false) => super::Priority::Retracted, + }; + Ok(VerifiedTransaction { + transaction: transaction::PendingTransaction { + transaction: tx, + condition, + }, + priority, + hash, + sender, + insertion_id: self.id.fetch_add(1, atomic::Ordering::AcqRel), + }) + } +} diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 5978acf6059..9da61d73441 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -109,6 +109,8 @@ impl Pool where ensure!(!self.by_hash.contains_key(transaction.hash()), error::ErrorKind::AlreadyImported(*transaction.hash())); + // TODO [ToDr] Most likely move this after the transsaction is inserted. + // Avoid using should_replace, but rather use scoring for that. { let remove_worst = |s: &mut Self, transaction| { match s.remove_worst(&transaction) { From 15a52b7759c88b63a7ff8a22c62af2db2b717318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 29 Jan 2018 02:45:17 +0100 Subject: [PATCH 02/77] Queue in progress. --- ethcore/src/block.rs | 1 + ethcore/src/miner/miner.rs | 192 +++++++++++++++---------------- ethcore/src/miner/mod.rs | 1 + miner/src/lib.rs | 1 + miner/src/pool/ready.rs | 42 ++++++- miner/src/pool/verifier.rs | 35 ++++-- miner/src/queue.rs | 86 ++++++++++++++ miner/src/queue.rs~ | 86 ++++++++++++++ transaction-pool/src/error.rs | 4 +- transaction-pool/src/lib.rs | 3 +- transaction-pool/src/listener.rs | 1 + transaction-pool/src/pool.rs | 6 +- transaction-pool/src/ready.rs | 12 ++ 13 files changed, 348 insertions(+), 122 deletions(-) create mode 100644 miner/src/queue.rs create mode 100644 miner/src/queue.rs~ diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index c4c82a17b68..18c8fe36dc5 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -859,3 +859,4 @@ mod tests { assert!(orig_db.journal_db().keys().iter().filter(|k| orig_db.journal_db().get(k.0) != db.journal_db().get(k.0)).next() == None); } } + diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 67567cc7d5f..802d65b9766 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -25,18 +25,18 @@ use parking_lot::{Mutex, RwLock}; use bytes::Bytes; use engines::{EthEngine, Seal}; use error::*; -use ethcore_miner::banning_queue::{BanningTransactionQueue, Threshold}; -use ethcore_miner::local_transactions::{Status as LocalTransactionStatus}; -use ethcore_miner::transaction_queue::{ - TransactionQueue, - RemovalReason, - TransactionDetailsProvider as TransactionQueueDetailsProvider, - PrioritizationStrategy, - AccountDetails, - TransactionOrigin, -}; -use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; -use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; +// use ethcore_miner::banning_queue::{BanningTransactionQueue, Threshold}; +// use ethcore_miner::local_transactions::{Status as LocalTransactionStatus}; +// use ethcore_miner::transaction_queue::{ +// TransactionQueue, +// RemovalReason, +// TransactionDetailsProvider as TransactionQueueDetailsProvider, +// PrioritizationStrategy, +// AccountDetails, +// TransactionOrigin, +// }; +// use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; +// use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; use miner::{MinerService, MinerStatus}; use price_info::fetch::Client as FetchClient; use price_info::{Client as PriceInfoClient, PriceInfo}; @@ -247,13 +247,12 @@ struct SealingWork { /// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work. pub struct Miner { // NOTE [ToDr] When locking always lock in this order! - transaction_queue: Arc>, sealing_work: Mutex, next_allowed_reseal: Mutex, next_mandatory_reseal: RwLock, sealing_block_last_request: Mutex, - // for sealing... options: MinerOptions, + transaction_queue: TransactionQueue, gas_range_target: RwLock<(U256, U256)>, author: RwLock
, @@ -312,8 +311,10 @@ impl Miner { false => ServiceTransactionAction::Check(ServiceTransactionChecker::default()), }; + let (limits, verifier_options) = unimplmented!(); + Miner { - transaction_queue: Arc::new(RwLock::new(txq)), + transaction_queue: TransactionQueue::new(limits, verifier_options), next_allowed_reseal: Mutex::new(Instant::now()), next_mandatory_reseal: RwLock::new(Instant::now() + options.reseal_max_period), sealing_block_last_request: Mutex::new(0), @@ -387,7 +388,7 @@ impl Miner { let nonce_cap = if chain_info.best_block_number + 1 >= self.engine.params().dust_protection_transition { Some((self.engine.params().nonce_cap_increment * (chain_info.best_block_number + 1)).into()) } else { None }; - let transactions = {self.transaction_queue.read().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp, nonce_cap)}; + let mut sealing_work = self.sealing_work.lock(); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); let best_hash = chain_info.best_block_hash; @@ -419,7 +420,13 @@ impl Miner { open_block.set_gas_limit(!U256::zero()); } - (transactions, open_block, last_work_hash) + let pending = self.transaction_queue.pending( + chain_info.best_block_number, + chain_info.best_block_timestamp, + nonce_cap, + ); + + (pending, open_block, last_work_hash) }; let mut invalid_transactions = HashSet::new(); @@ -427,37 +434,35 @@ impl Miner { let mut transactions_to_penalize = HashSet::new(); let block_number = open_block.block().fields().header.number(); - let mut tx_count: usize = 0; - let tx_total = transactions.len(); - for tx in transactions { + let mut tx_count = 0usize; + let mut skipped_transactions = 0usize; + let mut max_gas = open_block.header() + + for tx in pending.transactions() { let hash = tx.hash(); let start = Instant::now(); // Check whether transaction type is allowed for sender let result = match self.engine.machine().verify_transaction(&tx, open_block.header(), chain.as_block_chain_client()) { - Err(Error::Transaction(TransactionError::NotAllowed)) => { - Err(TransactionError::NotAllowed.into()) - } - _ => { - open_block.push_transaction(tx, None) - } + Err(Error::Transaction(TransactionError::NotAllowed)) => Err(TransactionError::NotAllowed.into()), + _ => open_block.push_transaction(tx, None), }; let took = start.elapsed(); // Check for heavy transactions - match self.options.tx_queue_banning { - Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { - match self.transaction_queue.write().ban_transaction(&hash) { - true => { - warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); - }, - false => { - transactions_to_penalize.insert(hash); - debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") - } - } - }, - _ => {}, - } + // match self.options.tx_queue_banning { + // Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { + // match self.transaction_queue.write().ban_transaction(&hash) { + // true => { + // warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); + // }, + // false => { + // transactions_to_penalize.insert(hash); + // debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") + // } + // } + // }, + // _ => {}, + // } trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); match result { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { @@ -465,12 +470,19 @@ impl Miner { // Penalize transaction if it's above current gas limit if gas > gas_limit { - transactions_to_penalize.insert(hash); + invalid_transactions.insert(hash); } // Exit early if gas left is smaller then min_tx_gas let min_tx_gas: U256 = 21000.into(); // TODO: figure this out properly. - if gas_limit - gas_used < min_tx_gas { + let gas_left = gas_limit - gas_used; + if gas_left < min_tx_gas { + break; + } + + // Avoid iterating over the entire queue in case block is almost full. + skipped_transactions += 1; + if skipped_transactions > 8 { break; } }, @@ -483,22 +495,19 @@ impl Miner { Err(Error::Transaction(TransactionError::AlreadyImported)) => {}, Err(Error::Transaction(TransactionError::NotAllowed)) => { non_allowed_transactions.insert(hash); - debug!(target: "miner", - "Skipping non-allowed transaction for sender {:?}", - hash); + debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); }, Err(e) => { invalid_transactions.insert(hash); - debug!(target: "miner", - "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", - block_number, hash, e); + debug!( + target: "miner", "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e + ); }, - _ => { - tx_count += 1; - } // imported ok + // imported ok + _ => tx_count += 1, } } - trace!(target: "miner", "Pushed {}/{} transactions", tx_count, tx_total); + trace!(target: "miner", "Pushed {} transactions", tx_count); let block = open_block.close(); @@ -883,12 +892,10 @@ impl MinerService for Miner { transactions: Vec ) -> Vec> { trace!(target: "external_tx", "Importing external transactions"); - let results = { - let mut transaction_queue = self.transaction_queue.write(); - self.add_transactions_to_queue( - chain, transactions, TransactionOrigin::External, None, &mut transaction_queue - ) - }; + let results = self.transaction_queue.import( + chain, + transactions.into_iter().map(pool::verifier::Transaction::Unverified).collect(), + ); if !results.is_empty() && self.options.reseal_on_external_tx && self.tx_reseal_allowed() { // -------------------------------------------------------------------------- @@ -908,25 +915,20 @@ impl MinerService for Miner { trace!(target: "own_tx", "Importing transaction: {:?}", pending); - let imported = { - // Be sure to release the lock before we call prepare_work_sealing - let mut transaction_queue = self.transaction_queue.write(); - // We need to re-validate transactions - let import = self.add_transactions_to_queue( - chain, vec![pending.transaction.into()], TransactionOrigin::Local, pending.condition, &mut transaction_queue - ).pop().expect("one result returned per added transaction; one added => one result; qed"); - - match import { - Ok(_) => { - trace!(target: "own_tx", "Status: {:?}", transaction_queue.status()); - }, - Err(ref e) => { - trace!(target: "own_tx", "Status: {:?}", transaction_queue.status()); - warn!(target: "own_tx", "Error importing transaction: {:?}", e); - }, - } - import - }; + let import = self.transaction_queue.import( + chain, + vec![pool::verifier::Transaction::Pending(pending)] + ).pop().expect("one result returned per added transaction; one added => one result; qed"); + + match import { + Ok(_) => { + trace!(target: "own_tx", "Status: {:?}", self.transaction_queue.status()); + }, + Err(ref e) => { + trace!(target: "own_tx", "Status: {:?}", self.transaction_queue.status()); + warn!(target: "own_tx", "Error importing transaction: {:?}", e); + }, + } // -------------------------------------------------------------------------- // | NOTE Code below requires transaction_queue and sealing_work locks. | @@ -1026,16 +1028,6 @@ impl MinerService for Miner { } } - fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option { - let mut queue = self.transaction_queue.write(); - let tx = queue.find(hash); - if tx.is_some() { - let fetch_nonce = |a: &Address| chain.latest_nonce(a); - queue.remove(hash, &fetch_nonce, RemovalReason::Canceled); - } - tx - } - fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option { self.from_pending_block( best_block, @@ -1087,7 +1079,8 @@ impl MinerService for Miner { } fn last_nonce(&self, address: &Address) -> Option { - self.transaction_queue.read().last_nonce(address) + // TODO [ToDr] missing! + unimplemented!() } fn can_produce_work_package(&self) -> bool { @@ -1189,28 +1182,23 @@ impl MinerService for Miner { // Then import all transactions... { - - let mut transaction_queue = self.transaction_queue.write(); + // TODO [ToDr] Parallelize for hash in retracted { let block = chain.block(BlockId::Hash(*hash)) .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); - let txs = block.transactions(); - let _ = self.add_transactions_to_queue( - chain, txs, TransactionOrigin::RetractedBlock, None, &mut transaction_queue + let txs = block.transactions() + .into_iter() + .map(|transaction| pool::verifier::Transaction::Pending(transaction.into())) + .collect(); + let _ = self.transaction_queue.import( + chain, + txs, ); } } // ...and at the end remove the old ones - { - let fetch_account = |a: &Address| AccountDetails { - nonce: chain.latest_nonce(a), - balance: chain.latest_balance(a), - }; - let time = chain.chain_info().best_block_number; - let mut transaction_queue = self.transaction_queue.write(); - transaction_queue.remove_old(&fetch_account, time); - } + self.transaction_queue.cull(client); if enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle) { // -------------------------------------------------------------------------- diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 34c416cc146..5d96f189f86 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -40,6 +40,7 @@ mod miner; mod stratum; +mod queue; pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOptions}; diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 1bb5943c961..4e30c9bd2bc 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -49,3 +49,4 @@ pub mod pool; pub mod service_transaction_checker; pub mod transaction_queue; pub mod work_notify; +pub mod queue; diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs index 65a6de2a055..e70ee30da4c 100644 --- a/miner/src/pool/ready.rs +++ b/miner/src/pool/ready.rs @@ -42,6 +42,7 @@ use std::cmp; use std::collections::HashMap; use ethereum_types::{U256, H160 as Address}; +use transaction; use txpool::{self, VerifiedTransaction as IVerifiedTransaction}; use super::client::Client; @@ -49,12 +50,22 @@ use super::VerifiedTransaction; /// Checks readiness of transactions by comparing the nonce to state nonce. #[derive(Debug)] -pub struct ClientReadiness { +pub struct State { nonces: HashMap, state: C, } -impl txpool::Ready for ClientReadiness { +impl State { + /// Create new State checker, given client interface. + pub fn new(state: C) -> Self { + State { + nonces: Default::default(), + state, + } + } +} + +impl txpool::Ready for State { fn is_ready(&mut self, tx: &VerifiedTransaction) -> txpool::Readiness { let sender = tx.sender(); let state = &self.state; @@ -72,4 +83,31 @@ impl txpool::Ready for ClientReadiness { } } +/// Checks readines of Pending transactions by comparing it with current time and block number. +#[derive(Debug)] +pub struct Condition { + block_number: u64, + now: u64, +} + +impl Condition { + /// Create a new condition checker given current block number and UTC timestamp. + pub fn new(block_number: u64, now: u64) -> Self { + Condition { + block_number, + now, + } + } +} + +impl txpool::Ready for Condition { + fn is_ready(&mut self, tx: &VerifiedTransaction) -> txpool::Readiness { + match tx.transaction.condition { + Some(transaction::Condition::Number(block)) if block > self.block_number => txpool::Readiness::Future, + Some(transaction::Condition::Timestamp(time)) if time > self.now => txpool::Readiness::Future, + _ => txpool::Readiness::Ready, + } + } +} + diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index e5c30c4bef4..a1f47cc0c9d 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -23,6 +23,7 @@ //! stalled transactions. use std::cmp; +use std::sync::Arc; use std::sync::atomic::{self, AtomicUsize}; use ethereum_types::{U256, H256}; @@ -33,7 +34,7 @@ use super::client::{Client, TransactionType}; use super::VerifiedTransaction; /// Verification options. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Options { /// Minimal allowed gas price. pub minimal_gas_price: U256, @@ -43,16 +44,6 @@ pub struct Options { pub tx_gas_limit: U256, } -/// Transaction verifier. -/// -/// Verification can be run in parallel for all incoming transactions. -#[derive(Debug)] -pub struct Verifier { - client: T, - options: Options, - id: AtomicUsize, -} - /// Transaction to verify. pub enum Transaction { /// Fresh, never verified transaction. @@ -74,7 +65,27 @@ impl Transaction { } } -impl txpool::Verifier for Verifier { +/// Transaction verifier. +/// +/// Verification can be run in parallel for all incoming transactions. +#[derive(Debug)] +pub struct Verifier { + client: C, + options: Options, + id: Arc, +} + +impl Verifier { + pub fn new(client: C, options: Options, id: Arc) -> Self { + Verifier { + client, + options, + id, + } + } +} + +impl txpool::Verifier for Verifier { type Error = transaction::Error; type VerifiedTransaction = VerifiedTransaction; diff --git a/miner/src/queue.rs b/miner/src/queue.rs new file mode 100644 index 00000000000..7ab600a4877 --- /dev/null +++ b/miner/src/queue.rs @@ -0,0 +1,86 @@ + +use std::sync::Arc; +use std::sync::atomic::AtomicUsize; + +use parking_lot::{RwLock, RwLockReadGuard}; +use transaction; +use txpool::{self, Verifier}; + +use pool::{self, scoring, verifier, client, ready}; + +type Pool = txpool::Pool; + +#[derive(Debug)] +pub struct TransactionQueue { + insertion_id: Arc, + pool: RwLock, + options: RwLock, +} + +impl TransactionQueue { + pub fn new(limits: txpool::Options, verification_options: verifier::Options) -> Self { + TransactionQueue { + insertion_id: Default::default(), + pool: RwLock::new(txpool::Pool::with_scoring(scoring::GasPrice, limits)), + options: RwLock::new(verification_options), + } + } + + pub fn import( + &self, + client: C, + transactions: Vec, + ) -> Vec> { + // Run verification + let options = self.options.read().clone(); + + // TODO [ToDr] parallelize + let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); + transactions + .into_iter() + .map(|transaction| verifier.verify_transaction(transaction)) + .map(|result| match result { + Ok(verified) => match self.pool.write().import(verified) { + Ok(imported) => Ok(()), + Err(txpool::Error(kind, _)) => unimplemented!(), + }, + Err(err) => Err(err), + }) + .collect() + } + + pub fn pending( + &self, + client: C, + block_number: u64, + current_timestamp: u64, + ) -> PendingReader<(ready::Condition, ready::State)> { + let pending_readiness = ready::Condition::new(block_number, current_timestamp); + let state_readiness = ready::State::new(client); + + PendingReader { + guard: self.pool.read(), + ready: Some((pending_readiness, state_readiness)), + } + } + + pub fn cull( + &self, + client: C, + ) { + let state_readiness = ready::State::new(client); + let removed = self.pool.write().cull(None, state_readiness); + debug!(target: "txqueue", "Removed {} stalled transactions.", removed); + } +} + +pub struct PendingReader<'a, R> { + guard: RwLockReadGuard<'a, Pool>, + ready: Option, +} + +impl<'a, R: txpool::Ready> PendingReader<'a, R> { + pub fn transactions(&'a mut self) -> txpool::PendingIterator { + self.guard.pending(self.ready.take().unwrap()) + } +} diff --git a/miner/src/queue.rs~ b/miner/src/queue.rs~ new file mode 100644 index 00000000000..177924fe397 --- /dev/null +++ b/miner/src/queue.rs~ @@ -0,0 +1,86 @@ + +use std::sync::Arc; +use std::sync::atomic::AtomicUsize; + +use parking_lot::RwLock; +use transaction; +use txpool::Pool; + +use pool::{scoring, verifier, client, ready}; + +type Pool = txpool::Pool; + +#[derive(Debug)] +pub struct TransactionQueue { + insertion_id: Arc, + pool: RwLock, + options: RwLock, +} + +impl TransactionQueue { + pub fn new(limits: txpool::Options, verification_options: verifier::Options) -> Self { + TransactionQueue { + insertion_id: Default::default(), + pool: RwLock::new(txpool::Pool::with_scoring(scoring::GasPrice, limits)), + options: RwLock::new(verification_options), + } + } + + pub fn import( + &self, + client: C, + transactions: Vec, + ) -> Vec { + // Run verification + let options = self.options.read().clone(); + + // TODO [ToDr] parallelize + let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); + transactions + .into_iter() + .map(|transaction| verifier.verify_transaction(transaction)) + .map(|result| match result { + Ok(verified) => match self.pool.write().import(verified) { + Ok(imported) => (), + Err(txpool::Error(kind, _)) => unimplemented!(), + }, + Err(err) => Err(err), + }) + .collect() + } + + pub fn pending( + &self, + client: C, + block_number: u64, + current_timestamp: u64, + ) -> PendingReader<(ready::Condition, ready::State)> { + let pending_readiness = ready::Condition::new(block_number, current_timestamp); + let state_readiness = ready::State::new(client); + + PendingReader { + guard: self.pool.read(), + ready: (pending_readiness, state_readiness), + } + } + + pub fn cull( + &self, + client: C, + ) { + let state_readiness = ready::State::new(client); + let removed = self.pool.write().cull(None, state_readiness); + debug!(target: "txqueue", "Removed {} stalled transactions.", removed); + } +} + +pub struct PendingReader<'a, R> { + guard: RwLockReadReader<'a, Pool>, + ready: Option, +} + +impl<'a, R: txpool::Ready> PendingReader<'a, R> { + pub fn transactions<'b: 'a>(&'a mut self) -> txpool::PendingIterator<'b, pool::VerifiedTransaction, R, scoring::GasPrice, txpool::NoopListener> { + self.guard.pending(self.ready.take().unwrap()) + } +} diff --git a/transaction-pool/src/error.rs b/transaction-pool/src/error.rs index 57fffebc0d9..ae363e52fc7 100644 --- a/transaction-pool/src/error.rs +++ b/transaction-pool/src/error.rs @@ -19,7 +19,7 @@ use ethereum_types::H256; error_chain! { errors { AlreadyImported(hash: H256) { - description("transaction is already in the queue"), + description("transaction is already in the pool"), display("[{:?}] transaction already imported", hash) } TooCheapToEnter(hash: H256) { @@ -27,7 +27,7 @@ error_chain! { display("[{:?}] transaction too cheap to enter the pool", hash) } TooCheapToReplace(old_hash: H256, hash: H256) { - description("transaction is too cheap to replace existing transaction in the queue"), + description("transaction is too cheap to replace existing transaction in the pool"), display("[{:?}] transaction too cheap to replace: {:?}", hash, old_hash) } } diff --git a/transaction-pool/src/lib.rs b/transaction-pool/src/lib.rs index 987fdfc4083..5069c9db190 100644 --- a/transaction-pool/src/lib.rs +++ b/transaction-pool/src/lib.rs @@ -90,9 +90,10 @@ mod verifier; pub mod scoring; +pub use self::error::{Error, ErrorKind}; pub use self::listener::{Listener, NoopListener}; pub use self::options::Options; -pub use self::pool::Pool; +pub use self::pool::{Pool, PendingIterator}; pub use self::ready::{Ready, Readiness}; pub use self::scoring::Scoring; pub use self::status::{LightStatus, Status}; diff --git a/transaction-pool/src/listener.rs b/transaction-pool/src/listener.rs index 36a5d9de221..2fc55528fe8 100644 --- a/transaction-pool/src/listener.rs +++ b/transaction-pool/src/listener.rs @@ -44,5 +44,6 @@ pub trait Listener { } /// A no-op implementation of `Listener`. +#[derive(Debug)] pub struct NoopListener; impl Listener for NoopListener {} diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 9da61d73441..4f4a63920f1 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -290,7 +290,7 @@ impl Pool where /// Removes single transaction from the pool. /// Depending on the `is_invalid` flag the listener /// will either get a `cancelled` or `invalid` notification. - pub fn remove(&mut self, hash: &H256, is_invalid: bool) -> bool { + pub fn remove(&mut self, hash: &H256, is_invalid: bool) -> Option> { if let Some(tx) = self.finalize_remove(hash) { self.remove_from_set(tx.sender(), |set, scoring| { set.remove(&tx, scoring) @@ -300,9 +300,9 @@ impl Pool where } else { self.listener.cancelled(&tx); } - true + Some(tx) } else { - false + None } } diff --git a/transaction-pool/src/ready.rs b/transaction-pool/src/ready.rs index 1d8e342db27..73524443227 100644 --- a/transaction-pool/src/ready.rs +++ b/transaction-pool/src/ready.rs @@ -40,3 +40,15 @@ impl Ready for F where F: FnMut(&T) -> Readiness { (*self)(tx) } } + +impl Ready for (A, B) where + A: Ready, + B: Ready, +{ + fn is_ready(&mut self, tx: &T) -> Readiness { + match self.0.is_ready(tx) { + Readiness::Ready => self.1.is_ready(tx), + r => r, + } + } +} From d358050a3320ef519e107c945e0c8f76c2f8b64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 30 Jan 2018 17:24:02 +0100 Subject: [PATCH 03/77] TransactionPool. --- Cargo.lock | 40 ++++- ethcore/src/miner/miner.rs | 264 ++++++++---------------------- ethcore/src/miner/mod.rs | 1 - transaction-pool/Cargo.toml | 2 +- transaction-pool/src/error.rs | 3 + transaction-pool/src/lib.rs | 1 - transaction-pool/src/tests/mod.rs | 2 +- 7 files changed, 113 insertions(+), 200 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57a8d499d1e..21efb0f55b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,6 +429,18 @@ dependencies = [ "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethbloom" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ethereum-types-serialize 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fixed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethcore" version = "1.9.0" @@ -744,6 +756,29 @@ dependencies = [ "uint 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ethereum-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ethbloom 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethereum-types-serialize 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fixed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "uint 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ethereum-types-serialize" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ethjson" version = "0.1.0" @@ -3241,7 +3276,7 @@ name = "transaction-pool" version = "1.9.0" dependencies = [ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethereum-types 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ethereum-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3610,7 +3645,10 @@ dependencies = [ "checksum eth-secp256k1 0.5.7 (git+https://github.com/paritytech/rust-secp256k1)" = "" "checksum ethabi 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c819a3adef0413a2519cbd9a19a35dd1c20c7a0110705beaba8aa4aa87eda95f" "checksum ethbloom 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd5f7fb5d27fb017f21c15d1e0b953831b58581c9942a5e5614fd2b6603697b7" +"checksum ethbloom 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4bc92037607dd89203fa0d936615947fee9c019ca6430b3c82de64842b7133ff" "checksum ethereum-types 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaa5b8ceafcce0bc3a68ef116ca5702958cc97d70a6eb008aeddb569b092b3" +"checksum ethereum-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f0cfa733a9b0b05a7c4605e4c1dc21a9e958e0acb55f295798fddb2cbffa30c" +"checksum ethereum-types-serialize 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94dc0212b245f0115f6780e0a7c5ab682adc03cb7eeda62d859847b4424c9384" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum fixed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "75f91321a3926b773df3c6e7e953e3c756d9c06f0e9f05d34a0e103b03fcc9a1" "checksum flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423" diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 802d65b9766..388ce2898da 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -35,7 +35,7 @@ use error::*; // AccountDetails, // TransactionOrigin, // }; -// use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; +use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; // use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; use miner::{MinerService, MinerStatus}; use price_info::fetch::Client as FetchClient; @@ -72,32 +72,32 @@ pub enum PendingSet { SealingOrElseQueue, } -/// Type of the gas limit to apply to the transaction queue. -#[derive(Debug, PartialEq)] -pub enum GasLimit { - /// Depends on the block gas limit and is updated with every block. - Auto, - /// No limit. - None, - /// Set to a fixed gas value. - Fixed(U256), -} - -/// Transaction queue banning settings. -#[derive(Debug, PartialEq, Clone)] -pub enum Banning { - /// Banning in transaction queue is disabled - Disabled, - /// Banning in transaction queue is enabled - Enabled { - /// Upper limit of transaction processing time before banning. - offend_threshold: Duration, - /// Number of similar offending transactions before banning. - min_offends: u16, - /// Number of seconds the offender is banned for. - ban_duration: Duration, - }, -} +// /// Type of the gas limit to apply to the transaction queue. +// #[derive(Debug, PartialEq)] +// pub enum GasLimit { +// /// Depends on the block gas limit and is updated with every block. +// Auto, +// /// No limit. +// None, +// /// Set to a fixed gas value. +// Fixed(U256), +// } +// +// /// Transaction queue banning settings. +// #[derive(Debug, PartialEq, Clone)] +// pub enum Banning { +// /// Banning in transaction queue is disabled +// Disabled, +// /// Banning in transaction queue is enabled +// Enabled { +// /// Upper limit of transaction processing time before banning. +// offend_threshold: Duration, +// /// Number of similar offending transactions before banning. +// min_offends: u16, +// /// Number of seconds the offender is banned for. +// ban_duration: Duration, +// }, +// } /// Configures the behaviour of the miner. #[derive(Debug, PartialEq)] @@ -112,30 +112,30 @@ pub struct MinerOptions { pub reseal_on_own_tx: bool, /// Reseal when new uncle block has been imported. pub reseal_on_uncle: bool, - /// Minimum period between transaction-inspired reseals. - pub reseal_min_period: Duration, - /// Maximum period between blocks (enables force sealing after that). - pub reseal_max_period: Duration, - /// Maximum amount of gas to bother considering for block insertion. - pub tx_gas_limit: U256, - /// Maximum size of the transaction queue. - pub tx_queue_size: usize, - /// Maximum memory usage of transactions in the queue (current / future). - pub tx_queue_memory_limit: Option, - /// Strategy to use for prioritizing transactions in the queue. - pub tx_queue_strategy: PrioritizationStrategy, + // /// Minimum period between transaction-inspired reseals. + // pub reseal_min_period: Duration, + // /// Maximum period between blocks (enables force sealing after that). + // pub reseal_max_period: Duration, + // /// Maximum amount of gas to bother considering for block insertion. + // pub tx_gas_limit: U256, + // /// Maximum size of the transaction queue. + // pub tx_queue_size: usize, + // /// Maximum memory usage of transactions in the queue (current / future). + // pub tx_queue_memory_limit: Option, + // / Strategy to use for prioritizing transactions in the queue. + // pub tx_queue_strategy: PrioritizationStrategy, /// Whether we should fallback to providing all the queue's transactions or just pending. pub pending_set: PendingSet, /// How many historical work packages can we store before running out? pub work_queue_size: usize, /// Can we submit two different solutions for the same block and expect both to result in an import? pub enable_resubmission: bool, - /// Global gas limit for all transaction in the queue except for local and retracted. - pub tx_queue_gas_limit: GasLimit, - /// Banning settings. - pub tx_queue_banning: Banning, - /// Do we refuse to accept service transactions even if sender is certified. - pub refuse_service_transactions: bool, + // / Global gas limit for all transaction in the queue except for local and retracted. + // pub tx_queue_gas_limit: GasLimit, + // / Banning settings. + // pub tx_queue_banning: Banning, + // / Do we refuse to accept service transactions even if sender is certified. + // pub refuse_service_transactions: bool, /// Create a pending block with maximal possible gas limit. /// NOTE: Such block will contain all pending transactions but /// will be invalid if mined. @@ -150,11 +150,11 @@ impl Default for MinerOptions { reseal_on_external_tx: false, reseal_on_own_tx: true, reseal_on_uncle: false, - tx_gas_limit: !U256::zero(), - tx_queue_size: 8192, - tx_queue_memory_limit: Some(2 * 1024 * 1024), - tx_queue_gas_limit: GasLimit::None, - tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, + // tx_gas_limit: !U256::zero(), + // tx_queue_size: 8192, + // tx_queue_memory_limit: Some(2 * 1024 * 1024), + // tx_queue_gas_limit: GasLimit::None, + // tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, pending_set: PendingSet::AlwaysQueue, reseal_min_period: Duration::from_secs(2), reseal_max_period: Duration::from_secs(120), @@ -262,7 +262,7 @@ pub struct Miner { accounts: Option>, notifiers: RwLock>>, gas_pricer: Mutex, - service_transaction_action: ServiceTransactionAction, + // service_transaction_action: ServiceTransactionAction, } impl Miner { @@ -279,10 +279,6 @@ impl Miner { /// Creates new instance of miner. fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { - let gas_limit = match options.tx_queue_gas_limit { - GasLimit::Fixed(ref limit) => *limit, - _ => !U256::zero(), - }; let mem_limit = options.tx_queue_memory_limit.unwrap_or_else(usize::max_value); let txq = TransactionQueue::with_limits( @@ -292,26 +288,21 @@ impl Miner { gas_limit, options.tx_gas_limit ); - let txq = match options.tx_queue_banning { - Banning::Disabled => BanningTransactionQueue::new(txq, Threshold::NeverBan, Duration::from_secs(180)), - Banning::Enabled { ban_duration, min_offends, .. } => BanningTransactionQueue::new( - txq, - Threshold::BanAfter(min_offends), - ban_duration, - ), - }; + // let txq = match options.tx_queue_banning { + // Banning::Disabled => BanningTransactionQueue::new(txq, Threshold::NeverBan, Duration::from_secs(180)), + // Banning::Enabled { ban_duration, min_offends, .. } => BanningTransactionQueue::new( + // txq, + // Threshold::BanAfter(min_offends), + // ban_duration, + // ), + // }; let notifiers: Vec> = match options.new_work_notify.is_empty() { true => Vec::new(), false => vec![Box::new(WorkPoster::new(&options.new_work_notify))], }; - let service_transaction_action = match options.refuse_service_transactions { - true => ServiceTransactionAction::Refuse, - false => ServiceTransactionAction::Check(ServiceTransactionChecker::default()), - }; - - let (limits, verifier_options) = unimplmented!(); + let (limits, verifier_options) = unimplemented!(); Miner { transaction_queue: TransactionQueue::new(limits, verifier_options), @@ -332,7 +323,6 @@ impl Miner { engine: spec.engine.clone(), notifiers: RwLock::new(notifiers), gas_pricer: Mutex::new(gas_pricer), - service_transaction_action: service_transaction_action, } } @@ -436,7 +426,7 @@ impl Miner { let mut tx_count = 0usize; let mut skipped_transactions = 0usize; - let mut max_gas = open_block.header() + let mut max_gas = open_block.header(); for tx in pending.transactions() { let hash = tx.hash(); @@ -525,6 +515,7 @@ impl Miner { queue.penalize(&hash); } } + (block, original_work_hash) } @@ -652,12 +643,13 @@ impl Miner { fn update_gas_limit(&self, client: &MiningBlockChainClient) { let gas_limit = client.best_block_header().gas_limit(); - let mut queue = self.transaction_queue.write(); - queue.set_gas_limit(gas_limit); - if let GasLimit::Auto = self.options.tx_queue_gas_limit { - // Set total tx queue gas limit to be 20x the block gas limit. - queue.set_total_gas_limit(gas_limit * 20.into()); - } + // let mut queue = self.transaction_queue.write(); + // queue.set_gas_limit(gas_limit); + // if let GasLimit::Auto = self.options.tx_queue_gas_limit { + // // Set total tx queue gas limit to be 20x the block gas limit. + // queue.set_total_gas_limit(gas_limit * 20.into()); + // } + unimplemented!() } /// Returns true if we had to prepare new pending block. @@ -693,60 +685,6 @@ impl Miner { prepare_new } - fn add_transactions_to_queue( - &self, - client: &MiningBlockChainClient, - transactions: Vec, - default_origin: TransactionOrigin, - condition: Option, - transaction_queue: &mut BanningTransactionQueue, - ) -> Vec> { - let best_block_header = client.best_block_header().decode(); - let insertion_time = client.chain_info().best_block_number; - - transactions.into_iter() - .map(|tx| { - let hash = tx.hash(); - if client.transaction_block(TransactionId::Hash(hash)).is_some() { - debug!(target: "miner", "Rejected tx {:?}: already in the blockchain", hash); - return Err(Error::Transaction(TransactionError::AlreadyImported)); - } - match self.engine.verify_transaction_basic(&tx, &best_block_header) - .and_then(|_| self.engine.verify_transaction_unordered(tx, &best_block_header)) - { - Err(e) => { - debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", hash, e); - Err(e) - }, - Ok(transaction) => { - // This check goes here because verify_transaction takes SignedTransaction parameter - self.engine.machine().verify_transaction(&transaction, &best_block_header, client.as_block_chain_client())?; - - let origin = self.accounts.as_ref().and_then(|accounts| { - match accounts.has_account(transaction.sender()).unwrap_or(false) { - true => Some(TransactionOrigin::Local), - false => None, - } - }).unwrap_or(default_origin); - - // try to install service transaction checker before appending transactions - self.service_transaction_action.update_from_chain_client(client); - - let details_provider = TransactionDetailsProvider::new(client, &self.service_transaction_action); - match origin { - TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { - Ok(transaction_queue.add(transaction, origin, insertion_time, condition.clone(), &details_provider)?) - }, - TransactionOrigin::External => { - Ok(transaction_queue.add_with_banlist(transaction, insertion_time, &details_provider)?) - }, - } - }, - } - }) - .collect() - } - /// Are we allowed to do a non-mandatory reseal? fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock() } @@ -1210,70 +1148,6 @@ impl MinerService for Miner { } } -/// Action when service transaction is received -enum ServiceTransactionAction { - /// Refuse service transaction immediately - Refuse, - /// Accept if sender is certified to send service transactions - Check(ServiceTransactionChecker), -} - -impl ServiceTransactionAction { - pub fn update_from_chain_client(&self, client: &MiningBlockChainClient) { - if let ServiceTransactionAction::Check(ref checker) = *self { - checker.update_from_chain_client(&client); - } - } - - pub fn check(&self, client: &MiningBlockChainClient, tx: &SignedTransaction) -> Result { - match *self { - ServiceTransactionAction::Refuse => Err("configured to refuse service transactions".to_owned()), - ServiceTransactionAction::Check(ref checker) => checker.check(&client, tx), - } - } -} - -impl<'a> ::ethcore_miner::service_transaction_checker::ContractCaller for &'a MiningBlockChainClient { - fn registry_address(&self, name: &str) -> Option
{ - MiningBlockChainClient::registry_address(*self, name.into()) - } - - fn call_contract(&self, block: BlockId, address: Address, data: Vec) -> Result, String> { - MiningBlockChainClient::call_contract(*self, block, address, data) - } -} - -struct TransactionDetailsProvider<'a> { - client: &'a MiningBlockChainClient, - service_transaction_action: &'a ServiceTransactionAction, -} - -impl<'a> TransactionDetailsProvider<'a> { - pub fn new(client: &'a MiningBlockChainClient, service_transaction_action: &'a ServiceTransactionAction) -> Self { - TransactionDetailsProvider { - client: client, - service_transaction_action: service_transaction_action, - } - } -} - -impl<'a> TransactionQueueDetailsProvider for TransactionDetailsProvider<'a> { - fn fetch_account(&self, address: &Address) -> AccountDetails { - AccountDetails { - nonce: self.client.latest_nonce(address), - balance: self.client.latest_balance(address), - } - } - - fn estimate_gas_required(&self, tx: &SignedTransaction) -> U256 { - tx.gas_required(&self.client.latest_schedule()).into() - } - - fn is_service_transaction_acceptable(&self, tx: &SignedTransaction) -> Result { - self.service_transaction_action.check(self.client, tx) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 5d96f189f86..34c416cc146 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -40,7 +40,6 @@ mod miner; mod stratum; -mod queue; pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOptions}; diff --git a/transaction-pool/Cargo.toml b/transaction-pool/Cargo.toml index 70aba5d044a..bd351160e5d 100644 --- a/transaction-pool/Cargo.toml +++ b/transaction-pool/Cargo.toml @@ -9,4 +9,4 @@ authors = ["Parity Technologies "] error-chain = "0.11" log = "0.3" smallvec = "0.4" -ethereum-types = { version = "0.1", features = ["heapsizeof"] } +ethereum-types = { version = "0.2", features = ["heapsizeof"] } diff --git a/transaction-pool/src/error.rs b/transaction-pool/src/error.rs index ae363e52fc7..706a5b77b19 100644 --- a/transaction-pool/src/error.rs +++ b/transaction-pool/src/error.rs @@ -18,14 +18,17 @@ use ethereum_types::H256; error_chain! { errors { + /// Transaction is already imported AlreadyImported(hash: H256) { description("transaction is already in the pool"), display("[{:?}] transaction already imported", hash) } + /// Transaction is too cheap to enter the queue TooCheapToEnter(hash: H256) { description("the pool is full and transaction is too cheap to replace any transaction"), display("[{:?}] transaction too cheap to enter the pool", hash) } + /// Transaction is too cheap to replace existing transaction that occupies the same slot. TooCheapToReplace(old_hash: H256, hash: H256) { description("transaction is too cheap to replace existing transaction in the pool"), display("[{:?}] transaction too cheap to replace: {:?}", hash, old_hash) diff --git a/transaction-pool/src/lib.rs b/transaction-pool/src/lib.rs index 5069c9db190..29353a10008 100644 --- a/transaction-pool/src/lib.rs +++ b/transaction-pool/src/lib.rs @@ -90,7 +90,6 @@ mod verifier; pub mod scoring; -pub use self::error::{Error, ErrorKind}; pub use self::listener::{Listener, NoopListener}; pub use self::options::Options; pub use self::pool::{Pool, PendingIterator}; diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index 32680116209..05b72284b5a 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -261,7 +261,7 @@ fn should_remove_transaction() { assert_eq!(txq.light_status().transaction_count, 3); // when - assert!(txq.remove(&tx2.hash(), false)); + assert!(txq.remove(&tx2.hash(), false).is_some()); // then assert_eq!(txq.light_status().transaction_count, 2); From 34e1eb1c30560dc36c835e3de446f8e6927817f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 30 Jan 2018 17:24:51 +0100 Subject: [PATCH 04/77] Prepare for txpool release. --- transaction-pool/Cargo.toml | 2 +- transaction-pool/src/error.rs | 7 +++++-- transaction-pool/src/lib.rs | 2 +- transaction-pool/src/listener.rs | 1 + transaction-pool/src/pool.rs | 8 +++++--- transaction-pool/src/ready.rs | 12 ++++++++++++ transaction-pool/src/tests/mod.rs | 2 +- 7 files changed, 26 insertions(+), 8 deletions(-) diff --git a/transaction-pool/Cargo.toml b/transaction-pool/Cargo.toml index 70aba5d044a..bd351160e5d 100644 --- a/transaction-pool/Cargo.toml +++ b/transaction-pool/Cargo.toml @@ -9,4 +9,4 @@ authors = ["Parity Technologies "] error-chain = "0.11" log = "0.3" smallvec = "0.4" -ethereum-types = { version = "0.1", features = ["heapsizeof"] } +ethereum-types = { version = "0.2", features = ["heapsizeof"] } diff --git a/transaction-pool/src/error.rs b/transaction-pool/src/error.rs index 57fffebc0d9..706a5b77b19 100644 --- a/transaction-pool/src/error.rs +++ b/transaction-pool/src/error.rs @@ -18,16 +18,19 @@ use ethereum_types::H256; error_chain! { errors { + /// Transaction is already imported AlreadyImported(hash: H256) { - description("transaction is already in the queue"), + description("transaction is already in the pool"), display("[{:?}] transaction already imported", hash) } + /// Transaction is too cheap to enter the queue TooCheapToEnter(hash: H256) { description("the pool is full and transaction is too cheap to replace any transaction"), display("[{:?}] transaction too cheap to enter the pool", hash) } + /// Transaction is too cheap to replace existing transaction that occupies the same slot. TooCheapToReplace(old_hash: H256, hash: H256) { - description("transaction is too cheap to replace existing transaction in the queue"), + description("transaction is too cheap to replace existing transaction in the pool"), display("[{:?}] transaction too cheap to replace: {:?}", hash, old_hash) } } diff --git a/transaction-pool/src/lib.rs b/transaction-pool/src/lib.rs index 987fdfc4083..29353a10008 100644 --- a/transaction-pool/src/lib.rs +++ b/transaction-pool/src/lib.rs @@ -92,7 +92,7 @@ pub mod scoring; pub use self::listener::{Listener, NoopListener}; pub use self::options::Options; -pub use self::pool::Pool; +pub use self::pool::{Pool, PendingIterator}; pub use self::ready::{Ready, Readiness}; pub use self::scoring::Scoring; pub use self::status::{LightStatus, Status}; diff --git a/transaction-pool/src/listener.rs b/transaction-pool/src/listener.rs index 36a5d9de221..2fc55528fe8 100644 --- a/transaction-pool/src/listener.rs +++ b/transaction-pool/src/listener.rs @@ -44,5 +44,6 @@ pub trait Listener { } /// A no-op implementation of `Listener`. +#[derive(Debug)] pub struct NoopListener; impl Listener for NoopListener {} diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 5978acf6059..4f4a63920f1 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -109,6 +109,8 @@ impl Pool where ensure!(!self.by_hash.contains_key(transaction.hash()), error::ErrorKind::AlreadyImported(*transaction.hash())); + // TODO [ToDr] Most likely move this after the transsaction is inserted. + // Avoid using should_replace, but rather use scoring for that. { let remove_worst = |s: &mut Self, transaction| { match s.remove_worst(&transaction) { @@ -288,7 +290,7 @@ impl Pool where /// Removes single transaction from the pool. /// Depending on the `is_invalid` flag the listener /// will either get a `cancelled` or `invalid` notification. - pub fn remove(&mut self, hash: &H256, is_invalid: bool) -> bool { + pub fn remove(&mut self, hash: &H256, is_invalid: bool) -> Option> { if let Some(tx) = self.finalize_remove(hash) { self.remove_from_set(tx.sender(), |set, scoring| { set.remove(&tx, scoring) @@ -298,9 +300,9 @@ impl Pool where } else { self.listener.cancelled(&tx); } - true + Some(tx) } else { - false + None } } diff --git a/transaction-pool/src/ready.rs b/transaction-pool/src/ready.rs index 1d8e342db27..73524443227 100644 --- a/transaction-pool/src/ready.rs +++ b/transaction-pool/src/ready.rs @@ -40,3 +40,15 @@ impl Ready for F where F: FnMut(&T) -> Readiness { (*self)(tx) } } + +impl Ready for (A, B) where + A: Ready, + B: Ready, +{ + fn is_ready(&mut self, tx: &T) -> Readiness { + match self.0.is_ready(tx) { + Readiness::Ready => self.1.is_ready(tx), + r => r, + } + } +} diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index 32680116209..05b72284b5a 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -261,7 +261,7 @@ fn should_remove_transaction() { assert_eq!(txq.light_status().transaction_count, 3); // when - assert!(txq.remove(&tx2.hash(), false)); + assert!(txq.remove(&tx2.hash(), false).is_some()); // then assert_eq!(txq.light_status().transaction_count, 2); From f77a46d9dd313b9b6023570381314eb9c92ae5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 2 Feb 2018 15:50:06 +0100 Subject: [PATCH 05/77] Miner refactor [WiP] --- Cargo.lock | 43 +- ethcore/Cargo.toml | 1 - ethcore/src/account_provider/mod.rs | 3 - ethcore/src/client/client.rs | 6 +- ethcore/src/client/test_client.rs | 2 +- ethcore/src/lib.rs | 1 - ethcore/src/miner/miner.rs | 623 +++++++++++++--------------- ethcore/src/miner/mod.rs | 96 ++--- miner/Cargo.toml | 2 + miner/src/gas_pricer.rs | 93 +++++ miner/src/lib.rs | 6 +- miner/src/pool/mod.rs | 3 + miner/src/{ => pool}/queue.rs | 9 + transaction-pool/Cargo.toml | 2 +- transaction-pool/src/lib.rs | 1 + util/using_queue/src/lib.rs | 4 +- 16 files changed, 439 insertions(+), 456 deletions(-) create mode 100644 miner/src/gas_pricer.rs rename miner/src/{ => pool}/queue.rs (94%) diff --git a/Cargo.lock b/Cargo.lock index 21efb0f55b4..1b11687e5ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,18 +429,6 @@ dependencies = [ "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ethbloom" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ethereum-types-serialize 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fixed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", - "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ethcore" version = "1.9.0" @@ -487,7 +475,6 @@ dependencies = [ "parity-machine 0.1.0", "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "patricia-trie 0.1.0", - "price-info 1.7.0", "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.1", @@ -612,6 +599,7 @@ dependencies = [ name = "ethcore-miner" version = "1.9.0" dependencies = [ + "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "common-types 0.1.0", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.9.0", @@ -626,6 +614,7 @@ dependencies = [ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "native-contracts 0.1.0", "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "price-info 1.7.0", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "table 0.1.0", "transaction-pool 1.9.0", @@ -756,29 +745,6 @@ dependencies = [ "uint 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ethereum-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ethbloom 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethereum-types-serialize 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fixed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", - "uint 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ethereum-types-serialize" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ethjson" version = "0.1.0" @@ -3276,7 +3242,7 @@ name = "transaction-pool" version = "1.9.0" dependencies = [ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethereum-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethereum-types 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3645,10 +3611,7 @@ dependencies = [ "checksum eth-secp256k1 0.5.7 (git+https://github.com/paritytech/rust-secp256k1)" = "" "checksum ethabi 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c819a3adef0413a2519cbd9a19a35dd1c20c7a0110705beaba8aa4aa87eda95f" "checksum ethbloom 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd5f7fb5d27fb017f21c15d1e0b953831b58581c9942a5e5614fd2b6603697b7" -"checksum ethbloom 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4bc92037607dd89203fa0d936615947fee9c019ca6430b3c82de64842b7133ff" "checksum ethereum-types 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaa5b8ceafcce0bc3a68ef116ca5702958cc97d70a6eb008aeddb569b092b3" -"checksum ethereum-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f0cfa733a9b0b05a7c4605e4c1dc21a9e958e0acb55f295798fddb2cbffa30c" -"checksum ethereum-types-serialize 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94dc0212b245f0115f6780e0a7c5ab682adc03cb7eeda62d859847b4424c9384" "checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa" "checksum fixed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "75f91321a3926b773df3c6e7e953e3c756d9c06f0e9f05d34a0e103b03fcc9a1" "checksum flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 3042ee1a1d5..cbd2b5dc4cd 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -42,7 +42,6 @@ num = "0.1" num_cpus = "1.2" parity-machine = { path = "../machine" } parking_lot = "0.5" -price-info = { path = "../price-info" } rayon = "0.8" rand = "0.3" rlp = { path = "../util/rlp" } diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index d31ded682db..15e62cd6f02 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -66,8 +66,6 @@ pub enum SignError { Hardware(HardwareError), /// Low-level error from store SStore(SSError), - /// Inappropriate chain - InappropriateChain, } impl fmt::Display for SignError { @@ -77,7 +75,6 @@ impl fmt::Display for SignError { SignError::NotFound => write!(f, "Account does not exist"), SignError::Hardware(ref e) => write!(f, "{}", e), SignError::SStore(ref e) => write!(f, "{}", e), - SignError::InappropriateChain => write!(f, "Inappropriate chain"), } } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f828b1b84a2..8c048c41ebc 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1813,10 +1813,12 @@ impl BlockChainClient for Client { } fn transact_contract(&self, address: Address, data: Bytes) -> Result { + let mining_params = self.miner.mining_params(); let transaction = Transaction { - nonce: self.latest_nonce(&self.miner.author()), + nonce: self.latest_nonce(&mining_params.author), action: Action::Call(address), - gas: self.miner.gas_floor_target(), + gas: mining_params.gas_range_target.0, + // TODO [ToDr] Do we need gas price here? gas_price: self.miner.sensible_gas_price(), value: U256::zero(), data: data, diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index fdc238a19d4..5f538e7fb2a 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -770,7 +770,7 @@ impl BlockChainClient for TestBlockChainClient { fn transact_contract(&self, address: Address, data: Bytes) -> Result { let transaction = Transaction { - nonce: self.latest_nonce(&self.miner.author()), + nonce: self.latest_nonce(&self.miner.mining_params().author), action: Action::Call(address), gas: self.spec.gas_limit, gas_price: U256::zero(), diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index c5f7050cdab..b6f067702d7 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -81,7 +81,6 @@ extern crate num_cpus; extern crate num; extern crate parity_machine; extern crate parking_lot; -extern crate price_info; extern crate rand; extern crate rayon; extern crate rlp; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 388ce2898da..accc87b2f74 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::fmt; use std::time::{Instant, Duration}; use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; @@ -35,20 +36,18 @@ use error::*; // AccountDetails, // TransactionOrigin, // }; +use ethcore_miner::pool::{self, TransactionQueue}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; +use ethcore_miner::gas_pricer::{GasPricer, GasPriceCalibratorOptions}; // use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; -use miner::{MinerService, MinerStatus}; -use price_info::fetch::Client as FetchClient; -use price_info::{Client as PriceInfoClient, PriceInfo}; +use miner::MinerService; use timer::PerfTimer; use transaction::{ + self, Action, UnverifiedTransaction, PendingTransaction, SignedTransaction, - Condition as TransactionCondition, - ImportResult as TransactionImportResult, - Error as TransactionError, }; use using_queue::{UsingQueue, GetAction}; @@ -112,10 +111,10 @@ pub struct MinerOptions { pub reseal_on_own_tx: bool, /// Reseal when new uncle block has been imported. pub reseal_on_uncle: bool, - // /// Minimum period between transaction-inspired reseals. - // pub reseal_min_period: Duration, - // /// Maximum period between blocks (enables force sealing after that). - // pub reseal_max_period: Duration, + /// Minimum period between transaction-inspired reseals. + pub reseal_min_period: Duration, + /// Maximum period between blocks (enables force sealing after that). + pub reseal_max_period: Duration, // /// Maximum amount of gas to bother considering for block insertion. // pub tx_gas_limit: U256, // /// Maximum size of the transaction queue. @@ -160,116 +159,53 @@ impl Default for MinerOptions { reseal_max_period: Duration::from_secs(120), work_queue_size: 20, enable_resubmission: true, - tx_queue_banning: Banning::Disabled, - refuse_service_transactions: false, + // tx_queue_banning: Banning::Disabled, + // refuse_service_transactions: false, infinite_pending_block: false, } } } -/// Options for the dynamic gas price recalibrator. -#[derive(Debug, PartialEq)] -pub struct GasPriceCalibratorOptions { - /// Base transaction price to match against. - pub usd_per_tx: f32, - /// How frequently we should recalibrate. - pub recalibration_period: Duration, -} - -/// The gas price validator variant for a `GasPricer`. -#[derive(Debug, PartialEq)] -pub struct GasPriceCalibrator { - options: GasPriceCalibratorOptions, - next_calibration: Instant, - price_info: PriceInfoClient, -} - -impl GasPriceCalibrator { - fn recalibrate(&mut self, set_price: F) { - trace!(target: "miner", "Recalibrating {:?} versus {:?}", Instant::now(), self.next_calibration); - if Instant::now() >= self.next_calibration { - let usd_per_tx = self.options.usd_per_tx; - trace!(target: "miner", "Getting price info"); - - self.price_info.get(move |price: PriceInfo| { - trace!(target: "miner", "Price info arrived: {:?}", price); - let usd_per_eth = price.ethusd; - let wei_per_usd: f32 = 1.0e18 / usd_per_eth; - let gas_per_tx: f32 = 21000.0; - let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx; - info!(target: "miner", "Updated conversion rate to Ξ1 = {} ({} wei/gas)", Colour::White.bold().paint(format!("US${:.2}", usd_per_eth)), Colour::Yellow.bold().paint(format!("{}", wei_per_gas))); - set_price(U256::from(wei_per_gas as u64)); - }); - - self.next_calibration = Instant::now() + self.options.recalibration_period; - } - } -} - -/// Struct to look after updating the acceptable gas price of a miner. -#[derive(Debug, PartialEq)] -pub enum GasPricer { - /// A fixed gas price in terms of Wei - always the argument given. - Fixed(U256), - /// Gas price is calibrated according to a fixed amount of USD. - Calibrated(GasPriceCalibrator), -} - -impl GasPricer { - /// Create a new Calibrated `GasPricer`. - pub fn new_calibrated(options: GasPriceCalibratorOptions, fetch: FetchClient) -> GasPricer { - GasPricer::Calibrated(GasPriceCalibrator { - options: options, - next_calibration: Instant::now(), - price_info: PriceInfoClient::new(fetch), - }) - } - - /// Create a new Fixed `GasPricer`. - pub fn new_fixed(gas_price: U256) -> GasPricer { - GasPricer::Fixed(gas_price) - } - - fn recalibrate(&mut self, set_price: F) { - match *self { - GasPricer::Fixed(ref max) => set_price(max.clone()), - GasPricer::Calibrated(ref mut cal) => cal.recalibrate(set_price), - } - } +#[derive(Debug, Default, Clone)] +pub struct MiningParams { + pub gas_range_target: (U256, U256), + pub author: Address, + pub extra_data: Bytes, } struct SealingWork { queue: UsingQueue, enabled: bool, + next_allowed_reseal: Instant, + next_mandatory_reseal: Instant, + sealing_block_last_request: u64, } /// Keeps track of transactions using priority queue and holds currently mined block. /// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work. pub struct Miner { // NOTE [ToDr] When locking always lock in this order! - sealing_work: Mutex, - next_allowed_reseal: Mutex, - next_mandatory_reseal: RwLock, - sealing_block_last_request: Mutex, + sealing: Mutex, + params: RwLock, + listeners: RwLock>>, + gas_pricer: Mutex, options: MinerOptions, transaction_queue: TransactionQueue, - - gas_range_target: RwLock<(U256, U256)>, - author: RwLock
, - extra_data: RwLock, engine: Arc, - accounts: Option>, - notifiers: RwLock>>, - gas_pricer: Mutex, + // TODO [TodR]] Check log order // service_transaction_action: ServiceTransactionAction, } impl Miner { /// Push notifier that will handle new jobs - pub fn push_notifier(&self, notifier: Box) { - self.notifiers.write().push(notifier); - self.sealing_work.lock().enabled = true; + pub fn add_work_listener(&self, notifier: Box) { + self.sealing.lock().enabled = true; + self.listeners.write().push(notifier); + } + + pub fn add_work_listener_url(&self, urls: &[String]) { + self.add_work_listener(Box::new(WorkPoster::new(&urls))); } /// Creates new instance of miner Arc. @@ -279,15 +215,16 @@ impl Miner { /// Creates new instance of miner. fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { - let mem_limit = options.tx_queue_memory_limit.unwrap_or_else(usize::max_value); - - let txq = TransactionQueue::with_limits( - options.tx_queue_strategy, - options.tx_queue_size, - mem_limit, - gas_limit, - options.tx_gas_limit - ); + // let mem_limit = options.tx_queue_memory_limit.unwrap_or_else(usize::max_value); + + let txq = unimplemented!(); + // let txq = TransactionQueue::with_limits( + // // options.tx_queue_strategy, + // options.tx_queue_size, + // mem_limit, + // // gas_limit, + // options.tx_gas_limit + // ); // let txq = match options.tx_queue_banning { // Banning::Disabled => BanningTransactionQueue::new(txq, Threshold::NeverBan, Duration::from_secs(180)), // Banning::Enabled { ban_duration, min_offends, .. } => BanningTransactionQueue::new( @@ -299,30 +236,28 @@ impl Miner { let notifiers: Vec> = match options.new_work_notify.is_empty() { true => Vec::new(), - false => vec![Box::new(WorkPoster::new(&options.new_work_notify))], + false => vec![)], }; let (limits, verifier_options) = unimplemented!(); Miner { - transaction_queue: TransactionQueue::new(limits, verifier_options), - next_allowed_reseal: Mutex::new(Instant::now()), - next_mandatory_reseal: RwLock::new(Instant::now() + options.reseal_max_period), - sealing_block_last_request: Mutex::new(0), - sealing_work: Mutex::new(SealingWork{ + sealing: RwLock::new(SealingWork{ queue: UsingQueue::new(options.work_queue_size), enabled: options.force_sealing || !options.new_work_notify.is_empty() - || spec.engine.seals_internally().is_some() + || spec.engine.seals_internally().is_some(), + next_allowed_reseal: Instant::now(), + next_mandatory_reseal: Instant::now() + options.reseal_max_period, + sealing_block_last_request: 0, }), - gas_range_target: RwLock::new((U256::zero(), U256::zero())), - author: RwLock::new(Address::default()), - extra_data: RwLock::new(Vec::new()), - options: options, - accounts: accounts, - engine: spec.engine.clone(), - notifiers: RwLock::new(notifiers), + params: RwLock::new(MiningParams::default()), + notifiers: RwLock::new(vec![]), gas_pricer: Mutex::new(gas_pricer), + options, + transaction_queue: TransactionQueue::new(limits, verifier_options), + accounts, + engine: spec.engine.clone(), } } @@ -342,7 +277,7 @@ impl Miner { /// Clear all pending block states pub fn clear(&self) { - self.sealing_work.lock().queue.reset(); + self.sealing.lock().queue.reset(); } /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. @@ -374,13 +309,13 @@ impl Miner { fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { let _timer = PerfTimer::new("prepare_block"); let chain_info = chain.chain_info(); - let (transactions, mut open_block, original_work_hash) = { + let (pending, mut open_block, original_work_hash) = { let nonce_cap = if chain_info.best_block_number + 1 >= self.engine.params().dust_protection_transition { Some((self.engine.params().nonce_cap_increment * (chain_info.best_block_number + 1)).into()) } else { None }; - let mut sealing_work = self.sealing_work.lock(); - let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); + let mut sealing = self.sealing.lock(); + let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); let best_hash = chain_info.best_block_hash; // check to see if last ClosedBlock in would_seals is actually same parent block. @@ -389,7 +324,7 @@ impl Miner { // if at least one was pushed successfully, close and enqueue new ClosedBlock; // otherwise, leave everything alone. // otherwise, author a fresh block. - let mut open_block = match sealing_work.queue.pop_if(|b| b.block().fields().header.parent_hash() == &best_hash) { + let mut open_block = match sealing.queue.pop_if(|b| b.block().fields().header.parent_hash() == &best_hash) { Some(old_block) => { trace!(target: "miner", "prepare_block: Already have previous work; updating and returning"); // add transactions to old_block @@ -398,10 +333,11 @@ impl Miner { None => { // block not found - create it. trace!(target: "miner", "prepare_block: No existing work - making new block"); + let params = self.params.read().clone(); chain.prepare_open_block( - self.author(), - (self.gas_floor_target(), self.gas_ceil_target()), - self.extra_data() + params.author, + params.gas_range_target, + params.extra_data, ) } }; @@ -433,7 +369,7 @@ impl Miner { let start = Instant::now(); // Check whether transaction type is allowed for sender let result = match self.engine.machine().verify_transaction(&tx, open_block.header(), chain.as_block_chain_client()) { - Err(Error::Transaction(TransactionError::NotAllowed)) => Err(TransactionError::NotAllowed.into()), + Err(Error::Transaction(transaction::Error::NotAllowed)) => Err(transaction::Error::NotAllowed.into()), _ => open_block.push_transaction(tx, None), }; let took = start.elapsed(); @@ -482,8 +418,8 @@ impl Miner { debug!(target: "miner", "Skipping adding transaction to block because of invalid nonce: {:?} (expected: {:?}, got: {:?})", hash, expected, got); }, // already have transaction - ignore - Err(Error::Transaction(TransactionError::AlreadyImported)) => {}, - Err(Error::Transaction(TransactionError::NotAllowed)) => { + Err(Error::Transaction(transaction::Error::AlreadyImported)) => {}, + Err(Error::Transaction(transaction::Error::NotAllowed)) => { non_allowed_transactions.insert(hash); debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); }, @@ -501,26 +437,28 @@ impl Miner { let block = open_block.close(); - let fetch_nonce = |a: &Address| chain.latest_nonce(a); + // let fetch_nonce = |a: &Address| chain.latest_nonce(a); { let mut queue = self.transaction_queue.write(); for hash in invalid_transactions { - queue.remove(&hash, &fetch_nonce, RemovalReason::Invalid); + // queue.remove(&hash, &fetch_nonce, RemovalReason::Invalid); + queue.remove(&hash, true) } for hash in non_allowed_transactions { - queue.remove(&hash, &fetch_nonce, RemovalReason::NotAllowed); - } - for hash in transactions_to_penalize { - queue.penalize(&hash); + // queue.remove(&hash, &fetch_nonce, RemovalReason::NotAllowed); + queue.remove(&hash, false) } + // for hash in transactions_to_penalize { + // queue.penalize(&hash); + // } } (block, original_work_hash) } /// Asynchronously updates minimal gas price for transaction queue - pub fn recalibrate_minimal_gas_price(&self) { + fn recalibrate_minimal_gas_price(&self) { debug!(target: "miner", "minimal_gas_price: recalibrating..."); let txq = self.transaction_queue.clone(); self.gas_pricer.lock().recalibrate(move |price| { @@ -531,105 +469,126 @@ impl Miner { /// Check is reseal is allowed and necessary. fn requires_reseal(&self, best_block: BlockNumber) -> bool { - let has_local_transactions = self.transaction_queue.read().has_local_pending_transactions(); - let mut sealing_work = self.sealing_work.lock(); - if sealing_work.enabled { - trace!(target: "miner", "requires_reseal: sealing enabled"); - let last_request = *self.sealing_block_last_request.lock(); - let should_disable_sealing = !self.forced_sealing() - && !has_local_transactions - && self.engine.seals_internally().is_none() - && best_block > last_request - && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS; - - trace!(target: "miner", "requires_reseal: should_disable_sealing={}; best_block={}, last_request={}", should_disable_sealing, best_block, last_request); - - if should_disable_sealing { - trace!(target: "miner", "Miner sleeping (current {}, last {})", best_block, last_request); - sealing_work.enabled = false; - sealing_work.queue.reset(); - false - } else { - // sealing enabled and we don't want to sleep. - *self.next_allowed_reseal.lock() = Instant::now() + self.options.reseal_min_period; - true - } - } else { + let mut sealing = self.sealing.lock(); + if sealing.enabled { trace!(target: "miner", "requires_reseal: sealing is disabled"); + return false + } + + let has_local_transactions = self.transaction_queue.has_local_pending_transactions(); + trace!(target: "miner", "requires_reseal: sealing enabled"); + + let last_request = sealing.sealing_block_last_request; + let should_disable_sealing = !self.forced_sealing() + && !has_local_transactions + && self.engine.seals_internally().is_none() + && best_block > last_request + && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS; + + trace!(target: "miner", "requires_reseal: should_disable_sealing={}; best_block={}, last_request={}", should_disable_sealing, best_block, last_request); + + if should_disable_sealing { + trace!(target: "miner", "Miner sleeping (current {}, last {})", best_block, last_request); + sealing.enabled = false; + sealing.queue.reset(); false + } else { + // sealing enabled and we don't want to sleep. + sealing.next_allowed_reseal = Instant::now() + self.options.reseal_min_period; + true } } /// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal. fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { - if !block.transactions().is_empty() || self.forced_sealing() || Instant::now() > *self.next_mandatory_reseal.read() { - trace!(target: "miner", "seal_block_internally: attempting internal seal."); + let mut sealing = self.sealing.lock(); + if block.transactions().is_empty() + && !self.forced_sealing() + && Instant::now() <= sealing.next_mandatory_reseal + { + return false + } - let parent_header = match chain.block_header(BlockId::Hash(*block.header().parent_hash())) { - Some(hdr) => hdr.decode(), - None => return false, - }; + trace!(target: "miner", "seal_block_internally: attempting internal seal."); - match self.engine.generate_seal(block.block(), &parent_header) { - // Save proposal for later seal submission and broadcast it. - Seal::Proposal(seal) => { - trace!(target: "miner", "Received a Proposal seal."); - *self.next_mandatory_reseal.write() = Instant::now() + self.options.reseal_max_period; - { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); - } - block - .lock() - .seal(&*self.engine, seal) - .map(|sealed| { chain.broadcast_proposal_block(sealed); true }) - .unwrap_or_else(|e| { - warn!("ERROR: seal failed when given internally generated seal: {}", e); - false - }) - }, - // Directly import a regular sealed block. - Seal::Regular(seal) => { - *self.next_mandatory_reseal.write() = Instant::now() + self.options.reseal_max_period; - block - .lock() - .seal(&*self.engine, seal) - .map(|sealed| chain.import_sealed_block(sealed).is_ok()) - .unwrap_or_else(|e| { - warn!("ERROR: seal failed when given internally generated seal: {}", e); - false - }) - }, - Seal::None => false, - } - } else { - false + let parent_header = match chain.block_header(BlockId::Hash(*block.header().parent_hash())) { + Some(hdr) => hdr.decode(), + None => return false, + }; + + match self.engine.generate_seal(block.block(), &parent_header) { + // Save proposal for later seal submission and broadcast it. + Seal::Proposal(seal) => { + trace!(target: "miner", "Received a Proposal seal."); + sealing.next_mandatory_reseal = Instant::now() + self.options.reseal_max_period; + sealing.queue.push(block.clone()); + sealing.queue.use_last_ref(); + + block + .lock() + .seal(&*self.engine, seal) + .map(|sealed| { + chain.broadcast_proposal_block(sealed); + true + }) + .unwrap_or_else(|e| { + warn!("ERROR: seal failed when given internally generated seal: {}", e); + false + }) + }, + // Directly import a regular sealed block. + Seal::Regular(seal) => { + sealing.next_mandatory_reseal = Instant::now() + self.options.reseal_max_period; + block + .lock() + .seal(&*self.engine, seal) + .map(|sealed| chain.import_sealed_block(sealed).is_ok()) + .unwrap_or_else(|e| { + warn!("ERROR: seal failed when given internally generated seal: {}", e); + false + }) + }, + Seal::None => false, } } /// Prepares work which has to be done to seal. fn prepare_work(&self, block: ClosedBlock, original_work_hash: Option) { let (work, is_new) = { - let mut sealing_work = self.sealing_work.lock(); - let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); - trace!(target: "miner", "prepare_work: Checking whether we need to reseal: orig={:?} last={:?}, this={:?}", original_work_hash, last_work_hash, block.block().fields().header.hash()); - let (work, is_new) = if last_work_hash.map_or(true, |h| h != block.block().fields().header.hash()) { - trace!(target: "miner", "prepare_work: Pushing a new, refreshed or borrowed pending {}...", block.block().fields().header.hash()); - let pow_hash = block.block().fields().header.hash(); - let number = block.block().fields().header.number(); - let difficulty = *block.block().fields().header.difficulty(); - let is_new = original_work_hash.map_or(true, |h| block.block().fields().header.hash() != h); - sealing_work.queue.push(block); + let block_header = block.block().fields().header; + let block_hash = block_header.hash(); + let mut sealing = self.sealing.lock(); + let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); + + trace!( + target: "miner", + "prepare_work: Checking whether we need to reseal: orig={:?} last={:?}, this={:?}", + original_work_hash, last_work_hash, block_hash + ); + + let (work, is_new) = if last_work_hash.map_or(true, |h| h != block_hash) { + trace!( + target: "miner", + "prepare_work: Pushing a new, refreshed or borrowed pending {}...", + block_hash + ); + let is_new = original_work_hash.map_or(true, |h| h != block_hash); + + sealing.queue.push(block); // If push notifications are enabled we assume all work items are used. - if !self.notifiers.read().is_empty() && is_new { - sealing_work.queue.use_last_ref(); + if is_new && !self.notifiers.read().is_empty() { + sealing.queue.use_last_ref(); } - (Some((pow_hash, difficulty, number)), is_new) + + (Some((block_hash, block_header.difficulty(), block_header.number())), is_new) } else { (None, false) }; - trace!(target: "miner", "prepare_work: leaving (last={:?})", sealing_work.queue.peek_last_ref().map(|b| b.block().fields().header.hash())); + trace!( + target: "miner", + "prepare_work: leaving (last={:?})", + sealing.queue.peek_last_ref().map(|b| b.block().fields().header.hash()) + ); (work, is_new) }; if is_new { @@ -656,29 +615,35 @@ impl Miner { fn prepare_work_sealing(&self, client: &MiningBlockChainClient) -> bool { trace!(target: "miner", "prepare_work_sealing: entering"); let prepare_new = { - let mut sealing_work = self.sealing_work.lock(); - let have_work = sealing_work.queue.peek_last_ref().is_some(); + let mut sealing = self.sealing.lock(); + let have_work = sealing.queue.peek_last_ref().is_some(); trace!(target: "miner", "prepare_work_sealing: have_work={}", have_work); if !have_work { - sealing_work.enabled = true; + sealing.enabled = true; true } else { false } }; + if prepare_new { // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing_work locks. | + // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- let (block, original_work_hash) = self.prepare_block(client); self.prepare_work(block, original_work_hash); } - let mut sealing_block_last_request = self.sealing_block_last_request.lock(); + let best_number = client.chain_info().best_block_number; - if *sealing_block_last_request != best_number { - trace!(target: "miner", "prepare_work_sealing: Miner received request (was {}, now {}) - waking up.", *sealing_block_last_request, best_number); - *sealing_block_last_request = best_number; + let mut sealing = self.sealing.lock(); + if sealing.sealing_block_last_request != best_number { + trace!( + target: "miner", + "prepare_work_sealing: Miner received request (was {}, now {}) - waking up.", + sealing.sealing_block_last_request, best_number + ); + sealing.sealing_block_last_request = best_number; } // Return if we restarted @@ -686,12 +651,14 @@ impl Miner { } /// Are we allowed to do a non-mandatory reseal? - fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock() } + fn tx_reseal_allowed(&self) -> bool { + Instant::now() > self.sealing.lock().next_allowed_reseal + } fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H where F: Fn() -> H, G: FnOnce(&ClosedBlock) -> H { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else( + let sealing = self.sealing.lock(); + sealing.queue.peek_last_ref().map_or_else( || from_chain(), |b| { if b.block().header().number() > latest_block_number { @@ -709,45 +676,39 @@ const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; impl MinerService for Miner { fn clear_and_reset(&self, chain: &MiningBlockChainClient) { - self.transaction_queue.write().clear(); + self.transaction_queue.clear(); // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing_work locks. | + // | NOTE Code below requires sealing lock. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- self.update_sealing(chain); } - fn status(&self) -> MinerStatus { - let status = self.transaction_queue.read().status(); - let sealing_work = self.sealing_work.lock(); - MinerStatus { - transactions_in_pending_queue: status.pending, - transactions_in_future_queue: status.future, - transactions_in_pending_block: sealing_work.queue.peek_last_ref().map_or(0, |b| b.transactions().len()), - } + fn mining_params(&self) -> MiningParams { + self.params.read().clone() } - fn set_author(&self, author: Address) { - if self.engine.seals_internally().is_some() { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = true; - } - *self.author.write() = author; + fn set_gas_range_target(&self, gas_range_target: (U256, U256)) { + self.params.write().gas_range_target = gas_range_target; + } + + fn set_extra_data(&self, extra_data: Bytes) { + self.params.write().extra_data = extra_data; } - fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + fn set_author(&self, address: Address, password: Option) -> Result<(), AccountError> { + self.params.write().author = address; + if self.engine.seals_internally().is_some() { if let Some(ref ap) = self.accounts { + let password = password.unwrap_or_default(); + // Sign test message ap.sign(address.clone(), Some(password.clone()), Default::default())?; - // Limit the scope of the locks. - { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.enabled = true; - *self.author.write() = address; - } + // Enable sealing + self.sealing.lock().enabled = true; // -------------------------------------------------------------------------- - // | NOTE Code below may require author and sealing_work locks | - // | (some `Engine`s call `EngineClient.update_sealing()`) |. + // | NOTE Code below may require author and sealing locks | + // | (some `Engine`s call `EngineClient.update_sealing()`) | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- self.engine.set_signer(ap.clone(), address, password); @@ -756,79 +717,23 @@ impl MinerService for Miner { warn!(target: "miner", "No account provider"); Err(AccountError::NotFound) } - } else { - warn!(target: "miner", "Cannot set engine signer on a PoW chain."); - Err(AccountError::InappropriateChain) } } - fn set_extra_data(&self, extra_data: Bytes) { - *self.extra_data.write() = extra_data; - } - - /// Set the gas limit we wish to target when sealing a new block. - fn set_gas_floor_target(&self, target: U256) { - self.gas_range_target.write().0 = target; - } - - fn set_gas_ceil_target(&self, target: U256) { - self.gas_range_target.write().1 = target; - } - - fn set_minimal_gas_price(&self, min_gas_price: U256) { - self.transaction_queue.write().set_minimal_gas_price(min_gas_price); - } - - fn minimal_gas_price(&self) -> U256 { - *self.transaction_queue.read().minimal_gas_price() - } - fn sensible_gas_price(&self) -> U256 { // 10% above our minimum. - *self.transaction_queue.read().minimal_gas_price() * 110.into() / 100.into() + *self.transaction_queue.minimal_gas_price() * 110.into() / 100.into() } fn sensible_gas_limit(&self) -> U256 { - self.gas_range_target.read().0 / 5.into() - } - - fn transactions_limit(&self) -> usize { - self.transaction_queue.read().limit() - } - - fn set_transactions_limit(&self, limit: usize) { - self.transaction_queue.write().set_limit(limit) - } - - fn set_tx_gas_limit(&self, limit: U256) { - self.transaction_queue.write().set_tx_gas_limit(limit) - } - - /// Get the author that we will seal blocks as. - fn author(&self) -> Address { - *self.author.read() - } - - /// Get the extra_data that we will seal blocks with. - fn extra_data(&self) -> Bytes { - self.extra_data.read().clone() - } - - /// Get the gas limit we wish to target when sealing a new block. - fn gas_floor_target(&self) -> U256 { - self.gas_range_target.read().0 - } - - /// Get the gas limit we wish to target when sealing a new block. - fn gas_ceil_target(&self) -> U256 { - self.gas_range_target.read().1 + self.params.read().0 / 5.into() } fn import_external_transactions( &self, chain: &MiningBlockChainClient, transactions: Vec - ) -> Vec> { + ) -> Vec> { trace!(target: "external_tx", "Importing external transactions"); let results = self.transaction_queue.import( chain, @@ -837,7 +742,7 @@ impl MinerService for Miner { if !results.is_empty() && self.options.reseal_on_external_tx && self.tx_reseal_allowed() { // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing_work locks. | + // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- self.update_sealing(chain); @@ -849,16 +754,16 @@ impl MinerService for Miner { &self, chain: &MiningBlockChainClient, pending: PendingTransaction, - ) -> Result { + ) -> Result { trace!(target: "own_tx", "Importing transaction: {:?}", pending); - let import = self.transaction_queue.import( + let imported = self.transaction_queue.import( chain, vec![pool::verifier::Transaction::Pending(pending)] ).pop().expect("one result returned per added transaction; one added => one result; qed"); - match import { + match imported { Ok(_) => { trace!(target: "own_tx", "Status: {:?}", self.transaction_queue.status()); }, @@ -869,7 +774,7 @@ impl MinerService for Miner { } // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing_work locks. | + // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { @@ -891,13 +796,13 @@ impl MinerService for Miner { queue.pending_transactions(BlockNumber::max_value(), u64::max_value()) } - fn local_transactions(&self) -> BTreeMap { - let queue = self.transaction_queue.read(); - queue.local_transactions() - .iter() - .map(|(hash, status)| (*hash, status.clone())) - .collect() - } + // fn local_transactions(&self) -> BTreeMap { + // let queue = self.transaction_queue.read(); + // queue.local_transactions() + // .iter() + // .map(|(hash, status)| (*hash, status.clone())) + // .collect() + // } fn future_transactions(&self) -> Vec { self.transaction_queue.read().future_transactions() @@ -1034,7 +939,7 @@ impl MinerService for Miner { if self.requires_reseal(chain.chain_info().best_block_number) { // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing_work locks. | + // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- trace!(target: "miner", "update_sealing: preparing a block"); @@ -1064,22 +969,22 @@ impl MinerService for Miner { } fn is_currently_sealing(&self) -> bool { - self.sealing_work.lock().queue.is_in_use() + self.sealing.lock().queue.is_in_use() } - fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { + fn map_pending_block(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { trace!(target: "miner", "map_sealing_work: entering"); self.prepare_work_sealing(chain); trace!(target: "miner", "map_sealing_work: sealing prepared"); - let mut sealing_work = self.sealing_work.lock(); - let ret = sealing_work.queue.use_last_ref(); + let mut sealing = self.sealing.lock(); + let ret = sealing.queue.use_last_ref(); trace!(target: "miner", "map_sealing_work: leaving use_last_ref={:?}", ret.as_ref().map(|b| b.block().fields().header.hash())); ret.map(f) } fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = - if let Some(b) = self.sealing_work.lock().queue.get_used_if( + if let Some(b) = self.sealing.lock().queue.get_used_if( if self.options.enable_resubmission { GetAction::Clone } else { @@ -1136,11 +1041,12 @@ impl MinerService for Miner { } // ...and at the end remove the old ones + let client = BlockChainClient { chain, engine: &*self.engine }; self.transaction_queue.cull(client); if enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle) { // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing_work locks. | + // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- self.update_sealing(chain); @@ -1148,6 +1054,55 @@ impl MinerService for Miner { } } +struct BlockChainClient<'a> { + chain: &'a MiningBlockChainClient, + engine: &'a EthEngine, +} + +impl<'a> fmt::Debug for BlockChainClient<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "BlockChainClient") + } +} + +impl<'a> pool::client::Client for BlockChainClient<'a> { + fn transaction_already_included(&self, hash: &H256) -> bool { + self.chain.transaction_block(TransactionId::Hash(*hash)).is_some() + } + + fn verify_transaction(&self, tx: UnverifiedTransaction) + -> Result + { + let best_block_header = self.chain.best_block_header().decode(); + + self.engine.verify_transaction_basic(&tx, &best_block_header)?; + self.engine.verify_transaction_unordered(tx, &best_block_header) + } + + fn account_details(&self, address: &Address) -> pool::client::AccountDetails { + pool::client::AccountDetails { + nonce: self.chain.latest_nonce(address), + balance: self.chain.latest_balance(address), + } + } + + fn account_nonce(&self, address: &Address) -> U256 { + self.chain.latest_nonce(address) + } + + /// Estimate minimal gas requirurement for given transaction. + fn required_gas(&self, tx: &SignedTransaction) -> U256 { + tx.gas_required(&self.chain.latest_schedule()).into() + } + + /// Classify transaction (check if transaction is filtered by some contracts). + fn transaction_type(&self, tx: &SignedTransaction) -> pool::client::TransactionType { + // TODO [ToDr] Transaction checker + // self.service_transaction_action.check(self.client, tx) + unimplemented!() + } +} + #[cfg(test)] mod tests { use super::*; @@ -1249,7 +1204,7 @@ mod tests { let res = miner.import_own_transaction(&client, PendingTransaction::new(transaction, None)); // then - assert_eq!(res.unwrap(), TransactionImportResult::Current); + assert_eq!(res.unwrap(), transaction::ImportResult::Current); assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.ready_transactions(best_block, 0).len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); @@ -1269,7 +1224,7 @@ mod tests { let res = miner.import_own_transaction(&client, PendingTransaction::new(transaction, None)); // then - assert_eq!(res.unwrap(), TransactionImportResult::Current); + assert_eq!(res.unwrap(), transaction::ImportResult::Current); assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); @@ -1287,7 +1242,7 @@ mod tests { let res = miner.import_external_transactions(&client, vec![transaction]).pop().unwrap(); // then - assert_eq!(res.unwrap(), TransactionImportResult::Current); + assert_eq!(res.unwrap(), transaction::ImportResult::Current); assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); @@ -1316,14 +1271,14 @@ mod tests { let client = generate_dummy_client(2); - assert_eq!(miner.import_external_transactions(&*client, vec![transaction_with_chain_id(spec.chain_id()).into()]).pop().unwrap().unwrap(), TransactionImportResult::Current); + assert_eq!(miner.import_external_transactions(&*client, vec![transaction_with_chain_id(spec.chain_id()).into()]).pop().unwrap().unwrap(), transaction::ImportResult::Current); miner.update_sealing(&*client); client.flush_queue(); assert!(miner.pending_block(0).is_none()); assert_eq!(client.chain_info().best_block_number, 3 as BlockNumber); - assert_eq!(miner.import_own_transaction(&*client, PendingTransaction::new(transaction_with_chain_id(spec.chain_id()).into(), None)).unwrap(), TransactionImportResult::Current); + assert_eq!(miner.import_own_transaction(&*client, PendingTransaction::new(transaction_with_chain_id(spec.chain_id()).into(), None)).unwrap(), transaction::ImportResult::Current); miner.update_sealing(&*client); client.flush_queue(); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 34c416cc146..3d0d1994070 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -41,7 +41,7 @@ mod miner; mod stratum; -pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; +pub use self::miner::{Miner, MinerOptions, PendingSet, MiningParams}; pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOptions}; pub use ethcore_miner::local_transactions::Status as LocalTransactionStatus; @@ -60,51 +60,42 @@ use transaction::{UnverifiedTransaction, PendingTransaction, ImportResult as Tra /// Miner client API pub trait MinerService : Send + Sync { - /// Returns miner's status. - fn status(&self) -> MinerStatus; - - /// Get the author that we will seal blocks as. - fn author(&self) -> Address; + /// Get the sealing work package and if `Some`, apply some transform. + fn map_pending_block(&self, chain: &MiningBlockChainClient, f: F) -> Option + where F: FnOnce(&ClosedBlock) -> T, Self: Sized; - /// Set the author that we will seal blocks as. - fn set_author(&self, author: Address); + /// Get a list of all pending receipts. + fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap; - /// Set info necessary to sign consensus messages. - fn set_engine_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::SignError>; + /// Get a particular receipt. + fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option; - /// Get the extra_data that we will seal blocks with. - fn extra_data(&self) -> Bytes; - /// Set the extra_data that we will seal blocks with. - fn set_extra_data(&self, extra_data: Bytes); + fn mining_params(&self) -> MiningParams; - /// Get current minimal gas price for transactions accepted to queue. - fn minimal_gas_price(&self) -> U256; + fn set_gas_range_target(&self, gas_range_target: (U256, U256)); - /// Set minimal gas price of transaction to be accepted for mining. - fn set_minimal_gas_price(&self, min_gas_price: U256); + fn set_extra_data(&self, extra_data: Bytes); - /// Get the lower bound of the gas limit we wish to target when sealing a new block. - fn gas_floor_target(&self) -> U256; + /// Set info necessary to sign consensus messages and block authoring. + /// + /// On PoW password is optional. + fn set_author(&self, address: Address, password: Option) -> Result<(), ::account_provider::SignError>; - /// Get the upper bound of the gas limit we wish to target when sealing a new block. - fn gas_ceil_target(&self) -> U256; - // TODO: coalesce into single set_range function. - /// Set the lower bound of gas limit we wish to target when sealing a new block. - fn set_gas_floor_target(&self, target: U256); + /// Is it currently sealing? + fn is_currently_sealing(&self) -> bool; - /// Set the upper bound of gas limit we wish to target when sealing a new block. - fn set_gas_ceil_target(&self, target: U256); + /// PoW chain - can produce work package + fn can_produce_work_package(&self) -> bool; - /// Get current transactions limit in queue. - fn transactions_limit(&self) -> usize; + /// New chain head event. Restart mining operation. + fn update_sealing(&self, chain: &MiningBlockChainClient); - /// Set maximal number of transactions kept in the queue (both current and future). - fn set_transactions_limit(&self, limit: usize); + /// Submit `seal` as a valid solution for the header of `pow_hash`. + /// Will check the seal, but not actually insert the block into the chain. + fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; - /// Set maximum amount of gas allowed for any single transaction to mine. - fn set_tx_gas_limit(&self, limit: U256); /// Imports transactions to transaction queue. fn import_external_transactions(&self, chain: &MiningBlockChainClient, transactions: Vec) -> @@ -123,26 +114,12 @@ pub trait MinerService : Send + Sync { /// Called when blocks are imported to chain, updates transactions queue. fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); - /// PoW chain - can produce work package - fn can_produce_work_package(&self) -> bool; - - /// New chain head event. Restart mining operation. - fn update_sealing(&self, chain: &MiningBlockChainClient); - - /// Submit `seal` as a valid solution for the header of `pow_hash`. - /// Will check the seal, but not actually insert the block into the chain. - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; - - /// Get the sealing work package and if `Some`, apply some transform. - fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option - where F: FnOnce(&ClosedBlock) -> T, Self: Sized; - /// Query pending transactions for hash. fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Removes transaction from the queue. /// NOTE: The transaction is not removed from pending block if mining. - fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option; + // fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option; /// Get a list of all pending transactions in the queue. fn pending_transactions(&self) -> Vec; @@ -154,34 +131,15 @@ pub trait MinerService : Send + Sync { fn future_transactions(&self) -> Vec; /// Get a list of local transactions with statuses. - fn local_transactions(&self) -> BTreeMap; - - /// Get a list of all pending receipts. - fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap; - - /// Get a particular reciept. - fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option; + // fn local_transactions(&self) -> BTreeMap; /// Returns highest transaction nonce for given address. fn last_nonce(&self, address: &Address) -> Option; - /// Is it currently sealing? - fn is_currently_sealing(&self) -> bool; /// Suggested gas price. fn sensible_gas_price(&self) -> U256; /// Suggested gas limit. - fn sensible_gas_limit(&self) -> U256 { 21000.into() } -} - -/// Mining status -#[derive(Debug)] -pub struct MinerStatus { - /// Number of transactions in queue with state `pending` (ready to be included in block) - pub transactions_in_pending_queue: usize, - /// Number of transactions in queue with state `future` (not yet ready to be included in block) - pub transactions_in_future_queue: usize, - /// Number of transactions included in currently mined block - pub transactions_in_pending_block: usize, + fn sensible_gas_limit(&self) -> U256; } diff --git a/miner/Cargo.toml b/miner/Cargo.toml index 873361f3d5a..fb4c3e855e8 100644 --- a/miner/Cargo.toml +++ b/miner/Cargo.toml @@ -10,6 +10,7 @@ authors = ["Parity Technologies "] # TODO [ToDr] Rewrite using reqwest hyper = { git = "https://github.com/paritytech/hyper", default-features = false } +ansi_term = "0.10" common-types = { path = "../ethcore/types" } error-chain = "0.11" ethash = { path = "../ethash" } @@ -23,6 +24,7 @@ linked-hash-map = "0.5" log = "0.3" native-contracts = { path = "../ethcore/native_contracts" } parking_lot = "0.5" +price-info = { path = "../price-info" } rustc-hex = "1.0" table = { path = "../util/table" } transaction-pool = { path = "../transaction-pool" } diff --git a/miner/src/gas_pricer.rs b/miner/src/gas_pricer.rs new file mode 100644 index 00000000000..3fb6048c36d --- /dev/null +++ b/miner/src/gas_pricer.rs @@ -0,0 +1,93 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +use std::time::{Instant, Duration}; + +use ansi_term::Colour; +use ethereum_types::U256; +use price_info::{Client as PriceInfoClient, PriceInfo}; +use price_info::fetch::Client as FetchClient; + +/// Options for the dynamic gas price recalibrator. +#[derive(Debug, PartialEq)] +pub struct GasPriceCalibratorOptions { + /// Base transaction price to match against. + pub usd_per_tx: f32, + /// How frequently we should recalibrate. + pub recalibration_period: Duration, +} + +/// The gas price validator variant for a `GasPricer`. +#[derive(Debug, PartialEq)] +pub struct GasPriceCalibrator { + options: GasPriceCalibratorOptions, + next_calibration: Instant, + price_info: PriceInfoClient, +} + +impl GasPriceCalibrator { + fn recalibrate(&mut self, set_price: F) { + trace!(target: "miner", "Recalibrating {:?} versus {:?}", Instant::now(), self.next_calibration); + if Instant::now() >= self.next_calibration { + let usd_per_tx = self.options.usd_per_tx; + trace!(target: "miner", "Getting price info"); + + self.price_info.get(move |price: PriceInfo| { + trace!(target: "miner", "Price info arrived: {:?}", price); + let usd_per_eth = price.ethusd; + let wei_per_usd: f32 = 1.0e18 / usd_per_eth; + let gas_per_tx: f32 = 21000.0; + let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx; + info!(target: "miner", "Updated conversion rate to Ξ1 = {} ({} wei/gas)", Colour::White.bold().paint(format!("US${:.2}", usd_per_eth)), Colour::Yellow.bold().paint(format!("{}", wei_per_gas))); + set_price(U256::from(wei_per_gas as u64)); + }); + + self.next_calibration = Instant::now() + self.options.recalibration_period; + } + } +} + +/// Struct to look after updating the acceptable gas price of a miner. +#[derive(Debug, PartialEq)] +pub enum GasPricer { + /// A fixed gas price in terms of Wei - always the argument given. + Fixed(U256), + /// Gas price is calibrated according to a fixed amount of USD. + Calibrated(GasPriceCalibrator), +} + +impl GasPricer { + /// Create a new Calibrated `GasPricer`. + pub fn new_calibrated(options: GasPriceCalibratorOptions, fetch: FetchClient) -> GasPricer { + GasPricer::Calibrated(GasPriceCalibrator { + options: options, + next_calibration: Instant::now(), + price_info: PriceInfoClient::new(fetch), + }) + } + + /// Create a new Fixed `GasPricer`. + pub fn new_fixed(gas_price: U256) -> GasPricer { + GasPricer::Fixed(gas_price) + } + + fn recalibrate(&mut self, set_price: F) { + match *self { + GasPricer::Fixed(ref max) => set_price(max.clone()), + GasPricer::Calibrated(ref mut cal) => cal.recalibrate(set_price), + } + } +} diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 4e30c9bd2bc..603633e4cf1 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -19,15 +19,17 @@ //! Miner module //! Keeps track of transactions and mined block. +extern crate ansi_term; extern crate common_types as types; -extern crate ethereum_types; extern crate ethcore_transaction as transaction; +extern crate ethereum_types; extern crate futures; extern crate heapsize; extern crate keccak_hash as hash; extern crate linked_hash_map; extern crate native_contracts; extern crate parking_lot; +extern crate price_info; extern crate table; extern crate transaction_pool as txpool; extern crate transient_hashmap; @@ -44,9 +46,9 @@ extern crate ethkey; pub mod banning_queue; pub mod external; +pub mod gas_pricer; pub mod local_transactions; pub mod pool; pub mod service_transaction_checker; pub mod transaction_queue; pub mod work_notify; -pub mod queue; diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 88c6440ca2b..355e95307eb 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -22,10 +22,13 @@ use transaction; use txpool; pub mod client; +pub mod queue; pub mod ready; pub mod scoring; pub mod verifier; +pub use self::queue::TransactionQueue; + #[derive(Debug, PartialEq, Clone, Copy)] pub(crate) enum Priority { Local, diff --git a/miner/src/queue.rs b/miner/src/pool/queue.rs similarity index 94% rename from miner/src/queue.rs rename to miner/src/pool/queue.rs index 7ab600a4877..0505abdcf57 100644 --- a/miner/src/queue.rs +++ b/miner/src/pool/queue.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicUsize; +use ethereum_types::H256; use parking_lot::{RwLock, RwLockReadGuard}; use transaction; use txpool::{self, Verifier}; @@ -72,6 +73,14 @@ impl TransactionQueue { let removed = self.pool.write().cull(None, state_readiness); debug!(target: "txqueue", "Removed {} stalled transactions.", removed); } + + pub fn remove( + &self, + hash: &H256, + is_invalid: bool, + ) { + self.pool.write().remove(hash, is_invalid); + } } pub struct PendingReader<'a, R> { diff --git a/transaction-pool/Cargo.toml b/transaction-pool/Cargo.toml index bd351160e5d..70aba5d044a 100644 --- a/transaction-pool/Cargo.toml +++ b/transaction-pool/Cargo.toml @@ -9,4 +9,4 @@ authors = ["Parity Technologies "] error-chain = "0.11" log = "0.3" smallvec = "0.4" -ethereum-types = { version = "0.2", features = ["heapsizeof"] } +ethereum-types = { version = "0.1", features = ["heapsizeof"] } diff --git a/transaction-pool/src/lib.rs b/transaction-pool/src/lib.rs index 29353a10008..5069c9db190 100644 --- a/transaction-pool/src/lib.rs +++ b/transaction-pool/src/lib.rs @@ -90,6 +90,7 @@ mod verifier; pub mod scoring; +pub use self::error::{Error, ErrorKind}; pub use self::listener::{Listener, NoopListener}; pub use self::options::Options; pub use self::pool::{Pool, PendingIterator}; diff --git a/util/using_queue/src/lib.rs b/util/using_queue/src/lib.rs index 3ce822094a7..03862e9c8a5 100644 --- a/util/using_queue/src/lib.rs +++ b/util/using_queue/src/lib.rs @@ -82,13 +82,13 @@ impl UsingQueue where T: Clone { /// Returns `Some` item which is the first that `f` returns `true` with a reference to it /// as a parameter or `None` if no such item exists in the queue. - pub fn take_used_if

(&mut self, predicate: P) -> Option where P: Fn(&T) -> bool { + fn take_used_if

(&mut self, predicate: P) -> Option where P: Fn(&T) -> bool { self.in_use.iter().position(|r| predicate(r)).map(|i| self.in_use.remove(i)) } /// Returns `Some` item which is the first that `f` returns `true` with a reference to it /// as a parameter or `None` if no such item exists in the queue. - pub fn clone_used_if

(&mut self, predicate: P) -> Option where P: Fn(&T) -> bool { + fn clone_used_if

(&mut self, predicate: P) -> Option where P: Fn(&T) -> bool { self.in_use.iter().find(|r| predicate(r)).cloned() } From 43e828915769faff66e8693f684a3ae47a8f0826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Feb 2018 12:13:15 +0100 Subject: [PATCH 06/77] WiP reworking miner. --- ethcore/res/wasm-tests | 2 +- ethcore/src/miner/miner.rs | 235 +++++++++++++++----------------- ethcore/src/miner/stratum.rs | 2 +- miner/src/gas_pricer.rs | 5 +- miner/src/pool/mod.rs | 1 + miner/src/pool/queue.rs | 19 ++- miner/src/pool/verifier.rs | 2 +- transaction-pool/src/options.rs | 2 +- transaction-pool/src/pool.rs | 5 + 9 files changed, 140 insertions(+), 133 deletions(-) diff --git a/ethcore/res/wasm-tests b/ethcore/res/wasm-tests index ff8504a7f3b..fb111c82def 160000 --- a/ethcore/res/wasm-tests +++ b/ethcore/res/wasm-tests @@ -1 +1 @@ -Subproject commit ff8504a7f3b3fe78af47fa4e0c24e4a75a7306fd +Subproject commit fb111c82deff8759f54a5038d07cecc77cb5a663 diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 5b0678f7345..ed6baf5ea1e 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -36,9 +36,9 @@ use error::*; // AccountDetails, // TransactionOrigin, // }; -use ethcore_miner::pool::{self, TransactionQueue}; +use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction, PoolVerifiedTransaction}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; -use ethcore_miner::gas_pricer::{GasPricer, GasPriceCalibratorOptions}; +use ethcore_miner::gas_pricer::GasPricer; // use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; use miner::MinerService; use timer::PerfTimer; @@ -97,12 +97,13 @@ pub enum PendingSet { // ban_duration: Duration, // }, // } +// +// +const DEFAULT_MINIMAL_GAS_PRICE: u64 = 20_000_000_000; /// Configures the behaviour of the miner. #[derive(Debug, PartialEq)] pub struct MinerOptions { - /// URLs to notify when there is new work. - pub new_work_notify: Vec, /// Force the miner to reseal, even when nobody has asked for work. pub force_sealing: bool, /// Reseal on receipt of new external transactions. @@ -139,12 +140,14 @@ pub struct MinerOptions { /// NOTE: Such block will contain all pending transactions but /// will be invalid if mined. pub infinite_pending_block: bool, + + pub pool_limits: pool::Options, + pub pool_verification_options: pool::verifier::Options, } impl Default for MinerOptions { fn default() -> Self { MinerOptions { - new_work_notify: vec![], force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, @@ -162,6 +165,16 @@ impl Default for MinerOptions { // tx_queue_banning: Banning::Disabled, // refuse_service_transactions: false, infinite_pending_block: false, + pool_limits: pool::Options { + max_count: 16_384, + max_per_sender: 64, + max_mem_usage: 8 * 1024 * 1024, + }, + pool_verification_options: pool::verifier::Options { + minimal_gas_price: DEFAULT_MINIMAL_GAS_PRICE.into(), + block_gas_limit: U256::max_value(), + tx_gas_limit: U256::max_value(), + }, } } } @@ -215,44 +228,20 @@ impl Miner { /// Creates new instance of miner. fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { - // let mem_limit = options.tx_queue_memory_limit.unwrap_or_else(usize::max_value); - - let txq = unimplemented!(); - // let txq = TransactionQueue::with_limits( - // // options.tx_queue_strategy, - // options.tx_queue_size, - // mem_limit, - // // gas_limit, - // options.tx_gas_limit - // ); - // let txq = match options.tx_queue_banning { - // Banning::Disabled => BanningTransactionQueue::new(txq, Threshold::NeverBan, Duration::from_secs(180)), - // Banning::Enabled { ban_duration, min_offends, .. } => BanningTransactionQueue::new( - // txq, - // Threshold::BanAfter(min_offends), - // ban_duration, - // ), - // }; - - let notifiers: Vec> = match options.new_work_notify.is_empty() { - true => Vec::new(), - false => vec![)], - }; - - let (limits, verifier_options) = unimplemented!(); + let limits = options.pool_limits.clone(); + let verifier_options = options.pool_verification_options.clone(); Miner { - sealing: RwLock::new(SealingWork{ + sealing: Mutex::new(SealingWork{ queue: UsingQueue::new(options.work_queue_size), enabled: options.force_sealing - || !options.new_work_notify.is_empty() || spec.engine.seals_internally().is_some(), next_allowed_reseal: Instant::now(), next_mandatory_reseal: Instant::now() + options.reseal_max_period, sealing_block_last_request: 0, }), params: RwLock::new(MiningParams::default()), - notifiers: RwLock::new(vec![]), + listeners: RwLock::new(vec![]), gas_pricer: Mutex::new(gas_pricer), options, transaction_queue: TransactionQueue::new(limits, verifier_options), @@ -262,17 +251,19 @@ impl Miner { } /// Creates new instance of miner with accounts and with given spec. + #[deprecated] pub fn with_spec_and_accounts(spec: &Spec, accounts: Option>) -> Miner { - Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, accounts) + Miner::new_raw(Default::default(), GasPricer::new_fixed(DEFAULT_MINIMAL_GAS_PRICE.into()), spec, accounts) } /// Creates new instance of miner without accounts, but with given spec. + #[deprecated] pub fn with_spec(spec: &Spec) -> Miner { - Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, None) + Miner::new_raw(Default::default(), GasPricer::new_fixed(DEFAULT_MINIMAL_GAS_PRICE.into()), spec, None) } fn forced_sealing(&self) -> bool { - self.options.force_sealing || !self.notifiers.read().is_empty() + self.options.force_sealing || !self.listeners.read().is_empty() } /// Clear all pending block states @@ -346,17 +337,19 @@ impl Miner { open_block.set_gas_limit(!U256::zero()); } + let client = BlockChainClient { chain, engine: &*self.engine }; let pending = self.transaction_queue.pending( + client, chain_info.best_block_number, chain_info.best_block_timestamp, - nonce_cap, + // nonce_cap, ); (pending, open_block, last_work_hash) }; let mut invalid_transactions = HashSet::new(); - let mut non_allowed_transactions = HashSet::new(); + let mut not_allowed_transactions = HashSet::new(); let mut transactions_to_penalize = HashSet::new(); let block_number = open_block.block().fields().header.number(); @@ -364,11 +357,12 @@ impl Miner { let mut skipped_transactions = 0usize; let mut max_gas = open_block.header(); + let header = open_block.header(); for tx in pending.transactions() { let hash = tx.hash(); let start = Instant::now(); // Check whether transaction type is allowed for sender - let result = match self.engine.machine().verify_transaction(&tx, open_block.header(), chain.as_block_chain_client()) { + let result = match self.engine.machine().verify_transaction(&*tx, header, chain.as_block_chain_client()) { Err(Error::Transaction(transaction::Error::NotAllowed)) => Err(transaction::Error::NotAllowed.into()), _ => open_block.push_transaction(tx, None), }; @@ -420,7 +414,7 @@ impl Miner { // already have transaction - ignore Err(Error::Transaction(transaction::Error::AlreadyImported)) => {}, Err(Error::Transaction(transaction::Error::NotAllowed)) => { - non_allowed_transactions.insert(hash); + not_allowed_transactions.insert(hash); debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); }, Err(e) => { @@ -437,18 +431,11 @@ impl Miner { let block = open_block.close(); - // let fetch_nonce = |a: &Address| chain.latest_nonce(a); - { - let mut queue = self.transaction_queue.write(); - for hash in invalid_transactions { - // queue.remove(&hash, &fetch_nonce, RemovalReason::Invalid); - queue.remove(&hash, true) - } - for hash in non_allowed_transactions { - // queue.remove(&hash, &fetch_nonce, RemovalReason::NotAllowed); - queue.remove(&hash, false) - } + self.transaction_queue.remove(invalid_transactions.values(), true); + self.transaction_queue.remove(not_allowed_transactions.values(), false); + + // TODO [ToDr] Penalize // for hash in transactions_to_penalize { // queue.penalize(&hash); // } @@ -457,16 +444,6 @@ impl Miner { (block, original_work_hash) } - /// Asynchronously updates minimal gas price for transaction queue - fn recalibrate_minimal_gas_price(&self) { - debug!(target: "miner", "minimal_gas_price: recalibrating..."); - let txq = self.transaction_queue.clone(); - self.gas_pricer.lock().recalibrate(move |price| { - debug!(target: "miner", "minimal_gas_price: Got gas price! {}", price); - txq.write().set_minimal_gas_price(price); - }); - } - /// Check is reseal is allowed and necessary. fn requires_reseal(&self, best_block: BlockNumber) -> bool { let mut sealing = self.sealing.lock(); @@ -479,11 +456,12 @@ impl Miner { trace!(target: "miner", "requires_reseal: sealing enabled"); let last_request = sealing.sealing_block_last_request; - let should_disable_sealing = !self.forced_sealing() - && !has_local_transactions - && self.engine.seals_internally().is_none() - && best_block > last_request - && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS; + let sealing_enabled = self.forced_sealing() + || has_local_transactions + || self.engine.seals_internally().is_some() + || (best_block > last_request && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS); + + let should_disable_sealing = !sealing_enabled; trace!(target: "miner", "requires_reseal: should_disable_sealing={}; best_block={}, last_request={}", should_disable_sealing, best_block, last_request); @@ -576,7 +554,7 @@ impl Miner { sealing.queue.push(block); // If push notifications are enabled we assume all work items are used. - if is_new && !self.notifiers.read().is_empty() { + if is_new && !self.listeners.read().is_empty() { sealing.queue.use_last_ref(); } @@ -593,22 +571,24 @@ impl Miner { }; if is_new { work.map(|(pow_hash, difficulty, number)| { - for notifier in self.notifiers.read().iter() { + for notifier in self.listeners.read().iter() { notifier.notify(pow_hash, difficulty, number) } }); } } - fn update_gas_limit(&self, client: &MiningBlockChainClient) { - let gas_limit = client.best_block_header().gas_limit(); - // let mut queue = self.transaction_queue.write(); - // queue.set_gas_limit(gas_limit); - // if let GasLimit::Auto = self.options.tx_queue_gas_limit { - // // Set total tx queue gas limit to be 20x the block gas limit. - // queue.set_total_gas_limit(gas_limit * 20.into()); - // } - unimplemented!() + fn update_transaction_queue_limits(&self, block_gas_limit: U256) { + debug!(target: "miner", "minimal_gas_price: recalibrating..."); + let txq = self.transaction_queue.clone(); + let mut options = self.options.pool_verification_options.clone(); + self.gas_pricer.lock().recalibrate(move |gas_price| { + debug!(target: "miner", "minimal_gas_price: Got gas price! {}", gas_price); + options.minimal_gas_price = gas_price; + options.block_gas_limit = block_gas_limit; + + txq.set.verifier_options(options); + }); } /// Returns true if we had to prepare new pending block. @@ -792,8 +772,9 @@ impl MinerService for Miner { } fn pending_transactions(&self) -> Vec { - let queue = self.transaction_queue.read(); - queue.pending_transactions(BlockNumber::max_value(), u64::max_value()) + unimplemented!() + // let queue = self.transaction_queue.read(); + // queue.pending_transactions(BlockNumber::max_value(), u64::max_value()) } // fn local_transactions(&self) -> BTreeMap { @@ -805,59 +786,61 @@ impl MinerService for Miner { // } fn future_transactions(&self) -> Vec { - self.transaction_queue.read().future_transactions() + unimplemented!() + // self.transaction_queue.read().future_transactions() } fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec { - let queue = self.transaction_queue.read(); - match self.options.pending_set { - PendingSet::AlwaysQueue => queue.pending_transactions(best_block, best_block_timestamp), - PendingSet::SealingOrElseQueue => { - self.from_pending_block( - best_block, - || queue.pending_transactions(best_block, best_block_timestamp), - |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() - ) - }, - PendingSet::AlwaysSealing => { - self.from_pending_block( - best_block, - || vec![], - |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() - ) - }, - } + unimplemented!() + // let queue = self.transaction_queue.read(); + // match self.options.pending_set { + // PendingSet::AlwaysQueue => queue.pending_transactions(best_block, best_block_timestamp), + // PendingSet::SealingOrElseQueue => { + // self.from_pending_block( + // best_block, + // || queue.pending_transactions(best_block, best_block_timestamp), + // |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() + // ) + // }, + // PendingSet::AlwaysSealing => { + // self.from_pending_block( + // best_block, + // || vec![], + // |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() + // ) + // }, + // } } fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec { - let queue = self.transaction_queue.read(); - match self.options.pending_set { - PendingSet::AlwaysQueue => queue.pending_hashes(), - PendingSet::SealingOrElseQueue => { - self.from_pending_block( - best_block, - || queue.pending_hashes(), - |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() - ) - }, - PendingSet::AlwaysSealing => { - self.from_pending_block( - best_block, - || vec![], - |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() - ) - }, - } + unimplemented!() + // let queue = self.transaction_queue.read(); + // match self.options.pending_set { + // PendingSet::AlwaysQueue => queue.pending_hashes(), + // PendingSet::SealingOrElseQueue => { + // self.from_pending_block( + // best_block, + // || queue.pending_hashes(), + // |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() + // ) + // }, + // PendingSet::AlwaysSealing => { + // self.from_pending_block( + // best_block, + // || vec![], + // |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() + // ) + // }, + // } } fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { - let queue = self.transaction_queue.read(); match self.options.pending_set { - PendingSet::AlwaysQueue => queue.find(hash), + PendingSet::AlwaysQueue => self.transaction_queue.find(hash), PendingSet::SealingOrElseQueue => { self.from_pending_block( best_block, - || queue.find(hash), + || self.transaction_queue.find(hash), |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) ) }, @@ -930,6 +913,8 @@ impl MinerService for Miner { self.engine.seals_internally().is_none() } + + // TODO [ToDr] Pass sealing lock guard /// Update sealing if required. /// Prepare the block and work if the Engine does not seal internally. fn update_sealing(&self, chain: &MiningBlockChainClient) { @@ -1017,11 +1002,9 @@ impl MinerService for Miner { // 2. We ignore blocks that are `invalid` because it doesn't have any meaning in terms of the transactions that // are in those blocks - // First update gas limit in transaction queue - self.update_gas_limit(chain); - - // Update minimal gas price - self.recalibrate_minimal_gas_price(); + // First update gas limit in transaction queue and minimal gas price. + let gas_limit = chain.best_block_header().gas_limit(); + self.update_transaction_queue_limits(gas_limit); // Then import all transactions... { @@ -1162,12 +1145,12 @@ mod tests { tx_gas_limit: !U256::zero(), tx_queue_size: 1024, tx_queue_memory_limit: None, - tx_queue_gas_limit: GasLimit::None, + // tx_queue_gas_limit: GasLimit::None, tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice, pending_set: PendingSet::AlwaysSealing, work_queue_size: 5, enable_resubmission: true, - tx_queue_banning: Banning::Disabled, + // tx_queue_banning: Banning::Disabled, refuse_service_transactions: false, infinite_pending_block: false, }, diff --git a/ethcore/src/miner/stratum.rs b/ethcore/src/miner/stratum.rs index c8c387e5106..1139370a0bd 100644 --- a/ethcore/src/miner/stratum.rs +++ b/ethcore/src/miner/stratum.rs @@ -248,7 +248,7 @@ impl Stratum { /// Start STRATUM job dispatcher and register it in the miner pub fn register(cfg: &Options, miner: Arc, client: Weak) -> Result<(), Error> { let stratum = miner::Stratum::start(cfg, Arc::downgrade(&miner.clone()), client)?; - miner.push_notifier(Box::new(stratum) as Box); + miner.push_listener(Box::new(stratum) as Box); Ok(()) } } diff --git a/miner/src/gas_pricer.rs b/miner/src/gas_pricer.rs index 3fb6048c36d..97dd39c6c42 100644 --- a/miner/src/gas_pricer.rs +++ b/miner/src/gas_pricer.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Auto-updates minimal gas price requirement. + use std::time::{Instant, Duration}; use ansi_term::Colour; @@ -84,7 +86,8 @@ impl GasPricer { GasPricer::Fixed(gas_price) } - fn recalibrate(&mut self, set_price: F) { + /// Recalibrate current gas price. + pub fn recalibrate(&mut self, set_price: F) { match *self { GasPricer::Fixed(ref max) => set_price(max.clone()), GasPricer::Calibrated(ref mut cal) => cal.recalibrate(set_price), diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 355e95307eb..f23ebcb4239 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -28,6 +28,7 @@ pub mod scoring; pub mod verifier; pub use self::queue::TransactionQueue; +pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; #[derive(Debug, PartialEq, Clone, Copy)] pub(crate) enum Priority { diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 0505abdcf57..f4b21e085bc 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -27,6 +27,10 @@ impl TransactionQueue { } } + pub fn set_verifier_options(&self, options: verifier::Options) { + *self.options.write() = options; + } + pub fn import( &self, client: C, @@ -55,6 +59,7 @@ impl TransactionQueue { client: C, block_number: u64, current_timestamp: u64, + // TODO [ToDr] Support nonce_cap ) -> PendingReader<(ready::Condition, ready::State)> { let pending_readiness = ready::Condition::new(block_number, current_timestamp); let state_readiness = ready::State::new(client); @@ -74,12 +79,22 @@ impl TransactionQueue { debug!(target: "txqueue", "Removed {} stalled transactions.", removed); } - pub fn remove( + pub fn find( &self, hash: &H256, + ) -> Option> { + self.pool.read().find(hash) + } + + pub fn remove<'a, T: IntoIterator>( + &self, + hashes: T, is_invalid: bool, ) { - self.pool.write().remove(hash, is_invalid); + let mut pool = self.pool.write(); + for hash in hashes { + pool.remove(hash, is_invalid); + } } } diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index a1f47cc0c9d..176c1358f53 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -34,7 +34,7 @@ use super::client::{Client, TransactionType}; use super::VerifiedTransaction; /// Verification options. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Options { /// Minimal allowed gas price. pub minimal_gas_price: U256, diff --git a/transaction-pool/src/options.rs b/transaction-pool/src/options.rs index ddec912864f..8ccf8adfd16 100644 --- a/transaction-pool/src/options.rs +++ b/transaction-pool/src/options.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . /// Transaction Pool options. -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Options { /// Maximal number of transactions in the pool. pub max_count: usize, diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 4f4a63920f1..a4070e47515 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -345,6 +345,11 @@ impl Pool where removed } + /// Returns a transaction if it's part of the pool or `None` otherwise. + pub fn find(&self, hash: &H256) -> Option> { + self.by_hash.get(hash).cloned() + } + /// Returns an iterator of pending (ready) transactions. pub fn pending>(&self, ready: R) -> PendingIterator { PendingIterator { From 5e783ebd2f810e70df72ced49f3f0c13abd0808c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 14 Feb 2018 14:26:21 +0100 Subject: [PATCH 07/77] Make it compile. --- ethcore/src/block.rs | 82 +++---- ethcore/src/client/client.rs | 53 ++-- ethcore/src/client/test_client.rs | 8 +- ethcore/src/client/traits.rs | 6 +- ethcore/src/engines/authority_round/mod.rs | 10 +- ethcore/src/engines/basic_authority.rs | 10 +- ethcore/src/engines/mod.rs | 10 +- ethcore/src/engines/tendermint/mod.rs | 18 +- ethcore/src/engines/validator_set/contract.rs | 4 +- ethcore/src/engines/validator_set/multi.rs | 8 +- .../engines/validator_set/safe_contract.rs | 6 +- ethcore/src/machine.rs | 6 +- ethcore/src/miner/miner.rs | 230 ++++++++++-------- ethcore/src/miner/mod.rs | 6 +- ethcore/src/miner/stratum.rs | 4 +- .../src/snapshot/tests/proof_of_authority.rs | 6 +- ethcore/transaction/src/lib.rs | 11 - miner/src/gas_pricer.rs | 4 +- miner/src/lib.rs | 4 +- miner/src/pool/mod.rs | 8 + miner/src/pool/queue.rs | 78 +++++- miner/src/pool/verifier.rs | 11 + price-info/src/lib.rs | 2 +- transaction-pool/src/pool.rs | 4 + 24 files changed, 345 insertions(+), 244 deletions(-) diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 18c8fe36dc5..f1c18eb40de 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -407,7 +407,7 @@ impl<'x> OpenBlock<'x> { // info!("env_info says gas_used={}", env_info.gas_used); match self.block.state.apply(&env_info, self.engine.machine(), &t, self.block.traces.is_some()) { Ok(outcome) => { - self.block.transactions_set.insert(h.unwrap_or_else(||t.hash())); + self.block.transactions_set.insert(h.unwrap_or_else(|| t.hash())); self.block.transactions.push(t.into()); let t = outcome.trace; self.block.traces.as_mut().map(|traces| traces.push(t)); @@ -419,8 +419,33 @@ impl<'x> OpenBlock<'x> { } /// Push transactions onto the block. - pub fn push_transactions(&mut self, transactions: &[SignedTransaction]) -> Result<(), Error> { - push_transactions(self, transactions) + #[cfg(not(feature = "slow-blocks"))] + fn push_transactions(&mut self, transactions: Vec) -> Result<(), Error> { + for t in transactions { + self.push_transaction(t, None)?; + } + Ok(()) + } + + /// Push transactions onto the block. + #[cfg(feature = "slow-blocks")] + fn push_transactions(&mut self, transactions: Vec) -> Result<(), Error> { + use std::time; + + let slow_tx = option_env!("SLOW_TX_DURATION").and_then(|v| v.parse().ok()).unwrap_or(100); + for t in transactions { + let hash = t.hash(); + let start = time::Instant::now(); + self.push_transaction(t, None)?; + let took = start.elapsed(); + let took_ms = took.as_secs() * 1000 + took.subsec_nanos() as u64 / 1000000; + if took > time::Duration::from_millis(slow_tx) { + warn!("Heavy ({} ms) transaction in block {:?}: {:?}", took_ms, block.header().number(), hash); + } + debug!(target: "tx", "Transaction {:?} took: {} ms", hash, took_ms); + } + + Ok(()) } /// Populate self from a header. @@ -610,10 +635,10 @@ impl IsBlock for SealedBlock { } /// Enact the block given by block header, transactions and uncles -pub fn enact( - header: &Header, - transactions: &[SignedTransaction], - uncles: &[Header], +fn enact( + header: Header, + transactions: Vec, + uncles: Vec

, engine: &EthEngine, tracing: bool, db: StateDB, @@ -643,48 +668,19 @@ pub fn enact( is_epoch_begin, )?; - b.populate_from(header); + b.populate_from(&header); b.push_transactions(transactions)?; for u in uncles { - b.push_uncle(u.clone())?; + b.push_uncle(u)?; } Ok(b.close_and_lock()) } -#[inline] -#[cfg(not(feature = "slow-blocks"))] -fn push_transactions(block: &mut OpenBlock, transactions: &[SignedTransaction]) -> Result<(), Error> { - for t in transactions { - block.push_transaction(t.clone(), None)?; - } - Ok(()) -} - -#[cfg(feature = "slow-blocks")] -fn push_transactions(block: &mut OpenBlock, transactions: &[SignedTransaction]) -> Result<(), Error> { - use std::time; - - let slow_tx = option_env!("SLOW_TX_DURATION").and_then(|v| v.parse().ok()).unwrap_or(100); - for t in transactions { - let hash = t.hash(); - let start = time::Instant::now(); - block.push_transaction(t.clone(), None)?; - let took = start.elapsed(); - let took_ms = took.as_secs() * 1000 + took.subsec_nanos() as u64 / 1000000; - if took > time::Duration::from_millis(slow_tx) { - warn!("Heavy ({} ms) transaction in block {:?}: {:?}", took_ms, block.header().number(), hash); - } - debug!(target: "tx", "Transaction {:?} took: {} ms", hash, took_ms); - } - Ok(()) -} - -// TODO [ToDr] Pass `PreverifiedBlock` by move, this will avoid unecessary allocation /// Enact the block given by `block_bytes` using `engine` on the database `db` with given `parent` block header pub fn enact_verified( - block: &PreverifiedBlock, + block: PreverifiedBlock, engine: &EthEngine, tracing: bool, db: StateDB, @@ -696,9 +692,9 @@ pub fn enact_verified( let view = BlockView::new(&block.bytes); enact( - &block.header, - &block.transactions, - &view.uncles(), + block.header, + block.transactions, + view.uncles(), engine, tracing, db, @@ -766,7 +762,7 @@ mod tests { )?; b.populate_from(&header); - b.push_transactions(&transactions)?; + b.push_transactions(transactions)?; for u in &block.uncles() { b.push_uncle(u.clone())?; diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b7e9aea6efb..995397c08d2 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -101,10 +101,10 @@ pub struct ClientReport { impl ClientReport { /// Alter internal reporting to reflect the additional `block` has been processed. - pub fn accrue_block(&mut self, block: &PreverifiedBlock) { + pub fn accrue_block(&mut self, header: &Header, transactions: usize) { self.blocks_imported += 1; - self.transactions_applied += block.transactions.len(); - self.gas_processed = self.gas_processed + block.header.gas_used().clone(); + self.transactions_applied += transactions; + self.gas_processed = self.gas_processed + *header.gas_used(); } } @@ -401,9 +401,9 @@ impl Client { Arc::new(last_hashes) } - fn check_and_close_block(&self, block: &PreverifiedBlock) -> Result { + fn check_and_close_block(&self, block: PreverifiedBlock) -> Result { let engine = &*self.engine; - let header = &block.header; + let header = block.header.clone(); let chain = self.chain.read(); // Check the block isn't so old we won't be able to enact it. @@ -424,7 +424,7 @@ impl Client { // Verify Block Family let verify_family_result = self.verifier.verify_block_family( - header, + &header, &parent, engine, Some((&block.bytes, &block.transactions, &**chain, self)), @@ -435,18 +435,19 @@ impl Client { return Err(()); }; - let verify_external_result = self.verifier.verify_block_external(header, engine); + let verify_external_result = self.verifier.verify_block_external(&header, engine); if let Err(e) = verify_external_result { warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); return Err(()); }; // Enact Verified Block - let last_hashes = self.build_last_hashes(header.parent_hash().clone()); + let last_hashes = self.build_last_hashes(*header.parent_hash()); let db = self.state_db.read().boxed_clone_canon(header.parent_hash()); let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some(); - let enact_result = enact_verified(block, + let enact_result = enact_verified( + block, engine, self.tracedb.read().tracing_enabled(), db, @@ -464,7 +465,7 @@ impl Client { } // Final Verification - if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { + if let Err(e) = self.verifier.verify_block_final(&header, locked_block.block().header()) { warn!(target: "client", "Stage 5 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); return Err(()); } @@ -522,23 +523,28 @@ impl Client { let start = precise_time_ns(); for block in blocks { - let header = &block.header; + let header = block.header.clone(); + let bytes = block.bytes.clone(); + let hash = header.hash(); + let is_invalid = invalid_blocks.contains(header.parent_hash()); if is_invalid { - invalid_blocks.insert(header.hash()); + invalid_blocks.insert(hash); continue; } - if let Ok(closed_block) = self.check_and_close_block(&block) { - if self.engine.is_proposal(&block.header) { - self.block_queue.mark_as_good(&[header.hash()]); - proposed_blocks.push(block.bytes); + if let Ok(closed_block) = self.check_and_close_block(block) { + if self.engine.is_proposal(&header) { + self.block_queue.mark_as_good(&[hash]); + proposed_blocks.push(bytes); } else { - imported_blocks.push(header.hash()); + imported_blocks.push(hash); + + let transactions_len = closed_block.transactions().len(); - let route = self.commit_block(closed_block, &header, &block.bytes); + let route = self.commit_block(closed_block, &header, &bytes); import_results.push(route); - self.report.write().accrue_block(&block); + self.report.write().accrue_block(&header, transactions_len); } } else { invalid_blocks.insert(header.hash()); @@ -1808,19 +1814,20 @@ impl BlockChainClient for Client { }) } - fn transact_contract(&self, address: Address, data: Bytes) -> Result { + fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { let mining_params = self.miner.mining_params(); let transaction = Transaction { nonce: self.latest_nonce(&mining_params.author), action: Action::Call(address), - gas: mining_params.gas_range_target.0, - // TODO [ToDr] Do we need gas price here? + // TODO [ToDr] Check that params carefuly. + gas: self.miner.sensible_gas_limit(), gas_price: self.miner.sensible_gas_price(), value: U256::zero(), data: data, }; let chain_id = self.engine.signing_chain_id(&self.latest_env_info()); - let signature = self.engine.sign(transaction.hash(chain_id))?; + let signature = self.engine.sign(transaction.hash(chain_id)) + .map_err(|e| transaction::Error::InvalidSignature(e.to_string()))?; let signed = SignedTransaction::new(transaction.with_signature(signature, chain_id))?; self.miner.import_own_transaction(self, signed.into()) } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 5f538e7fb2a..2a21bcb5232 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -45,7 +45,7 @@ use filter::Filter; use log_entry::LocalizedLogEntry; use receipt::{Receipt, LocalizedReceipt, TransactionOutcome}; use blockchain::extras::BlockReceipts; -use error::{ImportResult, Error as EthcoreError}; +use error::ImportResult; use evm::{Factory as EvmFactory, VMType}; use vm::Schedule; use miner::{Miner, MinerService}; @@ -336,8 +336,8 @@ impl TestBlockChainClient { self.set_balance(signed_tx.sender(), 10_000_000_000_000_000_000u64.into()); let hash = signed_tx.hash(); let res = self.miner.import_external_transactions(self, vec![signed_tx.into()]); - let res = res.into_iter().next().unwrap().expect("Successful import"); - assert_eq!(res, transaction::ImportResult::Current); + let res = res.into_iter().next().unwrap(); + assert!(res.is_ok()); hash } @@ -768,7 +768,7 @@ impl BlockChainClient for TestBlockChainClient { fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } - fn transact_contract(&self, address: Address, data: Bytes) -> Result { + fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { let transaction = Transaction { nonce: self.latest_nonce(&self.miner.mining_params().author), action: Action::Call(address), diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index bb35bcbaef8..436e772a2a4 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -21,7 +21,7 @@ use block::{OpenBlock, SealedBlock, ClosedBlock}; use blockchain::TreeRoute; use encoded; use vm::LastHashes; -use error::{ImportResult, CallError, Error as EthcoreError, BlockImportError}; +use error::{ImportResult, CallError, BlockImportError}; use evm::{Factory as EvmFactory, Schedule}; use executive::Executed; use filter::Filter; @@ -29,7 +29,7 @@ use header::{BlockNumber}; use log_entry::LocalizedLogEntry; use receipt::LocalizedReceipt; use trace::LocalizedTrace; -use transaction::{LocalizedTransaction, PendingTransaction, SignedTransaction, ImportResult as TransactionImportResult}; +use transaction::{self, LocalizedTransaction, PendingTransaction, SignedTransaction}; use verification::queue::QueueInfo as BlockQueueInfo; use ethereum_types::{H256, U256, Address}; @@ -273,7 +273,7 @@ pub trait BlockChainClient : Sync + Send { fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result; /// Import a transaction: used for misbehaviour reporting. - fn transact_contract(&self, address: Address, data: Bytes) -> Result; + fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error>; /// Get the address of the registry itself. fn registrar_address(&self) -> Option
; diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 913a5123dbe..453235e676b 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -21,7 +21,7 @@ use std::sync::{Weak, Arc}; use std::time::{UNIX_EPOCH, Duration}; use std::collections::{BTreeMap, HashSet}; -use account_provider::AccountProvider; +use account_provider::{AccountProvider, SignError}; use block::*; use client::EngineClient; use engines::{Engine, Seal, EngineError, ConstructedVerifier}; @@ -35,7 +35,7 @@ use super::validator_set::{ValidatorSet, SimpleList, new_validator_set}; use self::finality::RollingFinality; -use ethkey::{verify_address, Signature}; +use ethkey::{self, Signature}; use io::{IoContext, IoHandler, TimerToken, IoService}; use itertools::{self, Itertools}; use rlp::{UntrustedRlp, encode}; @@ -365,7 +365,7 @@ fn verify_external(header: &Header, validators: &ValidatorSet) -> Result<(), Err let proposer_signature = header_signature(header)?; let correct_proposer = validators.get(header.parent_hash(), header_step); let is_invalid_proposer = *header.author() != correct_proposer || - !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())?; + !ethkey::verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())?; if is_invalid_proposer { trace!(target: "engine", "verify_block_external: bad proposer for step: {}", header_step); @@ -880,8 +880,8 @@ impl Engine for AuthorityRound { self.signer.write().set(ap, address, password); } - fn sign(&self, hash: H256) -> Result { - self.signer.read().sign(hash).map_err(Into::into) + fn sign(&self, hash: H256) -> Result { + self.signer.read().sign(hash) } fn snapshot_components(&self) -> Option> { diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 82c1ac8450b..04945e6265e 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -19,8 +19,8 @@ use std::sync::{Weak, Arc}; use ethereum_types::{H256, H520, Address}; use parking_lot::RwLock; -use ethkey::{recover, public_to_address, Signature}; -use account_provider::AccountProvider; +use ethkey::{self, Signature}; +use account_provider::{AccountProvider, SignError}; use block::*; use engines::{Engine, Seal, ConstructedVerifier, EngineError}; use error::{BlockError, Error}; @@ -61,7 +61,7 @@ fn verify_external(header: &Header, validators: &ValidatorSet) -> Result<(), Err // Check if the signature belongs to a validator, can depend on parent state. let sig = UntrustedRlp::new(&header.seal()[0]).as_val::()?; - let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?); + let signer = ethkey::public_to_address(ðkey::recover(&sig.into(), &header.bare_hash())?); if *header.author() != signer { return Err(EngineError::NotAuthorized(*header.author()).into()) @@ -184,8 +184,8 @@ impl Engine for BasicAuthority { self.signer.write().set(ap, address, password); } - fn sign(&self, hash: H256) -> Result { - self.signer.read().sign(hash).map_err(Into::into) + fn sign(&self, hash: H256) -> Result { + self.signer.read().sign(hash) } fn snapshot_components(&self) -> Option> { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 9185a930085..e29c31df340 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -41,14 +41,14 @@ use std::fmt; use self::epoch::PendingTransition; -use account_provider::AccountProvider; +use account_provider::{AccountProvider, SignError}; use builtin::Builtin; use vm::{EnvInfo, Schedule, CreateContractAddress}; use error::Error; use header::{Header, BlockNumber}; use snapshot::SnapshotComponents; use spec::CommonParams; -use transaction::{UnverifiedTransaction, SignedTransaction}; +use transaction::{self, UnverifiedTransaction, SignedTransaction}; use ethkey::Signature; use parity_machine::{Machine, LocalizedMachine as Localized}; @@ -304,7 +304,7 @@ pub trait Engine: Sync + Send { fn set_signer(&self, _account_provider: Arc, _address: Address, _password: String) {} /// Sign using the EngineSigner, to be used for consensus tx signing. - fn sign(&self, _hash: H256) -> Result { unimplemented!() } + fn sign(&self, _hash: H256) -> Result { unimplemented!() } /// Add Client which can be used for sealing, potentially querying the state and sending messages. fn register_client(&self, _client: Weak) {} @@ -374,14 +374,14 @@ pub trait EthEngine: Engine<::machine::EthereumMachine> { } /// Verify a particular transaction is valid. - fn verify_transaction_unordered(&self, t: UnverifiedTransaction, header: &Header) -> Result { + fn verify_transaction_unordered(&self, t: UnverifiedTransaction, header: &Header) -> Result { self.machine().verify_transaction_unordered(t, header) } /// Additional verification for transactions in blocks. // TODO: Add flags for which bits of the transaction to check. // TODO: consider including State in the params. - fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> Result<(), Error> { + fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> Result<(), transaction::Error> { self.machine().verify_transaction_basic(t, header) } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index fd32294f452..20f65a06c76 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -37,8 +37,8 @@ use bytes::Bytes; use error::{Error, BlockError}; use header::{Header, BlockNumber}; use rlp::UntrustedRlp; -use ethkey::{Message, public_to_address, recover, Signature}; -use account_provider::AccountProvider; +use ethkey::{self, Message, Signature}; +use account_provider::{AccountProvider, SignError}; use block::*; use engines::{Engine, Seal, EngineError, ConstructedVerifier}; use io::IoService; @@ -519,8 +519,8 @@ impl Engine for Tendermint { let message: ConsensusMessage = rlp.as_val().map_err(fmt_err)?; if !self.votes.is_old_or_known(&message) { let msg_hash = keccak(rlp.at(1).map_err(fmt_err)?.as_raw()); - let sender = public_to_address( - &recover(&message.signature.into(), &msg_hash).map_err(fmt_err)? + let sender = ethkey::public_to_address( + ðkey::recover(&message.signature.into(), &msg_hash).map_err(fmt_err)? ); if !self.is_authority(&sender) { @@ -611,7 +611,7 @@ impl Engine for Tendermint { }; let address = match self.votes.get(&precommit) { Some(a) => a, - None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?), + None => ethkey::public_to_address(ðkey::recover(&precommit.signature.into(), &precommit_hash)?), }; if !self.validators.contains(header.parent_hash(), &address) { return Err(EngineError::NotAuthorized(address.to_owned()).into()); @@ -666,7 +666,7 @@ impl Engine for Tendermint { let verifier = Box::new(EpochVerifier { subchain_validators: list, recover: |signature: &Signature, message: &Message| { - Ok(public_to_address(&::ethkey::recover(&signature, &message)?)) + Ok(ethkey::public_to_address(ðkey::recover(&signature, &message)?)) }, }); @@ -686,8 +686,8 @@ impl Engine for Tendermint { self.to_step(Step::Propose); } - fn sign(&self, hash: H256) -> Result { - self.signer.read().sign(hash).map_err(Into::into) + fn sign(&self, hash: H256) -> Result { + self.signer.read().sign(hash) } fn snapshot_components(&self) -> Option> { @@ -1031,7 +1031,7 @@ mod tests { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_test_tendermint, Some(tap.clone())); let engine = client.engine(); - client.miner().set_engine_signer(v1.clone(), "1".into()).unwrap(); + client.miner().set_author(v1.clone(), Some("1".into())).unwrap(); let notify = Arc::new(TestNotify::default()); client.add_notify(notify.clone()); diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 545b2eaaea6..15d8146b71f 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -169,8 +169,8 @@ mod tests { let validator_contract = "0000000000000000000000000000000000000005".parse::
().unwrap(); // Make sure reporting can be done. - client.miner().set_gas_floor_target(1_000_000.into()); - client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.miner().set_gas_range_target((1_000_000.into(), 1_000_000.into())); + client.miner().set_author(v1, Some("".into())).unwrap(); // Check a block that is a bit in future, reject it but don't report the validator. let mut header = Header::default(); diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs index b607544cbb7..ffcd5c7a4ef 100644 --- a/ethcore/src/engines/validator_set/multi.rs +++ b/ethcore/src/engines/validator_set/multi.rs @@ -171,22 +171,22 @@ mod tests { client.engine().register_client(Arc::downgrade(&client) as _); // Make sure txs go through. - client.miner().set_gas_floor_target(1_000_000.into()); + client.miner().set_gas_range_target((1_000_000.into(), 1_000_000.into())); // Wrong signer for the first block. - client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.miner().set_author(v1, Some("".into())).unwrap(); client.transact_contract(Default::default(), Default::default()).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 0); // Right signer for the first block. - client.miner().set_engine_signer(v0, "".into()).unwrap(); + client.miner().set_author(v0, Some("".into())).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 1); // This time v0 is wrong. client.transact_contract(Default::default(), Default::default()).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 1); - client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.miner().set_author(v1, Some("".into())).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 2); // v1 is still good. diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 07edf5f88c2..9762cf91a44 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -484,7 +484,7 @@ mod tests { client.engine().register_client(Arc::downgrade(&client) as _); let validator_contract = "0000000000000000000000000000000000000005".parse::
().unwrap(); - client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.miner().set_author(v1, Some("".into())).unwrap(); // Remove "1" validator. let tx = Transaction { nonce: 0.into(), @@ -512,11 +512,11 @@ mod tests { assert_eq!(client.chain_info().best_block_number, 1); // Switch to the validator that is still there. - client.miner().set_engine_signer(v0, "".into()).unwrap(); + client.miner().set_author(v0, Some("".into())).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 2); // Switch back to the added validator, since the state is updated. - client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.miner().set_author(v1, Some("".into())).unwrap(); let tx = Transaction { nonce: 2.into(), gas_price: 0.into(), diff --git a/ethcore/src/machine.rs b/ethcore/src/machine.rs index 62b9a13b4eb..09a9eaf35d2 100644 --- a/ethcore/src/machine.rs +++ b/ethcore/src/machine.rs @@ -340,12 +340,12 @@ impl EthereumMachine { } /// Verify a particular transaction is valid, regardless of order. - pub fn verify_transaction_unordered(&self, t: UnverifiedTransaction, _header: &Header) -> Result { + pub fn verify_transaction_unordered(&self, t: UnverifiedTransaction, _header: &Header) -> Result { Ok(SignedTransaction::new(t)?) } /// Does basic verification of the transaction. - pub fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> Result<(), Error> { + pub fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> Result<(), transaction::Error> { let check_low_s = match self.ethash_extensions { Some(ref ext) => header.number() >= ext.homestead_transition, None => true, @@ -366,7 +366,7 @@ impl EthereumMachine { /// Does verification of the transaction against the parent state. // TODO: refine the bound here to be a "state provider" or similar as opposed // to full client functionality. - pub fn verify_transaction(&self, t: &SignedTransaction, header: &Header, client: &BlockChainClient) -> Result<(), Error> { + pub fn verify_transaction(&self, t: &SignedTransaction, header: &Header, client: &BlockChainClient) -> Result<(), transaction::Error> { if let Some(ref filter) = self.tx_filter.as_ref() { if !filter.transaction_allowed(header.parent_hash(), t, client) { return Err(transaction::Error::NotAllowed.into()) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index ed6baf5ea1e..085197e50f0 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -36,7 +36,7 @@ use error::*; // AccountDetails, // TransactionOrigin, // }; -use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction, PoolVerifiedTransaction}; +use ethcore_miner::pool::{self, TransactionQueue}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; use ethcore_miner::gas_pricer::GasPricer; // use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; @@ -203,7 +203,8 @@ pub struct Miner { listeners: RwLock>>, gas_pricer: Mutex, options: MinerOptions, - transaction_queue: TransactionQueue, + // TODO [ToDr] Arc is only required because of price updater + transaction_queue: Arc, engine: Arc, accounts: Option>, // TODO [ToDr] Check lock order @@ -244,7 +245,7 @@ impl Miner { listeners: RwLock::new(vec![]), gas_pricer: Mutex::new(gas_pricer), options, - transaction_queue: TransactionQueue::new(limits, verifier_options), + transaction_queue: Arc::new(TransactionQueue::new(limits, verifier_options)), accounts, engine: spec.engine.clone(), } @@ -273,20 +274,24 @@ impl Miner { /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. pub fn pending_state(&self, latest_block_number: BlockNumber) -> Option> { - self.map_pending_block(|b| b.state().clone(), latest_block_number) + self.map_existing_pending_block(|b| b.state().clone(), latest_block_number) } /// Get `Some` `clone()` of the current pending block or `None` if we're not sealing. pub fn pending_block(&self, latest_block_number: BlockNumber) -> Option { - self.map_pending_block(|b| b.to_base(), latest_block_number) + self.map_existing_pending_block(|b| b.to_base(), latest_block_number) } /// Get `Some` `clone()` of the current pending block header or `None` if we're not sealing. pub fn pending_block_header(&self, latest_block_number: BlockNumber) -> Option
{ - self.map_pending_block(|b| b.header().clone(), latest_block_number) + self.map_existing_pending_block(|b| b.header().clone(), latest_block_number) } - fn map_pending_block(&self, f: F, latest_block_number: BlockNumber) -> Option where + /// Retrieves an existing pending block iff it's not older than given block number. + /// + /// NOTE: This will not prepare a new pending block if it's not existing. + /// See `map_pending_block` for alternative behaviour. + fn map_existing_pending_block(&self, f: F, latest_block_number: BlockNumber) -> Option where F: FnOnce(&ClosedBlock) -> T, { self.from_pending_block( @@ -296,12 +301,34 @@ impl Miner { ) } + // TODO [ToDr] Get rid of this method. + // + // We should never fall back to client, this can be handled on RPC level by returning Option<> + fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H + where F: Fn() -> H, G: FnOnce(&ClosedBlock) -> H { + let sealing = self.sealing.lock(); + sealing.queue.peek_last_ref().map_or_else( + || from_chain(), + |b| { + if b.block().header().number() > latest_block_number { + map_block(b) + } else { + from_chain() + } + } + ) + } + + fn client<'a>(&'a self, chain: &'a MiningBlockChainClient) -> BlockChainClient<'a> { + BlockChainClient::new(chain, &*self.engine, self.accounts.as_ref().map(|x| &**x)) + } + /// Prepares new block for sealing including top transactions from queue. fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { let _timer = PerfTimer::new("prepare_block"); - let chain_info = chain.chain_info(); - let (pending, mut open_block, original_work_hash) = { - let nonce_cap = if chain_info.best_block_number + 1 >= self.engine.params().dust_protection_transition { + let (mut pending, mut open_block, original_work_hash) = { + let chain_info = chain.chain_info(); + let nonce_cap: Option = if chain_info.best_block_number + 1 >= self.engine.params().dust_protection_transition { Some((self.engine.params().nonce_cap_increment * (chain_info.best_block_number + 1)).into()) } else { None }; @@ -337,9 +364,8 @@ impl Miner { open_block.set_gas_limit(!U256::zero()); } - let client = BlockChainClient { chain, engine: &*self.engine }; let pending = self.transaction_queue.pending( - client, + self.client(chain), chain_info.best_block_number, chain_info.best_block_timestamp, // nonce_cap, @@ -350,22 +376,26 @@ impl Miner { let mut invalid_transactions = HashSet::new(); let mut not_allowed_transactions = HashSet::new(); - let mut transactions_to_penalize = HashSet::new(); + // let mut transactions_to_penalize = HashSet::new(); let block_number = open_block.block().fields().header.number(); let mut tx_count = 0usize; let mut skipped_transactions = 0usize; - let mut max_gas = open_block.header(); - let header = open_block.header(); + let client = self.client(chain); for tx in pending.transactions() { - let hash = tx.hash(); let start = Instant::now(); - // Check whether transaction type is allowed for sender - let result = match self.engine.machine().verify_transaction(&*tx, header, chain.as_block_chain_client()) { - Err(Error::Transaction(transaction::Error::NotAllowed)) => Err(transaction::Error::NotAllowed.into()), - _ => open_block.push_transaction(tx, None), - }; + + let transaction = tx.signed().clone(); + let hash = transaction.hash(); + + // Re-verify transaction again vs current state. + let result = client.verify_signed(&transaction) + .map_err(Error::Transaction) + .and_then(|_| { + open_block.push_transaction(transaction, None) + }); + let took = start.elapsed(); // Check for heavy transactions @@ -432,8 +462,8 @@ impl Miner { let block = open_block.close(); { - self.transaction_queue.remove(invalid_transactions.values(), true); - self.transaction_queue.remove(not_allowed_transactions.values(), false); + self.transaction_queue.remove(invalid_transactions.iter(), true); + self.transaction_queue.remove(not_allowed_transactions.iter(), false); // TODO [ToDr] Penalize // for hash in transactions_to_penalize { @@ -452,7 +482,7 @@ impl Miner { return false } - let has_local_transactions = self.transaction_queue.has_local_pending_transactions(); + let has_local_transactions = self.transaction_queue.has_local_transactions(); trace!(target: "miner", "requires_reseal: sealing enabled"); let last_request = sealing.sealing_block_last_request; @@ -533,8 +563,9 @@ impl Miner { /// Prepares work which has to be done to seal. fn prepare_work(&self, block: ClosedBlock, original_work_hash: Option) { let (work, is_new) = { - let block_header = block.block().fields().header; + let block_header = block.block().fields().header.clone(); let block_hash = block_header.hash(); + let mut sealing = self.sealing.lock(); let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); @@ -558,7 +589,7 @@ impl Miner { sealing.queue.use_last_ref(); } - (Some((block_hash, block_header.difficulty(), block_header.number())), is_new) + (Some((block_hash, *block_header.difficulty(), block_header.number())), is_new) } else { (None, false) }; @@ -586,8 +617,7 @@ impl Miner { debug!(target: "miner", "minimal_gas_price: Got gas price! {}", gas_price); options.minimal_gas_price = gas_price; options.block_gas_limit = block_gas_limit; - - txq.set.verifier_options(options); + txq.set_verifier_options(options); }); } @@ -634,21 +664,6 @@ impl Miner { fn tx_reseal_allowed(&self) -> bool { Instant::now() > self.sealing.lock().next_allowed_reseal } - - fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H - where F: Fn() -> H, G: FnOnce(&ClosedBlock) -> H { - let sealing = self.sealing.lock(); - sealing.queue.peek_last_ref().map_or_else( - || from_chain(), - |b| { - if b.block().header().number() > latest_block_number { - map_block(b) - } else { - from_chain() - } - } - ) - } } const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; @@ -658,7 +673,7 @@ impl MinerService for Miner { fn clear_and_reset(&self, chain: &MiningBlockChainClient) { self.transaction_queue.clear(); // -------------------------------------------------------------------------- - // | NOTE Code below requires sealing lock. | + // | NOTE Code below requires sealing lock. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- self.update_sealing(chain); @@ -697,36 +712,40 @@ impl MinerService for Miner { warn!(target: "miner", "No account provider"); Err(AccountError::NotFound) } + } else { + Ok(()) } } fn sensible_gas_price(&self) -> U256 { // 10% above our minimum. - *self.transaction_queue.minimal_gas_price() * 110u32 / 100.into() + self.transaction_queue.current_worst_gas_price() * 110u32 / 100.into() } fn sensible_gas_limit(&self) -> U256 { - self.params.read().0 / 5.into() + self.params.read().gas_range_target.0 / 5.into() } fn import_external_transactions( &self, chain: &MiningBlockChainClient, transactions: Vec - ) -> Vec> { + ) -> Vec> { trace!(target: "external_tx", "Importing external transactions"); + let client = self.client(chain); let results = self.transaction_queue.import( - chain, + client, transactions.into_iter().map(pool::verifier::Transaction::Unverified).collect(), ); if !results.is_empty() && self.options.reseal_on_external_tx && self.tx_reseal_allowed() { // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing locks. | + // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- self.update_sealing(chain); } + results } @@ -734,25 +753,16 @@ impl MinerService for Miner { &self, chain: &MiningBlockChainClient, pending: PendingTransaction, - ) -> Result { + ) -> Result<(), transaction::Error> { trace!(target: "own_tx", "Importing transaction: {:?}", pending); + let client = self.client(chain); let imported = self.transaction_queue.import( - chain, + client, vec![pool::verifier::Transaction::Pending(pending)] ).pop().expect("one result returned per added transaction; one added => one result; qed"); - match imported { - Ok(_) => { - trace!(target: "own_tx", "Status: {:?}", self.transaction_queue.status()); - }, - Err(ref e) => { - trace!(target: "own_tx", "Status: {:?}", self.transaction_queue.status()); - warn!(target: "own_tx", "Error importing transaction: {:?}", e); - }, - } - // -------------------------------------------------------------------------- // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | @@ -836,11 +846,11 @@ impl MinerService for Miner { fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { match self.options.pending_set { - PendingSet::AlwaysQueue => self.transaction_queue.find(hash), + PendingSet::AlwaysQueue => self.transaction_queue.find(hash).map(|x| x.pending().clone()), PendingSet::SealingOrElseQueue => { self.from_pending_block( best_block, - || self.transaction_queue.find(hash), + || self.transaction_queue.find(hash).map(|x| x.pending().clone()), |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) ) }, @@ -958,13 +968,8 @@ impl MinerService for Miner { } fn map_pending_block(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { - trace!(target: "miner", "map_sealing_work: entering"); self.prepare_work_sealing(chain); - trace!(target: "miner", "map_sealing_work: sealing prepared"); - let mut sealing = self.sealing.lock(); - let ret = sealing.queue.use_last_ref(); - trace!(target: "miner", "map_sealing_work: leaving use_last_ref={:?}", ret.as_ref().map(|b| b.block().fields().header.hash())); - ret.map(f) + self.map_existing_pending_block(f, chain.chain_info().best_block_number) } fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { @@ -1007,6 +1012,7 @@ impl MinerService for Miner { self.update_transaction_queue_limits(gas_limit); // Then import all transactions... + let client = self.client(chain); { // TODO [ToDr] Parallelize for hash in retracted { @@ -1014,17 +1020,16 @@ impl MinerService for Miner { .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); let txs = block.transactions() .into_iter() - .map(|transaction| pool::verifier::Transaction::Pending(transaction.into())) + .map(pool::verifier::Transaction::Unverified) .collect(); let _ = self.transaction_queue.import( - chain, + client.clone(), txs, ); } } // ...and at the end remove the old ones - let client = BlockChainClient { chain, engine: &*self.engine }; self.transaction_queue.cull(client); if enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle) { @@ -1037,9 +1042,28 @@ impl MinerService for Miner { } } +#[derive(Clone)] struct BlockChainClient<'a> { chain: &'a MiningBlockChainClient, engine: &'a EthEngine, + accounts: Option<&'a AccountProvider>, + best_block_header: Header, +} + +impl<'a> BlockChainClient<'a> { + pub fn new(chain: &'a MiningBlockChainClient, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>) -> Self { + let best_block_header = chain.best_block_header().decode(); + BlockChainClient { + chain, + engine, + accounts, + best_block_header, + } + } + + fn verify_signed(&self, tx: &SignedTransaction) -> Result<(), transaction::Error> { + self.engine.machine().verify_transaction(&tx, &self.best_block_header, self.chain.as_block_chain_client()) + } } impl<'a> fmt::Debug for BlockChainClient<'a> { @@ -1056,16 +1080,19 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { fn verify_transaction(&self, tx: UnverifiedTransaction) -> Result { - let best_block_header = self.chain.best_block_header().decode(); + self.engine.verify_transaction_basic(&tx, &self.best_block_header)?; + let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header)?; - self.engine.verify_transaction_basic(&tx, &best_block_header)?; - self.engine.verify_transaction_unordered(tx, &best_block_header) + self.verify_signed(&tx); + + Ok(tx) } fn account_details(&self, address: &Address) -> pool::client::AccountDetails { pool::client::AccountDetails { nonce: self.chain.latest_nonce(address), balance: self.chain.latest_balance(address), + is_local: self.accounts.map_or(false, |accounts| accounts.has_account(*address).unwrap_or(false)), } } @@ -1089,15 +1116,12 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { #[cfg(test)] mod tests { use super::*; - use ethcore_miner::transaction_queue::PrioritizationStrategy; - use ethereum_types::U256; use ethkey::{Generator, Random}; use hash::keccak; use rustc_hex::FromHex; - use transaction::Transaction; + use transaction::Transaction; use client::{BlockChainClient, TestBlockChainClient, EachBlockWith}; - use miner::MinerService; use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; #[test] @@ -1107,7 +1131,7 @@ mod tests { let miner = Miner::with_spec(&Spec::new_test()); // when - let sealing_work = miner.map_sealing_work(&client, |_| ()); + let sealing_work = miner.map_pending_block(&client, |_| ()); assert!(sealing_work.is_some(), "Expected closed block"); } @@ -1117,47 +1141,45 @@ mod tests { let client = TestBlockChainClient::default(); let miner = Miner::with_spec(&Spec::new_test()); - let res = miner.map_sealing_work(&client, |b| b.block().fields().header.hash()); + let res = miner.map_pending_block(&client, |b| b.block().fields().header.hash()); assert!(res.is_some()); assert!(miner.submit_seal(&client, res.unwrap(), vec![]).is_ok()); // two more blocks mined, work requested. client.add_blocks(1, EachBlockWith::Uncle); - miner.map_sealing_work(&client, |b| b.block().fields().header.hash()); + miner.map_pending_block(&client, |b| b.block().fields().header.hash()); client.add_blocks(1, EachBlockWith::Uncle); - miner.map_sealing_work(&client, |b| b.block().fields().header.hash()); + miner.map_pending_block(&client, |b| b.block().fields().header.hash()); // solution to original work submitted. assert!(miner.submit_seal(&client, res.unwrap(), vec![]).is_ok()); } fn miner() -> Miner { - Arc::try_unwrap(Miner::new( + Miner::new_raw( MinerOptions { - new_work_notify: Vec::new(), force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, reseal_on_uncle: false, reseal_min_period: Duration::from_secs(5), reseal_max_period: Duration::from_secs(120), - tx_gas_limit: !U256::zero(), - tx_queue_size: 1024, - tx_queue_memory_limit: None, - // tx_queue_gas_limit: GasLimit::None, - tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice, pending_set: PendingSet::AlwaysSealing, work_queue_size: 5, enable_resubmission: true, - // tx_queue_banning: Banning::Disabled, - refuse_service_transactions: false, infinite_pending_block: false, + pool_limits: Default::default(), + pool_verification_options: pool::verifier::Options { + minimal_gas_price: DEFAULT_MINIMAL_GAS_PRICE.into(), + block_gas_limit: U256::max_value(), + tx_gas_limit: U256::max_value(), + }, }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), None, // accounts provider - )).ok().expect("Miner was just created.") + ) } fn transaction() -> SignedTransaction { @@ -1187,7 +1209,7 @@ mod tests { let res = miner.import_own_transaction(&client, PendingTransaction::new(transaction, None)); // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); + assert_eq!(res.unwrap(), ()); assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.ready_transactions(best_block, 0).len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); @@ -1207,7 +1229,7 @@ mod tests { let res = miner.import_own_transaction(&client, PendingTransaction::new(transaction, None)); // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); + assert_eq!(res.unwrap(), ()); assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); @@ -1225,7 +1247,7 @@ mod tests { let res = miner.import_external_transactions(&client, vec![transaction]).pop().unwrap(); // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); + assert_eq!(res.unwrap(), ()); assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); @@ -1254,14 +1276,15 @@ mod tests { let client = generate_dummy_client(2); - assert_eq!(miner.import_external_transactions(&*client, vec![transaction_with_chain_id(spec.chain_id()).into()]).pop().unwrap().unwrap(), transaction::ImportResult::Current); + let import = miner.import_external_transactions(&*client, vec![transaction_with_chain_id(spec.chain_id()).into()]).pop().unwrap(); + assert!(import.is_ok()); miner.update_sealing(&*client); client.flush_queue(); assert!(miner.pending_block(0).is_none()); assert_eq!(client.chain_info().best_block_number, 3 as BlockNumber); - assert_eq!(miner.import_own_transaction(&*client, PendingTransaction::new(transaction_with_chain_id(spec.chain_id()).into(), None)).unwrap(), transaction::ImportResult::Current); + assert!(miner.import_own_transaction(&*client, PendingTransaction::new(transaction_with_chain_id(spec.chain_id()).into(), None)).is_ok()); miner.update_sealing(&*client); client.flush_queue(); @@ -1269,21 +1292,12 @@ mod tests { assert_eq!(client.chain_info().best_block_number, 4 as BlockNumber); } - #[test] - fn should_fail_setting_engine_signer_on_pow() { - let spec = Spec::new_pow_test_spec; - let tap = Arc::new(AccountProvider::transient_provider()); - let addr = tap.insert_account(keccak("1").into(), "").unwrap(); - let client = generate_dummy_client_with_spec_and_accounts(spec, Some(tap.clone())); - assert!(match client.miner().set_engine_signer(addr, "".into()) { Err(AccountError::InappropriateChain) => true, _ => false }) - } - #[test] fn should_fail_setting_engine_signer_without_account_provider() { let spec = Spec::new_instant; let tap = Arc::new(AccountProvider::transient_provider()); let addr = tap.insert_account(keccak("1").into(), "").unwrap(); let client = generate_dummy_client_with_spec_and_accounts(spec, None); - assert!(match client.miner().set_engine_signer(addr, "".into()) { Err(AccountError::NotFound) => true, _ => false }); + assert!(match client.miner().set_author(addr, Some("".into())) { Err(AccountError::NotFound) => true, _ => false }); } } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 3d0d1994070..aa0a0970790 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -55,7 +55,7 @@ use client::{MiningBlockChainClient}; use error::{Error}; use header::BlockNumber; use receipt::{RichReceipt, Receipt}; -use transaction::{UnverifiedTransaction, PendingTransaction, ImportResult as TransactionImportResult}; +use transaction::{self, UnverifiedTransaction, PendingTransaction}; /// Miner client API pub trait MinerService : Send + Sync { @@ -99,11 +99,11 @@ pub trait MinerService : Send + Sync { /// Imports transactions to transaction queue. fn import_external_transactions(&self, chain: &MiningBlockChainClient, transactions: Vec) -> - Vec>; + Vec>; /// Imports own (node owner) transaction to queue. fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: PendingTransaction) -> - Result; + Result<(), transaction::Error>; /// Returns hashes of transactions currently in pending fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec; diff --git a/ethcore/src/miner/stratum.rs b/ethcore/src/miner/stratum.rs index 1139370a0bd..515a47d96bf 100644 --- a/ethcore/src/miner/stratum.rs +++ b/ethcore/src/miner/stratum.rs @@ -120,7 +120,7 @@ impl JobDispatcher for StratumJobDispatcher { } fn job(&self) -> Option { - self.with_core(|client, miner| miner.map_sealing_work(&*client, |b| { + self.with_core(|client, miner| miner.map_pending_block(&*client, |b| { let pow_hash = b.hash(); let number = b.block().header().number(); let difficulty = b.block().header().difficulty(); @@ -248,7 +248,7 @@ impl Stratum { /// Start STRATUM job dispatcher and register it in the miner pub fn register(cfg: &Options, miner: Arc, client: Weak) -> Result<(), Error> { let stratum = miner::Stratum::start(cfg, Arc::downgrade(&miner.clone()), client)?; - miner.push_listener(Box::new(stratum) as Box); + miner.add_work_listener(Box::new(stratum) as Box); Ok(()) } } diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index c237acf786a..4523a23a286 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -105,13 +105,11 @@ fn make_chain(accounts: Arc, blocks_beyond: usize, transitions: trace!(target: "snapshot", "Pushing block #{}, {} txs, author={}", n, txs.len(), signers[idx]); - client.miner().set_author(signers[idx]); + client.miner().set_author(signers[idx], Some(PASS.into())); client.miner().import_external_transactions(&*client, txs.into_iter().map(Into::into).collect()); - let engine = client.engine(); - engine.set_signer(accounts.clone(), signers[idx], PASS.to_owned()); - engine.step(); + client.engine().step(); assert_eq!(client.chain_info().best_block_number, n); }; diff --git a/ethcore/transaction/src/lib.rs b/ethcore/transaction/src/lib.rs index 6d6c269f30c..6a478b94635 100644 --- a/ethcore/transaction/src/lib.rs +++ b/ethcore/transaction/src/lib.rs @@ -33,14 +33,3 @@ mod transaction; pub use error::Error; pub use transaction::*; - -// TODO [ToDr] Move to miner! - -/// Represents the result of importing transaction. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ImportResult { - /// Transaction was imported to current queue. - Current, - /// Transaction was imported to future queue. - Future -} diff --git a/miner/src/gas_pricer.rs b/miner/src/gas_pricer.rs index 97dd39c6c42..dac559ccbf5 100644 --- a/miner/src/gas_pricer.rs +++ b/miner/src/gas_pricer.rs @@ -41,7 +41,7 @@ pub struct GasPriceCalibrator { } impl GasPriceCalibrator { - fn recalibrate(&mut self, set_price: F) { + fn recalibrate(&mut self, set_price: F) { trace!(target: "miner", "Recalibrating {:?} versus {:?}", Instant::now(), self.next_calibration); if Instant::now() >= self.next_calibration { let usd_per_tx = self.options.usd_per_tx; @@ -87,7 +87,7 @@ impl GasPricer { } /// Recalibrate current gas price. - pub fn recalibrate(&mut self, set_price: F) { + pub fn recalibrate(&mut self, set_price: F) { match *self { GasPricer::Fixed(ref max) => set_price(max.clone()), GasPricer::Calibrated(ref mut cal) => cal.recalibrate(set_price), diff --git a/miner/src/lib.rs b/miner/src/lib.rs index b3a7499485c..dd8a4863e18 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -48,11 +48,11 @@ extern crate log; #[cfg(test)] extern crate rustc_hex; -pub mod banning_queue; +// pub mod banning_queue; pub mod external; pub mod gas_pricer; pub mod local_transactions; pub mod pool; pub mod service_transaction_checker; -pub mod transaction_queue; +// pub mod transaction_queue; pub mod work_notify; diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index f23ebcb4239..1c91d44acf5 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -51,6 +51,14 @@ impl VerifiedTransaction { pub(crate) fn priority(&self) -> Priority { self.priority } + + pub fn signed(&self) -> &transaction::SignedTransaction { + &self.transaction.transaction + } + + pub fn pending(&self) -> &transaction::PendingTransaction { + &self.transaction + } } impl txpool::VerifiedTransaction for VerifiedTransaction { diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index f4b21e085bc..2218cf34dba 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use std::sync::atomic::AtomicUsize; -use ethereum_types::H256; +use ethereum_types::{H256, U256}; use parking_lot::{RwLock, RwLockReadGuard}; use transaction; use txpool::{self, Verifier}; use pool::{self, scoring, verifier, client, ready}; +// TODO [ToDr] Support logging listener (and custom listeners) type Pool = txpool::Pool; #[derive(Debug)] @@ -96,6 +97,26 @@ impl TransactionQueue { pool.remove(hash, is_invalid); } } + + pub fn clear(&self) { + self.pool.write().clear(); + } + + pub fn current_worst_gas_price(&self) -> U256 { + match self.pool.read().worst_transaction() { + Some(tx) => tx.signed().gas_price, + None => self.options.read().minimal_gas_price, + } + } + + pub fn status(&self) -> txpool::LightStatus { + self.pool.read().light_status() + } + + pub fn has_local_transactions(&self) -> bool { + // TODO [ToDr] Take from the listener + false + } } pub struct PendingReader<'a, R> { @@ -104,7 +125,60 @@ pub struct PendingReader<'a, R> { } impl<'a, R: txpool::Ready> PendingReader<'a, R> { - pub fn transactions(&'a mut self) -> txpool::PendingIterator { + pub fn transactions(&mut self) -> txpool::PendingIterator { self.guard.pending(self.ready.take().unwrap()) } } + +#[cfg(test)] +mod tests { + use super::*; + use ethereum_types::Address; + + #[derive(Debug)] + struct TestClient; + impl client::Client for TestClient { + fn transaction_already_included(&self, hash: &H256) -> bool { + false + } + + fn verify_transaction(&self, tx: transaction::UnverifiedTransaction) -> Result { + Ok(transaction::SignedTransaction::new(tx)?) + } + + /// Fetch account details for given sender. + fn account_details(&self, _address: &Address) -> client::AccountDetails { + client::AccountDetails { + balance: 5_000_000.into(), + nonce: 0.into(), + is_local: false, + } + } + + /// Fetch only account nonce for given sender. + fn account_nonce(&self, _address: &Address) -> U256 { + 0.into() + } + + /// Estimate minimal gas requirurement for given transaction. + fn required_gas(&self, _tx: &transaction::SignedTransaction) -> U256 { + 0.into() + } + + /// Classify transaction (check if transaction is filtered by some contracts). + fn transaction_type(&self, tx: &transaction::SignedTransaction) -> client::TransactionType { + client::TransactionType::Regular + } + } + + #[test] + fn should_get_pending_transactions() { + let queue = TransactionQueue::new(txpool::Options::default(), verifier::Options::default()); + + let mut pending = queue.pending(TestClient, 0, 0); + + for tx in pending.transactions() { + assert!(tx.signed().nonce > 0.into()); + } + } +} diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 176c1358f53..3060f74a674 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -44,6 +44,17 @@ pub struct Options { pub tx_gas_limit: U256, } +#[cfg(test)] +impl Default for Options { + fn default() -> Self { + Options { + minimal_gas_price: 0.into(), + block_gas_limit: U256::max_value(), + tx_gas_limit: U256::max_value(), + } + } +} + /// Transaction to verify. pub enum Transaction { /// Fresh, never verified transaction. diff --git a/price-info/src/lib.rs b/price-info/src/lib.rs index ec3c363a42d..6c9ec476777 100644 --- a/price-info/src/lib.rs +++ b/price-info/src/lib.rs @@ -91,7 +91,7 @@ impl Client { } /// Gets the current ETH price and calls `set_price` with the result. - pub fn get(&self, set_price: G) { + pub fn get(&self, set_price: G) { self.fetch.process_and_forget(self.fetch.fetch(&self.api_endpoint) .map_err(|err| Error::Fetch(err)) .and_then(move |mut response| { diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index a4070e47515..d0663ee5d50 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -350,6 +350,10 @@ impl Pool where self.by_hash.get(hash).cloned() } + pub fn worst_transaction(&self) -> Option> { + self.worst_transactions.iter().next().map(|x| x.transaction.clone()) + } + /// Returns an iterator of pending (ready) transactions. pub fn pending>(&self, ready: R) -> PendingIterator { PendingIterator { From 71fa691a5a5547ac45c7909497dfc109b4ccf4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 14 Feb 2018 15:10:32 +0100 Subject: [PATCH 08/77] Add some docs. --- ethcore/src/client/client.rs | 4 +- ethcore/src/client/test_client.rs | 2 +- ethcore/src/miner/miner.rs | 189 ++++++++++-------- ethcore/src/miner/mod.rs | 8 +- .../src/snapshot/tests/proof_of_authority.rs | 2 +- miner/src/pool/client.rs | 16 ++ miner/src/pool/mod.rs | 3 + miner/src/pool/queue.rs | 62 +++++- miner/src/pool/verifier.rs | 1 + transaction-pool/src/pool.rs | 1 + transaction-pool/src/tests/mod.rs | 14 ++ 11 files changed, 206 insertions(+), 96 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 995397c08d2..65d1734f994 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1815,9 +1815,9 @@ impl BlockChainClient for Client { } fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { - let mining_params = self.miner.mining_params(); + let authoring_params = self.miner.authoring_params(); let transaction = Transaction { - nonce: self.latest_nonce(&mining_params.author), + nonce: self.latest_nonce(&authoring_params.author), action: Action::Call(address), // TODO [ToDr] Check that params carefuly. gas: self.miner.sensible_gas_limit(), diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 2a21bcb5232..4040f2ab1a8 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -770,7 +770,7 @@ impl BlockChainClient for TestBlockChainClient { fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { let transaction = Transaction { - nonce: self.latest_nonce(&self.miner.mining_params().author), + nonce: self.latest_nonce(&self.miner.authoring_params().author), action: Action::Call(address), gas: self.spec.gas_limit, gas_price: U256::zero(), diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 085197e50f0..ccc15a06020 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -141,7 +141,9 @@ pub struct MinerOptions { /// will be invalid if mined. pub infinite_pending_block: bool, + /// Transaction pool limits. pub pool_limits: pool::Options, + /// Initial transaction verification options. pub pool_verification_options: pool::verifier::Options, } @@ -179,10 +181,14 @@ impl Default for MinerOptions { } } +/// Configurable parameters of block authoring. #[derive(Debug, Default, Clone)] -pub struct MiningParams { +pub struct AuthoringParams { + /// Lower and upper bound of block gas limit that we are targeting pub gas_range_target: (U256, U256), + /// Block author pub author: Address, + /// Block extra data pub extra_data: Bytes, } @@ -199,7 +205,7 @@ struct SealingWork { pub struct Miner { // NOTE [ToDr] When locking always lock in this order! sealing: Mutex, - params: RwLock, + params: RwLock, listeners: RwLock>>, gas_pricer: Mutex, options: MinerOptions, @@ -212,12 +218,13 @@ pub struct Miner { } impl Miner { - /// Push notifier that will handle new jobs + /// Push listener that will handle new jobs pub fn add_work_listener(&self, notifier: Box) { self.sealing.lock().enabled = true; self.listeners.write().push(notifier); } + /// Push an URL that will get new job notifications. pub fn add_work_listener_url(&self, urls: &[String]) { self.add_work_listener(Box::new(WorkPoster::new(&urls))); } @@ -241,7 +248,7 @@ impl Miner { next_mandatory_reseal: Instant::now() + options.reseal_max_period, sealing_block_last_request: 0, }), - params: RwLock::new(MiningParams::default()), + params: RwLock::new(AuthoringParams::default()), listeners: RwLock::new(vec![]), gas_pricer: Mutex::new(gas_pricer), options, @@ -326,12 +333,10 @@ impl Miner { /// Prepares new block for sealing including top transactions from queue. fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { let _timer = PerfTimer::new("prepare_block"); - let (mut pending, mut open_block, original_work_hash) = { - let chain_info = chain.chain_info(); - let nonce_cap: Option = if chain_info.best_block_number + 1 >= self.engine.params().dust_protection_transition { - Some((self.engine.params().nonce_cap_increment * (chain_info.best_block_number + 1)).into()) - } else { None }; + let chain_info = chain.chain_info(); + // Open block + let (mut open_block, original_work_hash) = { let mut sealing = self.sealing.lock(); let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); let best_hash = chain_info.best_block_hash; @@ -364,14 +369,7 @@ impl Miner { open_block.set_gas_limit(!U256::zero()); } - let pending = self.transaction_queue.pending( - self.client(chain), - chain_info.best_block_number, - chain_info.best_block_timestamp, - // nonce_cap, - ); - - (pending, open_block, last_work_hash) + (open_block, last_work_hash) }; let mut invalid_transactions = HashSet::new(); @@ -383,78 +381,93 @@ impl Miner { let mut skipped_transactions = 0usize; let client = self.client(chain); - for tx in pending.transactions() { - let start = Instant::now(); - - let transaction = tx.signed().clone(); - let hash = transaction.hash(); - - // Re-verify transaction again vs current state. - let result = client.verify_signed(&transaction) - .map_err(Error::Transaction) - .and_then(|_| { - open_block.push_transaction(transaction, None) - }); - - let took = start.elapsed(); - - // Check for heavy transactions - // match self.options.tx_queue_banning { - // Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { - // match self.transaction_queue.write().ban_transaction(&hash) { - // true => { - // warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); - // }, - // false => { - // transactions_to_penalize.insert(hash); - // debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") - // } - // } - // }, - // _ => {}, - // } - trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); - match result { - Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { - debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); + let engine_params = self.engine.params(); + let nonce_cap: Option = if chain_info.best_block_number + 1 >= engine_params.dust_protection_transition { + Some((engine_params.nonce_cap_increment * (chain_info.best_block_number + 1)).into()) + } else { + None + }; + { + let mut pending = self.transaction_queue.pending( + client.clone(), + chain_info.best_block_number, + chain_info.best_block_timestamp, + // nonce_cap, + ); - // Penalize transaction if it's above current gas limit - if gas > gas_limit { - invalid_transactions.insert(hash); - } + for tx in pending.transactions() { + let start = Instant::now(); + + let transaction = tx.signed().clone(); + let hash = transaction.hash(); + + // Re-verify transaction again vs current state. + let result = client.verify_signed(&transaction) + .map_err(Error::Transaction) + .and_then(|_| { + open_block.push_transaction(transaction, None) + }); + + let took = start.elapsed(); + + // Check for heavy transactions + // match self.options.tx_queue_banning { + // Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { + // match self.transaction_queue.write().ban_transaction(&hash) { + // true => { + // warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); + // }, + // false => { + // transactions_to_penalize.insert(hash); + // debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") + // } + // } + // }, + // _ => {}, + // } + trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); + match result { + Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { + debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); + + // Penalize transaction if it's above current gas limit + if gas > gas_limit { + invalid_transactions.insert(hash); + } - // Exit early if gas left is smaller then min_tx_gas - let min_tx_gas: U256 = 21000.into(); // TODO: figure this out properly. - let gas_left = gas_limit - gas_used; - if gas_left < min_tx_gas { - break; - } + // Exit early if gas left is smaller then min_tx_gas + let min_tx_gas: U256 = 21000.into(); // TODO: figure this out properly. + let gas_left = gas_limit - gas_used; + if gas_left < min_tx_gas { + break; + } - // Avoid iterating over the entire queue in case block is almost full. - skipped_transactions += 1; - if skipped_transactions > 8 { - break; - } - }, - // Invalid nonce error can happen only if previous transaction is skipped because of gas limit. - // If there is errornous state of transaction queue it will be fixed when next block is imported. - Err(Error::Execution(ExecutionError::InvalidNonce { expected, got })) => { - debug!(target: "miner", "Skipping adding transaction to block because of invalid nonce: {:?} (expected: {:?}, got: {:?})", hash, expected, got); - }, - // already have transaction - ignore - Err(Error::Transaction(transaction::Error::AlreadyImported)) => {}, - Err(Error::Transaction(transaction::Error::NotAllowed)) => { - not_allowed_transactions.insert(hash); - debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); - }, - Err(e) => { - invalid_transactions.insert(hash); - debug!( - target: "miner", "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e - ); - }, - // imported ok - _ => tx_count += 1, + // Avoid iterating over the entire queue in case block is almost full. + skipped_transactions += 1; + if skipped_transactions > 8 { + break; + } + }, + // Invalid nonce error can happen only if previous transaction is skipped because of gas limit. + // If there is errornous state of transaction queue it will be fixed when next block is imported. + Err(Error::Execution(ExecutionError::InvalidNonce { expected, got })) => { + debug!(target: "miner", "Skipping adding transaction to block because of invalid nonce: {:?} (expected: {:?}, got: {:?})", hash, expected, got); + }, + // already have transaction - ignore + Err(Error::Transaction(transaction::Error::AlreadyImported)) => {}, + Err(Error::Transaction(transaction::Error::NotAllowed)) => { + not_allowed_transactions.insert(hash); + debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); + }, + Err(e) => { + invalid_transactions.insert(hash); + debug!( + target: "miner", "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e + ); + }, + // imported ok + _ => tx_count += 1, + } } } trace!(target: "miner", "Pushed {} transactions", tx_count); @@ -679,7 +692,7 @@ impl MinerService for Miner { self.update_sealing(chain); } - fn mining_params(&self) -> MiningParams { + fn authoring_params(&self) -> AuthoringParams { self.params.read().clone() } @@ -1083,7 +1096,7 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { self.engine.verify_transaction_basic(&tx, &self.best_block_header)?; let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header)?; - self.verify_signed(&tx); + self.verify_signed(&tx)?; Ok(tx) } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index aa0a0970790..92fb0c8d8bd 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -41,7 +41,7 @@ mod miner; mod stratum; -pub use self::miner::{Miner, MinerOptions, PendingSet, MiningParams}; +pub use self::miner::{Miner, MinerOptions, PendingSet, AuthoringParams}; pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOptions}; pub use ethcore_miner::local_transactions::Status as LocalTransactionStatus; @@ -70,11 +70,13 @@ pub trait MinerService : Send + Sync { /// Get a particular receipt. fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option; + /// Get current authoring parameters. + fn authoring_params(&self) -> AuthoringParams; - fn mining_params(&self) -> MiningParams; - + /// Set the lower and upper bound of gas limit we wish to target when sealing a new block. fn set_gas_range_target(&self, gas_range_target: (U256, U256)); + /// Set the extra_data that we will seal blocks with. fn set_extra_data(&self, extra_data: Bytes); /// Set info necessary to sign consensus messages and block authoring. diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index 4523a23a286..fc70e453500 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -105,7 +105,7 @@ fn make_chain(accounts: Arc, blocks_beyond: usize, transitions: trace!(target: "snapshot", "Pushing block #{}, {} txs, author={}", n, txs.len(), signers[idx]); - client.miner().set_author(signers[idx], Some(PASS.into())); + client.miner().set_author(signers[idx], Some(PASS.into())).unwrap(); client.miner().import_external_transactions(&*client, txs.into_iter().map(Into::into).collect()); diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs index eba66d5372e..680705a4cdf 100644 --- a/miner/src/pool/client.rs +++ b/miner/src/pool/client.rs @@ -1,3 +1,19 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + //! Transaction Pool state client. //! //! `Client` encapsulates all external data required for the verifaction and readiness. diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 1c91d44acf5..d8d8a4c1100 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -48,14 +48,17 @@ pub struct VerifiedTransaction { } impl VerifiedTransaction { + /// Gets transaction priority. pub(crate) fn priority(&self) -> Priority { self.priority } + /// Gets wrapped `SignedTransaction` pub fn signed(&self) -> &transaction::SignedTransaction { &self.transaction.transaction } + /// Gets wrapped `PendingTransaction` pub fn pending(&self) -> &transaction::PendingTransaction { &self.transaction } diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 2218cf34dba..a8265b6e686 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -1,3 +1,20 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +//! Ethereum Transaction Queue use std::sync::Arc; use std::sync::atomic::AtomicUsize; @@ -12,6 +29,12 @@ use pool::{self, scoring, verifier, client, ready}; // TODO [ToDr] Support logging listener (and custom listeners) type Pool = txpool::Pool; +/// Ethereum Transaction Queue +/// +/// Responsible for: +/// - verifying incoming transactions +/// - maintaining a pool of verified transactions. +/// - returning an iterator for transactions that are ready to be included in block (pending) #[derive(Debug)] pub struct TransactionQueue { insertion_id: Arc, @@ -20,6 +43,7 @@ pub struct TransactionQueue { } impl TransactionQueue { + /// Create new queue with given pool limits and initial verification options. pub fn new(limits: txpool::Options, verification_options: verifier::Options) -> Self { TransactionQueue { insertion_id: Default::default(), @@ -28,10 +52,17 @@ impl TransactionQueue { } } + /// Update verification options + /// + /// Some parameters of verification may vary in time (like block gas limit or minimal gas price). pub fn set_verifier_options(&self, options: verifier::Options) { *self.options.write() = options; } + /// Import a set of transactions to the pool. + /// + /// Given blockchain and state access (Client) + /// verifies and imports transactions to the pool. pub fn import( &self, client: C, @@ -47,7 +78,7 @@ impl TransactionQueue { .map(|transaction| verifier.verify_transaction(transaction)) .map(|result| match result { Ok(verified) => match self.pool.write().import(verified) { - Ok(imported) => Ok(()), + Ok(_imported) => Ok(()), Err(txpool::Error(kind, _)) => unimplemented!(), }, Err(err) => Err(err), @@ -55,6 +86,10 @@ impl TransactionQueue { .collect() } + /// Returns a queue guard that allows to get an iterator for pending transactions. + /// + /// NOTE: During pending iteration importing to the queue is not allowed. + /// Make sure to drop the guard in reasonable time. pub fn pending( &self, client: C, @@ -71,6 +106,7 @@ impl TransactionQueue { } } + /// Culls all stalled transactions from the pool. pub fn cull( &self, client: C, @@ -80,6 +116,10 @@ impl TransactionQueue { debug!(target: "txqueue", "Removed {} stalled transactions.", removed); } + /// Retrieve a transaction from the pool. + /// + /// Given transaction hash looks up that transaction in the pool + /// and returns a shared pointer to it or `None` if it's not present. pub fn find( &self, hash: &H256, @@ -87,6 +127,12 @@ impl TransactionQueue { self.pool.read().find(hash) } + /// Remove a set of transactions from the pool. + /// + /// Given an iterator of transaction hashes + /// removes them from the pool. + /// That method should be used if invalid transactions are detected + /// or you want to cancel a transaction. pub fn remove<'a, T: IntoIterator>( &self, hashes: T, @@ -98,10 +144,12 @@ impl TransactionQueue { } } + /// Clear the entire pool. pub fn clear(&self) { self.pool.write().clear(); } + /// Returns gas price of currently the worst transaction in the pool. pub fn current_worst_gas_price(&self) -> U256 { match self.pool.read().worst_transaction() { Some(tx) => tx.signed().gas_price, @@ -109,22 +157,34 @@ impl TransactionQueue { } } + /// Returns a status of the pool. pub fn status(&self) -> txpool::LightStatus { self.pool.read().light_status() } + /// Check if there are any local transactions in the pool. + /// + /// Returns `true` if there are any transactions in the pool + /// that has been marked as local. + /// + /// Local transactions are the ones from accounts managed by this node + /// and transactions submitted via local RPC (`eth_sendRawTransaction`) pub fn has_local_transactions(&self) -> bool { // TODO [ToDr] Take from the listener false } } +/// A pending transactions guard. pub struct PendingReader<'a, R> { guard: RwLockReadGuard<'a, Pool>, ready: Option, } impl<'a, R: txpool::Ready> PendingReader<'a, R> { + /// Returns an iterator over currently pending transactions. + /// + /// NOTE: This method will panic if used twice! pub fn transactions(&mut self) -> txpool::PendingIterator { self.guard.pending(self.ready.take().unwrap()) } diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 3060f74a674..a28fde388dc 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -87,6 +87,7 @@ pub struct Verifier { } impl Verifier { + /// Creates new transaction verfier with specified options. pub fn new(client: C, options: Options, id: Arc) -> Self { Verifier { client, diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index d0663ee5d50..4f837b3fabb 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -350,6 +350,7 @@ impl Pool where self.by_hash.get(hash).cloned() } + /// Returns worst transaction in the queue (if any). pub fn worst_transaction(&self) -> Option> { self.worst_transactions.iter().next().map(|x| x.transaction.clone()) } diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index 05b72284b5a..55f39a2b6ce 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -375,6 +375,20 @@ fn should_re_insert_after_cull() { }); } +#[test] +fn should_return_worst_transaction() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + assert!(txq.worst_transaction().is_none()); + + // when + txq.import(b.tx().nonce(0).gas_price(5).new()).unwrap(); + + // then + assert!(txq.worst_transaction().is_some()); +} + mod listener { use std::cell::RefCell; use std::rc::Rc; From a70f1c366a370d5d16be594fe7267ae3eadec5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 15 Feb 2018 17:18:49 +0100 Subject: [PATCH 09/77] Split blockchain access to a separate file. --- Cargo.lock | 1 - ethcore/src/miner/blockchain_client.rs | 129 ++++++++++++++++++ ethcore/src/miner/miner.rs | 162 ++++------------------- ethcore/src/miner/mod.rs | 1 + miner/Cargo.toml | 1 - miner/src/lib.rs | 1 - miner/src/pool/client.rs | 4 +- miner/src/pool/verifier.rs | 33 ++--- miner/src/service_transaction_checker.rs | 19 +-- 9 files changed, 186 insertions(+), 165 deletions(-) create mode 100644 ethcore/src/miner/blockchain_client.rs diff --git a/Cargo.lock b/Cargo.lock index 44bc8fba2e9..9b6a232ddac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,7 +619,6 @@ name = "ethcore-miner" version = "1.9.0" dependencies = [ "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "common-types 0.1.0", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi-contract 5.0.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs new file mode 100644 index 00000000000..a3021ad82cc --- /dev/null +++ b/ethcore/src/miner/blockchain_client.rs @@ -0,0 +1,129 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +use std::fmt; + +use ethereum_types::{H256, U256, Address}; +use ethcore_miner::pool; +use ethcore_miner::service_transaction_checker::{self, ServiceTransactionChecker}; +use transaction::{ + self, + UnverifiedTransaction, + SignedTransaction, +}; + +use account_provider::AccountProvider; +use client::{MiningBlockChainClient, BlockId, TransactionId}; +use engines::EthEngine; +use header::Header; + +#[derive(Clone)] +pub struct BlockChainClient<'a> { + chain: &'a MiningBlockChainClient, + engine: &'a EthEngine, + accounts: Option<&'a AccountProvider>, + best_block_header: Header, + service_transaction_checker: Option, +} + +impl<'a> BlockChainClient<'a> { + pub fn new( + chain: &'a MiningBlockChainClient, + engine: &'a EthEngine, + accounts: Option<&'a AccountProvider>, + refuse_service_transactions: bool, + ) -> Self { + let best_block_header = chain.best_block_header().decode(); + BlockChainClient { + chain, + engine, + accounts, + best_block_header, + service_transaction_checker: if refuse_service_transactions { + None + } else { + Some(Default::default()) + }, + } + } + + pub fn verify_signed(&self, tx: &SignedTransaction) -> Result<(), transaction::Error> { + self.engine.machine().verify_transaction(&tx, &self.best_block_header, self.chain.as_block_chain_client()) + } +} + +impl<'a> fmt::Debug for BlockChainClient<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "BlockChainClient") + } +} + +impl<'a> pool::client::Client for BlockChainClient<'a> { + fn transaction_already_included(&self, hash: &H256) -> bool { + self.chain.transaction_block(TransactionId::Hash(*hash)).is_some() + } + + fn verify_transaction(&self, tx: UnverifiedTransaction) + -> Result + { + self.engine.verify_transaction_basic(&tx, &self.best_block_header)?; + let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header)?; + + self.verify_signed(&tx)?; + + Ok(tx) + } + + fn account_details(&self, address: &Address) -> pool::client::AccountDetails { + pool::client::AccountDetails { + nonce: self.chain.latest_nonce(address), + balance: self.chain.latest_balance(address), + is_local: self.accounts.map_or(false, |accounts| accounts.has_account(*address).unwrap_or(false)), + } + } + + fn account_nonce(&self, address: &Address) -> U256 { + self.chain.latest_nonce(address) + } + + fn required_gas(&self, tx: &SignedTransaction) -> U256 { + tx.gas_required(&self.chain.latest_schedule()).into() + } + + fn transaction_type(&self, tx: &SignedTransaction) -> pool::client::TransactionType { + match self.service_transaction_checker { + None => pool::client::TransactionType::Regular, + Some(ref checker) => match checker.check(self, &tx.sender()) { + Ok(true) => pool::client::TransactionType::Service, + Ok(false) => pool::client::TransactionType::Regular, + Err(e) => { + debug!(target: "txqueue", "Unable to verify service transaction: {:?}", e); + pool::client::TransactionType::Regular + }, + } + } + } +} + +impl<'a> service_transaction_checker::ContractCaller for BlockChainClient<'a> { + fn registry_address(&self, name: &str) -> Option
{ + self.chain.registry_address(name.into()) + } + + fn call_contract(&self, address: Address, data: Vec) -> Result, String> { + self.chain.call_contract(BlockId::Latest, address, data) + } +} diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index ccc15a06020..410277d88e9 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -14,47 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::fmt; use std::time::{Instant, Duration}; use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; -use account_provider::{AccountProvider, SignError as AccountError}; use ansi_term::Colour; use ethereum_types::{H256, U256, Address}; use parking_lot::{Mutex, RwLock}; use bytes::Bytes; use engines::{EthEngine, Seal}; -use error::*; -// use ethcore_miner::banning_queue::{BanningTransactionQueue, Threshold}; -// use ethcore_miner::local_transactions::{Status as LocalTransactionStatus}; -// use ethcore_miner::transaction_queue::{ -// TransactionQueue, -// RemovalReason, -// TransactionDetailsProvider as TransactionQueueDetailsProvider, -// PrioritizationStrategy, -// AccountDetails, -// TransactionOrigin, -// }; +use error::{Error, ExecutionError}; use ethcore_miner::pool::{self, TransactionQueue}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; use ethcore_miner::gas_pricer::GasPricer; -// use ethcore_miner::service_transaction_checker::ServiceTransactionChecker; -use miner::MinerService; use timer::PerfTimer; use transaction::{ self, Action, UnverifiedTransaction, PendingTransaction, - SignedTransaction, }; use using_queue::{UsingQueue, GetAction}; +use account_provider::{AccountProvider, SignError as AccountError}; use block::{ClosedBlock, IsBlock, Block}; -use client::{MiningBlockChainClient, BlockId, TransactionId}; +use client::{MiningBlockChainClient, BlockId}; use executive::contract_address; use header::{Header, BlockNumber}; +use miner::MinerService; +use miner::blockchain_client::BlockChainClient; use receipt::{Receipt, RichReceipt}; use spec::Spec; use state::State; @@ -67,21 +55,8 @@ pub enum PendingSet { /// Always just the transactions in the sealing block. These have had full checks but /// may be empty if the node is not actively mining or has force_sealing enabled. AlwaysSealing, - /// Try the sealing block, but if it is not currently sealing, fallback to the queue. - SealingOrElseQueue, } -// /// Type of the gas limit to apply to the transaction queue. -// #[derive(Debug, PartialEq)] -// pub enum GasLimit { -// /// Depends on the block gas limit and is updated with every block. -// Auto, -// /// No limit. -// None, -// /// Set to a fixed gas value. -// Fixed(U256), -// } -// // /// Transaction queue banning settings. // #[derive(Debug, PartialEq, Clone)] // pub enum Banning { @@ -116,31 +91,24 @@ pub struct MinerOptions { pub reseal_min_period: Duration, /// Maximum period between blocks (enables force sealing after that). pub reseal_max_period: Duration, - // /// Maximum amount of gas to bother considering for block insertion. - // pub tx_gas_limit: U256, - // /// Maximum size of the transaction queue. - // pub tx_queue_size: usize, - // /// Maximum memory usage of transactions in the queue (current / future). - // pub tx_queue_memory_limit: Option, - // / Strategy to use for prioritizing transactions in the queue. - // pub tx_queue_strategy: PrioritizationStrategy, /// Whether we should fallback to providing all the queue's transactions or just pending. pub pending_set: PendingSet, /// How many historical work packages can we store before running out? pub work_queue_size: usize, /// Can we submit two different solutions for the same block and expect both to result in an import? pub enable_resubmission: bool, - // / Global gas limit for all transaction in the queue except for local and retracted. - // pub tx_queue_gas_limit: GasLimit, - // / Banning settings. - // pub tx_queue_banning: Banning, - // / Do we refuse to accept service transactions even if sender is certified. - // pub refuse_service_transactions: bool, /// Create a pending block with maximal possible gas limit. /// NOTE: Such block will contain all pending transactions but /// will be invalid if mined. pub infinite_pending_block: bool, + + // / Strategy to use for prioritizing transactions in the queue. + // pub tx_queue_strategy: PrioritizationStrategy, + // / Banning settings. + // pub tx_queue_banning: Banning, + /// Do we refuse to accept service transactions even if sender is certified. + pub refuse_service_transactions: bool, /// Transaction pool limits. pub pool_limits: pool::Options, /// Initial transaction verification options. @@ -154,19 +122,15 @@ impl Default for MinerOptions { reseal_on_external_tx: false, reseal_on_own_tx: true, reseal_on_uncle: false, - // tx_gas_limit: !U256::zero(), - // tx_queue_size: 8192, - // tx_queue_memory_limit: Some(2 * 1024 * 1024), - // tx_queue_gas_limit: GasLimit::None, - // tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, pending_set: PendingSet::AlwaysQueue, reseal_min_period: Duration::from_secs(2), reseal_max_period: Duration::from_secs(120), work_queue_size: 20, enable_resubmission: true, - // tx_queue_banning: Banning::Disabled, - // refuse_service_transactions: false, infinite_pending_block: false, + // tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, + // tx_queue_banning: Banning::Disabled, + refuse_service_transactions: false, pool_limits: pool::Options { max_count: 16_384, max_per_sender: 64, @@ -213,8 +177,6 @@ pub struct Miner { transaction_queue: Arc, engine: Arc, accounts: Option>, - // TODO [ToDr] Check lock order - // service_transaction_action: ServiceTransactionAction, } impl Miner { @@ -327,7 +289,12 @@ impl Miner { } fn client<'a>(&'a self, chain: &'a MiningBlockChainClient) -> BlockChainClient<'a> { - BlockChainClient::new(chain, &*self.engine, self.accounts.as_ref().map(|x| &**x)) + BlockChainClient::new( + chain, + &*self.engine, + self.accounts.as_ref().map(|x| &**x), + self.options.refuse_service_transactions, + ) } /// Prepares new block for sealing including top transactions from queue. @@ -773,7 +740,7 @@ impl MinerService for Miner { let client = self.client(chain); let imported = self.transaction_queue.import( client, - vec![pool::verifier::Transaction::Pending(pending)] + vec![pool::verifier::Transaction::Local(pending)] ).pop().expect("one result returned per added transaction; one added => one result; qed"); // -------------------------------------------------------------------------- @@ -860,13 +827,6 @@ impl MinerService for Miner { fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { match self.options.pending_set { PendingSet::AlwaysQueue => self.transaction_queue.find(hash).map(|x| x.pending().clone()), - PendingSet::SealingOrElseQueue => { - self.from_pending_block( - best_block, - || self.transaction_queue.find(hash).map(|x| x.pending().clone()), - |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) - ) - }, PendingSet::AlwaysSealing => { self.from_pending_block( best_block, @@ -1033,7 +993,7 @@ impl MinerService for Miner { .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); let txs = block.transactions() .into_iter() - .map(pool::verifier::Transaction::Unverified) + .map(pool::verifier::Transaction::Retracted) .collect(); let _ = self.transaction_queue.import( client.clone(), @@ -1055,77 +1015,6 @@ impl MinerService for Miner { } } -#[derive(Clone)] -struct BlockChainClient<'a> { - chain: &'a MiningBlockChainClient, - engine: &'a EthEngine, - accounts: Option<&'a AccountProvider>, - best_block_header: Header, -} - -impl<'a> BlockChainClient<'a> { - pub fn new(chain: &'a MiningBlockChainClient, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>) -> Self { - let best_block_header = chain.best_block_header().decode(); - BlockChainClient { - chain, - engine, - accounts, - best_block_header, - } - } - - fn verify_signed(&self, tx: &SignedTransaction) -> Result<(), transaction::Error> { - self.engine.machine().verify_transaction(&tx, &self.best_block_header, self.chain.as_block_chain_client()) - } -} - -impl<'a> fmt::Debug for BlockChainClient<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "BlockChainClient") - } -} - -impl<'a> pool::client::Client for BlockChainClient<'a> { - fn transaction_already_included(&self, hash: &H256) -> bool { - self.chain.transaction_block(TransactionId::Hash(*hash)).is_some() - } - - fn verify_transaction(&self, tx: UnverifiedTransaction) - -> Result - { - self.engine.verify_transaction_basic(&tx, &self.best_block_header)?; - let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header)?; - - self.verify_signed(&tx)?; - - Ok(tx) - } - - fn account_details(&self, address: &Address) -> pool::client::AccountDetails { - pool::client::AccountDetails { - nonce: self.chain.latest_nonce(address), - balance: self.chain.latest_balance(address), - is_local: self.accounts.map_or(false, |accounts| accounts.has_account(*address).unwrap_or(false)), - } - } - - fn account_nonce(&self, address: &Address) -> U256 { - self.chain.latest_nonce(address) - } - - /// Estimate minimal gas requirurement for given transaction. - fn required_gas(&self, tx: &SignedTransaction) -> U256 { - tx.gas_required(&self.chain.latest_schedule()).into() - } - - /// Classify transaction (check if transaction is filtered by some contracts). - fn transaction_type(&self, tx: &SignedTransaction) -> pool::client::TransactionType { - // TODO [ToDr] Transaction checker - // self.service_transaction_action.check(self.client, tx) - unimplemented!() - } -} - #[cfg(test)] mod tests { use super::*; @@ -1133,7 +1022,7 @@ mod tests { use hash::keccak; use rustc_hex::FromHex; - use transaction::Transaction; + use transaction::{Transaction, SignedTransaction}; use client::{BlockChainClient, TestBlockChainClient, EachBlockWith}; use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; @@ -1182,6 +1071,7 @@ mod tests { work_queue_size: 5, enable_resubmission: true, infinite_pending_block: false, + refuse_service_transactions: false, pool_limits: Default::default(), pool_verification_options: pool::verifier::Options { minimal_gas_price: DEFAULT_MINIMAL_GAS_PRICE.into(), @@ -1206,7 +1096,7 @@ mod tests { value: U256::zero(), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), - gas_price: U256::zero(), + gas_price: U256::from(DEFAULT_MINIMAL_GAS_PRICE), nonce: U256::zero(), }.sign(keypair.secret(), Some(chain_id)) } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 92fb0c8d8bd..8eb6be3a02d 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -38,6 +38,7 @@ //! } //! ``` +mod blockchain_client; mod miner; mod stratum; diff --git a/miner/Cargo.toml b/miner/Cargo.toml index c679b4a991d..1f3b7717587 100644 --- a/miner/Cargo.toml +++ b/miner/Cargo.toml @@ -11,7 +11,6 @@ authors = ["Parity Technologies "] hyper = { git = "https://github.com/paritytech/hyper", default-features = false } ansi_term = "0.10" -common-types = { path = "../ethcore/types" } error-chain = "0.11" ethabi = "5.1" ethabi-contract = "5.0" diff --git a/miner/src/lib.rs b/miner/src/lib.rs index dd8a4863e18..d8a3fdd77c1 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -20,7 +20,6 @@ //! Keeps track of transactions and mined block. extern crate ansi_term; -extern crate common_types as types; extern crate ethabi; extern crate ethcore_transaction as transaction; extern crate ethereum_types; diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs index 680705a4cdf..2bc17fc25aa 100644 --- a/miner/src/pool/client.rs +++ b/miner/src/pool/client.rs @@ -41,10 +41,8 @@ pub struct AccountDetails { pub enum TransactionType { /// Regular transaction Regular, - /// Service transaction (allowed by a contract) + /// Service transaction (allowed by a contract to have gas_price=0) Service, - /// Denied transaction (disallowed by a contract) - Denied, } /// State client. diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index a28fde388dc..4f47ec92834 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -61,17 +61,24 @@ pub enum Transaction { /// /// We need to do full verification of such transactions Unverified(transaction::UnverifiedTransaction), + + /// Transaction from retracted block. + /// + /// We could skip some parts of verification of such transactions + Retracted(transaction::UnverifiedTransaction), + /// Locally signed or retracted transaction. /// /// We can skip consistency verifications and just verify readiness. - Pending(transaction::PendingTransaction), + Local(transaction::PendingTransaction), } impl Transaction { fn hash(&self) -> H256 { match *self { Transaction::Unverified(ref tx) => tx.hash(), - Transaction::Pending(ref tx) => tx.transaction.hash(), + Transaction::Retracted(ref tx) => tx.hash(), + Transaction::Local(ref tx) => tx.transaction.hash(), } } } @@ -109,16 +116,17 @@ impl txpool::Verifier for Verifier { bail!(transaction::Error::AlreadyImported) } - let was_unverified = if let Transaction::Unverified(_) = tx { true } else { false }; + let is_retracted = if let Transaction::Retracted(_) = tx { true } else { false }; + let is_own = if let Transaction::Local(_) = tx { true } else { false }; let (tx, condition) = match tx { - Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { + Transaction::Retracted(tx) | Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { Ok(signed) => (signed, None), Err(err) => { - debug!(target: "txqueue", "Rejected tx {:?}: invalid signature: {:?}", hash, err); + debug!(target: "txqueue", "Rejected tx {:?}: {:?}", hash, err); bail!(err) }, }, - Transaction::Pending(tx) => (tx.transaction, tx.condition), + Transaction::Local(tx) => (tx.transaction, tx.condition), }; let gas_limit = cmp::min(self.options.tx_gas_limit, self.options.block_gas_limit); @@ -150,18 +158,13 @@ impl txpool::Verifier for Verifier { } let transaction_type = self.client.transaction_type(&tx); - if let TransactionType::Denied = transaction_type { - debug!(target: "txqueue", "Rejected tx {:?}: denied by contract.", hash); - bail!(transaction::Error::NotAllowed) - } - let sender = tx.sender(); let account_details = self.client.account_details(&sender); if tx.gas_price < self.options.minimal_gas_price { if let TransactionType::Service = transaction_type { trace!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); - } else if account_details.is_local { + } else if is_own || account_details.is_local { trace!(target: "txqueue", "Local tx {:?} below minimal gas price accepted", hash); } else { debug!( @@ -204,10 +207,10 @@ impl txpool::Verifier for Verifier { bail!(transaction::Error::AlreadyImported); } - let priority = match (account_details.is_local, was_unverified) { + let priority = match (account_details.is_local, is_retracted) { (true, _) => super::Priority::Local, - (false, true) => super::Priority::Regular, - (false, false) => super::Priority::Retracted, + (false, false) => super::Priority::Regular, + (false, true) => super::Priority::Retracted, }; Ok(VerifiedTransaction { transaction: transaction::PendingTransaction { diff --git a/miner/src/service_transaction_checker.rs b/miner/src/service_transaction_checker.rs index 806acf29b54..a97d144d9c7 100644 --- a/miner/src/service_transaction_checker.rs +++ b/miner/src/service_transaction_checker.rs @@ -17,8 +17,6 @@ //! A service transactions contract checker. use ethereum_types::Address; -use transaction::SignedTransaction; -use types::ids::BlockId; use_contract!(service_transaction, "ServiceTransaction", "res/service_transaction.json"); @@ -30,7 +28,7 @@ pub trait ContractCaller { fn registry_address(&self, name: &str) -> Option
; /// Executes a contract call at given block. - fn call_contract(&self, BlockId, Address, Vec) -> Result, String>; + fn call_contract(&self, Address, Vec) -> Result, String>; } /// Service transactions checker. @@ -39,11 +37,16 @@ pub struct ServiceTransactionChecker { contract: service_transaction::ServiceTransaction, } -impl ServiceTransactionChecker { - /// Checks if service transaction can be appended to the transaction queue. - pub fn check(&self, client: &ContractCaller, tx: &SignedTransaction) -> Result { - assert!(tx.gas_price.is_zero()); +// TODO [ToDr] https://github.com/paritytech/ethabi/pull/84 +impl Clone for ServiceTransactionChecker { + fn clone(&self) -> Self { + Default::default() + } +} +impl ServiceTransactionChecker { + /// Checks if given address is whitelisted to send service transactions. + pub fn check(&self, client: &ContractCaller, sender: &Address) -> Result { let address = client.registry_address(SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME) .ok_or_else(|| "contract is not configured")?; @@ -51,7 +54,7 @@ impl ServiceTransactionChecker { self.contract.functions() .certified() - .call(tx.sender(), &|data| client.call_contract(BlockId::Latest, address, data)) + .call(*sender, &|data| client.call_contract(address, data)) .map_err(|e| e.to_string()) } } From afae75b57cd70d81e968fc66319431871a79ff3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 16 Feb 2018 16:21:08 +0100 Subject: [PATCH 10/77] Work on miner API. --- ethcore/light/src/net/tests/mod.rs | 2 +- ethcore/src/client/client.rs | 13 +- ethcore/src/client/test_client.rs | 10 +- ethcore/src/json_tests/chain.rs | 2 +- ethcore/src/miner/miner.rs | 366 ++++++++++------------ ethcore/src/miner/mod.rs | 85 +++-- ethcore/src/miner/stratum.rs | 4 +- ethcore/src/service.rs | 2 +- ethcore/src/snapshot/tests/service.rs | 2 +- ethcore/src/tests/client.rs | 16 +- ethcore/src/tests/helpers.rs | 4 +- ethcore/src/tests/trace.rs | 2 +- ethcore/src/tx_filter.rs | 2 +- miner/src/pool/mod.rs | 20 +- miner/src/pool/queue.rs | 37 +-- miner/src/pool/ready.rs | 2 +- miner/src/pool/verifier.rs | 47 ++- miner/src/queue.rs~ | 86 ----- parity/blockchain.rs | 4 +- parity/run.rs | 2 +- parity/snapshot.rs | 4 +- rpc/src/v1/tests/eth.rs | 26 +- rpc/src/v1/tests/helpers/miner_service.rs | 5 - sync/src/tests/helpers.rs | 2 +- 24 files changed, 303 insertions(+), 442 deletions(-) delete mode 100644 miner/src/queue.rs~ diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 721aee13fed..2d689fd8c70 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -172,7 +172,7 @@ impl Provider for TestProvider { }) } - fn ready_transactions(&self) -> Vec { + fn ready_transactions(&self) -> Vec { self.0.client.ready_transactions() } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 65d1734f994..2d90d8bd7cd 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -66,7 +66,7 @@ use state::{self, State}; use trace; use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase}; use trace::FlatTransactionTraces; -use transaction::{self, LocalizedTransaction, UnverifiedTransaction, SignedTransaction, Transaction, PendingTransaction, Action}; +use transaction::{self, LocalizedTransaction, UnverifiedTransaction, PendingTransaction, SignedTransaction, Transaction, Action}; use types::filter::Filter; use types::mode::Mode as IpcMode; use verification; @@ -1769,11 +1769,12 @@ impl BlockChainClient for Client { } fn ready_transactions(&self) -> Vec { - let (number, timestamp) = { - let chain = self.chain.read(); - (chain.best_block_number(), chain.best_block_timestamp()) - }; - self.miner.ready_transactions(number, timestamp) + // TODO [ToDr] Avoid cloning and propagate miner transaction further. + self.miner.ready_transactions(self) + .into_iter() + .map(|x| x.signed().clone()) + .map(Into::into) + .collect() } fn queue_consensus_message(&self, message: Bytes) { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 4040f2ab1a8..d950ca7ae48 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -166,7 +166,7 @@ impl TestBlockChainClient { receipts: RwLock::new(HashMap::new()), logs: RwLock::new(Vec::new()), queue_size: AtomicUsize::new(0), - miner: Arc::new(Miner::with_spec(&spec)), + miner: Arc::new(Miner::new_for_tests(&spec, None)), spec: spec, vm_factory: EvmFactory::new(VMType::Interpreter, 1024 * 1024), latest_block_timestamp: RwLock::new(10_000_000), @@ -741,9 +741,13 @@ impl BlockChainClient for TestBlockChainClient { self.spec.engine.handle_message(&message).unwrap(); } + // TODO [ToDr] Avoid cloning fn ready_transactions(&self) -> Vec { - let info = self.chain_info(); - self.miner.ready_transactions(info.best_block_number, info.best_block_timestamp) + self.miner.ready_transactions(self) + .into_iter() + .map(|tx| tx.signed().clone()) + .map(Into::into) + .collect() } fn signing_chain_id(&self) -> Option { None } diff --git a/ethcore/src/json_tests/chain.rs b/ethcore/src/json_tests/chain.rs index e82bc774036..8775cbf6787 100644 --- a/ethcore/src/json_tests/chain.rs +++ b/ethcore/src/json_tests/chain.rs @@ -64,7 +64,7 @@ pub fn json_chain_test(json_data: &[u8]) -> Vec { config, &spec, db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); for b in &blockchain.blocks_rlp() { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 410277d88e9..4b788801e92 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -24,7 +24,7 @@ use parking_lot::{Mutex, RwLock}; use bytes::Bytes; use engines::{EthEngine, Seal}; use error::{Error, ExecutionError}; -use ethcore_miner::pool::{self, TransactionQueue}; +use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; use ethcore_miner::gas_pricer::GasPricer; use timer::PerfTimer; @@ -32,6 +32,7 @@ use transaction::{ self, Action, UnverifiedTransaction, + SignedTransaction, PendingTransaction, }; use using_queue::{UsingQueue, GetAction}; @@ -55,6 +56,7 @@ pub enum PendingSet { /// Always just the transactions in the sealing block. These have had full checks but /// may be empty if the node is not actively mining or has force_sealing enabled. AlwaysSealing, + // TODO [ToDr] Enable mining if AlwaysSealing } // /// Transaction queue banning settings. @@ -164,6 +166,14 @@ struct SealingWork { sealing_block_last_request: u64, } +impl SealingWork { + /// Are we allowed to do a non-mandatory reseal? + fn reseal_allowed(&self) -> bool { + Instant::now() > self.next_allowed_reseal + } + +} + /// Keeps track of transactions using priority queue and holds currently mined block. /// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work. pub struct Miner { @@ -192,12 +202,7 @@ impl Miner { } /// Creates new instance of miner Arc. - pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { - Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) - } - - /// Creates new instance of miner. - fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { let limits = options.pool_limits.clone(); let verifier_options = options.pool_verification_options.clone(); @@ -220,16 +225,19 @@ impl Miner { } } - /// Creates new instance of miner with accounts and with given spec. - #[deprecated] - pub fn with_spec_and_accounts(spec: &Spec, accounts: Option>) -> Miner { - Miner::new_raw(Default::default(), GasPricer::new_fixed(DEFAULT_MINIMAL_GAS_PRICE.into()), spec, accounts) - } - - /// Creates new instance of miner without accounts, but with given spec. - #[deprecated] - pub fn with_spec(spec: &Spec) -> Miner { - Miner::new_raw(Default::default(), GasPricer::new_fixed(DEFAULT_MINIMAL_GAS_PRICE.into()), spec, None) + /// Creates new instance of miner with given spec and accounts. + /// + /// NOTE This should be only used for tests. + pub fn new_for_tests(spec: &Spec, accounts: Option>) -> Miner { + let minimal_gas_price = 0.into(); + Miner::new(MinerOptions { + pool_verification_options: pool::verifier::Options { + minimal_gas_price, + block_gas_limit: U256::max_value(), + tx_gas_limit: U256::max_value(), + }, + ..Default::default() + }, GasPricer::new_fixed(minimal_gas_price), spec, accounts) } fn forced_sealing(&self) -> bool { @@ -354,87 +362,88 @@ impl Miner { } else { None }; - { - let mut pending = self.transaction_queue.pending( - client.clone(), - chain_info.best_block_number, - chain_info.best_block_timestamp, - // nonce_cap, - ); - for tx in pending.transactions() { - let start = Instant::now(); - - let transaction = tx.signed().clone(); - let hash = transaction.hash(); - - // Re-verify transaction again vs current state. - let result = client.verify_signed(&transaction) - .map_err(Error::Transaction) - .and_then(|_| { - open_block.push_transaction(transaction, None) - }); - - let took = start.elapsed(); - - // Check for heavy transactions - // match self.options.tx_queue_banning { - // Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { - // match self.transaction_queue.write().ban_transaction(&hash) { - // true => { - // warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); - // }, - // false => { - // transactions_to_penalize.insert(hash); - // debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") - // } - // } - // }, - // _ => {}, - // } - trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); - match result { - Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { - debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); - - // Penalize transaction if it's above current gas limit - if gas > gas_limit { - invalid_transactions.insert(hash); - } + let pending: Vec> = self.transaction_queue.pending( + client.clone(), + chain_info.best_block_number, + chain_info.best_block_timestamp, + // TODO [ToDr] Take only part? + |transactions| transactions.collect(), + // nonce_cap, + ); - // Exit early if gas left is smaller then min_tx_gas - let min_tx_gas: U256 = 21000.into(); // TODO: figure this out properly. - let gas_left = gas_limit - gas_used; - if gas_left < min_tx_gas { - break; - } + for tx in pending { + let start = Instant::now(); + + let transaction = tx.signed().clone(); + let hash = transaction.hash(); + + // Re-verify transaction again vs current state. + let result = client.verify_signed(&transaction) + .map_err(Error::Transaction) + .and_then(|_| { + open_block.push_transaction(transaction, None) + }); + + let took = start.elapsed(); + + // Check for heavy transactions + // match self.options.tx_queue_banning { + // Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { + // match self.transaction_queue.write().ban_transaction(&hash) { + // true => { + // warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); + // }, + // false => { + // transactions_to_penalize.insert(hash); + // debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") + // } + // } + // }, + // _ => {}, + // } + trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); + match result { + Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { + debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); - // Avoid iterating over the entire queue in case block is almost full. - skipped_transactions += 1; - if skipped_transactions > 8 { - break; - } - }, - // Invalid nonce error can happen only if previous transaction is skipped because of gas limit. - // If there is errornous state of transaction queue it will be fixed when next block is imported. - Err(Error::Execution(ExecutionError::InvalidNonce { expected, got })) => { - debug!(target: "miner", "Skipping adding transaction to block because of invalid nonce: {:?} (expected: {:?}, got: {:?})", hash, expected, got); - }, - // already have transaction - ignore - Err(Error::Transaction(transaction::Error::AlreadyImported)) => {}, - Err(Error::Transaction(transaction::Error::NotAllowed)) => { - not_allowed_transactions.insert(hash); - debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); - }, - Err(e) => { + // Penalize transaction if it's above current gas limit + if gas > gas_limit { invalid_transactions.insert(hash); - debug!( - target: "miner", "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e - ); - }, - // imported ok - _ => tx_count += 1, - } + } + + // Exit early if gas left is smaller then min_tx_gas + let min_tx_gas: U256 = 21000.into(); // TODO: figure this out properly. + let gas_left = gas_limit - gas_used; + if gas_left < min_tx_gas { + break; + } + + // Avoid iterating over the entire queue in case block is almost full. + skipped_transactions += 1; + if skipped_transactions > 8 { + break; + } + }, + // Invalid nonce error can happen only if previous transaction is skipped because of gas limit. + // If there is errornous state of transaction queue it will be fixed when next block is imported. + Err(Error::Execution(ExecutionError::InvalidNonce { expected, got })) => { + debug!(target: "miner", "Skipping adding transaction to block because of invalid nonce: {:?} (expected: {:?}, got: {:?})", hash, expected, got); + }, + // already have transaction - ignore + Err(Error::Transaction(transaction::Error::AlreadyImported)) => {}, + Err(Error::Transaction(transaction::Error::NotAllowed)) => { + not_allowed_transactions.insert(hash); + debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); + }, + Err(e) => { + invalid_transactions.insert(hash); + debug!( + target: "miner", "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e + ); + }, + // imported ok + _ => tx_count += 1, } } trace!(target: "miner", "Pushed {} transactions", tx_count); @@ -639,26 +648,12 @@ impl Miner { // Return if we restarted prepare_new } - - /// Are we allowed to do a non-mandatory reseal? - fn tx_reseal_allowed(&self) -> bool { - Instant::now() > self.sealing.lock().next_allowed_reseal - } } const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; impl MinerService for Miner { - fn clear_and_reset(&self, chain: &MiningBlockChainClient) { - self.transaction_queue.clear(); - // -------------------------------------------------------------------------- - // | NOTE Code below requires sealing lock. | - // | Make sure to release the locks before calling that method. | - // -------------------------------------------------------------------------- - self.update_sealing(chain); - } - fn authoring_params(&self) -> AuthoringParams { self.params.read().clone() } @@ -718,7 +713,7 @@ impl MinerService for Miner { transactions.into_iter().map(pool::verifier::Transaction::Unverified).collect(), ); - if !results.is_empty() && self.options.reseal_on_external_tx && self.tx_reseal_allowed() { + if !results.is_empty() && self.options.reseal_on_external_tx && self.sealing.lock().reseal_allowed() { // -------------------------------------------------------------------------- // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | @@ -747,7 +742,7 @@ impl MinerService for Miner { // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { + if imported.is_ok() && self.options.reseal_on_own_tx && self.sealing.lock().reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing. if self.engine.seals_internally().unwrap_or(false) || !self.prepare_work_sealing(chain) { @@ -761,12 +756,6 @@ impl MinerService for Miner { imported } - fn pending_transactions(&self) -> Vec { - unimplemented!() - // let queue = self.transaction_queue.read(); - // queue.pending_transactions(BlockNumber::max_value(), u64::max_value()) - } - // fn local_transactions(&self) -> BTreeMap { // let queue = self.transaction_queue.read(); // queue.local_transactions() @@ -775,58 +764,41 @@ impl MinerService for Miner { // .collect() // } - fn future_transactions(&self) -> Vec { + fn future_transactions(&self) -> Vec> { unimplemented!() // self.transaction_queue.read().future_transactions() } - fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec { - unimplemented!() - // let queue = self.transaction_queue.read(); - // match self.options.pending_set { - // PendingSet::AlwaysQueue => queue.pending_transactions(best_block, best_block_timestamp), - // PendingSet::SealingOrElseQueue => { - // self.from_pending_block( - // best_block, - // || queue.pending_transactions(best_block, best_block_timestamp), - // |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() - // ) - // }, - // PendingSet::AlwaysSealing => { - // self.from_pending_block( - // best_block, - // || vec![], - // |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() - // ) - // }, - // } - } - - fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec { - unimplemented!() - // let queue = self.transaction_queue.read(); - // match self.options.pending_set { - // PendingSet::AlwaysQueue => queue.pending_hashes(), - // PendingSet::SealingOrElseQueue => { - // self.from_pending_block( - // best_block, - // || queue.pending_hashes(), - // |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() - // ) - // }, - // PendingSet::AlwaysSealing => { - // self.from_pending_block( - // best_block, - // || vec![], - // |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() - // ) - // }, - // } + fn ready_transactions(&self, chain: &MiningBlockChainClient) -> Vec> { + let chain_info = chain.chain_info(); + match self.options.pending_set { + PendingSet::AlwaysQueue => { + let client = self.client(chain); + + self.transaction_queue.pending( + client, + chain_info.best_block_number, + chain_info.best_block_timestamp, + |transactions| transactions.collect(), + ) + }, + PendingSet::AlwaysSealing => { + self.from_pending_block( + chain_info.best_block_number, + Vec::new, + |sealing| sealing.transactions() + .iter() + .map(|signed| pool::VerifiedTransaction::from_pending_block_transaction(signed.clone())) + .map(Arc::new) + .collect() + ) + }, + } } fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { match self.options.pending_set { - PendingSet::AlwaysQueue => self.transaction_queue.find(hash).map(|x| x.pending().clone()), + PendingSet::AlwaysQueue => self.transaction_queue.find(hash).map(|t| t.pending().clone()), PendingSet::AlwaysSealing => { self.from_pending_block( best_block, @@ -837,9 +809,24 @@ impl MinerService for Miner { } } + fn last_nonce(&self, address: &Address) -> Option { + // TODO [ToDr] missing! + unimplemented!() + } + + fn pending_transactions(&self, best_block: BlockNumber) -> Option> { + self.from_pending_block( + best_block, + || None, + |pending| Some(pending.transactions().to_vec()), + ) + } + + // TODO [ToDr] This is pretty inconsistent (you can get a ready_transaction, but no receipt for it) fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option { self.from_pending_block( best_block, + // TODO [ToDr] Should try to find transaction in best block! || None, |pending| { let txs = pending.transactions(); @@ -847,9 +834,10 @@ impl MinerService for Miner { .map(|t| t.hash()) .position(|t| t == *hash) .map(|index| { - let prev_gas = if index == 0 { Default::default() } else { pending.receipts()[index - 1].gas_used }; + let receipts = pending.receipts(); + let prev_gas = if index == 0 { Default::default() } else { receipts[index - 1].gas_used }; let tx = &txs[index]; - let receipt = &pending.receipts()[index]; + let receipt = &receipts[index]; RichReceipt { transaction_hash: hash.clone(), transaction_index: index, @@ -871,27 +859,20 @@ impl MinerService for Miner { ) } - fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap { + fn pending_receipts(&self, best_block: BlockNumber) -> Option> { self.from_pending_block( best_block, - BTreeMap::new, + // TODO [ToDr] This is wrong should look in latest block! + || None, |pending| { - let hashes = pending.transactions() - .iter() - .map(|t| t.hash()); - + let hashes = pending.transactions().iter().map(|t| t.hash()); let receipts = pending.receipts().iter().cloned(); - hashes.zip(receipts).collect() + Some(hashes.zip(receipts).collect()) } ) } - fn last_nonce(&self, address: &Address) -> Option { - // TODO [ToDr] missing! - unimplemented!() - } - fn can_produce_work_package(&self) -> bool { self.engine.seals_internally().is_none() } @@ -1022,7 +1003,7 @@ mod tests { use hash::keccak; use rustc_hex::FromHex; - use transaction::{Transaction, SignedTransaction}; + use transaction::Transaction; use client::{BlockChainClient, TestBlockChainClient, EachBlockWith}; use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; @@ -1030,7 +1011,7 @@ mod tests { fn should_prepare_block_to_seal() { // given let client = TestBlockChainClient::default(); - let miner = Miner::with_spec(&Spec::new_test()); + let miner = Miner::new_for_tests(&Spec::new_test(), None); // when let sealing_work = miner.map_pending_block(&client, |_| ()); @@ -1041,7 +1022,7 @@ mod tests { fn should_still_work_after_a_couple_of_blocks() { // given let client = TestBlockChainClient::default(); - let miner = Miner::with_spec(&Spec::new_test()); + let miner = Miner::new_for_tests(&Spec::new_test(), None); let res = miner.map_pending_block(&client, |b| b.block().fields().header.hash()); assert!(res.is_some()); @@ -1059,7 +1040,7 @@ mod tests { } fn miner() -> Miner { - Miner::new_raw( + Miner::new( MinerOptions { force_sealing: false, reseal_on_external_tx: false, @@ -1074,7 +1055,7 @@ mod tests { refuse_service_transactions: false, pool_limits: Default::default(), pool_verification_options: pool::verifier::Options { - minimal_gas_price: DEFAULT_MINIMAL_GAS_PRICE.into(), + minimal_gas_price: 0.into(), block_gas_limit: U256::max_value(), tx_gas_limit: U256::max_value(), }, @@ -1096,7 +1077,7 @@ mod tests { value: U256::zero(), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), - gas_price: U256::from(DEFAULT_MINIMAL_GAS_PRICE), + gas_price: U256::zero(), nonce: U256::zero(), }.sign(keypair.secret(), Some(chain_id)) } @@ -1113,10 +1094,9 @@ mod tests { // then assert_eq!(res.unwrap(), ()); - assert_eq!(miner.pending_transactions().len(), 1); - assert_eq!(miner.ready_transactions(best_block, 0).len(), 1); - assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); - assert_eq!(miner.pending_receipts(best_block).len(), 1); + assert_eq!(miner.pending_transactions(best_block).unwrap().len(), 1); + assert_eq!(miner.pending_receipts(best_block).unwrap().len(), 1); + assert_eq!(miner.ready_transactions(&client).len(), 1); // This method will let us know if pending block was created (before calling that method) assert!(!miner.prepare_work_sealing(&client)); } @@ -1133,10 +1113,9 @@ mod tests { // then assert_eq!(res.unwrap(), ()); - assert_eq!(miner.pending_transactions().len(), 1); - assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); - assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); - assert_eq!(miner.pending_receipts(best_block).len(), 0); + assert_eq!(miner.pending_transactions(best_block).unwrap().len(), 1); + assert_eq!(miner.pending_receipts(best_block).unwrap().len(), 0); + assert_eq!(miner.ready_transactions(&client).len(), 0); } #[test] @@ -1151,10 +1130,9 @@ mod tests { // then assert_eq!(res.unwrap(), ()); - assert_eq!(miner.pending_transactions().len(), 1); - assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); - assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); - assert_eq!(miner.pending_receipts(best_block).len(), 0); + assert_eq!(miner.pending_transactions(best_block).unwrap().len(), 0); + assert_eq!(miner.pending_receipts(best_block).unwrap().len(), 0); + assert_eq!(miner.ready_transactions(&client).len(), 1); // This method will let us know if pending block was created (before calling that method) assert!(miner.prepare_work_sealing(&client)); } @@ -1175,12 +1153,12 @@ mod tests { #[test] fn internal_seals_without_work() { let spec = Spec::new_instant(); - let miner = Miner::with_spec(&spec); + let miner = Miner::new_for_tests(&spec, None); let client = generate_dummy_client(2); let import = miner.import_external_transactions(&*client, vec![transaction_with_chain_id(spec.chain_id()).into()]).pop().unwrap(); - assert!(import.is_ok()); + assert_eq!(import.unwrap(), ()); miner.update_sealing(&*client); client.flush_queue(); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 8eb6be3a02d..0c33c98b20a 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -17,60 +17,49 @@ #![warn(missing_docs)] //! Miner module -//! Keeps track of transactions and mined block. -//! -//! Usage example: -//! -//! ```rust -//! extern crate ethcore; -//! use std::env; -//! use ethcore::ethereum; -//! use ethcore::client::{Client, ClientConfig}; -//! use ethcore::miner::{Miner, MinerService}; -//! -//! fn main() { -//! let miner: Miner = Miner::with_spec(ðereum::new_foundation(&env::temp_dir())); -//! // get status -//! assert_eq!(miner.status().transactions_in_pending_queue, 0); -//! -//! // Check block for sealing -//! //assert!(miner.sealing_block(&*client).lock().is_some()); -//! } -//! ``` +//! Keeps track of transactions and currently sealed pending block. mod blockchain_client; mod miner; -mod stratum; -pub use self::miner::{Miner, MinerOptions, PendingSet, AuthoringParams}; -pub use self::stratum::{Stratum, Error as StratumError, Options as StratumOptions}; +pub mod stratum; -pub use ethcore_miner::local_transactions::Status as LocalTransactionStatus; +pub use self::miner::{Miner, MinerOptions, PendingSet, AuthoringParams}; +use std::sync::Arc; use std::collections::BTreeMap; -use ethereum_types::{H256, U256, Address}; + use bytes::Bytes; +use ethereum_types::{H256, U256, Address}; +use ethcore_miner::pool::VerifiedTransaction; use block::ClosedBlock; use client::{MiningBlockChainClient}; use error::{Error}; use header::BlockNumber; use receipt::{RichReceipt, Receipt}; -use transaction::{self, UnverifiedTransaction, PendingTransaction}; +use transaction::{self, UnverifiedTransaction, PendingTransaction, SignedTransaction}; /// Miner client API pub trait MinerService : Send + Sync { + // Pending block + /// Get the sealing work package and if `Some`, apply some transform. fn map_pending_block(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T, Self: Sized; - /// Get a list of all pending receipts. - fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap; + /// Get a list of all pending receipts from pending block. + fn pending_receipts(&self, best_block: BlockNumber) -> Option>; - /// Get a particular receipt. + /// Get a particular receipt from pending block. fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option; + /// Get all transactions in pending block or `None` if not sealing. + fn pending_transactions(&self, best_block: BlockNumber) -> Option>; + + // Block authoring / sealing + /// Get current authoring parameters. fn authoring_params(&self) -> AuthoringParams; @@ -92,13 +81,17 @@ pub trait MinerService : Send + Sync { /// PoW chain - can produce work package fn can_produce_work_package(&self) -> bool; - /// New chain head event. Restart mining operation. - fn update_sealing(&self, chain: &MiningBlockChainClient); - /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; + /// New chain head event. Restart mining operation. + fn update_sealing(&self, chain: &MiningBlockChainClient); + + /// Called when blocks are imported to chain, updates transactions queue. + fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); + + // Transactions and Pool /// Imports transactions to transaction queue. fn import_external_transactions(&self, chain: &MiningBlockChainClient, transactions: Vec) -> @@ -108,30 +101,22 @@ pub trait MinerService : Send + Sync { fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: PendingTransaction) -> Result<(), transaction::Error>; - /// Returns hashes of transactions currently in pending - fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec; - - /// Removes all transactions from the queue and restart mining operation. - fn clear_and_reset(&self, chain: &MiningBlockChainClient); - - /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); - - /// Query pending transactions for hash. - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; - /// Removes transaction from the queue. /// NOTE: The transaction is not removed from pending block if mining. // fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option; - /// Get a list of all pending transactions in the queue. - fn pending_transactions(&self) -> Vec; + /// Query pending transaction given it's hash. + /// + /// Depending on the settings may look in transaction pool or only in pending block. + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; - /// Get a list of all transactions that can go into the given block. - fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec; + /// Get a list of all ready transactions. + /// + /// Depending on the settings may look in transaction pool or only in pending block. + fn ready_transactions(&self, chain: &MiningBlockChainClient) -> Vec>; - /// Get a list of all future transactions. - fn future_transactions(&self) -> Vec; + /// Get a list of all transactions in the pool (some of them might not be ready for inclusion yet). + fn future_transactions(&self) -> Vec>; /// Get a list of local transactions with statuses. // fn local_transactions(&self) -> BTreeMap; diff --git a/ethcore/src/miner/stratum.rs b/ethcore/src/miner/stratum.rs index 515a47d96bf..d6cd933a96e 100644 --- a/ethcore/src/miner/stratum.rs +++ b/ethcore/src/miner/stratum.rs @@ -30,7 +30,7 @@ use ethcore_stratum::{ JobDispatcher, PushWorkHandler, Stratum as StratumService, Error as StratumServiceError, }; -use miner::{self, Miner, MinerService}; +use miner::{Miner, MinerService}; use parking_lot::Mutex; use rlp::encode; @@ -247,7 +247,7 @@ impl Stratum { /// Start STRATUM job dispatcher and register it in the miner pub fn register(cfg: &Options, miner: Arc, client: Weak) -> Result<(), Error> { - let stratum = miner::Stratum::start(cfg, Arc::downgrade(&miner.clone()), client)?; + let stratum = Stratum::start(cfg, Arc::downgrade(&miner.clone()), client)?; miner.add_work_listener(Box::new(stratum) as Box); Ok(()) } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 61f9ecd3b7e..d8a1eeed852 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -242,7 +242,7 @@ mod tests { &client_path, &snapshot_path, tempdir.path(), - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), ); assert!(service.is_ok()); drop(service.unwrap()); diff --git a/ethcore/src/snapshot/tests/service.rs b/ethcore/src/snapshot/tests/service.rs index 35e98f805f1..428f6e689a6 100644 --- a/ethcore/src/snapshot/tests/service.rs +++ b/ethcore/src/snapshot/tests/service.rs @@ -58,7 +58,7 @@ fn restored_is_equivalent() { Default::default(), &spec, Arc::new(client_db), - Arc::new(::miner::Miner::with_spec(&spec)), + Arc::new(::miner::Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 9f92fb3ea5d..bd1743436d2 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -46,7 +46,7 @@ fn imports_from_empty() { ClientConfig::default(), &spec, client_db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); client.import_verified_blocks(); @@ -64,7 +64,7 @@ fn should_return_registrar() { ClientConfig::default(), &spec, client_db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); let params = client.additional_params(); @@ -94,7 +94,7 @@ fn imports_good_block() { ClientConfig::default(), &spec, client_db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); let good_block = get_good_dummy_block(); @@ -119,7 +119,7 @@ fn query_none_block() { ClientConfig::default(), &spec, client_db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); let non_existant = client.block_header(BlockId::Number(188)); @@ -274,7 +274,7 @@ fn change_history_size() { ClientConfig::default(), &test_spec, client_db.clone(), - Arc::new(Miner::with_spec(&test_spec)), + Arc::new(Miner::new_for_tests(&test_spec, None)), IoChannel::disconnected() ).unwrap(); @@ -292,7 +292,7 @@ fn change_history_size() { config, &test_spec, client_db, - Arc::new(Miner::with_spec(&test_spec)), + Arc::new(Miner::new_for_tests(&test_spec, None)), IoChannel::disconnected(), ).unwrap(); assert_eq!(client.state().balance(&address).unwrap(), 100.into()); @@ -323,11 +323,11 @@ fn does_not_propagate_delayed_transactions() { client.miner().import_own_transaction(&*client, tx0).unwrap(); client.miner().import_own_transaction(&*client, tx1).unwrap(); assert_eq!(0, client.ready_transactions().len()); - assert_eq!(2, client.miner().pending_transactions().len()); + assert_eq!(2, client.miner().ready_transactions(&*client).len()); push_blocks_to_client(&client, 53, 2, 2); client.flush_queue(); assert_eq!(2, client.ready_transactions().len()); - assert_eq!(2, client.miner().pending_transactions().len()); + assert_eq!(2, client.miner().ready_transactions(&*client).len()); } #[test] diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 626aa8349c3..3d7656bd7d0 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -115,7 +115,7 @@ pub fn generate_dummy_client_with_spec_accounts_and_data(get_test_spec: F, ac ClientConfig::default(), &test_spec, client_db, - Arc::new(Miner::with_spec_and_accounts(&test_spec, accounts)), + Arc::new(Miner::new_for_tests(&test_spec, accounts)), IoChannel::disconnected(), ).unwrap(); let test_engine = &*test_spec.engine; @@ -215,7 +215,7 @@ pub fn get_test_client_with_blocks(blocks: Vec) -> Arc { ClientConfig::default(), &test_spec, client_db, - Arc::new(Miner::with_spec(&test_spec)), + Arc::new(Miner::new_for_tests(&test_spec, None)), IoChannel::disconnected(), ).unwrap(); diff --git a/ethcore/src/tests/trace.rs b/ethcore/src/tests/trace.rs index 3bf4b822862..c127787129f 100644 --- a/ethcore/src/tests/trace.rs +++ b/ethcore/src/tests/trace.rs @@ -50,7 +50,7 @@ fn can_trace_block_and_uncle_reward() { client_config, &spec, client_db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); diff --git a/ethcore/src/tx_filter.rs b/ethcore/src/tx_filter.rs index 75bd16842d2..8ac2b3f55cb 100644 --- a/ethcore/src/tx_filter.rs +++ b/ethcore/src/tx_filter.rs @@ -175,7 +175,7 @@ mod test { ClientConfig::default(), &spec, client_db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); let key1 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000001")).unwrap(); diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index d8d8a4c1100..286e29b2c36 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -30,7 +30,7 @@ pub mod verifier; pub use self::queue::TransactionQueue; pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(crate) enum Priority { Local, Retracted, @@ -38,9 +38,10 @@ pub(crate) enum Priority { } /// Verified transaction stored in the pool. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct VerifiedTransaction { transaction: transaction::PendingTransaction, + // TODO [ToDr] hash and sender should go directly from transaction hash: H256, sender: Address, priority: Priority, @@ -48,6 +49,19 @@ pub struct VerifiedTransaction { } impl VerifiedTransaction { + // Hack? + pub fn from_pending_block_transaction(tx: transaction::SignedTransaction) -> Self { + let hash = tx.hash(); + let sender = tx.sender(); + VerifiedTransaction { + transaction: tx.into(), + hash, + sender, + priority: Priority::Retracted, + insertion_id: 0, + } + } + /// Gets transaction priority. pub(crate) fn priority(&self) -> Priority { self.priority @@ -55,7 +69,7 @@ impl VerifiedTransaction { /// Gets wrapped `SignedTransaction` pub fn signed(&self) -> &transaction::SignedTransaction { - &self.transaction.transaction + &self.transaction } /// Gets wrapped `PendingTransaction` diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index a8265b6e686..93c163dcc3e 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicUsize; use ethereum_types::{H256, U256}; -use parking_lot::{RwLock, RwLockReadGuard}; +use parking_lot::RwLock; use transaction; use txpool::{self, Verifier}; @@ -90,20 +90,28 @@ impl TransactionQueue { /// /// NOTE: During pending iteration importing to the queue is not allowed. /// Make sure to drop the guard in reasonable time. - pub fn pending( + pub fn pending( &self, client: C, block_number: u64, current_timestamp: u64, // TODO [ToDr] Support nonce_cap - ) -> PendingReader<(ready::Condition, ready::State)> { + collect: F, + ) -> T where + C: client::Client, + F: FnOnce(txpool::PendingIterator< + pool::VerifiedTransaction, + (ready::Condition, ready::State), + scoring::GasPrice, + txpool::NoopListener + >) -> T, + { let pending_readiness = ready::Condition::new(block_number, current_timestamp); let state_readiness = ready::State::new(client); - PendingReader { - guard: self.pool.read(), - ready: Some((pending_readiness, state_readiness)), - } + let ready = (pending_readiness, state_readiness); + + collect(self.pool.read().pending(ready)) } /// Culls all stalled transactions from the pool. @@ -175,21 +183,6 @@ impl TransactionQueue { } } -/// A pending transactions guard. -pub struct PendingReader<'a, R> { - guard: RwLockReadGuard<'a, Pool>, - ready: Option, -} - -impl<'a, R: txpool::Ready> PendingReader<'a, R> { - /// Returns an iterator over currently pending transactions. - /// - /// NOTE: This method will panic if used twice! - pub fn transactions(&mut self) -> txpool::PendingIterator { - self.guard.pending(self.ready.take().unwrap()) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs index e70ee30da4c..614a0326007 100644 --- a/miner/src/pool/ready.rs +++ b/miner/src/pool/ready.rs @@ -43,7 +43,7 @@ use std::collections::HashMap; use ethereum_types::{U256, H160 as Address}; use transaction; -use txpool::{self, VerifiedTransaction as IVerifiedTransaction}; +use txpool::{self, VerifiedTransaction as PoolVerifiedTransaction}; use super::client::Client; use super::VerifiedTransaction; diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 4f47ec92834..b720f5f3f3d 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -78,7 +78,7 @@ impl Transaction { match *self { Transaction::Unverified(ref tx) => tx.hash(), Transaction::Retracted(ref tx) => tx.hash(), - Transaction::Local(ref tx) => tx.transaction.hash(), + Transaction::Local(ref tx) => tx.hash(), } } } @@ -117,51 +117,51 @@ impl txpool::Verifier for Verifier { } let is_retracted = if let Transaction::Retracted(_) = tx { true } else { false }; - let is_own = if let Transaction::Local(_) = tx { true } else { false }; - let (tx, condition) = match tx { + let is_own = if let Transaction::Local(..) = tx { true } else { false }; + let transaction = match tx { Transaction::Retracted(tx) | Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { - Ok(signed) => (signed, None), + Ok(signed) => signed.into(), Err(err) => { debug!(target: "txqueue", "Rejected tx {:?}: {:?}", hash, err); bail!(err) }, }, - Transaction::Local(tx) => (tx.transaction, tx.condition), + Transaction::Local(tx) => tx, }; let gas_limit = cmp::min(self.options.tx_gas_limit, self.options.block_gas_limit); - if tx.gas > gas_limit { + if transaction.gas > gas_limit { debug!( target: "txqueue", "Dropping transaction above gas limit: {:?} ({} > min({}, {}))", hash, - tx.gas, + transaction.gas, self.options.block_gas_limit, self.options.tx_gas_limit, ); - bail!(transaction::Error::GasLimitExceeded { limit: gas_limit, got: tx.gas }); + bail!(transaction::Error::GasLimitExceeded { limit: gas_limit, got: transaction.gas }); } - let minimal_gas = self.client.required_gas(&tx); - if tx.gas < minimal_gas { + let minimal_gas = self.client.required_gas(&transaction); + if transaction.gas < minimal_gas { trace!(target: "txqueue", "Dropping transaction with insufficient gas: {:?} ({} > {})", - tx.hash(), - tx.gas, + transaction.hash(), + transaction.gas, minimal_gas, ); bail!(transaction::Error::InsufficientGas { minimal: minimal_gas, - got: tx.gas, + got: transaction.gas, }) } - let transaction_type = self.client.transaction_type(&tx); - let sender = tx.sender(); + let transaction_type = self.client.transaction_type(&transaction); + let sender = transaction.sender(); let account_details = self.client.account_details(&sender); - if tx.gas_price < self.options.minimal_gas_price { + if transaction.gas_price < self.options.minimal_gas_price { if let TransactionType::Service = transaction_type { trace!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); } else if is_own || account_details.is_local { @@ -171,17 +171,17 @@ impl txpool::Verifier for Verifier { target: "txqueue", "Rejected tx {:?}: below minimal gas price threshold (gp: {} < {})", hash, - tx.gas_price, + transaction.gas_price, self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, - got: tx.gas_price, + got: transaction.gas_price, }); } } - let cost = tx.value + tx.gas_price * tx.gas; + let cost = transaction.value + transaction.gas_price * transaction.gas; if account_details.balance < cost { debug!( target: "txqueue", @@ -196,12 +196,12 @@ impl txpool::Verifier for Verifier { }); } - if tx.nonce < account_details.nonce { + if transaction.nonce < account_details.nonce { debug!( target: "txqueue", "Rejected tx {:?}: old nonce ({} < {})", hash, - tx.nonce, + transaction.nonce, account_details.nonce, ); bail!(transaction::Error::AlreadyImported); @@ -213,10 +213,7 @@ impl txpool::Verifier for Verifier { (false, true) => super::Priority::Retracted, }; Ok(VerifiedTransaction { - transaction: transaction::PendingTransaction { - transaction: tx, - condition, - }, + transaction, priority, hash, sender, diff --git a/miner/src/queue.rs~ b/miner/src/queue.rs~ deleted file mode 100644 index 177924fe397..00000000000 --- a/miner/src/queue.rs~ +++ /dev/null @@ -1,86 +0,0 @@ - -use std::sync::Arc; -use std::sync::atomic::AtomicUsize; - -use parking_lot::RwLock; -use transaction; -use txpool::Pool; - -use pool::{scoring, verifier, client, ready}; - -type Pool = txpool::Pool; - -#[derive(Debug)] -pub struct TransactionQueue { - insertion_id: Arc, - pool: RwLock, - options: RwLock, -} - -impl TransactionQueue { - pub fn new(limits: txpool::Options, verification_options: verifier::Options) -> Self { - TransactionQueue { - insertion_id: Default::default(), - pool: RwLock::new(txpool::Pool::with_scoring(scoring::GasPrice, limits)), - options: RwLock::new(verification_options), - } - } - - pub fn import( - &self, - client: C, - transactions: Vec, - ) -> Vec { - // Run verification - let options = self.options.read().clone(); - - // TODO [ToDr] parallelize - let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); - transactions - .into_iter() - .map(|transaction| verifier.verify_transaction(transaction)) - .map(|result| match result { - Ok(verified) => match self.pool.write().import(verified) { - Ok(imported) => (), - Err(txpool::Error(kind, _)) => unimplemented!(), - }, - Err(err) => Err(err), - }) - .collect() - } - - pub fn pending( - &self, - client: C, - block_number: u64, - current_timestamp: u64, - ) -> PendingReader<(ready::Condition, ready::State)> { - let pending_readiness = ready::Condition::new(block_number, current_timestamp); - let state_readiness = ready::State::new(client); - - PendingReader { - guard: self.pool.read(), - ready: (pending_readiness, state_readiness), - } - } - - pub fn cull( - &self, - client: C, - ) { - let state_readiness = ready::State::new(client); - let removed = self.pool.write().cull(None, state_readiness); - debug!(target: "txqueue", "Removed {} stalled transactions.", removed); - } -} - -pub struct PendingReader<'a, R> { - guard: RwLockReadReader<'a, Pool>, - ready: Option, -} - -impl<'a, R: txpool::Ready> PendingReader<'a, R> { - pub fn transactions<'b: 'a>(&'a mut self) -> txpool::PendingIterator<'b, pool::VerifiedTransaction, R, scoring::GasPrice, txpool::NoopListener> { - self.guard.pending(self.ready.take().unwrap()) - } -} diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 6f6507b0151..05014720bcc 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -368,7 +368,9 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { &client_path, &snapshot_path, &cmd.dirs.ipc_path(), - Arc::new(Miner::with_spec(&spec)), + // TODO [ToDr] don't use test miner here + // (actually don't require miner at all) + Arc::new(Miner::new_for_tests(&spec, None)), ).map_err(|e| format!("Client service error: {:?}", e))?; // free up the spec in memory. diff --git a/parity/run.rs b/parity/run.rs index 585b7244397..d412ca6f76e 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -521,7 +521,7 @@ pub fn execute_impl(cmd: RunCmd, can_restart: bool, logger: Arc) // create miner let initial_min_gas_price = cmd.gas_pricer_conf.initial_min(); - let miner = Miner::new(cmd.miner_options, cmd.gas_pricer_conf.to_gas_pricer(fetch.clone()), &spec, Some(account_provider.clone())); + let miner = Arc::new(Miner::new(cmd.miner_options, cmd.gas_pricer_conf.to_gas_pricer(fetch.clone()), &spec, Some(account_provider.clone()))); miner.set_author(cmd.miner_extras.author); miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target); miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); diff --git a/parity/snapshot.rs b/parity/snapshot.rs index 34aa3252fb7..8afe1c5406f 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -187,7 +187,9 @@ impl SnapshotCommand { &client_path, &snapshot_path, &self.dirs.ipc_path(), - Arc::new(Miner::with_spec(&spec)) + // TODO [ToDr] don't use test miner here + // (actually don't require miner at all) + Arc::new(Miner::new_for_tests(&spec, None)), ).map_err(|e| format!("Client service error: {:?}", e))?; Ok(service) diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index c8444d30400..6704d4e6e96 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -58,31 +58,7 @@ fn sync_provider() -> Arc { } fn miner_service(spec: &Spec, accounts: Arc) -> Arc { - Miner::new( - MinerOptions { - new_work_notify: vec![], - force_sealing: true, - reseal_on_external_tx: true, - reseal_on_own_tx: true, - reseal_on_uncle: false, - tx_queue_size: 1024, - tx_gas_limit: !U256::zero(), - tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, - tx_queue_gas_limit: GasLimit::None, - tx_queue_banning: Banning::Disabled, - tx_queue_memory_limit: None, - pending_set: PendingSet::SealingOrElseQueue, - reseal_min_period: Duration::from_secs(0), - reseal_max_period: Duration::from_secs(120), - work_queue_size: 50, - enable_resubmission: true, - refuse_service_transactions: false, - infinite_pending_block: false, - }, - GasPricer::new_fixed(20_000_000_000u64.into()), - &spec, - Some(accounts), - ) + Arc::new(Miner::new_for_tests(spec, Some(accounts))) } fn snapshot_service() -> Arc { diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 994cd544a0a..1b58136e26a 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -200,11 +200,6 @@ impl MinerService for TestMinerService { vec![] } - /// Removes all transactions from the queue and restart mining operation. - fn clear_and_reset(&self, _chain: &MiningBlockChainClient) { - unimplemented!(); - } - /// Called when blocks are imported to chain, updates transactions queue. fn chain_new_blocks(&self, _chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) { unimplemented!(); diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 32a90b414fe..31417e1aa38 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -292,7 +292,7 @@ impl TestNet> { ClientConfig::default(), &spec, Arc::new(::kvdb_memorydb::create(::ethcore::db::NUM_COLUMNS.unwrap_or(0))), - Arc::new(Miner::with_spec_and_accounts(&spec, accounts)), + Arc::new(Miner::new_for_tests(&spec, accounts)), IoChannel::disconnected(), ).unwrap(); From 47e89286331c0c54eb27c4703f468b48d2f1d3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 20 Feb 2018 16:20:49 +0100 Subject: [PATCH 11/77] Fix ethcore tests. --- ethcore/src/miner/miner.rs | 59 ++++++++++++++++++++++++------------- ethcore/src/tests/client.rs | 2 +- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 4b788801e92..09e4e76a134 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -466,7 +466,7 @@ impl Miner { /// Check is reseal is allowed and necessary. fn requires_reseal(&self, best_block: BlockNumber) -> bool { let mut sealing = self.sealing.lock(); - if sealing.enabled { + if !sealing.enabled { trace!(target: "miner", "requires_reseal: sealing is disabled"); return false } @@ -478,7 +478,8 @@ impl Miner { let sealing_enabled = self.forced_sealing() || has_local_transactions || self.engine.seals_internally().is_some() - || (best_block > last_request && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS); + // Disable sealing if there were no requests for SEALING_TIMEOUT_IN_BLOCKS + || (best_block > last_request && best_block - last_request <= SEALING_TIMEOUT_IN_BLOCKS); let should_disable_sealing = !sealing_enabled; @@ -498,12 +499,14 @@ impl Miner { /// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal. fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { - let mut sealing = self.sealing.lock(); - if block.transactions().is_empty() - && !self.forced_sealing() - && Instant::now() <= sealing.next_mandatory_reseal { - return false + let sealing = self.sealing.lock(); + if block.transactions().is_empty() + && !self.forced_sealing() + && Instant::now() <= sealing.next_mandatory_reseal + { + return false + } } trace!(target: "miner", "seal_block_internally: attempting internal seal."); @@ -517,9 +520,12 @@ impl Miner { // Save proposal for later seal submission and broadcast it. Seal::Proposal(seal) => { trace!(target: "miner", "Received a Proposal seal."); - sealing.next_mandatory_reseal = Instant::now() + self.options.reseal_max_period; - sealing.queue.push(block.clone()); - sealing.queue.use_last_ref(); + { + let mut sealing = self.sealing.lock(); + sealing.next_mandatory_reseal = Instant::now() + self.options.reseal_max_period; + sealing.queue.push(block.clone()); + sealing.queue.use_last_ref(); + } block .lock() @@ -535,11 +541,17 @@ impl Miner { }, // Directly import a regular sealed block. Seal::Regular(seal) => { - sealing.next_mandatory_reseal = Instant::now() + self.options.reseal_max_period; + { + let mut sealing = self.sealing.lock(); + sealing.next_mandatory_reseal = Instant::now() + self.options.reseal_max_period; + } + block .lock() .seal(&*self.engine, seal) - .map(|sealed| chain.import_sealed_block(sealed).is_ok()) + .map(|sealed| { + chain.import_sealed_block(sealed).is_ok() + }) .unwrap_or_else(|e| { warn!("ERROR: seal failed when given internally generated seal: {}", e); false @@ -923,6 +935,8 @@ impl MinerService for Miner { fn map_pending_block(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { self.prepare_work_sealing(chain); + // mark block as used + self.sealing.lock().queue.use_last_ref(); self.map_existing_pending_block(f, chain.chain_info().best_block_number) } @@ -945,6 +959,7 @@ impl MinerService for Miner { warn!(target: "miner", "Submitted solution rejected: Block unknown or out of date."); Err(Error::PowHashInvalid) }; + result.and_then(|sealed| { let n = sealed.header().number(); let h = sealed.header().hash(); @@ -1025,8 +1040,8 @@ mod tests { let miner = Miner::new_for_tests(&Spec::new_test(), None); let res = miner.map_pending_block(&client, |b| b.block().fields().header.hash()); - assert!(res.is_some()); - assert!(miner.submit_seal(&client, res.unwrap(), vec![]).is_ok()); + let hash = res.unwrap(); + assert_eq!(miner.submit_seal(&client, hash, vec![]).unwrap(), ()); // two more blocks mined, work requested. client.add_blocks(1, EachBlockWith::Uncle); @@ -1113,9 +1128,9 @@ mod tests { // then assert_eq!(res.unwrap(), ()); - assert_eq!(miner.pending_transactions(best_block).unwrap().len(), 1); - assert_eq!(miner.pending_receipts(best_block).unwrap().len(), 0); - assert_eq!(miner.ready_transactions(&client).len(), 0); + assert_eq!(miner.pending_transactions(best_block), None); + assert_eq!(miner.pending_receipts(best_block), None); + assert_eq!(miner.ready_transactions(&client).len(), 1); } #[test] @@ -1130,11 +1145,15 @@ mod tests { // then assert_eq!(res.unwrap(), ()); - assert_eq!(miner.pending_transactions(best_block).unwrap().len(), 0); - assert_eq!(miner.pending_receipts(best_block).unwrap().len(), 0); - assert_eq!(miner.ready_transactions(&client).len(), 1); + // By default we don't reseal on external transactions + assert_eq!(miner.pending_transactions(best_block), None); + assert_eq!(miner.pending_receipts(best_block), None); + // By default we use PendingSet::AlwaysSealing, so no transactions yet. + assert_eq!(miner.ready_transactions(&client).len(), 0); // This method will let us know if pending block was created (before calling that method) assert!(miner.prepare_work_sealing(&client)); + // After pending block is created we should see a transaction. + assert_eq!(miner.ready_transactions(&client).len(), 1); } #[test] diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index bd1743436d2..5400605f3ba 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -323,7 +323,7 @@ fn does_not_propagate_delayed_transactions() { client.miner().import_own_transaction(&*client, tx0).unwrap(); client.miner().import_own_transaction(&*client, tx1).unwrap(); assert_eq!(0, client.ready_transactions().len()); - assert_eq!(2, client.miner().ready_transactions(&*client).len()); + assert_eq!(0, client.miner().ready_transactions(&*client).len()); push_blocks_to_client(&client, 53, 2, 2); client.flush_queue(); assert_eq!(2, client.ready_transactions().len()); From 4b25b861739e96a2d114872e27e5b4df5387206c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 20 Feb 2018 17:21:52 +0100 Subject: [PATCH 12/77] Refactor miner interface for sealing/work packages. --- ethcore/src/client/client.rs | 5 +- ethcore/src/client/test_client.rs | 5 +- ethcore/src/miner/miner.rs | 148 ++++++++++++---------- ethcore/src/miner/mod.rs | 46 +++---- ethcore/src/miner/stratum.rs | 19 ++- rpc/src/v1/impls/eth.rs | 72 ++++++----- rpc/src/v1/tests/helpers/miner_service.rs | 5 - 7 files changed, 152 insertions(+), 148 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 2d90d8bd7cd..40df3cd3d4c 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1990,8 +1990,9 @@ impl super::traits::EngineClient for Client { } fn submit_seal(&self, block_hash: H256, seal: Vec) { - if self.miner.submit_seal(self, block_hash, seal).is_err() { - warn!(target: "poa", "Wrong internal seal submission!") + let import = self.miner.submit_seal(block_hash, seal).and_then(|block| self.import_sealed_block(block)); + if let Err(err) = import { + warn!(target: "poa", "Wrong internal seal submission! {:?}", err); } } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index d950ca7ae48..68f01afcbe1 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -818,8 +818,9 @@ impl super::traits::EngineClient for TestBlockChainClient { } fn submit_seal(&self, block_hash: H256, seal: Vec) { - if self.miner.submit_seal(self, block_hash, seal).is_err() { - warn!(target: "poa", "Wrong internal seal submission!") + let import = self.miner.submit_seal(block_hash, seal).and_then(|block| self.import_sealed_block(block)); + if let Err(err) = import { + warn!(target: "poa", "Wrong internal seal submission! {:?}", err); } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 09e4e76a134..7e6f45fa9f7 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -38,7 +38,7 @@ use transaction::{ use using_queue::{UsingQueue, GetAction}; use account_provider::{AccountProvider, SignError as AccountError}; -use block::{ClosedBlock, IsBlock, Block}; +use block::{ClosedBlock, IsBlock, Block, SealedBlock}; use client::{MiningBlockChainClient, BlockId}; use executive::contract_address; use header::{Header, BlockNumber}; @@ -163,7 +163,8 @@ struct SealingWork { enabled: bool, next_allowed_reseal: Instant, next_mandatory_reseal: Instant, - sealing_block_last_request: u64, + // block number when sealing work was last requested + last_request: u64, } impl SealingWork { @@ -213,7 +214,7 @@ impl Miner { || spec.engine.seals_internally().is_some(), next_allowed_reseal: Instant::now(), next_mandatory_reseal: Instant::now() + options.reseal_max_period, - sealing_block_last_request: 0, + last_request: 0, }), params: RwLock::new(AuthoringParams::default()), listeners: RwLock::new(vec![]), @@ -471,22 +472,24 @@ impl Miner { return false } - let has_local_transactions = self.transaction_queue.has_local_transactions(); trace!(target: "miner", "requires_reseal: sealing enabled"); - let last_request = sealing.sealing_block_last_request; + // Disable sealing if there were no requests for SEALING_TIMEOUT_IN_BLOCKS + let had_requests = best_block > sealing.last_request + && best_block - sealing.last_request <= SEALING_TIMEOUT_IN_BLOCKS; + + // keep sealing enabled if any of the conditions is met let sealing_enabled = self.forced_sealing() - || has_local_transactions + || self.transaction_queue.has_local_transactions() || self.engine.seals_internally().is_some() - // Disable sealing if there were no requests for SEALING_TIMEOUT_IN_BLOCKS - || (best_block > last_request && best_block - last_request <= SEALING_TIMEOUT_IN_BLOCKS); + || had_requests; let should_disable_sealing = !sealing_enabled; - trace!(target: "miner", "requires_reseal: should_disable_sealing={}; best_block={}, last_request={}", should_disable_sealing, best_block, last_request); + trace!(target: "miner", "requires_reseal: should_disable_sealing={}; best_block={}, last_request={}", should_disable_sealing, best_block, sealing.last_request); if should_disable_sealing { - trace!(target: "miner", "Miner sleeping (current {}, last {})", best_block, last_request); + trace!(target: "miner", "Miner sleeping (current {}, last {})", best_block, sealing.last_request); sealing.enabled = false; sealing.queue.reset(); false @@ -623,12 +626,12 @@ impl Miner { } /// Returns true if we had to prepare new pending block. - fn prepare_work_sealing(&self, client: &MiningBlockChainClient) -> bool { - trace!(target: "miner", "prepare_work_sealing: entering"); + fn prepare_pending_block(&self, client: &MiningBlockChainClient) -> bool { + trace!(target: "miner", "prepare_pending_block: entering"); let prepare_new = { let mut sealing = self.sealing.lock(); let have_work = sealing.queue.peek_last_ref().is_some(); - trace!(target: "miner", "prepare_work_sealing: have_work={}", have_work); + trace!(target: "miner", "prepare_pending_block: have_work={}", have_work); if !have_work { sealing.enabled = true; true @@ -648,13 +651,13 @@ impl Miner { let best_number = client.chain_info().best_block_number; let mut sealing = self.sealing.lock(); - if sealing.sealing_block_last_request != best_number { + if sealing.last_request != best_number { trace!( target: "miner", - "prepare_work_sealing: Miner received request (was {}, now {}) - waking up.", - sealing.sealing_block_last_request, best_number + "prepare_pending_block: Miner received request (was {}, now {}) - waking up.", + sealing.last_request, best_number ); - sealing.sealing_block_last_request = best_number; + sealing.last_request = best_number; } // Return if we restarted @@ -757,7 +760,7 @@ impl MinerService for Miner { if imported.is_ok() && self.options.reseal_on_own_tx && self.sealing.lock().reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing. - if self.engine.seals_internally().unwrap_or(false) || !self.prepare_work_sealing(chain) { + if self.engine.seals_internally().unwrap_or(false) || !self.prepare_pending_block(chain) { // If new block has not been prepared (means we already had one) // or Engine might be able to seal internally, // we need to update sealing. @@ -885,47 +888,45 @@ impl MinerService for Miner { ) } - fn can_produce_work_package(&self) -> bool { - self.engine.seals_internally().is_none() - } - - // TODO [ToDr] Pass sealing lock guard /// Update sealing if required. /// Prepare the block and work if the Engine does not seal internally. fn update_sealing(&self, chain: &MiningBlockChainClient) { trace!(target: "miner", "update_sealing"); - const NO_NEW_CHAIN_WITH_FORKS: &str = "Your chain specification contains one or more hard forks which are required to be \ - on by default. Please remove these forks and start your chain again."; - if self.requires_reseal(chain.chain_info().best_block_number) { - // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing locks. | - // | Make sure to release the locks before calling that method. | - // -------------------------------------------------------------------------- - trace!(target: "miner", "update_sealing: preparing a block"); - let (block, original_work_hash) = self.prepare_block(chain); - - // refuse to seal the first block of the chain if it contains hard forks - // which should be on by default. - if block.block().fields().header.number() == 1 && self.engine.params().contains_bugfix_hard_fork() { - warn!("{}", NO_NEW_CHAIN_WITH_FORKS); - return; - } + // Do nothing if reseal is not required, + // but note that `requires_reseal` updates internal state. + if !self.requires_reseal(chain.chain_info().best_block_number) { + return; + } - match self.engine.seals_internally() { - Some(true) => { - trace!(target: "miner", "update_sealing: engine indicates internal sealing"); - if self.seal_and_import_block_internally(chain, block) { - trace!(target: "miner", "update_sealing: imported internally sealed block"); - } - }, - Some(false) => trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now"), - None => { - trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); - self.prepare_work(block, original_work_hash) - }, - } + // -------------------------------------------------------------------------- + // | NOTE Code below requires transaction_queue and sealing locks. | + // | Make sure to release the locks before calling that method. | + // -------------------------------------------------------------------------- + trace!(target: "miner", "update_sealing: preparing a block"); + let (block, original_work_hash) = self.prepare_block(chain); + + // refuse to seal the first block of the chain if it contains hard forks + // which should be on by default. + if block.block().fields().header.number() == 1 && self.engine.params().contains_bugfix_hard_fork() { + warn!("Your chain specification contains one or more hard forks which are required to be \ + on by default. Please remove these forks and start your chain again."); + return; + } + + match self.engine.seals_internally() { + Some(true) => { + trace!(target: "miner", "update_sealing: engine indicates internal sealing"); + if self.seal_and_import_block_internally(chain, block) { + trace!(target: "miner", "update_sealing: imported internally sealed block"); + } + }, + Some(false) => trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now"), + None => { + trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); + self.prepare_work(block, original_work_hash) + }, } } @@ -933,14 +934,21 @@ impl MinerService for Miner { self.sealing.lock().queue.is_in_use() } - fn map_pending_block(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { - self.prepare_work_sealing(chain); - // mark block as used - self.sealing.lock().queue.use_last_ref(); - self.map_existing_pending_block(f, chain.chain_info().best_block_number) + fn work_package(&self, chain: &MiningBlockChainClient) -> Option<(H256, BlockNumber, u64, U256)> { + if self.engine.seals_internally().is_some() { + return None; + } + + self.prepare_pending_block(chain); + + self.sealing.lock().queue.use_last_ref().map(|b| { + let header = b.header(); + (header.hash(), header.number(), header.timestamp(), *header.difficulty()) + }) } - fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { + // Note used for external submission (PoW) and internally by sealing engines. + fn submit_seal(&self, block_hash: H256, seal: Vec) -> Result { let result = if let Some(b) = self.sealing.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -963,9 +971,8 @@ impl MinerService for Miner { result.and_then(|sealed| { let n = sealed.header().number(); let h = sealed.header().hash(); - chain.import_sealed_block(sealed)?; info!(target: "miner", "Submitted block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(format!("{:x}", h))); - Ok(()) + Ok(sealed) }) } @@ -1029,7 +1036,7 @@ mod tests { let miner = Miner::new_for_tests(&Spec::new_test(), None); // when - let sealing_work = miner.map_pending_block(&client, |_| ()); + let sealing_work = miner.work_package(&client); assert!(sealing_work.is_some(), "Expected closed block"); } @@ -1039,19 +1046,20 @@ mod tests { let client = TestBlockChainClient::default(); let miner = Miner::new_for_tests(&Spec::new_test(), None); - let res = miner.map_pending_block(&client, |b| b.block().fields().header.hash()); - let hash = res.unwrap(); - assert_eq!(miner.submit_seal(&client, hash, vec![]).unwrap(), ()); + let res = miner.work_package(&client); + let hash = res.unwrap().0; + let block = miner.submit_seal(hash, vec![]).unwrap(); + client.import_sealed_block(block).unwrap(); // two more blocks mined, work requested. client.add_blocks(1, EachBlockWith::Uncle); - miner.map_pending_block(&client, |b| b.block().fields().header.hash()); + miner.work_package(&client); client.add_blocks(1, EachBlockWith::Uncle); - miner.map_pending_block(&client, |b| b.block().fields().header.hash()); + miner.work_package(&client); // solution to original work submitted. - assert!(miner.submit_seal(&client, res.unwrap(), vec![]).is_ok()); + assert!(miner.submit_seal(hash, vec![]).is_ok()); } fn miner() -> Miner { @@ -1113,7 +1121,7 @@ mod tests { assert_eq!(miner.pending_receipts(best_block).unwrap().len(), 1); assert_eq!(miner.ready_transactions(&client).len(), 1); // This method will let us know if pending block was created (before calling that method) - assert!(!miner.prepare_work_sealing(&client)); + assert!(!miner.prepare_pending_block(&client)); } #[test] @@ -1151,7 +1159,7 @@ mod tests { // By default we use PendingSet::AlwaysSealing, so no transactions yet. assert_eq!(miner.ready_transactions(&client).len(), 0); // This method will let us know if pending block was created (before calling that method) - assert!(miner.prepare_work_sealing(&client)); + assert!(miner.prepare_pending_block(&client)); // After pending block is created we should see a transaction. assert_eq!(miner.ready_transactions(&client).len(), 1); } @@ -1164,7 +1172,7 @@ mod tests { assert!(!miner.requires_reseal(1u8.into())); miner.import_external_transactions(&client, vec![transaction().into()]).pop().unwrap().unwrap(); - assert!(miner.prepare_work_sealing(&client)); + assert!(miner.prepare_pending_block(&client)); // Unless asked to prepare work. assert!(miner.requires_reseal(1u8.into())); } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 0c33c98b20a..24b10d52143 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -33,7 +33,7 @@ use bytes::Bytes; use ethereum_types::{H256, U256, Address}; use ethcore_miner::pool::VerifiedTransaction; -use block::ClosedBlock; +use block::SealedBlock; use client::{MiningBlockChainClient}; use error::{Error}; use header::BlockNumber; @@ -43,11 +43,28 @@ use transaction::{self, UnverifiedTransaction, PendingTransaction, SignedTransac /// Miner client API pub trait MinerService : Send + Sync { - // Pending block - /// Get the sealing work package and if `Some`, apply some transform. - fn map_pending_block(&self, chain: &MiningBlockChainClient, f: F) -> Option - where F: FnOnce(&ClosedBlock) -> T, Self: Sized; + /// Get the sealing work package preparing it if doesn't exist yet. + /// + /// Returns `None` if engine seals internally. + fn work_package(&self, chain: &MiningBlockChainClient) -> Option<(H256, BlockNumber, u64, U256)>; + + /// Submit `seal` as a valid solution for the header of `pow_hash`. + /// Will check the seal, but not actually insert the block into the chain. + fn submit_seal(&self, pow_hash: H256, seal: Vec) -> Result; + + /// Is it currently sealing? + fn is_currently_sealing(&self) -> bool; + + /// Update current pending block + fn update_sealing(&self, chain: &MiningBlockChainClient); + + // Notifications + + /// Called when blocks are imported to chain, updates transactions queue. + fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); + + // Pending block /// Get a list of all pending receipts from pending block. fn pending_receipts(&self, best_block: BlockNumber) -> Option>; @@ -58,7 +75,7 @@ pub trait MinerService : Send + Sync { /// Get all transactions in pending block or `None` if not sealing. fn pending_transactions(&self, best_block: BlockNumber) -> Option>; - // Block authoring / sealing + // Block authoring /// Get current authoring parameters. fn authoring_params(&self) -> AuthoringParams; @@ -74,23 +91,6 @@ pub trait MinerService : Send + Sync { /// On PoW password is optional. fn set_author(&self, address: Address, password: Option) -> Result<(), ::account_provider::SignError>; - - /// Is it currently sealing? - fn is_currently_sealing(&self) -> bool; - - /// PoW chain - can produce work package - fn can_produce_work_package(&self) -> bool; - - /// Submit `seal` as a valid solution for the header of `pow_hash`. - /// Will check the seal, but not actually insert the block into the chain. - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error>; - - /// New chain head event. Restart mining operation. - fn update_sealing(&self, chain: &MiningBlockChainClient); - - /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, chain: &MiningBlockChainClient, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]); - // Transactions and Pool /// Imports transactions to transaction queue. diff --git a/ethcore/src/miner/stratum.rs b/ethcore/src/miner/stratum.rs index d6cd933a96e..4cb7e63ec99 100644 --- a/ethcore/src/miner/stratum.rs +++ b/ethcore/src/miner/stratum.rs @@ -20,8 +20,7 @@ use std::sync::{Arc, Weak}; use std::net::{SocketAddr, AddrParseError}; use std::fmt; -use block::IsBlock; -use client::Client; +use client::{Client, MiningBlockChainClient}; use ethereum_types::{H64, H256, clean_0x, U256}; use ethereum::ethash::Ethash; use ethash::SeedHashCompute; @@ -120,14 +119,9 @@ impl JobDispatcher for StratumJobDispatcher { } fn job(&self) -> Option { - self.with_core(|client, miner| miner.map_pending_block(&*client, |b| { - let pow_hash = b.hash(); - let number = b.block().header().number(); - let difficulty = b.block().header().difficulty(); - - self.payload(pow_hash, *difficulty, number) - }) - ) + self.with_core(|client, miner| miner.work_package(&*client).map(|(pow_hash, number, _timestamp, difficulty)| { + self.payload(pow_hash, difficulty, number) + })) } fn submit(&self, payload: Vec) -> Result<(), StratumServiceError> { @@ -145,7 +139,10 @@ impl JobDispatcher for StratumJobDispatcher { self.with_core_result(|client, miner| { let seal = vec![encode(&payload.mix_hash).into_vec(), encode(&payload.nonce).into_vec()]; - match miner.submit_seal(&*client, payload.pow_hash, seal) { + + let import = miner.submit_seal(payload.pow_hash, seal) + .and_then(|block| client.import_sealed_block(block)); + match import { Ok(_) => Ok(()), Err(e) => { warn!(target: "stratum", "submit_seal error: {:?}", e); diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 08ce7fbbcd0..6468917a54a 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -534,11 +534,6 @@ impl Eth for EthClient where } fn work(&self, no_new_work_timeout: Trailing) -> Result { - if !self.miner.can_produce_work_package() { - warn!(target: "miner", "Cannot give work package - engine seals internally."); - return Err(errors::no_work_required()) - } - let no_new_work_timeout = no_new_work_timeout.unwrap_or_default(); // check if we're still syncing and return empty strings in that case @@ -561,45 +556,52 @@ impl Eth for EthClient where warn!(target: "miner", "Cannot give work package - no author is configured. Use --author to configure!"); return Err(errors::no_author()) } - self.miner.map_sealing_work(&*self.client, |b| { - let pow_hash = b.hash(); - let target = Ethash::difficulty_to_boundary(b.block().header().difficulty()); - let seed_hash = self.seed_compute.lock().hash_block_number(b.block().header().number()); - - if no_new_work_timeout > 0 && b.block().header().timestamp() + no_new_work_timeout < get_time().sec as u64 { - Err(errors::no_new_work()) - } else if self.options.send_block_number_in_get_work { - let block_number = b.block().header().number(); - Ok(Work { - pow_hash: pow_hash.into(), - seed_hash: seed_hash.into(), - target: target.into(), - number: Some(block_number), - }) - } else { - Ok(Work { - pow_hash: pow_hash.into(), - seed_hash: seed_hash.into(), - target: target.into(), - number: None - }) - } - }).unwrap_or(Err(errors::internal("No work found.", ""))) - } - fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result { - if !self.miner.can_produce_work_package() { - warn!(target: "miner", "Cannot submit work - engine seals internally."); - return Err(errors::no_work_required()) + let work = self.miner.work_package(&*client).ok_or_else(|| { + warn!(target: "miner", "Cannot give work package - engine seals internally."); + Err(errors::no_work_required()) + })?; + let (pow_hash, number, timestamp, difficulty) = work; + let target = Ethash::difficulty_to_boundary(difficulty); + let seed_hash = self.seed_compute.lock().hash_block_number(number); + + if no_new_work_timeout > 0 && timestamp + no_new_work_timeout < get_time().sec as u64 { + Err(errors::no_new_work()) + } else if self.options.send_block_number_in_get_work { + Ok(Work { + pow_hash: pow_hash.into(), + seed_hash: seed_hash.into(), + target: target.into(), + number: Some(number), + }) + } else { + Ok(Work { + pow_hash: pow_hash.into(), + seed_hash: seed_hash.into(), + target: target.into(), + number: None + }) } + } + fn submit_work(&self, nonce: RpcH64, pow_hash: RpcH256, mix_hash: RpcH256) -> Result { + // TODO [ToDr] Should disallow submissions in case of PoA? let nonce: H64 = nonce.into(); let pow_hash: H256 = pow_hash.into(); let mix_hash: H256 = mix_hash.into(); trace!(target: "miner", "submit_work: Decoded: nonce={}, pow_hash={}, mix_hash={}", nonce, pow_hash, mix_hash); let seal = vec![rlp::encode(&mix_hash).into_vec(), rlp::encode(&nonce).into_vec()]; - Ok(self.miner.submit_seal(&*self.client, pow_hash, seal).is_ok()) + let import = self.miner.submit_seal(pow_hash, seal) + .and_then(|block| self.client.import_sealed_block(block)); + + match import { + Ok(_) => Ok(true), + Err(err) => { + warn!(target: "miner", "Cannot submit work - {:?}.", err); + Ok(false) + }, + } } fn submit_hashrate(&self, rate: RpcU256, id: RpcH256) -> Result { diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 1b58136e26a..92a5addb168 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -205,11 +205,6 @@ impl MinerService for TestMinerService { unimplemented!(); } - /// PoW chain - can produce work package - fn can_produce_work_package(&self) -> bool { - true - } - /// New chain head event. Restart mining operation. fn update_sealing(&self, _chain: &MiningBlockChainClient) { unimplemented!(); From 81a96e492fef1eac3f6f9ecccde082bfcfd0370a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 20 Feb 2018 17:57:43 +0100 Subject: [PATCH 13/77] Implement next nonce. --- ethcore/src/miner/miner.rs | 11 ++++++----- ethcore/src/miner/mod.rs | 11 ++++++++--- miner/src/pool/queue.rs | 18 ++++++++++++++++-- transaction-pool/src/pool.rs | 19 +++++++++++++++++++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 7e6f45fa9f7..daf1aca5d6f 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -811,6 +811,12 @@ impl MinerService for Miner { } } + fn next_nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { + let client = self.client(chain); + self.transaction_queue.next_nonce(client, address) + .unwrap_or_else(|| chain.nonce(address, BlockId::Latest).unwrap_or_default()) + } + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { match self.options.pending_set { PendingSet::AlwaysQueue => self.transaction_queue.find(hash).map(|t| t.pending().clone()), @@ -824,11 +830,6 @@ impl MinerService for Miner { } } - fn last_nonce(&self, address: &Address) -> Option { - // TODO [ToDr] missing! - unimplemented!() - } - fn pending_transactions(&self, best_block: BlockNumber) -> Option> { self.from_pending_block( best_block, diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 24b10d52143..bb5f764b0d0 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -110,6 +110,14 @@ pub trait MinerService : Send + Sync { /// Depending on the settings may look in transaction pool or only in pending block. fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; + /// Returns next valid nonce for given address. + /// + /// This includes nonces of all transactions from this address in the pending queue + /// if they are consecutive. + /// NOTE: pool may contain some future transactions that will become pending after + /// transaction with nonce returned from this function is signed on. + fn next_nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256; + /// Get a list of all ready transactions. /// /// Depending on the settings may look in transaction pool or only in pending block. @@ -121,9 +129,6 @@ pub trait MinerService : Send + Sync { /// Get a list of local transactions with statuses. // fn local_transactions(&self) -> BTreeMap; - /// Returns highest transaction nonce for given address. - fn last_nonce(&self, address: &Address) -> Option; - /// Suggested gas price. fn sensible_gas_price(&self) -> U256; diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 93c163dcc3e..50f72e9716b 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicUsize; -use ethereum_types::{H256, U256}; +use ethereum_types::{H256, U256, Address}; use parking_lot::RwLock; use transaction; use txpool::{self, Verifier}; @@ -120,10 +120,25 @@ impl TransactionQueue { client: C, ) { let state_readiness = ready::State::new(client); + let removed = self.pool.write().cull(None, state_readiness); debug!(target: "txqueue", "Removed {} stalled transactions.", removed); } + /// Returns next valid nonce for given sender + /// or `None` if there are no pending transactions from that sender. + pub fn next_nonce( + &self, + client: C, + address: &Address, + ) -> Option { + let state_readiness = ready::State::new(client); + + self.pool.read().pending_from_sender(state_readiness, address) + .last() + .map(|tx| tx.signed().nonce + 1.into()) + } + /// Retrieve a transaction from the pool. /// /// Given transaction hash looks up that transaction in the pool @@ -186,7 +201,6 @@ impl TransactionQueue { #[cfg(test)] mod tests { use super::*; - use ethereum_types::Address; #[derive(Debug)] struct TestClient; diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 4f837b3fabb..bb49d4423dc 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -364,6 +364,25 @@ impl Pool where } } + /// Returns pending (ready) transactions from given sender. + pub fn pending_from_sender>(&self, ready: R, sender: &Sender) -> PendingIterator { + let best_transactions = self.transactions.get(sender) + .and_then(|transactions| transactions.worst_and_best()) + .map(|(_, best)| ScoreWithRef::new(best.0, best.1)) + .map(|s| { + let mut set = BTreeSet::new(); + set.insert(s); + set + }) + .unwrap_or_default(); + + PendingIterator { + ready, + best_transactions, + pool: self + } + } + /// Computes the full status of the pool (including readiness). pub fn status>(&self, mut ready: R) -> Status { let mut status = Status::default(); From b5384136158df4d8b5b04707f8c8ac4393112794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 21 Feb 2018 15:31:30 +0100 Subject: [PATCH 14/77] RPC compiles. --- ethcore/light/src/transaction_queue.rs | 24 ++- rpc/src/v1/helpers/dispatch.rs | 3 +- rpc/src/v1/helpers/errors.rs | 9 - rpc/src/v1/impls/eth.rs | 32 ++-- rpc/src/v1/impls/eth_filter.rs | 13 +- rpc/src/v1/impls/parity.rs | 47 +++-- rpc/src/v1/impls/parity_set.rs | 31 ++-- rpc/src/v1/tests/eth.rs | 6 +- rpc/src/v1/tests/helpers/miner_service.rs | 202 ++++++++-------------- rpc/src/v1/tests/mocked/eth.rs | 20 +-- rpc/src/v1/tests/mocked/parity.rs | 8 +- rpc/src/v1/tests/mocked/parity_set.rs | 12 +- rpc/src/v1/tests/mocked/personal.rs | 2 +- rpc/src/v1/tests/mocked/signing.rs | 2 +- rpc/src/v1/types/transaction.rs | 6 +- 15 files changed, 184 insertions(+), 233 deletions(-) diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index ff4baf7761d..fee0b713c50 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -113,6 +113,18 @@ impl AccountTransactions { } } +/// Transaction import result. +pub enum ImportResult { + /// Transaction has been imported to the current queue. + /// + /// It's going to be propagated to peers. + Current, + /// Transaction has been imported to future queue. + /// + /// It means it won't be propagated until the gap is filled. + Future +} + /// Light transaction queue. See module docs for more details. #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct TransactionQueue { @@ -122,7 +134,7 @@ pub struct TransactionQueue { impl TransactionQueue { /// Import a pending transaction to be queued. - pub fn import(&mut self, tx: PendingTransaction) -> Result { + pub fn import(&mut self, tx: PendingTransaction) -> Result { let sender = tx.sender(); let hash = tx.hash(); let nonce = tx.nonce; @@ -138,7 +150,7 @@ impl TransactionQueue { future: BTreeMap::new(), }); - transaction::ImportResult::Current + ImportResult::Current } Entry::Occupied(mut entry) => { let acct_txs = entry.get_mut(); @@ -160,7 +172,7 @@ impl TransactionQueue { let old = ::std::mem::replace(&mut acct_txs.current[idx], tx_info); self.by_hash.remove(&old.hash); - transaction::ImportResult::Current + ImportResult::Current } Err(idx) => { let cur_len = acct_txs.current.len(); @@ -182,13 +194,13 @@ impl TransactionQueue { acct_txs.future.insert(future_nonce, future); } - transaction::ImportResult::Current + ImportResult::Current } else if idx == cur_len && acct_txs.current.last().map_or(false, |f| f.nonce + 1.into() != nonce) { trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce); let future_nonce = nonce; acct_txs.future.insert(future_nonce, tx_info); - transaction::ImportResult::Future + ImportResult::Future } else { trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce); @@ -196,7 +208,7 @@ impl TransactionQueue { acct_txs.current.insert(idx, tx_info); acct_txs.adjust_future(); - transaction::ImportResult::Current + ImportResult::Current } } } diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index b082320d579..c6f8b9b2458 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -119,8 +119,7 @@ impl Clone for FullDispatcher { impl FullDispatcher { fn state_nonce(&self, from: &Address) -> U256 { - self.miner.last_nonce(from).map(|nonce| nonce + U256::one()) - .unwrap_or_else(|| self.client.latest_nonce(from)) + self.miner.next_nonce(&*self.client, from) } /// Imports transaction to the miner's queue. diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 9393fc53a01..0b7e159f9cf 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -35,7 +35,6 @@ mod codes { pub const TRANSACTION_ERROR: i64 = -32010; pub const EXECUTION_ERROR: i64 = -32015; pub const EXCEPTION_ERROR: i64 = -32016; - pub const DATABASE_ERROR: i64 = -32017; pub const ACCOUNT_LOCKED: i64 = -32020; pub const PASSWORD_INVALID: i64 = -32021; pub const ACCOUNT_ERROR: i64 = -32023; @@ -256,14 +255,6 @@ pub fn encoding(error: T) -> Error { } } -pub fn database(error: T) -> Error { - Error { - code: ErrorCode::ServerError(codes::DATABASE_ERROR), - message: "Database error.".into(), - data: Some(Value::String(format!("{:?}", error))), - } -} - pub fn fetch(error: T) -> Error { Error { code: ErrorCode::ServerError(codes::FETCH_ERROR), diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 6468917a54a..f80d10e8caf 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -27,7 +27,6 @@ use parking_lot::Mutex; use ethash::SeedHashCompute; use ethcore::account_provider::{AccountProvider, DappId}; -use ethcore::block::IsBlock; use ethcore::client::{MiningBlockChainClient, BlockId, TransactionId, UncleId}; use ethcore::ethereum::Ethash; use ethcore::filter::Filter as EthcoreFilter; @@ -244,7 +243,7 @@ impl EthClient where } pub fn pending_logs(miner: &M, best_block: EthBlockNumber, filter: &EthcoreFilter) -> Vec where M: MinerService { - let receipts = miner.pending_receipts(best_block); + let receipts = miner.pending_receipts(best_block).unwrap_or_default(); let pending_logs = receipts.into_iter() .flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::>()) @@ -323,7 +322,7 @@ impl Eth for EthClient where fn author(&self, meta: Metadata) -> Result { let dapp = meta.dapp_id(); - let mut miner = self.miner.author(); + let mut miner = self.miner.authoring_params().author; if miner == 0.into() { miner = self.dapp_accounts(dapp.into())?.get(0).cloned().unwrap_or_default(); } @@ -388,13 +387,7 @@ impl Eth for EthClient where let res = match num.unwrap_or_default() { BlockNumber::Pending if self.options.pending_nonce_from_queue => { - let nonce = self.miner.last_nonce(&address) - .map(|n| n + 1.into()) - .or_else(|| self.client.nonce(&address, BlockNumber::Pending.into())); - match nonce { - Some(nonce) => Ok(nonce.into()), - None => Err(errors::database("latest nonce missing")) - } + Ok(self.miner.next_nonce(&*self.client, &address).into()) } id => { try_bf!(check_known(&*self.client, id.clone())); @@ -414,13 +407,11 @@ impl Eth for EthClient where } fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture> { + let block_number = self.client.chain_info().best_block_number; + Box::new(future::ok(match num { - BlockNumber::Pending => Some( - self.miner.status().transactions_in_pending_block.into() - ), - _ => - self.client.block(num.into()) - .map(|block| block.transactions_count().into()) + BlockNumber::Pending => self.miner.pending_transactions(block_number).map(|x| x.len().into()), + _ => self.client.block(num.into()).map(|block| block.transactions_count().into()), })) } @@ -552,17 +543,18 @@ impl Eth for EthClient where } } - if self.miner.author().is_zero() { + if self.miner.authoring_params().author.is_zero() { warn!(target: "miner", "Cannot give work package - no author is configured. Use --author to configure!"); return Err(errors::no_author()) } - let work = self.miner.work_package(&*client).ok_or_else(|| { + let work = self.miner.work_package(&*self.client).ok_or_else(|| { warn!(target: "miner", "Cannot give work package - engine seals internally."); - Err(errors::no_work_required()) + errors::no_work_required() })?; + let (pow_hash, number, timestamp, difficulty) = work; - let target = Ethash::difficulty_to_boundary(difficulty); + let target = Ethash::difficulty_to_boundary(&difficulty); let seed_hash = self.seed_compute.lock().hash_block_number(number); if no_new_work_timeout > 0 && timestamp + no_new_work_timeout < get_time().sec as u64 { diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index 4803c2541cb..5002fac145b 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -21,7 +21,7 @@ use std::collections::HashSet; use ethcore::miner::MinerService; use ethcore::filter::Filter as EthcoreFilter; -use ethcore::client::{BlockChainClient, BlockId}; +use ethcore::client::{MiningBlockChainClient, BlockId}; use ethereum_types::H256; use parking_lot::Mutex; @@ -56,7 +56,7 @@ pub trait Filterable { /// Eth filter rpc implementation for a full node. pub struct EthFilterClient where - C: BlockChainClient, + C: MiningBlockChainClient, M: MinerService { client: Arc, @@ -64,7 +64,7 @@ pub struct EthFilterClient where polls: Mutex>, } -impl EthFilterClient where C: BlockChainClient, M: MinerService { +impl EthFilterClient where C: MiningBlockChainClient, M: MinerService { /// Creates new Eth filter client. pub fn new(client: Arc, miner: Arc) -> Self { EthFilterClient { @@ -75,7 +75,7 @@ impl EthFilterClient where C: BlockChainClient, M: MinerService { } } -impl Filterable for EthFilterClient where C: BlockChainClient, M: MinerService { +impl Filterable for EthFilterClient where C: MiningBlockChainClient, M: MinerService { fn best_block_number(&self) -> u64 { self.client.chain_info().best_block_number } @@ -85,7 +85,10 @@ impl Filterable for EthFilterClient where C: BlockChainClient, M: Mi } fn pending_transactions_hashes(&self, best: u64) -> Vec { - self.miner.pending_transactions_hashes(best) + self.miner.ready_transactions(&*self.client) + .into_iter() + .map(|tx| tx.signed().hash()) + .collect() } fn logs(&self, filter: EthcoreFilter) -> BoxFuture> { diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index b822bfd56eb..df0c49c8c02 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -167,23 +167,23 @@ impl Parity for ParityClient where } fn transactions_limit(&self) -> Result { - Ok(self.miner.transactions_limit()) + unimplemented!() } fn min_gas_price(&self) -> Result { - Ok(U256::from(self.miner.minimal_gas_price())) + unimplemented!() } fn extra_data(&self) -> Result { - Ok(Bytes::new(self.miner.extra_data())) + Ok(Bytes::new(self.miner.authoring_params().extra_data)) } fn gas_floor_target(&self) -> Result { - Ok(U256::from(self.miner.gas_floor_target())) + Ok(U256::from(self.miner.authoring_params().gas_range_target.0)) } fn gas_ceil_target(&self) -> Result { - Ok(U256::from(self.miner.gas_ceil_target())) + Ok(U256::from(self.miner.authoring_params().gas_range_target.1)) } fn dev_logs(&self) -> Result> { @@ -294,12 +294,24 @@ impl Parity for ParityClient where fn pending_transactions(&self) -> Result> { let block_number = self.client.chain_info().best_block_number; - Ok(self.miner.pending_transactions().into_iter().map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)).collect::>()) + let ready_transactions = self.miner.ready_transactions(&*self.client); + + Ok(ready_transactions + .into_iter() + .map(|t| Transaction::from_pending(t.pending().clone(), block_number, self.eip86_transition)) + .collect() + ) } fn future_transactions(&self) -> Result> { let block_number = self.client.chain_info().best_block_number; - Ok(self.miner.future_transactions().into_iter().map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)).collect::>()) + let future_transactions = self.miner.future_transactions(); + + Ok(future_transactions + .into_iter() + .map(|t| Transaction::from_pending(t.pending().clone(), block_number, self.eip86_transition)) + .collect() + ) } fn pending_transactions_stats(&self) -> Result> { @@ -316,13 +328,14 @@ impl Parity for ParityClient where return Ok(BTreeMap::new()); } - let transactions = self.miner.local_transactions(); - let block_number = self.client.chain_info().best_block_number; - Ok(transactions - .into_iter() - .map(|(hash, status)| (hash.into(), LocalTransactionStatus::from(status, block_number, self.eip86_transition))) - .collect() - ) + unimplemented!() + // let transactions = self.miner.local_transactions(); + // let block_number = self.client.chain_info().best_block_number; + // Ok(transactions + // .into_iter() + // .map(|(hash, status)| (hash.into(), LocalTransactionStatus::from(status, block_number, self.eip86_transition))) + // .collect() + // ) } fn dapps_url(&self) -> Result { @@ -338,11 +351,7 @@ impl Parity for ParityClient where fn next_nonce(&self, address: H160) -> BoxFuture { let address: Address = address.into(); - Box::new(future::ok(self.miner.last_nonce(&address) - .map(|n| n + 1.into()) - .unwrap_or_else(|| self.client.latest_nonce(&address)) - .into() - )) + Box::new(future::ok(self.miner.next_nonce(&*self.client, &address).into())) } fn mode(&self) -> Result { diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 4864e780611..04e22cd028c 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -76,17 +76,20 @@ impl ParitySet for ParitySetClient where { fn set_min_gas_price(&self, gas_price: U256) -> Result { - self.miner.set_minimal_gas_price(gas_price.into()); - Ok(true) + unimplemented!() } fn set_gas_floor_target(&self, target: U256) -> Result { - self.miner.set_gas_floor_target(target.into()); + let mut range = self.miner.authoring_params().gas_range_target.clone(); + range.0 = target.into(); + self.miner.set_gas_range_target(range); Ok(true) } fn set_gas_ceil_target(&self, target: U256) -> Result { - self.miner.set_gas_ceil_target(target.into()); + let mut range = self.miner.authoring_params().gas_range_target.clone(); + range.1 = target.into(); + self.miner.set_gas_range_target(range); Ok(true) } @@ -95,24 +98,22 @@ impl ParitySet for ParitySetClient where Ok(true) } - fn set_author(&self, author: H160) -> Result { - self.miner.set_author(author.into()); + fn set_author(&self, address: H160) -> Result { + self.miner.set_author(address.into(), None).map_err(Into::into).map_err(errors::password)?; Ok(true) } fn set_engine_signer(&self, address: H160, password: String) -> Result { - self.miner.set_engine_signer(address.into(), password).map_err(Into::into).map_err(errors::password)?; + self.miner.set_author(address.into(), Some(password)).map_err(Into::into).map_err(errors::password)?; Ok(true) } fn set_transactions_limit(&self, limit: usize) -> Result { - self.miner.set_transactions_limit(limit); - Ok(true) + unimplemented!() } fn set_tx_gas_limit(&self, limit: U256) -> Result { - self.miner.set_tx_gas_limit(limit.into()); - Ok(true) + unimplemented!() } fn add_reserved_peer(&self, peer: String) -> Result { @@ -193,9 +194,11 @@ impl ParitySet for ParitySetClient where } fn remove_transaction(&self, hash: H256) -> Result> { - let block_number = self.client.chain_info().best_block_number; - let hash = hash.into(); + // let block_number = self.client.chain_info().best_block_number; + // let hash = hash.into(); - Ok(self.miner.remove_pending_transaction(&*self.client, &hash).map(|t| Transaction::from_pending(t, block_number, self.eip86_transition))) + // TODO [ToDr] + unimplemented!() + // Ok(self.miner.remove_pending_transaction(&*self.client, &hash).map(|t| Transaction::from_pending(t, block_number, self.eip86_transition))) } } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 6704d4e6e96..208089ae210 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -17,15 +17,14 @@ //! rpc integration tests. use std::env; use std::sync::Arc; -use std::time::Duration; -use ethereum_types::{U256, H256, Address}; +use ethereum_types::{H256, Address}; use ethcore::account_provider::AccountProvider; use ethcore::block::Block; use ethcore::client::{BlockChainClient, Client, ClientConfig}; use ethcore::ethereum; use ethcore::ids::BlockId; -use ethcore::miner::{MinerOptions, Banning, GasPricer, MinerService, Miner, PendingSet, GasLimit}; +use ethcore::miner::{MinerService, Miner}; use ethcore::spec::{Genesis, Spec}; use ethcore::views::BlockView; use ethjson::blockchain::BlockChain; @@ -33,7 +32,6 @@ use ethjson::state::test::ForkSpec; use io::IoChannel; use kvdb_memorydb; use miner::external::ExternalMiner; -use miner::transaction_queue::PrioritizationStrategy; use parking_lot::Mutex; use jsonrpc_core::IoHandler; diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 92a5addb168..0a3b4580c6b 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -16,188 +16,121 @@ //! Test implementation of miner service. +use std::sync::Arc; use std::collections::{BTreeMap, HashMap}; -use std::collections::hash_map::Entry; -use ethereum_types::{H256, U256, Address}; + use bytes::Bytes; use ethcore::account_provider::SignError as AccountError; -use ethcore::block::ClosedBlock; +use ethcore::block::{SealedBlock, IsBlock}; use ethcore::client::MiningBlockChainClient; use ethcore::error::Error; use ethcore::header::BlockNumber; -use ethcore::miner::{MinerService, MinerStatus}; -use miner::local_transactions::Status as LocalTransactionStatus; +use ethcore::miner::{MinerService, AuthoringParams}; use ethcore::receipt::{Receipt, RichReceipt}; +use ethereum_types::{H256, U256, Address}; +use miner::pool::VerifiedTransaction; +use miner::local_transactions::Status as LocalTransactionStatus; use parking_lot::{RwLock, Mutex}; -use transaction::{UnverifiedTransaction, SignedTransaction, PendingTransaction, ImportResult as TransactionImportResult}; +use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction}; /// Test miner service. pub struct TestMinerService { /// Imported transactions. pub imported_transactions: Mutex>, - /// Latest closed block. - pub latest_closed_block: Mutex>, /// Pre-existed pending transactions pub pending_transactions: Mutex>, /// Pre-existed local transactions pub local_transactions: Mutex>, /// Pre-existed pending receipts pub pending_receipts: Mutex>, - /// Last nonces. - pub last_nonces: RwLock>, + /// Next nonces. + pub next_nonces: RwLock>, /// Password held by Engine. pub password: RwLock, - min_gas_price: RwLock, - gas_range_target: RwLock<(U256, U256)>, - author: RwLock
, - extra_data: RwLock, - limit: RwLock, - tx_gas_limit: RwLock, + authoring_params: RwLock, } impl Default for TestMinerService { fn default() -> TestMinerService { TestMinerService { imported_transactions: Mutex::new(Vec::new()), - latest_closed_block: Mutex::new(None), pending_transactions: Mutex::new(HashMap::new()), local_transactions: Mutex::new(BTreeMap::new()), pending_receipts: Mutex::new(BTreeMap::new()), - last_nonces: RwLock::new(HashMap::new()), - min_gas_price: RwLock::new(U256::from(20_000_000)), - gas_range_target: RwLock::new((U256::from(12345), U256::from(54321))), - author: RwLock::new(Address::zero()), + next_nonces: RwLock::new(HashMap::new()), password: RwLock::new(String::new()), - extra_data: RwLock::new(vec![1, 2, 3, 4]), - limit: RwLock::new(1024), - tx_gas_limit: RwLock::new(!U256::zero()), + authoring_params: RwLock::new(AuthoringParams { + author: Address::zero(), + gas_range_target: (12345.into(), 54321.into()), + extra_data: vec![1, 2, 3, 4], + }), } } } impl TestMinerService { - /// Increments last nonce for given address. - pub fn increment_last_nonce(&self, address: Address) { - let mut last_nonces = self.last_nonces.write(); - match last_nonces.entry(address) { - Entry::Occupied(mut occupied) => { - let val = *occupied.get(); - *occupied.get_mut() = val + 1.into(); - }, - Entry::Vacant(vacant) => { - vacant.insert(0.into()); - }, - } + /// Increments nonce for given address. + pub fn increment_nonce(&self, address: &Address) { + let mut next_nonces = self.next_nonces.write(); + let nonce = next_nonces.entry(*address).or_insert_with(|| 0.into()); + *nonce = *nonce + 1.into(); } } impl MinerService for TestMinerService { - /// Returns miner's status. - fn status(&self) -> MinerStatus { - MinerStatus { - transactions_in_pending_queue: 0, - transactions_in_future_queue: 0, - transactions_in_pending_block: 1 - } + fn authoring_params(&self) -> AuthoringParams { + self.authoring_params.read().clone() } - fn set_author(&self, author: Address) { - *self.author.write() = author; - } - - fn set_engine_signer(&self, address: Address, password: String) -> Result<(), AccountError> { - *self.author.write() = address; - *self.password.write() = password; + fn set_author(&self, author: Address, password: Option) -> Result<(), AccountError> { + self.authoring_params.write().author = author; + if let Some(password) = password { + *self.password.write() = password; + } Ok(()) } fn set_extra_data(&self, extra_data: Bytes) { - *self.extra_data.write() = extra_data; - } - - /// Set the lower gas limit we wish to target when sealing a new block. - fn set_gas_floor_target(&self, target: U256) { - self.gas_range_target.write().0 = target; - } - - /// Set the upper gas limit we wish to target when sealing a new block. - fn set_gas_ceil_target(&self, target: U256) { - self.gas_range_target.write().1 = target; - } - - fn set_minimal_gas_price(&self, min_gas_price: U256) { - *self.min_gas_price.write() = min_gas_price; - } - - fn set_transactions_limit(&self, limit: usize) { - *self.limit.write() = limit; - } - - fn set_tx_gas_limit(&self, limit: U256) { - *self.tx_gas_limit.write() = limit; + self.authoring_params.write().extra_data = extra_data; } - fn transactions_limit(&self) -> usize { - *self.limit.read() - } - - fn author(&self) -> Address { - *self.author.read() - } - - fn minimal_gas_price(&self) -> U256 { - *self.min_gas_price.read() - } - - fn extra_data(&self) -> Bytes { - self.extra_data.read().clone() - } - - fn gas_floor_target(&self) -> U256 { - self.gas_range_target.read().0 - } - - fn gas_ceil_target(&self) -> U256 { - self.gas_range_target.read().1 + fn set_gas_range_target(&self, target: (U256, U256)) { + self.authoring_params.write().gas_range_target = target; } /// Imports transactions to transaction queue. - fn import_external_transactions(&self, _chain: &MiningBlockChainClient, transactions: Vec) -> - Vec> { + fn import_external_transactions(&self, chain: &MiningBlockChainClient, transactions: Vec) -> + Vec> { // lets assume that all txs are valid let transactions: Vec<_> = transactions.into_iter().map(|tx| SignedTransaction::new(tx).unwrap()).collect(); self.imported_transactions.lock().extend_from_slice(&transactions); for sender in transactions.iter().map(|tx| tx.sender()) { - let nonce = self.last_nonce(&sender).expect("last_nonce must be populated in tests"); - self.last_nonces.write().insert(sender, nonce + U256::from(1)); + let nonce = self.next_nonce(chain, &sender); + self.next_nonces.write().insert(sender, nonce); } + transactions .iter() - .map(|_| Ok(TransactionImportResult::Current)) + .map(|_| Ok(())) .collect() } /// Imports transactions to transaction queue. fn import_own_transaction(&self, chain: &MiningBlockChainClient, pending: PendingTransaction) -> - Result { + Result<(), transaction::Error> { // keep the pending nonces up to date let sender = pending.transaction.sender(); - let nonce = self.last_nonce(&sender).unwrap_or(chain.latest_nonce(&sender)); - self.last_nonces.write().insert(sender, nonce + U256::from(1)); + let nonce = self.next_nonce(chain, &sender); + self.next_nonces.write().insert(sender, nonce); // lets assume that all txs are valid self.imported_transactions.lock().push(pending.transaction); - Ok(TransactionImportResult::Current) - } - - /// Returns hashes of transactions currently in pending - fn pending_transactions_hashes(&self, _best_block: BlockNumber) -> Vec { - vec![] + Ok(()) } /// Called when blocks are imported to chain, updates transactions queue. @@ -210,38 +143,43 @@ impl MinerService for TestMinerService { unimplemented!(); } - fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { - let open_block = chain.prepare_open_block(self.author(), *self.gas_range_target.write(), self.extra_data()); - Some(f(&open_block.close())) + fn work_package(&self, chain: &MiningBlockChainClient) -> Option<(H256, BlockNumber, u64, U256)> { + let params = self.authoring_params(); + let open_block = chain.prepare_open_block(params.author, params.gas_range_target, params.extra_data); + let closed = open_block.close(); + let header = closed.header(); + + Some((header.hash(), header.number(), header.timestamp(), *header.difficulty())) } fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { self.pending_transactions.lock().get(hash).cloned().map(Into::into) } - fn remove_pending_transaction(&self, _chain: &MiningBlockChainClient, hash: &H256) -> Option { - self.pending_transactions.lock().remove(hash).map(Into::into) - } + // fn remove_pending_transaction(&self, _chain: &MiningBlockChainClient, hash: &H256) -> Option { + // self.pending_transactions.lock().remove(hash).map(Into::into) + // } - fn pending_transactions(&self) -> Vec { - self.pending_transactions.lock().values().cloned().map(Into::into).collect() + fn pending_transactions(&self, best_block: BlockNumber) -> Option> { + Some(self.pending_transactions.lock().values().cloned().collect()) } - fn local_transactions(&self) -> BTreeMap { - self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() - } + // fn local_transactions(&self) -> BTreeMap { + // self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() + // } - fn ready_transactions(&self, _best_block: BlockNumber, _best_timestamp: u64) -> Vec { - self.pending_transactions.lock().values().cloned().map(Into::into).collect() + fn ready_transactions(&self, chain: &MiningBlockChainClient) -> Vec> { + // self.pending_transactions.lock().values().cloned().map(Into::into).collect() + unimplemented!() } - fn future_transactions(&self) -> Vec { + fn future_transactions(&self) -> Vec> { vec![] } fn pending_receipt(&self, _best_block: BlockNumber, hash: &H256) -> Option { // Not much point implementing this since the logic is complex and the only thing it relies on is pending_receipts, which is already tested. - self.pending_receipts(0).get(hash).map(|r| + self.pending_receipts(0).unwrap().get(hash).map(|r| RichReceipt { transaction_hash: Default::default(), transaction_index: Default::default(), @@ -255,12 +193,12 @@ impl MinerService for TestMinerService { ) } - fn pending_receipts(&self, _best_block: BlockNumber) -> BTreeMap { - self.pending_receipts.lock().clone() + fn pending_receipts(&self, _best_block: BlockNumber) -> Option> { + Some(self.pending_receipts.lock().clone()) } - fn last_nonce(&self, address: &Address) -> Option { - self.last_nonces.read().get(address).cloned() + fn next_nonce(&self, _chain: &MiningBlockChainClient, address: &Address) -> U256 { + self.next_nonces.read().get(address).cloned().unwrap_or_default() } fn is_currently_sealing(&self) -> bool { @@ -269,11 +207,15 @@ impl MinerService for TestMinerService { /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. - fn submit_seal(&self, _chain: &MiningBlockChainClient, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, _pow_hash: H256, _seal: Vec) -> Result { unimplemented!(); } fn sensible_gas_price(&self) -> U256 { - 20000000000u64.into() + 20_000_000_000u64.into() + } + + fn sensible_gas_limit(&self) -> U256 { + 0x5208.into() } } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index c66da9b1c09..ca2245cb237 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -346,7 +346,7 @@ fn rpc_eth_author() { for i in 0..20 { let addr = tester.accounts_provider.new_account(&format!("{}", i)).unwrap(); - tester.miner.set_author(addr.clone()); + tester.miner.set_author(addr.clone(), None).unwrap(); assert_eq!(tester.io.handle_request_sync(req), Some(make_res(addr))); } @@ -355,7 +355,7 @@ fn rpc_eth_author() { #[test] fn rpc_eth_mining() { let tester = EthTester::default(); - tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); + tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), None).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "eth_mining", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; @@ -476,7 +476,7 @@ fn rpc_eth_transaction_count_next_nonce() { let tester = EthTester::new_with_options(EthClientOptions::with(|options| { options.pending_nonce_from_queue = true; })); - tester.miner.increment_last_nonce(1.into()); + tester.miner.increment_nonce(&1.into()); let request1 = r#"{ "jsonrpc": "2.0", @@ -531,7 +531,7 @@ fn rpc_eth_transaction_count_by_number_pending() { "params": ["pending"], "id": 1 }"#; - let response = r#"{"jsonrpc":"2.0","result":"0x1","id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x0","id":1}"#; assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned())); } @@ -813,7 +813,7 @@ fn rpc_eth_send_transaction() { assert_eq!(tester.io.handle_request_sync(&request), Some(response)); - tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); + tester.miner.increment_nonce(&address); let t = Transaction { nonce: U256::one(), @@ -883,7 +883,7 @@ fn rpc_eth_sign_transaction() { r#""value":"0x9184e72a""# + r#"}},"id":1}"#; - tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); + tester.miner.increment_nonce(&address); assert_eq!(tester.io.handle_request_sync(&request), Some(response)); } @@ -1096,7 +1096,7 @@ fn rpc_get_work_returns_no_work_if_cant_mine() { #[test] fn rpc_get_work_returns_correct_work_package() { let eth_tester = EthTester::default(); - eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); + eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), None).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x76c7bd86693aee93d1a80a408a09a0585b1a1292afcb56192f171d925ea18e2d","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000800000000000000000000000000000000000000000000000000000000000","0x1"],"id":1}"#; @@ -1109,7 +1109,7 @@ fn rpc_get_work_should_not_return_block_number() { let eth_tester = EthTester::new_with_options(EthClientOptions::with(|options| { options.send_block_number_in_get_work = false; })); - eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); + eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), None).unwrap(); let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["0x76c7bd86693aee93d1a80a408a09a0585b1a1292afcb56192f171d925ea18e2d","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000800000000000000000000000000000000000000000000000000000000000"],"id":1}"#; @@ -1120,9 +1120,9 @@ fn rpc_get_work_should_not_return_block_number() { #[test] fn rpc_get_work_should_timeout() { let eth_tester = EthTester::default(); - eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); + eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), None).unwrap(); eth_tester.client.set_latest_block_timestamp(get_time().sec as u64 - 1000); // Set latest block to 1000 seconds ago - let hash = eth_tester.miner.map_sealing_work(&*eth_tester.client, |b| b.hash()).unwrap(); + let hash = eth_tester.miner.work_package(&*eth_tester.client).unwrap().0; // Request without providing timeout. This should work since we're disabling timeout. let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#; diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 4b75e07fedb..0650dd31c2c 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -17,13 +17,13 @@ use std::sync::Arc; use ethcore::account_provider::AccountProvider; use ethcore::client::{TestBlockChainClient, Executed}; -use ethcore::miner::LocalTransactionStatus; use ethcore_logger::RotatingLogger; +use ethereum_types::{Address, U256, H256}; use ethstore::ethkey::{Generator, Random}; use ethsync::ManageNetwork; +use miner::local_transactions::Status as LocalTransactionStatus; use node_health::{self, NodeHealth}; use parity_reactor; -use ethereum_types::{Address, U256, H256}; use jsonrpc_core::IoHandler; use v1::{Parity, ParityClient}; @@ -455,7 +455,9 @@ fn rpc_parity_next_nonce() { let address = Address::default(); let io1 = deps.default_client(); let deps = Dependencies::new(); - deps.miner.last_nonces.write().insert(address.clone(), 2.into()); + deps.miner.increment_nonce(&address); + deps.miner.increment_nonce(&address); + deps.miner.increment_nonce(&address); let io2 = deps.default_client(); let request = r#"{ diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 0666d4f42cf..9fdc4d91e33 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -108,7 +108,7 @@ fn rpc_parity_set_min_gas_price() { let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - assert_eq!(miner.minimal_gas_price(), U256::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); + // assert_eq!(miner.minimal_gas_price(), U256::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); } #[test] @@ -125,7 +125,7 @@ fn rpc_parity_set_gas_floor_target() { let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - assert_eq!(miner.gas_floor_target(), U256::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); + assert_eq!(miner.authoring_params().gas_range_target.0, U256::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); } #[test] @@ -142,7 +142,7 @@ fn rpc_parity_set_extra_data() { let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - assert_eq!(miner.extra_data(), "cd1722f3947def4cf144679da39c4c32bdc35681".from_hex().unwrap()); + assert_eq!(miner.authoring_params().extra_data, "cd1722f3947def4cf144679da39c4c32bdc35681".from_hex().unwrap()); } #[test] @@ -158,7 +158,7 @@ fn rpc_parity_set_author() { let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); + assert_eq!(miner.authoring_params().author, Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); } #[test] @@ -174,7 +174,7 @@ fn rpc_parity_set_engine_signer() { let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); + assert_eq!(miner.authoring_params().author, Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); assert_eq!(*miner.password.read(), "password".to_string()); } @@ -192,7 +192,7 @@ fn rpc_parity_set_transactions_limit() { let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - assert_eq!(miner.transactions_limit(), 10_240_240); + // assert_eq!(miner.transactions_limit(), 10_240_240); } #[test] diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 6dd1da567fe..1c3b163c4b0 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -220,7 +220,7 @@ fn sign_and_send_test(method: &str) { assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response)); - tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); + tester.miner.increment_nonce(&address); let t = Transaction { nonce: U256::one(), diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 5bf5119ec60..a37d488e709 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -327,7 +327,7 @@ fn should_add_sign_transaction_to_the_queue() { r#"}},"id":1}"#; // then - tester.miner.last_nonces.write().insert(address.clone(), U256::zero()); + tester.miner.increment_nonce(&address); let promise = tester.io.handle_request(&request); // the future must be polled at least once before request is queued. diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index c5dd63624f5..2796e3f1842 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -16,8 +16,8 @@ use serde::{Serialize, Serializer}; use serde::ser::SerializeStruct; -use ethcore::miner; use ethcore::{contract_address, CreateContractAddress}; +use miner; use transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; use v1::helpers::errors; use v1::types::{Bytes, H160, H256, U256, H512, U64, TransactionCondition}; @@ -248,8 +248,8 @@ impl Transaction { impl LocalTransactionStatus { /// Convert `LocalTransactionStatus` into RPC `LocalTransactionStatus`. - pub fn from(s: miner::LocalTransactionStatus, block_number: u64, eip86_transition: u64) -> Self { - use ethcore::miner::LocalTransactionStatus::*; + pub fn from(s: miner::local_transactions::Status, block_number: u64, eip86_transition: u64) -> Self { + use miner::local_transactions::Status::*; match s { Pending => LocalTransactionStatus::Pending, Future => LocalTransactionStatus::Future, From 336832495d12d1a2b6d91fdf4a4ca9b28d5e30db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 22 Feb 2018 17:07:12 +0100 Subject: [PATCH 15/77] Implement couple of missing methdods for RPC. --- Cargo.lock | 1 + ethcore/src/miner/miner.rs | 25 ++++++------ ethcore/src/miner/mod.rs | 29 +++++++++----- miner/src/pool/mod.rs | 2 +- miner/src/pool/queue.rs | 37 ++++++++++++++---- rpc/Cargo.toml | 5 ++- rpc/src/lib.rs | 2 + rpc/src/v1/impls/eth.rs | 4 +- rpc/src/v1/impls/eth_filter.rs | 10 ++--- rpc/src/v1/impls/light/eth.rs | 2 +- rpc/src/v1/impls/parity.rs | 4 +- rpc/src/v1/impls/parity_set.rs | 33 ++++++++-------- rpc/src/v1/tests/helpers/miner_service.rs | 46 ++++++++++++++++++----- rpc/src/v1/tests/mocked/parity_set.rs | 6 +-- transaction-pool/src/pool.rs | 5 +++ transaction-pool/src/status.rs | 4 +- 16 files changed, 142 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b6a232ddac..60832b6f2a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2185,6 +2185,7 @@ dependencies = [ "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "transaction-pool 1.9.0", "transient-hashmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "vm 0.1.0", ] diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index daf1aca5d6f..5afd9ebb1d3 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -24,7 +24,7 @@ use parking_lot::{Mutex, RwLock}; use bytes::Bytes; use engines::{EthEngine, Seal}; use error::{Error, ExecutionError}; -use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction}; +use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction, QueueStatus}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; use ethcore_miner::gas_pricer::GasPricer; use timer::PerfTimer; @@ -817,17 +817,18 @@ impl MinerService for Miner { .unwrap_or_else(|| chain.nonce(address, BlockId::Latest).unwrap_or_default()) } - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { - match self.options.pending_set { - PendingSet::AlwaysQueue => self.transaction_queue.find(hash).map(|t| t.pending().clone()), - PendingSet::AlwaysSealing => { - self.from_pending_block( - best_block, - || None, - |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned().map(Into::into) - ) - }, - } + fn transaction(&self, hash: &H256) -> Option> { + self.transaction_queue.find(hash) + } + + fn remove_transaction(&self, hash: &H256) -> Option> { + self.transaction_queue.remove(::std::iter::once(hash), false) + .pop() + .expect("remove() returns one result per hash; one hash passed; qed") + } + + fn queue_status(&self) -> QueueStatus { + self.transaction_queue.status() } fn pending_transactions(&self, best_block: BlockNumber) -> Option> { diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index bb5f764b0d0..dd843fde1e8 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -31,7 +31,7 @@ use std::collections::BTreeMap; use bytes::Bytes; use ethereum_types::{H256, U256, Address}; -use ethcore_miner::pool::VerifiedTransaction; +use ethcore_miner::pool::{VerifiedTransaction, QueueStatus}; use block::SealedBlock; use client::{MiningBlockChainClient}; @@ -40,9 +40,13 @@ use header::BlockNumber; use receipt::{RichReceipt, Receipt}; use transaction::{self, UnverifiedTransaction, PendingTransaction, SignedTransaction}; +// TODO [ToDr] Split into smaller traits? +// TODO [ToDr] get rid of from_pending_block in miner/miner.rs + /// Miner client API pub trait MinerService : Send + Sync { + // Sealing /// Get the sealing work package preparing it if doesn't exist yet. /// @@ -91,7 +95,7 @@ pub trait MinerService : Send + Sync { /// On PoW password is optional. fn set_author(&self, address: Address, password: Option) -> Result<(), ::account_provider::SignError>; - // Transactions and Pool + // Transaction Pool /// Imports transactions to transaction queue. fn import_external_transactions(&self, chain: &MiningBlockChainClient, transactions: Vec) -> @@ -101,14 +105,15 @@ pub trait MinerService : Send + Sync { fn import_own_transaction(&self, chain: &MiningBlockChainClient, transaction: PendingTransaction) -> Result<(), transaction::Error>; - /// Removes transaction from the queue. - /// NOTE: The transaction is not removed from pending block if mining. - // fn remove_pending_transaction(&self, chain: &MiningBlockChainClient, hash: &H256) -> Option; - - /// Query pending transaction given it's hash. + /// Removes transaction from the pool. /// - /// Depending on the settings may look in transaction pool or only in pending block. - fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; + /// Attempts to "cancel" a transaction. If it was not propagated yet (or not accepted by other peers) + /// there is a good chance that the transaction will actually be removed. + /// NOTE: The transaction is not removed from pending block if there is one. + fn remove_transaction(&self, hash: &H256) -> Option>; + + /// Query transaction from the pool given it's hash. + fn transaction(&self, hash: &H256) -> Option>; /// Returns next valid nonce for given address. /// @@ -129,6 +134,12 @@ pub trait MinerService : Send + Sync { /// Get a list of local transactions with statuses. // fn local_transactions(&self) -> BTreeMap; + /// Get current queue status. + /// + /// Status includes verification thresholds and current pool utilization and limits. + fn queue_status(&self) -> QueueStatus; + + // Misc /// Suggested gas price. fn sensible_gas_price(&self) -> U256; diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 286e29b2c36..81e476643e3 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -27,7 +27,7 @@ pub mod ready; pub mod scoring; pub mod verifier; -pub use self::queue::TransactionQueue; +pub use self::queue::{TransactionQueue, Status as QueueStatus}; pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 50f72e9716b..470f7f31f6d 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -29,6 +29,18 @@ use pool::{self, scoring, verifier, client, ready}; // TODO [ToDr] Support logging listener (and custom listeners) type Pool = txpool::Pool; + +/// Transaction queue status +#[derive(Debug, Clone, PartialEq)] +pub struct Status { + /// Verifier options + pub options: verifier::Options, + /// Current status of the transaction pool. + pub status: txpool::LightStatus, + /// Current limits of the transaction pool. + pub limits: txpool::Options, +} + /// Ethereum Transaction Queue /// /// Responsible for: @@ -160,11 +172,13 @@ impl TransactionQueue { &self, hashes: T, is_invalid: bool, - ) { + ) -> Vec>> { let mut pool = self.pool.write(); - for hash in hashes { - pool.remove(hash, is_invalid); - } + + hashes + .into_iter() + .map(|hash| pool.remove(hash, is_invalid)) + .collect() } /// Clear the entire pool. @@ -180,9 +194,18 @@ impl TransactionQueue { } } - /// Returns a status of the pool. - pub fn status(&self) -> txpool::LightStatus { - self.pool.read().light_status() + /// Returns a status of the queue. + pub fn status(&self) -> Status { + let pool = self.pool.read(); + let status = pool.light_status(); + let limits = pool.options(); + let options = self.options.read().clone(); + + Status { + options, + status, + limits, + } } /// Check if there are any local transactions in the pool. diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 96a8e836f65..a2c54b23b35 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -65,7 +65,8 @@ stats = { path = "../util/stats" } vm = { path = "../ethcore/vm" } [dev-dependencies] -pretty_assertions = "0.1" -macros = { path = "../util/macros" } ethcore-network = { path = "../util/network" } kvdb-memorydb = { path = "../util/kvdb-memorydb" } +macros = { path = "../util/macros" } +pretty_assertions = "0.1" +transaction-pool = { path = "../transaction-pool" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index d8e5495cc8d..baab30dd4ee 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -78,6 +78,8 @@ extern crate serde_derive; #[cfg(test)] extern crate ethjson; +#[cfg(test)] +extern crate transaction_pool as txpool; #[cfg(test)] #[macro_use] diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index f80d10e8caf..9e682aa971b 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -455,8 +455,8 @@ impl Eth for EthClient where let hash: H256 = hash.into(); let block_number = self.client.chain_info().best_block_number; let tx = try_bf!(self.transaction(TransactionId::Hash(hash))).or_else(|| { - self.miner.transaction(block_number, &hash) - .map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)) + self.miner.transaction(&hash) + .map(|t| Transaction::from_pending(t.pending().clone(), block_number + 1, self.eip86_transition)) }); Box::new(future::ok(tx)) diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index 5002fac145b..49d698ff2c2 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -42,7 +42,7 @@ pub trait Filterable { fn block_hash(&self, id: BlockId) -> Option; /// pending transaction hashes at the given block. - fn pending_transactions_hashes(&self, block_number: u64) -> Vec; + fn pending_transactions_hashes(&self) -> Vec; /// Get logs that match the given filter. fn logs(&self, filter: EthcoreFilter) -> BoxFuture>; @@ -84,7 +84,7 @@ impl Filterable for EthFilterClient where C: MiningBlockChainClient, self.client.block_hash(id).map(Into::into) } - fn pending_transactions_hashes(&self, best: u64) -> Vec { + fn pending_transactions_hashes(&self) -> Vec { self.miner.ready_transactions(&*self.client) .into_iter() .map(|tx| tx.signed().hash()) @@ -120,8 +120,7 @@ impl EthFilter for T { fn new_pending_transaction_filter(&self) -> Result { let mut polls = self.polls().lock(); - let best_block = self.best_block_number(); - let pending_transactions = self.pending_transactions_hashes(best_block); + let pending_transactions = self.pending_transactions_hashes(); let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); Ok(id.into()) } @@ -145,8 +144,7 @@ impl EthFilter for T { }, PollFilter::PendingTransaction(ref mut previous_hashes) => { // get hashes of pending transactions - let best_block = self.best_block_number(); - let current_hashes = self.pending_transactions_hashes(best_block); + let current_hashes = self.pending_transactions_hashes(); let new_hashes = { diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 7a5fb288699..25509fc7b10 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -516,7 +516,7 @@ impl Filterable for EthClient { self.client.block_hash(id).map(Into::into) } - fn pending_transactions_hashes(&self, _block_number: u64) -> Vec<::ethereum_types::H256> { + fn pending_transactions_hashes(&self) -> Vec<::ethereum_types::H256> { Vec::new() } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index df0c49c8c02..5a71640b8c8 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -167,11 +167,11 @@ impl Parity for ParityClient where } fn transactions_limit(&self) -> Result { - unimplemented!() + Ok(self.miner.queue_status().limits.max_count) } fn min_gas_price(&self) -> Result { - unimplemented!() + Ok(self.miner.queue_status().options.minimal_gas_price.into()) } fn extra_data(&self) -> Result { diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 04e22cd028c..c28b3ff7995 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -75,8 +75,19 @@ impl ParitySet for ParitySetClient where F: Fetch + 'static, { - fn set_min_gas_price(&self, gas_price: U256) -> Result { - unimplemented!() + fn set_min_gas_price(&self, _gas_price: U256) -> Result { + warn!("setMinGasPrice is deprecated. Ignoring request."); + Ok(false) + } + + fn set_transactions_limit(&self, _limit: usize) -> Result { + warn!("setTransactionsLimit is deprecated. Ignoring request."); + Ok(false) + } + + fn set_tx_gas_limit(&self, _limit: U256) -> Result { + warn!("setTxGasLimit is deprecated. Ignoring request."); + Ok(false) } fn set_gas_floor_target(&self, target: U256) -> Result { @@ -108,14 +119,6 @@ impl ParitySet for ParitySetClient where Ok(true) } - fn set_transactions_limit(&self, limit: usize) -> Result { - unimplemented!() - } - - fn set_tx_gas_limit(&self, limit: U256) -> Result { - unimplemented!() - } - fn add_reserved_peer(&self, peer: String) -> Result { match self.net.add_reserved_peer(peer) { Ok(()) => Ok(true), @@ -194,11 +197,11 @@ impl ParitySet for ParitySetClient where } fn remove_transaction(&self, hash: H256) -> Result> { - // let block_number = self.client.chain_info().best_block_number; - // let hash = hash.into(); + let block_number = self.client.chain_info().best_block_number; + let hash = hash.into(); - // TODO [ToDr] - unimplemented!() - // Ok(self.miner.remove_pending_transaction(&*self.client, &hash).map(|t| Transaction::from_pending(t, block_number, self.eip86_transition))) + Ok(self.miner.remove_transaction(&hash) + .map(|t| Transaction::from_pending(t.pending().clone(), block_number + 1, self.eip86_transition)) + ) } } diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 0a3b4580c6b..c289ad38997 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -28,8 +28,9 @@ use ethcore::header::BlockNumber; use ethcore::miner::{MinerService, AuthoringParams}; use ethcore::receipt::{Receipt, RichReceipt}; use ethereum_types::{H256, U256, Address}; -use miner::pool::VerifiedTransaction; +use miner::pool::{verifier, VerifiedTransaction, QueueStatus}; use miner::local_transactions::Status as LocalTransactionStatus; +use txpool; use parking_lot::{RwLock, Mutex}; use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction}; @@ -152,15 +153,19 @@ impl MinerService for TestMinerService { Some((header.hash(), header.number(), header.timestamp(), *header.difficulty())) } - fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { - self.pending_transactions.lock().get(hash).cloned().map(Into::into) + fn transaction(&self, hash: &H256) -> Option> { + self.pending_transactions.lock().get(hash).cloned().map(|tx| { + Arc::new(VerifiedTransaction::from_pending_block_transaction(tx)) + }) } - // fn remove_pending_transaction(&self, _chain: &MiningBlockChainClient, hash: &H256) -> Option { - // self.pending_transactions.lock().remove(hash).map(Into::into) - // } + fn remove_transaction(&self, hash: &H256) -> Option> { + self.pending_transactions.lock().remove(hash).map(|tx| { + Arc::new(VerifiedTransaction::from_pending_block_transaction(tx)) + }) + } - fn pending_transactions(&self, best_block: BlockNumber) -> Option> { + fn pending_transactions(&self, _best_block: BlockNumber) -> Option> { Some(self.pending_transactions.lock().values().cloned().collect()) } @@ -168,9 +173,10 @@ impl MinerService for TestMinerService { // self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() // } - fn ready_transactions(&self, chain: &MiningBlockChainClient) -> Vec> { - // self.pending_transactions.lock().values().cloned().map(Into::into).collect() - unimplemented!() + fn ready_transactions(&self, _chain: &MiningBlockChainClient) -> Vec> { + self.pending_transactions.lock().values().cloned().map(|tx| { + Arc::new(VerifiedTransaction::from_pending_block_transaction(tx)) + }).collect() } fn future_transactions(&self) -> Vec> { @@ -205,6 +211,26 @@ impl MinerService for TestMinerService { false } + fn queue_status(&self) -> QueueStatus { + QueueStatus { + options: verifier::Options { + minimal_gas_price: 0x1312d00.into(), + block_gas_limit: 5_000_000.into(), + tx_gas_limit: 5_000_000.into(), + }, + status: txpool::LightStatus { + mem_usage: 1_000, + transaction_count: 52, + senders: 1, + }, + limits: txpool::Options { + max_count: 1_024, + max_per_sender: 16, + max_mem_usage: 5_000, + }, + } + } + /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. fn submit_seal(&self, _pow_hash: H256, _seal: Vec) -> Result { diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 9fdc4d91e33..878cedf827d 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -105,10 +105,9 @@ fn rpc_parity_set_min_gas_price() { io.extend_with(parity_set_client(&client, &miner, &updater, &network).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "parity_setMinGasPrice", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - // assert_eq!(miner.minimal_gas_price(), U256::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); } #[test] @@ -189,10 +188,9 @@ fn rpc_parity_set_transactions_limit() { io.extend_with(parity_set_client(&client, &miner, &updater, &network).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "parity_setTransactionsLimit", "params":[10240240], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); - // assert_eq!(miner.transactions_limit(), 10_240_240); } #[test] diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index bb49d4423dc..071d8d181e0 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -412,6 +412,11 @@ impl Pool where senders: self.transactions.len(), } } + + /// Returns current pool options. + pub fn options(&self) -> Options { + self.options.clone() + } } /// An iterator over all pending (ready) transactions. diff --git a/transaction-pool/src/status.rs b/transaction-pool/src/status.rs index 5862f75a1eb..a03bc6b062a 100644 --- a/transaction-pool/src/status.rs +++ b/transaction-pool/src/status.rs @@ -16,7 +16,7 @@ /// Light pool status. /// This status is cheap to compute and can be called frequently. -#[derive(Default, Debug, PartialEq, Eq)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct LightStatus { /// Memory usage in bytes. pub mem_usage: usize, @@ -29,7 +29,7 @@ pub struct LightStatus { /// A full queue status. /// To compute this status it is required to provide `Ready`. /// NOTE: To compute the status we need to visit each transaction in the pool. -#[derive(Default, Debug, PartialEq, Eq)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct Status { /// Number of stalled transactions. pub stalled: usize, From fc4a0aab9be93b3bd2306d85826062293ff6809c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 15:29:52 +0100 Subject: [PATCH 16/77] Add transaction queue listeners. --- ethcore/src/miner/miner.rs | 21 +- ethcore/src/miner/mod.rs | 4 +- miner/src/lib.rs | 1 - miner/src/pool/listener.rs | 147 +++++++++++++ miner/src/{ => pool}/local_transactions.rs | 235 ++++++++++++--------- miner/src/pool/mod.rs | 11 + miner/src/pool/queue.rs | 37 ++-- rpc/src/v1/impls/parity.rs | 15 +- rpc/src/v1/tests/helpers/miner_service.rs | 8 +- rpc/src/v1/tests/mocked/parity.rs | 17 +- rpc/src/v1/types/transaction.rs | 40 ++-- transaction-pool/src/listener.rs | 37 +++- transaction-pool/src/pool.rs | 16 +- 13 files changed, 427 insertions(+), 162 deletions(-) create mode 100644 miner/src/pool/listener.rs rename miner/src/{ => pool}/local_transactions.rs (50%) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index f06f96b946d..03902787938 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -186,8 +186,6 @@ pub struct Miner { options: MinerOptions, // TODO [ToDr] Arc is only required because of price updater transaction_queue: Arc, - // TODO [ToDr] move listeners to queue - transaction_listener: RwLock>>, engine: Arc, accounts: Option>, } @@ -206,7 +204,7 @@ impl Miner { /// Set a callback to be notified about imported transactions' hashes. pub fn add_transactions_listener(&self, f: Box) { - self.transaction_listener.write().push(f); + self.transaction_queue.add_listener(f); } /// Creates new instance of miner Arc. @@ -228,7 +226,6 @@ impl Miner { gas_pricer: Mutex::new(gas_pricer), options, transaction_queue: Arc::new(TransactionQueue::new(limits, verifier_options)), - transaction_listener: RwLock::new(vec![]), accounts, engine: spec.engine.clone(), } @@ -418,6 +415,7 @@ impl Miner { // Penalize transaction if it's above current gas limit if gas > gas_limit { + debug!(target: "txqueue", "[{:?}] Transaction above block gas limit.", hash); invalid_transactions.insert(hash); } @@ -446,10 +444,11 @@ impl Miner { debug!(target: "miner", "Skipping non-allowed transaction for sender {:?}", hash); }, Err(e) => { - invalid_transactions.insert(hash); + debug!(target: "txqueue", "[{:?}] Marking as invalid: {:?}.", hash, e); debug!( target: "miner", "Error adding transaction to block: number={}. transaction_hash={:?}, Error: {:?}", block_number, hash, e ); + invalid_transactions.insert(hash); }, // imported ok _ => tx_count += 1, @@ -488,7 +487,7 @@ impl Miner { // keep sealing enabled if any of the conditions is met let sealing_enabled = self.forced_sealing() - || self.transaction_queue.has_local_transactions() + || self.transaction_queue.has_local_pending_transactions() || self.engine.seals_internally().is_some() || had_requests; @@ -779,13 +778,9 @@ impl MinerService for Miner { imported } - // fn local_transactions(&self) -> BTreeMap { - // let queue = self.transaction_queue.read(); - // queue.local_transactions() - // .iter() - // .map(|(hash, status)| (*hash, status.clone())) - // .collect() - // } + fn local_transactions(&self) -> BTreeMap { + self.transaction_queue.local_transactions() + } fn future_transactions(&self) -> Vec> { unimplemented!() diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index dd843fde1e8..f34f57aaa57 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -31,7 +31,7 @@ use std::collections::BTreeMap; use bytes::Bytes; use ethereum_types::{H256, U256, Address}; -use ethcore_miner::pool::{VerifiedTransaction, QueueStatus}; +use ethcore_miner::pool::{VerifiedTransaction, QueueStatus, local_transactions}; use block::SealedBlock; use client::{MiningBlockChainClient}; @@ -132,7 +132,7 @@ pub trait MinerService : Send + Sync { fn future_transactions(&self) -> Vec>; /// Get a list of local transactions with statuses. - // fn local_transactions(&self) -> BTreeMap; + fn local_transactions(&self) -> BTreeMap; /// Get current queue status. /// diff --git a/miner/src/lib.rs b/miner/src/lib.rs index d8a3fdd77c1..226c7ebda86 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -50,7 +50,6 @@ extern crate rustc_hex; // pub mod banning_queue; pub mod external; pub mod gas_pricer; -pub mod local_transactions; pub mod pool; pub mod service_transaction_checker; // pub mod transaction_queue; diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs new file mode 100644 index 00000000000..a62322ae60e --- /dev/null +++ b/miner/src/pool/listener.rs @@ -0,0 +1,147 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +//! Notifier for new transaction hashes. + +use std::fmt; +use std::sync::Arc; + +use ethereum_types::H256; +use txpool::{self, VerifiedTransaction}; + +use pool::VerifiedTransaction as Transaction; + +type Listener = Box; + +/// Manages notifications to pending transaction listeners. +#[derive(Default)] +pub struct Notifier { + listeners: Vec, + pending: Vec, +} + +impl fmt::Debug for Notifier { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("Notifier") + .field("listeners", &self.listeners.len()) + .field("pending", &self.pending) + .finish() + } +} + +impl Notifier { + /// Add new listener to receive notifications. + pub fn add(&mut self, f: Listener) { + self.listeners.push(f) + } + + /// Notify listeners about all currently pending transactions. + pub fn notify(&mut self) { + for l in &self.listeners { + (l)(&self.pending); + } + + self.pending.clear(); + } +} + +impl txpool::Listener for Notifier { + fn added(&mut self, tx: &Arc, _old: Option<&Arc>) { + self.pending.push(*tx.hash()); + } +} + + +/// Transaction pool logger. +#[derive(Default, Debug)] +pub struct Logger; + +impl txpool::Listener for Logger { + fn added(&mut self, tx: &Arc, old: Option<&Arc>) { + debug!(target: "txqueue", "[{:?}] Added to the pool.", tx.hash()); + + if let Some(old) = old { + debug!(target: "txqueue", "[{:?}] Dropped. Replaced by [{:?}]", old.hash(), tx.hash()); + } + } + + fn rejected(&mut self, tx: &Arc) { + debug!(target: "txqueue", "[{:?}] Rejected. Too cheap to enter.", tx.hash()); + } + + fn dropped(&mut self, tx: &Arc) { + debug!(target: "txqueue", "[{:?}] Dropped because of limit.", tx.hash()); + } + + fn invalid(&mut self, tx: &Arc) { + debug!(target: "txqueue", "[{:?}] Marked as invalid by executor.", tx.hash()); + } + + fn cancelled(&mut self, tx: &Arc) { + debug!(target: "txqueue", "[{:?}] Cancelled by the user.", tx.hash()); + } + + fn mined(&mut self, tx: &Arc) { + debug!(target: "txqueue", "[{:?}] Mined.", tx.hash()); + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use parking_lot::Mutex; + use transaction; + use txpool::Listener; + + #[test] + fn should_notify_listeners() { + // given + let received = Arc::new(Mutex::new(vec![])); + let r = received.clone(); + let listener = Box::new(move |hashes: &[H256]| { + *r.lock() = hashes.iter().map(|x| *x).collect(); + }); + + let mut tx_listener = Notifier::default(); + tx_listener.add(listener); + + // when + let tx = new_tx(); + tx_listener.added(&tx, None); + assert_eq!(*received.lock(), vec![]); + + // then + tx_listener.notify(); + assert_eq!( + *received.lock(), + vec!["13aff4201ac1dc49daf6a7cf07b558ed956511acbaabf9502bdacc353953766d".parse().unwrap()] + ); + } + + fn new_tx() -> Arc { + let signed = transaction::Transaction { + action: transaction::Action::Create, + data: vec![1, 2, 3], + nonce: 5.into(), + gas: 21_000.into(), + gas_price: 5.into(), + value: 0.into(), + }.fake_sign(5.into()); + + Arc::new(Transaction::from_pending_block_transaction(signed)) + } +} diff --git a/miner/src/local_transactions.rs b/miner/src/pool/local_transactions.rs similarity index 50% rename from miner/src/local_transactions.rs rename to miner/src/pool/local_transactions.rs index e8d1988a4f6..b3ad30d1e03 100644 --- a/miner/src/local_transactions.rs +++ b/miner/src/pool/local_transactions.rs @@ -16,9 +16,12 @@ //! Local Transactions List. -use ethereum_types::{H256, U256}; +use std::sync::Arc; + +use ethereum_types::H256; use linked_hash_map::LinkedHashMap; -use transaction::{self, SignedTransaction, PendingTransaction}; +use pool::VerifiedTransaction as Transaction; +use txpool::{self, VerifiedTransaction}; /// Status of local transaction. /// Can indicate that the transaction is currently part of the queue (`Pending/Future`) @@ -26,26 +29,33 @@ use transaction::{self, SignedTransaction, PendingTransaction}; #[derive(Debug, PartialEq, Clone)] pub enum Status { /// The transaction is currently in the transaction queue. - Pending, - /// The transaction is in future part of the queue. - Future, + Pending(Arc), /// Transaction is already mined. - Mined(SignedTransaction), + Mined(Arc), /// Transaction is dropped because of limit - Dropped(SignedTransaction), + Dropped(Arc), /// Replaced because of higher gas price of another transaction. - Replaced(SignedTransaction, U256, H256), + Replaced { + /// Transaction object + tx: Arc, + /// Transaction that replaced this one. + by: Arc, + }, /// Transaction was never accepted to the queue. - Rejected(SignedTransaction, transaction::Error), + /// It means that it was too cheap to replace any transaction already in the pool. + Rejected(Arc), /// Transaction is invalid. - Invalid(SignedTransaction), + Invalid(Arc), /// Transaction was canceled. - Canceled(PendingTransaction), + Canceled(Arc), } impl Status { - fn is_current(&self) -> bool { - *self == Status::Pending || *self == Status::Future + fn is_pending(&self) -> bool { + match *self { + Status::Pending(_) => true, + _ => false, + } } } @@ -66,67 +76,11 @@ impl LocalTransactionsList { /// Create a new list of local transactions. pub fn new(max_old: usize) -> Self { LocalTransactionsList { - max_old: max_old, + max_old, transactions: Default::default(), } } - /// Mark transaction with given hash as pending. - pub fn mark_pending(&mut self, hash: H256) { - debug!(target: "own_tx", "Imported to Current (hash {:?})", hash); - self.clear_old(); - self.transactions.insert(hash, Status::Pending); - } - - /// Mark transaction with given hash as future. - pub fn mark_future(&mut self, hash: H256) { - debug!(target: "own_tx", "Imported to Future (hash {:?})", hash); - self.transactions.insert(hash, Status::Future); - self.clear_old(); - } - - /// Mark given transaction as rejected from the queue. - pub fn mark_rejected(&mut self, tx: SignedTransaction, err: transaction::Error) { - debug!(target: "own_tx", "Transaction rejected (hash {:?}): {:?}", tx.hash(), err); - self.transactions.insert(tx.hash(), Status::Rejected(tx, err)); - self.clear_old(); - } - - /// Mark the transaction as replaced by transaction with given hash. - pub fn mark_replaced(&mut self, tx: SignedTransaction, gas_price: U256, hash: H256) { - debug!(target: "own_tx", "Transaction replaced (hash {:?}) by {:?} (new gas price: {:?})", tx.hash(), hash, gas_price); - self.transactions.insert(tx.hash(), Status::Replaced(tx, gas_price, hash)); - self.clear_old(); - } - - /// Mark transaction as invalid. - pub fn mark_invalid(&mut self, tx: SignedTransaction) { - warn!(target: "own_tx", "Transaction marked invalid (hash {:?})", tx.hash()); - self.transactions.insert(tx.hash(), Status::Invalid(tx)); - self.clear_old(); - } - - /// Mark transaction as canceled. - pub fn mark_canceled(&mut self, tx: PendingTransaction) { - warn!(target: "own_tx", "Transaction canceled (hash {:?})", tx.hash()); - self.transactions.insert(tx.hash(), Status::Canceled(tx)); - self.clear_old(); - } - - /// Mark transaction as dropped because of limit. - pub fn mark_dropped(&mut self, tx: SignedTransaction) { - warn!(target: "own_tx", "Transaction dropped (hash {:?})", tx.hash()); - self.transactions.insert(tx.hash(), Status::Dropped(tx)); - self.clear_old(); - } - - /// Mark transaction as mined. - pub fn mark_mined(&mut self, tx: SignedTransaction) { - info!(target: "own_tx", "Transaction mined (hash {:?})", tx.hash()); - self.transactions.insert(tx.hash(), Status::Mined(tx)); - self.clear_old(); - } - /// Returns true if the transaction is already in local transactions. pub fn contains(&self, hash: &H256) -> bool { self.transactions.contains_key(hash) @@ -137,10 +91,17 @@ impl LocalTransactionsList { &self.transactions } + /// Returns true if there are pending local transactions. + pub fn has_pending(&self) -> bool { + self.transactions + .values() + .any(|status| status.is_pending()) + } + fn clear_old(&mut self) { let number_of_old = self.transactions .values() - .filter(|status| !status.is_current()) + .filter(|status| !status.is_pending()) .count(); if self.max_old >= number_of_old { @@ -149,7 +110,7 @@ impl LocalTransactionsList { let to_remove = self.transactions .iter() - .filter(|&(_, status)| !status.is_current()) + .filter(|&(_, status)| !status.is_pending()) .map(|(hash, _)| *hash) .take(number_of_old - self.max_old) .collect::>(); @@ -160,61 +121,145 @@ impl LocalTransactionsList { } } +impl txpool::Listener for LocalTransactionsList { + fn added(&mut self, tx: &Arc, old: Option<&Arc>) { + if !tx.priority().is_local() { + return; + } + + debug!(target: "own_tx", "Imported to the pool (hash {:?})", tx.hash()); + self.clear_old(); + self.transactions.insert(*tx.hash(), Status::Pending(tx.clone())); + + if let Some(old) = old { + if self.transactions.contains_key(old.hash()) { + self.transactions.insert(*old.hash(), Status::Replaced { + tx: old.clone(), + by: tx.clone(), + }); + } + } + } + + fn rejected(&mut self, tx: &Arc) { + if !tx.priority().is_local() { + return; + } + + debug!(target: "own_tx", "Transaction rejected because it was too cheap (hash {:?})", tx.hash()); + self.transactions.insert(*tx.hash(), Status::Rejected(tx.clone())); + self.clear_old(); + } + + fn dropped(&mut self, tx: &Arc) { + if !tx.priority().is_local() { + return; + } + + warn!(target: "own_tx", "Transaction dropped because of limit (hash {:?})", tx.hash()); + self.transactions.insert(*tx.hash(), Status::Dropped(tx.clone())); + self.clear_old(); + } + + fn invalid(&mut self, tx: &Arc) { + if !tx.priority().is_local() { + return; + } + + warn!(target: "own_tx", "Transaction marked invalid (hash {:?})", tx.hash()); + self.transactions.insert(*tx.hash(), Status::Invalid(tx.clone())); + self.clear_old(); + } + + fn cancelled(&mut self, tx: &Arc) { + if !tx.priority().is_local() { + return; + } + + warn!(target: "own_tx", "Transaction canceled (hash {:?})", tx.hash()); + self.transactions.insert(*tx.hash(), Status::Canceled(tx.clone())); + self.clear_old(); + } + + + /// The transaction has been mined. + fn mined(&mut self, tx: &Arc) { + if !tx.priority().is_local() { + return; + } + + info!(target: "own_tx", "Transaction mined (hash {:?})", tx.hash()); + self.transactions.insert(*tx.hash(), Status::Mined(tx.clone())); + self.clear_old(); + } +} + #[cfg(test)] mod tests { use super::*; use ethereum_types::U256; use ethkey::{Random, Generator}; + use transaction; + use txpool::Listener; + + use pool; #[test] fn should_add_transaction_as_pending() { // given let mut list = LocalTransactionsList::default(); + let tx1 = new_tx(10); + let tx2 = new_tx(20); // when - list.mark_pending(10.into()); - list.mark_future(20.into()); + list.added(&tx1, None); + list.added(&tx2, None); // then - assert!(list.contains(&10.into()), "Should contain the transaction."); - assert!(list.contains(&20.into()), "Should contain the transaction."); + assert!(list.contains(tx1.hash()), "Should contain the transaction."); + assert!(list.contains(tx2.hash()), "Should contain the transaction."); let statuses = list.all_transactions().values().cloned().collect::>(); - assert_eq!(statuses, vec![Status::Pending, Status::Future]); + assert_eq!(statuses, vec![Status::Pending(tx1), Status::Pending(tx2)]); } #[test] fn should_clear_old_transactions() { // given let mut list = LocalTransactionsList::new(1); - let tx1 = new_tx(10.into()); - let tx1_hash = tx1.hash(); - let tx2 = new_tx(50.into()); - let tx2_hash = tx2.hash(); - - list.mark_pending(10.into()); - list.mark_invalid(tx1); - list.mark_dropped(tx2); - assert!(list.contains(&tx2_hash)); - assert!(!list.contains(&tx1_hash)); - assert!(list.contains(&10.into())); + let tx1 = new_tx(10); + let tx2 = new_tx(50); + let tx3 = new_tx(51); + + list.added(&tx1, None); + list.invalid(&tx1); + list.dropped(&tx2); + assert!(!list.contains(tx1.hash())); + assert!(list.contains(tx2.hash())); + assert!(!list.contains(tx3.hash())); // when - list.mark_future(15.into()); + list.added(&tx3, Some(&tx1)); // then - assert!(list.contains(&10.into())); - assert!(list.contains(&15.into())); + assert!(!list.contains(tx1.hash())); + assert!(list.contains(tx2.hash())); + assert!(list.contains(tx3.hash())); } - fn new_tx(nonce: U256) -> SignedTransaction { + fn new_tx>(nonce: T) -> Arc { let keypair = Random.generate().unwrap(); - transaction::Transaction { + let signed = transaction::Transaction { action: transaction::Action::Create, value: U256::from(100), data: Default::default(), gas: U256::from(10), gas_price: U256::from(1245), - nonce: nonce - }.sign(keypair.secret(), None) + nonce: nonce.into(), + }.sign(keypair.secret(), None); + + let mut tx = Transaction::from_pending_block_transaction(signed); + tx.priority = pool::Priority::Local; + + Arc::new(tx) } } diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 81e476643e3..745bb4e038e 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -22,6 +22,8 @@ use transaction; use txpool; pub mod client; +pub mod listener; +pub mod local_transactions; pub mod queue; pub mod ready; pub mod scoring; @@ -37,6 +39,15 @@ pub(crate) enum Priority { Regular, } +impl Priority { + fn is_local(&self) -> bool { + match *self { + Priority::Local => true, + _ => false, + } + } +} + /// Verified transaction stored in the pool. #[derive(Debug, PartialEq, Eq)] pub struct VerifiedTransaction { diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 470f7f31f6d..3c7ffc1a555 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -18,17 +18,18 @@ use std::sync::Arc; use std::sync::atomic::AtomicUsize; +use std::collections::BTreeMap; use ethereum_types::{H256, U256, Address}; use parking_lot::RwLock; use transaction; use txpool::{self, Verifier}; -use pool::{self, scoring, verifier, client, ready}; - -// TODO [ToDr] Support logging listener (and custom listeners) -type Pool = txpool::Pool; +use pool::{self, scoring, verifier, client, ready, listener}; +use pool::local_transactions::LocalTransactionsList; +type Listener = (LocalTransactionsList, (listener::Notifier, listener::Logger)); +type Pool = txpool::Pool; /// Transaction queue status #[derive(Debug, Clone, PartialEq)] @@ -59,7 +60,7 @@ impl TransactionQueue { pub fn new(limits: txpool::Options, verification_options: verifier::Options) -> Self { TransactionQueue { insertion_id: Default::default(), - pool: RwLock::new(txpool::Pool::with_scoring(scoring::GasPrice, limits)), + pool: RwLock::new(txpool::Pool::new(Default::default(), scoring::GasPrice, limits)), options: RwLock::new(verification_options), } } @@ -115,7 +116,7 @@ impl TransactionQueue { pool::VerifiedTransaction, (ready::Condition, ready::State), scoring::GasPrice, - txpool::NoopListener + Listener, >) -> T, { let pending_readiness = ready::Condition::new(block_number, current_timestamp); @@ -215,9 +216,19 @@ impl TransactionQueue { /// /// Local transactions are the ones from accounts managed by this node /// and transactions submitted via local RPC (`eth_sendRawTransaction`) - pub fn has_local_transactions(&self) -> bool { - // TODO [ToDr] Take from the listener - false + pub fn has_local_pending_transactions(&self) -> bool { + self.pool.read().listener().0.has_pending() + } + + /// Returns status of recently seen local transactions. + pub fn local_transactions(&self) -> BTreeMap { + self.pool.read().listener().0.all_transactions().iter().map(|(a, b)| (*a, b.clone())).collect() + } + + /// Add a callback to be notified about all transactions entering the pool. + pub fn add_listener(&self, f: Box) { + let mut pool = self.pool.write(); + (pool.listener_mut().1).0.add(f); } } @@ -228,7 +239,7 @@ mod tests { #[derive(Debug)] struct TestClient; impl client::Client for TestClient { - fn transaction_already_included(&self, hash: &H256) -> bool { + fn transaction_already_included(&self, _hash: &H256) -> bool { false } @@ -256,7 +267,7 @@ mod tests { } /// Classify transaction (check if transaction is filtered by some contracts). - fn transaction_type(&self, tx: &transaction::SignedTransaction) -> client::TransactionType { + fn transaction_type(&self, _tx: &transaction::SignedTransaction) -> client::TransactionType { client::TransactionType::Regular } } @@ -265,9 +276,9 @@ mod tests { fn should_get_pending_transactions() { let queue = TransactionQueue::new(txpool::Options::default(), verifier::Options::default()); - let mut pending = queue.pending(TestClient, 0, 0); + let pending: Vec<_> = queue.pending(TestClient, 0, 0, |x| x.collect()); - for tx in pending.transactions() { + for tx in pending { assert!(tx.signed().nonce > 0.into()); } } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 5a71640b8c8..0c27bb97eb6 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -328,14 +328,13 @@ impl Parity for ParityClient where return Ok(BTreeMap::new()); } - unimplemented!() - // let transactions = self.miner.local_transactions(); - // let block_number = self.client.chain_info().best_block_number; - // Ok(transactions - // .into_iter() - // .map(|(hash, status)| (hash.into(), LocalTransactionStatus::from(status, block_number, self.eip86_transition))) - // .collect() - // ) + let transactions = self.miner.local_transactions(); + let block_number = self.client.chain_info().best_block_number; + Ok(transactions + .into_iter() + .map(|(hash, status)| (hash.into(), LocalTransactionStatus::from(status, block_number, self.eip86_transition))) + .collect() + ) } fn dapps_url(&self) -> Result { diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index c289ad38997..c2d073e2bcc 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -29,7 +29,7 @@ use ethcore::miner::{MinerService, AuthoringParams}; use ethcore::receipt::{Receipt, RichReceipt}; use ethereum_types::{H256, U256, Address}; use miner::pool::{verifier, VerifiedTransaction, QueueStatus}; -use miner::local_transactions::Status as LocalTransactionStatus; +use miner::pool::local_transactions::Status as LocalTransactionStatus; use txpool; use parking_lot::{RwLock, Mutex}; use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction}; @@ -169,9 +169,9 @@ impl MinerService for TestMinerService { Some(self.pending_transactions.lock().values().cloned().collect()) } - // fn local_transactions(&self) -> BTreeMap { - // self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() - // } + fn local_transactions(&self) -> BTreeMap { + self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() + } fn ready_transactions(&self, _chain: &MiningBlockChainClient) -> Vec> { self.pending_transactions.lock().values().cloned().map(|tx| { diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 0650dd31c2c..5b17605121c 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -21,7 +21,7 @@ use ethcore_logger::RotatingLogger; use ethereum_types::{Address, U256, H256}; use ethstore::ethkey::{Generator, Random}; use ethsync::ManageNetwork; -use miner::local_transactions::Status as LocalTransactionStatus; +use miner::pool::local_transactions::Status as LocalTransactionStatus; use node_health::{self, NodeHealth}; use parity_reactor; @@ -488,11 +488,20 @@ fn rpc_parity_transactions_stats() { fn rpc_parity_local_transactions() { let deps = Dependencies::new(); let io = deps.default_client(); - deps.miner.local_transactions.lock().insert(10.into(), LocalTransactionStatus::Pending); - deps.miner.local_transactions.lock().insert(15.into(), LocalTransactionStatus::Future); + let tx = ::transaction::Transaction { + value: 5.into(), + gas: 3.into(), + gas_price: 2.into(), + action: ::transaction::Action::Create, + data: vec![1, 2, 3], + nonce: 0.into(), + }.fake_sign(3.into()); + let tx = Arc::new(::miner::pool::VerifiedTransaction::from_pending_block_transaction(tx)); + deps.miner.local_transactions.lock().insert(10.into(), LocalTransactionStatus::Pending(tx.clone())); + deps.miner.local_transactions.lock().insert(15.into(), LocalTransactionStatus::Pending(tx.clone())); let request = r#"{"jsonrpc": "2.0", "method": "parity_localTransactions", "params":[], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":{"0x000000000000000000000000000000000000000000000000000000000000000a":{"status":"pending"},"0x000000000000000000000000000000000000000000000000000000000000000f":{"status":"future"}},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"0x000000000000000000000000000000000000000000000000000000000000000a":{"status":"pending"},"0x000000000000000000000000000000000000000000000000000000000000000f":{"status":"pending"}},"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 2796e3f1842..3329b944a18 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -14,12 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::sync::Arc; + use serde::{Serialize, Serializer}; use serde::ser::SerializeStruct; use ethcore::{contract_address, CreateContractAddress}; use miner; use transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; -use v1::helpers::errors; use v1::types::{Bytes, H160, H256, U256, H512, U64, TransactionCondition}; /// Transaction @@ -88,7 +89,7 @@ pub enum LocalTransactionStatus { /// Transaction was replaced by transaction with higher gas price. Replaced(Transaction, U256, H256), /// Transaction never got into the queue. - Rejected(Transaction, String), + Rejected(Transaction), /// Transaction is invalid. Invalid(Transaction), /// Transaction was canceled. @@ -131,10 +132,9 @@ impl Serialize for LocalTransactionStatus { struc.serialize_field(status, "invalid")?; struc.serialize_field(transaction, tx)?; }, - Rejected(ref tx, ref reason) => { + Rejected(ref tx) => { struc.serialize_field(status, "rejected")?; struc.serialize_field(transaction, tx)?; - struc.serialize_field("error", reason)?; }, Replaced(ref tx, ref gas_price, ref hash) => { struc.serialize_field(status, "replaced")?; @@ -248,17 +248,23 @@ impl Transaction { impl LocalTransactionStatus { /// Convert `LocalTransactionStatus` into RPC `LocalTransactionStatus`. - pub fn from(s: miner::local_transactions::Status, block_number: u64, eip86_transition: u64) -> Self { - use miner::local_transactions::Status::*; + pub fn from(s: miner::pool::local_transactions::Status, block_number: u64, eip86_transition: u64) -> Self { + let convert = |tx: Arc| { + Transaction::from_signed(tx.signed().clone(), block_number, eip86_transition) + }; + use miner::pool::local_transactions::Status::*; match s { - Pending => LocalTransactionStatus::Pending, - Future => LocalTransactionStatus::Future, - Mined(tx) => LocalTransactionStatus::Mined(Transaction::from_signed(tx, block_number, eip86_transition)), - Dropped(tx) => LocalTransactionStatus::Dropped(Transaction::from_signed(tx, block_number, eip86_transition)), - Rejected(tx, err) => LocalTransactionStatus::Rejected(Transaction::from_signed(tx, block_number, eip86_transition), errors::transaction_message(err)), - Replaced(tx, gas_price, hash) => LocalTransactionStatus::Replaced(Transaction::from_signed(tx, block_number, eip86_transition), gas_price.into(), hash.into()), - Invalid(tx) => LocalTransactionStatus::Invalid(Transaction::from_signed(tx, block_number, eip86_transition)), - Canceled(tx) => LocalTransactionStatus::Canceled(Transaction::from_pending(tx, block_number, eip86_transition)), + Pending(_) => LocalTransactionStatus::Pending, + Mined(tx) => LocalTransactionStatus::Mined(convert(tx)), + Dropped(tx) => LocalTransactionStatus::Dropped(convert(tx)), + Rejected(tx) => LocalTransactionStatus::Rejected(convert(tx)), + Invalid(tx) => LocalTransactionStatus::Invalid(convert(tx)), + Canceled(tx) => LocalTransactionStatus::Canceled(convert(tx)), + Replaced { tx, by } => LocalTransactionStatus::Replaced( + convert(tx), + by.signed().gas_price.into(), + by.signed().hash().into(), + ), } } } @@ -283,7 +289,7 @@ mod tests { let status3 = LocalTransactionStatus::Mined(Transaction::default()); let status4 = LocalTransactionStatus::Dropped(Transaction::default()); let status5 = LocalTransactionStatus::Invalid(Transaction::default()); - let status6 = LocalTransactionStatus::Rejected(Transaction::default(), "Just because".into()); + let status6 = LocalTransactionStatus::Rejected(Transaction::default()); let status7 = LocalTransactionStatus::Replaced(Transaction::default(), 5.into(), 10.into()); assert_eq!( @@ -308,9 +314,7 @@ mod tests { ); assert_eq!( serde_json::to_string(&status6).unwrap(), - r#"{"status":"rejected","transaction":"#.to_owned() + - &format!("{}", tx_ser) + - r#","error":"Just because"}"# + r#"{"status":"rejected","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# ); assert_eq!( serde_json::to_string(&status7).unwrap(), diff --git a/transaction-pool/src/listener.rs b/transaction-pool/src/listener.rs index 2fc55528fe8..b6a9788ab5b 100644 --- a/transaction-pool/src/listener.rs +++ b/transaction-pool/src/listener.rs @@ -28,7 +28,7 @@ pub trait Listener { /// The transaction was rejected from the pool. /// It means that it was too cheap to replace any transaction already in the pool. - fn rejected(&mut self, _tx: T) {} + fn rejected(&mut self, _tx: &Arc) {} /// The transaction was dropped from the pool because of a limit. fn dropped(&mut self, _tx: &Arc) {} @@ -47,3 +47,38 @@ pub trait Listener { #[derive(Debug)] pub struct NoopListener; impl Listener for NoopListener {} + +impl Listener for (A, B) where + A: Listener, + B: Listener, +{ + fn added(&mut self, tx: &Arc, old: Option<&Arc>) { + self.0.added(tx, old); + self.1.added(tx, old); + } + + fn rejected(&mut self, tx: &Arc) { + self.0.rejected(tx); + self.1.rejected(tx); + } + + fn dropped(&mut self, tx: &Arc) { + self.0.rejected(tx); + self.1.rejected(tx); + } + + fn invalid(&mut self, tx: &Arc) { + self.0.invalid(tx); + self.1.invalid(tx); + } + + fn cancelled(&mut self, tx: &Arc) { + self.0.cancelled(tx); + self.1.cancelled(tx); + } + + fn mined(&mut self, tx: &Arc) { + self.0.mined(tx); + self.1.mined(tx); + } +} diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 071d8d181e0..cc7e07d0aed 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -115,7 +115,7 @@ impl Pool where let remove_worst = |s: &mut Self, transaction| { match s.remove_worst(&transaction) { Err(err) => { - s.listener.rejected(transaction); + s.listener.rejected(&Arc::new(transaction)); Err(err) }, Ok(removed) => { @@ -161,12 +161,12 @@ impl Pool where }, AddResult::TooCheap { new, old } => { let hash = *new.hash(); - self.listener.rejected(new); + self.listener.rejected(&Arc::new(new)); bail!(error::ErrorKind::TooCheapToReplace(*old.hash(), hash)) }, AddResult::TooCheapToEnter(new) => { let hash = *new.hash(); - self.listener.rejected(new); + self.listener.rejected(&Arc::new(new)); bail!(error::ErrorKind::TooCheapToEnter(hash)) } } @@ -417,6 +417,16 @@ impl Pool where pub fn options(&self) -> Options { self.options.clone() } + + /// Borrows listener instance. + pub fn listener(&self) -> &L { + &self.listener + } + + /// Borrows listener mutably. + pub fn listener_mut(&mut self) -> &mut L { + &mut self.listener + } } /// An iterator over all pending (ready) transactions. From e595e836f81aa20ce2e11676abe77aea0d25ab06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 18:05:25 +0100 Subject: [PATCH 17/77] Compiles! --- ethcore/src/miner/miner.rs | 95 ++++++++++++++++++++++---------------- miner/src/pool/mod.rs | 14 +++++- miner/src/pool/queue.rs | 17 ++++++- parity/blockchain.rs | 4 +- parity/configuration.rs | 77 +++++++++++++++++------------- parity/helpers.rs | 16 ++----- parity/params.rs | 36 ++++++--------- parity/run.rs | 41 ++++++++-------- 8 files changed, 172 insertions(+), 128 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 03902787938..455e4e207c0 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -54,9 +54,10 @@ pub enum PendingSet { /// Always just the transactions in the queue. These have had only cheap checks. AlwaysQueue, /// Always just the transactions in the sealing block. These have had full checks but - /// may be empty if the node is not actively mining or has force_sealing enabled. + /// may be empty if the node is not actively mining or has no force_sealing enabled. AlwaysSealing, - // TODO [ToDr] Enable mining if AlwaysSealing + /// Takes from sealing if mining, from queue otherwise. + SealingOrElseQueue, } // /// Transaction queue banning settings. @@ -107,8 +108,6 @@ pub struct MinerOptions { // / Strategy to use for prioritizing transactions in the queue. // pub tx_queue_strategy: PrioritizationStrategy, - // / Banning settings. - // pub tx_queue_banning: Banning, /// Do we refuse to accept service transactions even if sender is certified. pub refuse_service_transactions: bool, /// Transaction pool limits. @@ -246,15 +245,26 @@ impl Miner { }, GasPricer::new_fixed(minimal_gas_price), spec, accounts) } - fn forced_sealing(&self) -> bool { - self.options.force_sealing || !self.listeners.read().is_empty() - } - /// Clear all pending block states pub fn clear(&self) { self.sealing.lock().queue.reset(); } + /// Updates transaction queue verification limits. + /// + /// Limits consist of current block gas limit and minimal gas price. + pub fn update_transaction_queue_limits(&self, block_gas_limit: U256) { + debug!(target: "miner", "minimal_gas_price: recalibrating..."); + let txq = self.transaction_queue.clone(); + let mut options = self.options.pool_verification_options.clone(); + self.gas_pricer.lock().recalibrate(move |gas_price| { + debug!(target: "miner", "minimal_gas_price: Got gas price! {}", gas_price); + options.minimal_gas_price = gas_price; + options.block_gas_limit = block_gas_limit; + txq.set_verifier_options(options); + }); + } + /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. pub fn pending_state(&self, latest_block_number: BlockNumber) -> Option> { self.map_existing_pending_block(|b| b.state().clone(), latest_block_number) @@ -287,8 +297,10 @@ impl Miner { // TODO [ToDr] Get rid of this method. // // We should never fall back to client, this can be handled on RPC level by returning Option<> - fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H - where F: Fn() -> H, G: FnOnce(&ClosedBlock) -> H { + fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H where + F: Fn() -> H, + G: FnOnce(&ClosedBlock) -> H, + { let sealing = self.sealing.lock(); sealing.queue.peek_last_ref().map_or_else( || from_chain(), @@ -471,6 +483,10 @@ impl Miner { (block, original_work_hash) } + fn forced_sealing(&self) -> bool { + self.options.force_sealing || !self.listeners.read().is_empty() + } + /// Check is reseal is allowed and necessary. fn requires_reseal(&self, best_block: BlockNumber) -> bool { let mut sealing = self.sealing.lock(); @@ -620,18 +636,6 @@ impl Miner { } } - fn update_transaction_queue_limits(&self, block_gas_limit: U256) { - debug!(target: "miner", "minimal_gas_price: recalibrating..."); - let txq = self.transaction_queue.clone(); - let mut options = self.options.pool_verification_options.clone(); - self.gas_pricer.lock().recalibrate(move |gas_price| { - debug!(target: "miner", "minimal_gas_price: Got gas price! {}", gas_price); - options.minimal_gas_price = gas_price; - options.block_gas_limit = block_gas_limit; - txq.set_verifier_options(options); - }); - } - /// Returns true if we had to prepare new pending block. fn prepare_pending_block(&self, client: &MiningBlockChainClient) -> bool { trace!(target: "miner", "prepare_pending_block: entering"); @@ -789,27 +793,40 @@ impl MinerService for Miner { fn ready_transactions(&self, chain: &MiningBlockChainClient) -> Vec> { let chain_info = chain.chain_info(); - match self.options.pending_set { - PendingSet::AlwaysQueue => { - let client = self.client(chain); - self.transaction_queue.pending( - client, - chain_info.best_block_number, - chain_info.best_block_timestamp, - |transactions| transactions.collect(), + let from_queue = || { + let client = self.client(chain); + + self.transaction_queue.pending( + client, + chain_info.best_block_number, + chain_info.best_block_timestamp, + |transactions| transactions.collect(), + ) + }; + + let from_pending = || { + self.from_pending_block( + chain_info.best_block_number, + || None, + |sealing| Some(sealing.transactions() + .iter() + .map(|signed| pool::VerifiedTransaction::from_pending_block_transaction(signed.clone())) + .map(Arc::new) + .collect() ) + ) + }; + + match self.options.pending_set { + PendingSet::AlwaysQueue => { + from_queue() }, PendingSet::AlwaysSealing => { - self.from_pending_block( - chain_info.best_block_number, - Vec::new, - |sealing| sealing.transactions() - .iter() - .map(|signed| pool::VerifiedTransaction::from_pending_block_transaction(signed.clone())) - .map(Arc::new) - .collect() - ) + from_pending().unwrap_or_default() + }, + PendingSet::SealingOrElseQueue => { + from_pending().unwrap_or_else(from_queue) }, } } diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 745bb4e038e..84630a0a39c 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -32,6 +32,14 @@ pub mod verifier; pub use self::queue::{TransactionQueue, Status as QueueStatus}; pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; +// TODO [ToDr] Actually use that parameter and implement more strategies. + +/// How to prioritize transactions in the pool +pub enum PrioritizationStrategy { + /// Simple gas-price based prioritization. + GasPrice, +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(crate) enum Priority { Local, @@ -60,7 +68,11 @@ pub struct VerifiedTransaction { } impl VerifiedTransaction { - // Hack? + /// Create `VerifiedTransaction` directly from `SignedTransaction`. + /// + /// This method should be used only: + /// 1. for tests + /// 2. In case we are converting pending block transactions that are already in the queue to match function signature. pub fn from_pending_block_transaction(tx: transaction::SignedTransaction) -> Self { let hash = tx.hash(); let sender = tx.sender(); diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 3c7ffc1a555..26cf14d7ee1 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -92,7 +92,7 @@ impl TransactionQueue { .map(|result| match result { Ok(verified) => match self.pool.write().import(verified) { Ok(_imported) => Ok(()), - Err(txpool::Error(kind, _)) => unimplemented!(), + Err(err) => Err(convert_error(err)), }, Err(err) => Err(err), }) @@ -232,6 +232,21 @@ impl TransactionQueue { } } + +fn convert_error(err: txpool::Error) -> transaction::Error { + use self::txpool::ErrorKind; + + match *err.kind() { + ErrorKind::AlreadyImported(..) => transaction::Error::AlreadyImported, + ErrorKind::TooCheapToEnter(..) => transaction::Error::LimitReached, + ErrorKind::TooCheapToReplace(..) => transaction::Error::TooCheapToReplace, + ref e => { + warn!(target: "txqueue", "Unknown import error: {:?}", e); + transaction::Error::NotAllowed + }, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 05014720bcc..2aa722186ed 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -552,7 +552,9 @@ fn start_client( &client_path, &snapshot_path, &dirs.ipc_path(), - Arc::new(Miner::with_spec(&spec)), + // It's fine to use test version here, + // since we don't care about miner parameters at all + Arc::new(Miner::new_for_tests(&spec, None)), ).map_err(|e| format!("Client service error: {:?}", e))?; drop(spec); diff --git a/parity/configuration.rs b/parity/configuration.rs index 480fefe7da4..27069338759 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -30,15 +30,16 @@ use ansi_term::Colour; use ethsync::{NetworkConfiguration, validate_node_url, self}; use ethcore::ethstore::ethkey::{Secret, Public}; use ethcore::client::{VMType}; -use ethcore::miner::{MinerOptions, Banning, StratumOptions}; +use ethcore::miner::{stratum, MinerOptions}; use ethcore::verification::queue::VerifierSettings; +use miner::pool; use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration, UiConfiguration}; use rpc_apis::ApiSet; use parity_rpc::NetworkSettings; use cache::CacheConfig; use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, geth_ipc_path, parity_ipc_path, -to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy}; +to_bootnodes, to_addresses, to_address, to_queue_strategy}; use dir::helpers::{replace_home, replace_home_and_local}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, SpecType}; use ethcore_logger::Config as LogConfig; @@ -394,12 +395,14 @@ impl Configuration { } fn miner_extras(&self) -> Result { + let floor = to_u256(&self.args.arg_gas_floor_target)?; + let ceil = to_u256(&self.args.arg_gas_cap)?; let extras = MinerExtras { author: self.author()?, extra_data: self.extra_data()?, - gas_floor_target: to_u256(&self.args.arg_gas_floor_target)?, - gas_ceil_target: to_u256(&self.args.arg_gas_cap)?, + gas_range_target: (floor, ceil), engine_signer: self.engine_signer()?, + work_notify: self.work_notify(), }; Ok(extras) @@ -496,9 +499,9 @@ impl Configuration { Ok(cfg) } - fn stratum_options(&self) -> Result, String> { + fn stratum_options(&self) -> Result, String> { if self.args.flag_stratum { - Ok(Some(StratumOptions { + Ok(Some(stratum::Options { io_path: self.directories().db, listen_addr: self.stratum_interface(), port: self.args.arg_ports_shift + self.args.arg_stratum_port, @@ -513,44 +516,59 @@ impl Configuration { return Err("Force sealing can't be used with reseal_min_period = 0".into()); } + if let Some(_) = self.args.arg_tx_time_limit { + warn!("Banning is not available in this version."); + } + let reseal = self.args.arg_reseal_on_txs.parse::()?; let options = MinerOptions { - new_work_notify: self.work_notify(), force_sealing: self.args.flag_force_sealing, reseal_on_external_tx: reseal.external, reseal_on_own_tx: reseal.own, reseal_on_uncle: self.args.flag_reseal_on_uncle, - tx_gas_limit: match self.args.arg_tx_gas_limit { - Some(ref d) => to_u256(d)?, - None => U256::max_value(), - }, - tx_queue_size: self.args.arg_tx_queue_size, - tx_queue_memory_limit: if self.args.arg_tx_queue_mem_limit > 0 { - Some(self.args.arg_tx_queue_mem_limit as usize * 1024 * 1024) - } else { None }, - tx_queue_gas_limit: to_gas_limit(&self.args.arg_tx_queue_gas)?, - tx_queue_strategy: to_queue_strategy(&self.args.arg_tx_queue_strategy)?, - pending_set: to_pending_set(&self.args.arg_relay_set)?, reseal_min_period: Duration::from_millis(self.args.arg_reseal_min_period), reseal_max_period: Duration::from_millis(self.args.arg_reseal_max_period), + + pending_set: to_pending_set(&self.args.arg_relay_set)?, work_queue_size: self.args.arg_work_queue_size, enable_resubmission: !self.args.flag_remove_solved, - tx_queue_banning: match self.args.arg_tx_time_limit { - Some(limit) => Banning::Enabled { - min_offends: self.args.arg_tx_queue_ban_count, - offend_threshold: Duration::from_millis(limit), - ban_duration: Duration::from_secs(self.args.arg_tx_queue_ban_time as u64), - }, - None => Banning::Disabled, - }, - refuse_service_transactions: self.args.flag_refuse_service_transactions, infinite_pending_block: self.args.flag_infinite_pending_block, + + refuse_service_transactions: self.args.flag_refuse_service_transactions, + + pool_limits: self.pool_limits()?, + pool_verification_options: self.pool_verification_options()?, }; Ok(options) } + fn pool_limits(&self) -> Result { + Ok(pool::Options { + max_count: self.args.arg_tx_queue_size, + // TODO [ToDr] Add seperate parameter for that! + max_per_sender: self.args.arg_tx_queue_size, + max_mem_usage: if self.args.arg_tx_queue_mem_limit > 0 { + self.args.arg_tx_queue_mem_limit as usize * 1024 * 1024 + } else { + usize::max_value() + }, + }) + } + + fn pool_verification_options(&self) -> Result{ + Ok(pool::verifier::Options { + // NOTE min_gas_price and block_gas_limit will be overwritten right after start. + minimal_gas_price: U256::from(20_000_000) * 1_000u32, + block_gas_limit: to_u256(&self.args.arg_gas_floor_target)?, + tx_gas_limit: match self.args.arg_tx_gas_limit { + Some(ref d) => to_u256(d)?, + None => U256::max_value(), + }, + }) + } + fn ui_port(&self) -> u16 { self.args.arg_ports_shift + self.args.arg_ui_port } @@ -667,12 +685,7 @@ impl Configuration { let usd_per_tx = to_price(&self.args.arg_usd_per_tx)?; if "auto" == self.args.arg_usd_per_eth.as_str() { - // Just a very rough estimate to avoid accepting - // ZGP transactions before the price is fetched - // if user does not want it. - let last_known_usd_per_eth = 10.0; return Ok(GasPricerConfig::Calibrated { - initial_minimum: wei_per_gas(usd_per_tx, last_known_usd_per_eth), usd_per_tx: usd_per_tx, recalibration_period: to_duration(self.args.arg_price_update_period.as_str())?, }); diff --git a/parity/helpers.rs b/parity/helpers.rs index 959dddba92d..7e7d11c813c 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -22,8 +22,8 @@ use ethereum_types::{U256, clean_0x, Address}; use kvdb_rocksdb::CompactionProfile; use journaldb::Algorithm; use ethcore::client::{Mode, BlockId, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; -use ethcore::miner::{PendingSet, GasLimit}; -use miner::transaction_queue::PrioritizationStrategy; +use ethcore::miner::PendingSet; +use miner::pool::PrioritizationStrategy; use cache::CacheConfig; use dir::DatabaseDirectories; use dir::helpers::replace_home; @@ -97,19 +97,9 @@ pub fn to_pending_set(s: &str) -> Result { } } -pub fn to_gas_limit(s: &str) -> Result { - match s { - "auto" => Ok(GasLimit::Auto), - "off" => Ok(GasLimit::None), - other => Ok(GasLimit::Fixed(to_u256(other)?)), - } -} - pub fn to_queue_strategy(s: &str) -> Result { match s { - "gas" => Ok(PrioritizationStrategy::GasAndGasPrice), - "gas_price" => Ok(PrioritizationStrategy::GasPriceOnly), - "gas_factor" => Ok(PrioritizationStrategy::GasFactorAndGasPrice), + "gas_price" => Ok(PrioritizationStrategy::GasPrice), other => Err(format!("Invalid queue strategy: {}", other)), } } diff --git a/parity/params.rs b/parity/params.rs index ba750ae761b..3084dfffddf 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -16,14 +16,15 @@ use std::{str, fs, fmt}; use std::time::Duration; -use ethereum_types::{U256, Address}; -use parity_version::version_data; -use journaldb::Algorithm; -use ethcore::spec::{Spec, SpecParams}; -use ethcore::ethereum; + use ethcore::client::Mode; -use ethcore::miner::{GasPricer, GasPriceCalibratorOptions}; +use ethcore::ethereum; +use ethcore::spec::{Spec, SpecParams}; +use ethereum_types::{U256, Address}; use hash_fetch::fetch::Client as FetchClient; +use journaldb::Algorithm; +use miner::gas_pricer::{GasPricer, GasPriceCalibratorOptions}; +use parity_version::version_data; use user_defaults::UserDefaults; #[derive(Debug, PartialEq)] @@ -214,25 +215,14 @@ impl Default for AccountsConfig { pub enum GasPricerConfig { Fixed(U256), Calibrated { - initial_minimum: U256, usd_per_tx: f32, recalibration_period: Duration, } } -impl GasPricerConfig { - pub fn initial_min(&self) -> U256 { - match *self { - GasPricerConfig::Fixed(ref min) => min.clone(), - GasPricerConfig::Calibrated { ref initial_minimum, .. } => initial_minimum.clone(), - } - } -} - impl Default for GasPricerConfig { fn default() -> Self { GasPricerConfig::Calibrated { - initial_minimum: 476190464u64.into(), usd_per_tx: 0.0001f32, recalibration_period: Duration::from_secs(3600), } @@ -259,20 +249,20 @@ impl GasPricerConfig { #[derive(Debug, PartialEq)] pub struct MinerExtras { pub author: Address, - pub extra_data: Vec, - pub gas_floor_target: U256, - pub gas_ceil_target: U256, pub engine_signer: Address, + pub extra_data: Vec, + pub gas_range_target: (U256, U256), + pub work_notify: Vec, } impl Default for MinerExtras { fn default() -> Self { MinerExtras { author: Default::default(), - extra_data: version_data(), - gas_floor_target: U256::from(4_700_000), - gas_ceil_target: U256::from(6_283_184), engine_signer: Default::default(), + extra_data: version_data(), + gas_range_target: (4_700_000.into(), 6_283_184.into()), + work_notify: Default::default(), } } } diff --git a/parity/run.rs b/parity/run.rs index 4af7c80a980..7392cdf3ca3 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -25,8 +25,7 @@ use ctrlc::CtrlC; use ethcore::account_provider::{AccountProvider, AccountProviderSettings}; use ethcore::client::{Client, Mode, DatabaseCompactionProfile, VMType, BlockChainClient}; use ethcore::ethstore::ethkey; -use ethcore::miner::{Miner, MinerService, MinerOptions}; -use ethcore::miner::{StratumOptions, Stratum}; +use ethcore::miner::{stratum, Miner, MinerService, MinerOptions}; use ethcore::service::ClientService; use ethcore::snapshot; use ethcore::spec::{SpecParams, OptimizeFor}; @@ -120,7 +119,7 @@ pub struct RunCmd { pub ui: bool, pub name: String, pub custom_bootnodes: bool, - pub stratum: Option, + pub stratum: Option, pub no_periodic_snapshot: bool, pub check_seal: bool, pub download_old_blocks: bool, @@ -166,11 +165,12 @@ impl ::local_store::NodeInfo for FullNodeInfo { None => return Vec::new(), }; - let local_txs = miner.local_transactions(); - miner.pending_transactions() - .into_iter() - .chain(miner.future_transactions()) - .filter(|tx| local_txs.contains_key(&tx.hash())) + miner.local_transactions() + .values() + .filter_map(|status| match *status { + ::miner::pool::local_transactions::Status::Pending(ref tx) => Some(tx.pending().clone()), + _ => None, + }) .collect() } } @@ -520,16 +520,18 @@ pub fn execute_impl(cmd: RunCmd, can_restart: bool, logger: Arc) let fetch = FetchClient::new().map_err(|e| format!("Error starting fetch client: {:?}", e))?; // create miner - let initial_min_gas_price = cmd.gas_pricer_conf.initial_min(); - let miner = Arc::new(Miner::new(cmd.miner_options, cmd.gas_pricer_conf.to_gas_pricer(fetch.clone()), &spec, Some(account_provider.clone()))); - miner.set_author(cmd.miner_extras.author); - miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target); - miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); + let miner = Arc::new(Miner::new( + cmd.miner_options, + cmd.gas_pricer_conf.to_gas_pricer(fetch.clone()), + &spec, + Some(account_provider.clone()) + )); + miner.set_author(cmd.miner_extras.author, None).expect("Fails only if password is Some; password is None; qed"); + miner.set_gas_range_target(cmd.miner_extras.gas_range_target); miner.set_extra_data(cmd.miner_extras.extra_data); - miner.set_minimal_gas_price(initial_min_gas_price); - miner.recalibrate_minimal_gas_price(); - let engine_signer = cmd.miner_extras.engine_signer; + miner.add_work_listener_url(&cmd.miner_extras.work_notify); + let engine_signer = cmd.miner_extras.engine_signer; if engine_signer != Default::default() { // Check if engine signer exists if !account_provider.has_account(engine_signer).unwrap_or(false) { @@ -542,7 +544,7 @@ pub fn execute_impl(cmd: RunCmd, can_restart: bool, logger: Arc) } // Attempt to sign in the engine signer. - if !passwords.iter().any(|p| miner.set_engine_signer(engine_signer, (*p).clone()).is_ok()) { + if !passwords.iter().any(|p| miner.set_author(engine_signer, Some(p.to_owned())).is_ok()) { return Err(format!("No valid password for the consensus signer {}. {}", engine_signer, VERIFY_PASSWORD_HINT)); } } @@ -591,6 +593,9 @@ pub fn execute_impl(cmd: RunCmd, can_restart: bool, logger: Arc) // take handle to client let client = service.client(); + // Update miners block gas limit + miner.update_transaction_queue_limits(client.best_block_header().gas_limit()); + let connection_filter = connection_filter_address.map(|a| Arc::new(NodeFilter::new(Arc::downgrade(&client) as Weak, a))); let snapshot_service = service.snapshot_service(); @@ -637,7 +642,7 @@ pub fn execute_impl(cmd: RunCmd, can_restart: bool, logger: Arc) // start stratum if let Some(ref stratum_config) = cmd.stratum { - Stratum::register(stratum_config, miner.clone(), Arc::downgrade(&client)) + stratum::Stratum::register(stratum_config, miner.clone(), Arc::downgrade(&client)) .map_err(|e| format!("Stratum start error: {:?}", e))?; } From 5a6cdf58e6da7f430762dd7359deeae0d5a509e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 20:30:41 +0100 Subject: [PATCH 18/77] Clean-up and parallelize. --- Cargo.lock | 112 +++++++--- ethcore/Cargo.toml | 3 +- ethcore/src/lib.rs | 1 - ethcore/src/miner/blockchain_client.rs | 2 +- ethcore/src/miner/miner.rs | 34 +-- miner/Cargo.toml | 9 +- miner/src/lib.rs | 8 +- miner/src/pool/client.rs | 2 +- miner/src/pool/listener.rs | 2 +- miner/src/pool/queue.rs | 23 +- miner/src/service_transaction_checker.rs | 6 +- util/table/Cargo.toml | 6 - util/table/src/lib.rs | 261 ----------------------- 13 files changed, 131 insertions(+), 338 deletions(-) delete mode 100644 util/table/Cargo.toml delete mode 100644 util/table/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 452d4f74708..3c9dde851bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ name = "backtrace-sys" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -172,10 +172,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -216,15 +216,6 @@ dependencies = [ "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "coco" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "common-types" version = "0.1.0" @@ -278,6 +269,37 @@ name = "crossbeam" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "crossbeam-deque" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crunchy" version = "0.1.6" @@ -378,7 +400,7 @@ version = "0.5.7" source = "git+https://github.com/paritytech/rust-secp256k1#c1fc9daedee67e1b4028ad1b3669275ed49c22cf" dependencies = [ "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -486,7 +508,7 @@ dependencies = [ "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "patricia-trie 0.1.0", "rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.1", "rlp_compress 0.1.0", "rlp_derive 0.1.0", @@ -495,7 +517,6 @@ dependencies = [ "snappy 0.1.0 (git+https://github.com/paritytech/rust-snappy)", "stats 0.1.0", "stop-guard 0.1.0", - "table 0.1.0", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "trie-standardmap 0.1.0", @@ -614,10 +635,9 @@ dependencies = [ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "price-info 1.7.0", + "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "table 0.1.0", "transaction-pool 1.9.0", - "transient-hashmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1083,7 +1103,7 @@ name = "hidapi" version = "0.3.1" source = "git+https://github.com/paritytech/hidapi-rs#e77ea09c98f29ea8855dd9cd9461125a28ca9125" dependencies = [ - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1354,7 +1374,7 @@ dependencies = [ name = "keccak-hash" version = "0.1.0" dependencies = [ - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1451,7 +1471,7 @@ name = "libusb-sys" version = "0.2.4" source = "git+https://github.com/paritytech/libusb-sys#14bdb698003731b6344a79e1d814704e44363e7c" dependencies = [ - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1531,6 +1551,11 @@ dependencies = [ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memoffset" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memory-cache" version = "0.1.0" @@ -1597,7 +1622,7 @@ name = "miniz-sys" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1863,7 +1888,7 @@ name = "openssl-sys" version = "0.9.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2577,16 +2602,34 @@ name = "rayon" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rayon-core" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2692,7 +2735,7 @@ name = "rocksdb-sys" version = "0.3.0" source = "git+https://github.com/paritytech/rust-rocksdb#ecf06adf3148ab10f6f7686b724498382ff4f36e" dependencies = [ - "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "local-encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "snappy-sys 0.1.0 (git+https://github.com/paritytech/rust-snappy)", @@ -3051,10 +3094,6 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "table" -version = "0.1.0" - [[package]] name = "take" version = "0.1.0" @@ -3614,16 +3653,18 @@ dependencies = [ "checksum bn 0.4.4 (git+https://github.com/paritytech/bn)" = "" "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" "checksum bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7db437d718977f6dc9b2e3fd6fc343c02ac6b899b73fdd2179163447bd9ce9" -"checksum cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1" +"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum cid 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d85ee025368e69063c420cbb2ed9f852cb03a5e69b73be021e65726ce03585b6" "checksum clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8f4a2b3bb7ef3c672d7c13d15613211d5a6976b6892c598b0fcb5d40765f19c2" -"checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" "checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" "checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" "checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" +"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" +"checksum crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59796cc6cbbdc6bb319161349db0c3250ec73ec7fcb763a51065ec4e2e158552" +"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" "checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" "checksum ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)" = "" "checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" @@ -3703,6 +3744,7 @@ dependencies = [ "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff" +"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" "checksum mime 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e3d709ffbb330e1566dc2f2a3c9b58a5ad4a381f740b810cd305dc3f089bc160" "checksum mime_guess 2.0.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "27a5e6679a0614e25adc14c6434ba84e41632b765a6d9cb2031a0cca682699ae" @@ -3766,7 +3808,9 @@ dependencies = [ "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" "checksum rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5f78082e6a6d042862611e9640cf20776185fee506cf6cf67e93c6225cee31" "checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8" -"checksum rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b609139d83da75902f88fd6c01820046840a18471e4dfcd5ac7c0f46bea53" +"checksum rayon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed02d09394c94ffbdfdc755ad62a132e94c3224a8354e78a1200ced34df12edf" +"checksum rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "485541959c8ecc49865526fe6c4de9653dd6e60d829d6edf0be228167b60372d" +"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8" "checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509" "checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa" "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index d18a6f2bdb5..d857cdad145 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -42,7 +42,7 @@ num = "0.1" num_cpus = "1.2" parity-machine = { path = "../machine" } parking_lot = "0.5" -rayon = "0.8" +rayon = "1.0" rand = "0.4" rlp = { path = "../util/rlp" } rlp_compress = { path = "../util/rlp_compress" } @@ -60,7 +60,6 @@ rustc-hex = "1.0" stats = { path = "../util/stats" } time = "0.1" using_queue = { path = "../util/using_queue" } -table = { path = "../util/table" } vm = { path = "vm" } wasm = { path = "wasm" } keccak-hash = { path = "../util/hash" } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 30b6d6955c0..56e5cbfbef6 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -109,7 +109,6 @@ extern crate stats; extern crate stop_guard; extern crate time; extern crate using_queue; -extern crate table; extern crate vm; extern crate wasm; extern crate memory_cache; diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index ed17f9301b4..94613cd50d5 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -106,7 +106,7 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { fn transaction_type(&self, tx: &SignedTransaction) -> pool::client::TransactionType { match self.service_transaction_checker { None => pool::client::TransactionType::Regular, - Some(ref checker) => match checker.check(self, &tx.sender()) { + Some(ref checker) => match checker.check(self, &tx.sender(), &tx.hash()) { Ok(true) => pool::client::TransactionType::Service, Ok(false) => pool::client::TransactionType::Regular, Err(e) => { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 455e4e207c0..7fffe5511ef 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,14 +19,15 @@ use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; use ansi_term::Colour; -use ethereum_types::{H256, U256, Address}; -use parking_lot::{Mutex, RwLock}; use bytes::Bytes; use engines::{EthEngine, Seal}; use error::{Error, ExecutionError}; +use ethcore_miner::gas_pricer::GasPricer; use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction, QueueStatus}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; -use ethcore_miner::gas_pricer::GasPricer; +use ethereum_types::{H256, U256, Address}; +use parking_lot::{Mutex, RwLock}; +use rayon::prelude::*; use timer::PerfTimer; use transaction::{ self, @@ -1012,19 +1013,20 @@ impl MinerService for Miner { // Then import all transactions... let client = self.client(chain); { - // TODO [ToDr] Parallelize - for hash in retracted { - let block = chain.block(BlockId::Hash(*hash)) - .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); - let txs = block.transactions() - .into_iter() - .map(pool::verifier::Transaction::Retracted) - .collect(); - let _ = self.transaction_queue.import( - client.clone(), - txs, - ); - } + retracted + .par_iter() + .for_each(|hash| { + let block = chain.block(BlockId::Hash(*hash)) + .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); + let txs = block.transactions() + .into_iter() + .map(pool::verifier::Transaction::Retracted) + .collect(); + let _ = self.transaction_queue.import( + client.clone(), + txs, + ); + }); } // ...and at the end remove the old ones diff --git a/miner/Cargo.toml b/miner/Cargo.toml index 1f3b7717587..0286af56089 100644 --- a/miner/Cargo.toml +++ b/miner/Cargo.toml @@ -18,7 +18,6 @@ ethabi-derive = "5.0" ethash = { path = "../ethash" } ethcore-transaction = { path = "../ethcore/transaction" } ethereum-types = "0.2" -ethkey = { path = "../ethkey" } futures = "0.1" heapsize = "0.4" keccak-hash = { path = "../util/hash" } @@ -26,7 +25,9 @@ linked-hash-map = "0.5" log = "0.3" parking_lot = "0.5" price-info = { path = "../price-info" } -rustc-hex = "1.0" -table = { path = "../util/table" } +rayon = "1.0" transaction-pool = { path = "../transaction-pool" } -transient-hashmap = "0.4" + +[dev-dependencies] +ethkey = { path = "../ethkey" } +rustc-hex = "1.0" diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 226c7ebda86..917aa1aedcc 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -29,9 +29,8 @@ extern crate keccak_hash as hash; extern crate linked_hash_map; extern crate parking_lot; extern crate price_info; -extern crate table; +extern crate rayon; extern crate transaction_pool as txpool; -extern crate transient_hashmap; #[macro_use] extern crate error_chain; @@ -39,18 +38,17 @@ extern crate error_chain; extern crate ethabi_derive; #[macro_use] extern crate ethabi_contract; -#[cfg(test)] -extern crate ethkey; #[macro_use] extern crate log; #[cfg(test)] extern crate rustc_hex; +#[cfg(test)] +extern crate ethkey; // pub mod banning_queue; pub mod external; pub mod gas_pricer; pub mod pool; pub mod service_transaction_checker; -// pub mod transaction_queue; pub mod work_notify; diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs index 2bc17fc25aa..eea38cd085a 100644 --- a/miner/src/pool/client.rs +++ b/miner/src/pool/client.rs @@ -46,7 +46,7 @@ pub enum TransactionType { } /// State client. -pub trait Client: fmt::Debug { +pub trait Client: fmt::Debug + Sync { /// Is transaction with given hash already in the blockchain? fn transaction_already_included(&self, hash: &H256) -> bool; diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs index a62322ae60e..697957b7698 100644 --- a/miner/src/pool/listener.rs +++ b/miner/src/pool/listener.rs @@ -79,7 +79,7 @@ impl txpool::Listener for Logger { } fn rejected(&mut self, tx: &Arc) { - debug!(target: "txqueue", "[{:?}] Rejected. Too cheap to enter.", tx.hash()); + trace!(target: "txqueue", "[{:?}] Rejected. Too cheap to enter.", tx.hash()); } fn dropped(&mut self, tx: &Arc) { diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 26cf14d7ee1..1c7268ccbfc 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -16,12 +16,14 @@ //! Ethereum Transaction Queue +use std::fmt; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::collections::BTreeMap; use ethereum_types::{H256, U256, Address}; use parking_lot::RwLock; +use rayon::prelude::*; use transaction; use txpool::{self, Verifier}; @@ -42,6 +44,22 @@ pub struct Status { pub limits: txpool::Options, } +impl fmt::Display for Status { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + writeln!( + fmt, + "Pool: {current}/{max} ({senders} senders; {mem}/{mem_max} kB) [minGasPrice: {gp} gwei, maxGas: {max_gas}]", + current = self.status.transaction_count, + max = self.limits.max_count, + senders = self.status.senders, + mem = self.status.mem_usage / 1024, + mem_max = self.status.mem_usage / 1024, + gp = self.options.minimal_gas_price / 1_000_000_000.into(), + max_gas = ::std::cmp::min(self.options.block_gas_limit, self.options.tx_gas_limit), + ) + } +} + /// Ethereum Transaction Queue /// /// Responsible for: @@ -84,10 +102,9 @@ impl TransactionQueue { // Run verification let options = self.options.read().clone(); - // TODO [ToDr] parallelize let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); transactions - .into_iter() + .into_par_iter() .map(|transaction| verifier.verify_transaction(transaction)) .map(|result| match result { Ok(verified) => match self.pool.write().import(verified) { @@ -135,7 +152,7 @@ impl TransactionQueue { let state_readiness = ready::State::new(client); let removed = self.pool.write().cull(None, state_readiness); - debug!(target: "txqueue", "Removed {} stalled transactions.", removed); + debug!(target: "txqueue", "Removed {} stalled transactions. {}", removed, self.status()); } /// Returns next valid nonce for given sender diff --git a/miner/src/service_transaction_checker.rs b/miner/src/service_transaction_checker.rs index a97d144d9c7..6df2e9da8f1 100644 --- a/miner/src/service_transaction_checker.rs +++ b/miner/src/service_transaction_checker.rs @@ -16,7 +16,7 @@ //! A service transactions contract checker. -use ethereum_types::Address; +use ethereum_types::{H256, Address}; use_contract!(service_transaction, "ServiceTransaction", "res/service_transaction.json"); @@ -46,11 +46,11 @@ impl Clone for ServiceTransactionChecker { impl ServiceTransactionChecker { /// Checks if given address is whitelisted to send service transactions. - pub fn check(&self, client: &ContractCaller, sender: &Address) -> Result { + pub fn check(&self, client: &ContractCaller, sender: &Address, hash: &H256) -> Result { let address = client.registry_address(SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME) .ok_or_else(|| "contract is not configured")?; - trace!(target: "txqueue", "Checking service transaction checker contract from {}", address); + trace!(target: "txqueue", "[{:?}] Checking service transaction checker contract from {}", hash, sender); self.contract.functions() .certified() diff --git a/util/table/Cargo.toml b/util/table/Cargo.toml deleted file mode 100644 index f2faca184dc..00000000000 --- a/util/table/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "table" -version = "0.1.0" -authors = ["Parity Technologies "] - -[dependencies] diff --git a/util/table/src/lib.rs b/util/table/src/lib.rs deleted file mode 100644 index 78b2c646ae0..00000000000 --- a/util/table/src/lib.rs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity 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 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. If not, see . - -//! A collection associating pair of keys (row and column) with a single value. - -use std::hash::Hash; -use std::collections::HashMap; -use std::collections::hash_map::Keys; - -/// Structure to hold double-indexed values -/// -/// You can obviously use `HashMap<(Row,Col), Val>`, but this structure gives -/// you better access to all `Columns` in Specific `Row`. Namely you can get sub-hashmap -/// `HashMap` for specific `Row` -#[derive(Default, Debug, PartialEq)] -pub struct Table - where Row: Eq + Hash + Clone, - Col: Eq + Hash { - map: HashMap>, -} - -impl Table - where Row: Eq + Hash + Clone, - Col: Eq + Hash { - /// Creates new Table - pub fn new() -> Self { - Table { - map: HashMap::new(), - } - } - - /// Returns keys iterator for this Table. - pub fn keys(&self) -> Keys> { - self.map.keys() - } - - /// Removes all elements from this Table - pub fn clear(&mut self) { - self.map.clear(); - } - - /// Returns length of the Table (number of (row, col, val) tuples) - pub fn len(&self) -> usize { - self.map.values().fold(0, |acc, v| acc + v.len()) - } - - /// Check if there is any element in this Table - pub fn is_empty(&self) -> bool { - self.map.is_empty() || self.map.values().all(|v| v.is_empty()) - } - - /// Get mutable reference for single Table row. - pub fn row_mut(&mut self, row: &Row) -> Option<&mut HashMap> { - self.map.get_mut(row) - } - - /// Checks if row is defined for that table (note that even if defined it might be empty) - pub fn has_row(&self, row: &Row) -> bool { - self.map.contains_key(row) - } - - /// Get immutable reference for single row in this Table - pub fn row(&self, row: &Row) -> Option<&HashMap> { - self.map.get(row) - } - - /// Get element in cell described by `(row, col)` - pub fn get(&self, row: &Row, col: &Col) -> Option<&Val> { - self.map.get(row).and_then(|r| r.get(col)) - } - - /// Remove value from specific cell - /// - /// It will remove the row if it's the last value in it - pub fn remove(&mut self, row: &Row, col: &Col) -> Option { - let (val, is_empty) = { - let row_map = self.map.get_mut(row); - if let None = row_map { - return None; - } - let row_map = row_map.unwrap(); - let val = row_map.remove(col); - (val, row_map.is_empty()) - }; - // Clean row - if is_empty { - self.map.remove(row); - } - val - } - - /// Remove given row from Table if there are no values defined in it - /// - /// When using `#row_mut` it may happen that all values from some row are drained. - /// Table however will not be aware that row is empty. - /// You can use this method to explicitly remove row entry from the Table. - pub fn clear_if_empty(&mut self, row: &Row) { - let is_empty = self.map.get(row).map_or(false, |m| m.is_empty()); - if is_empty { - self.map.remove(row); - } - } - - /// Inserts new value to specified cell - /// - /// Returns previous value (if any) - pub fn insert(&mut self, row: Row, col: Col, val: Val) -> Option { - self.map.entry(row).or_insert_with(HashMap::new).insert(col, val) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn should_create_empty_table() { - // when - let table : Table = Table::new(); - - // then - assert!(table.is_empty()); - assert_eq!(table.len(), 0); - } - - #[test] - fn should_insert_elements_and_return_previous_if_any() { - // given - let mut table = Table::new(); - - // when - let r1 = table.insert(5, 4, true); - let r2 = table.insert(10, 4, true); - let r3 = table.insert(10, 10, true); - let r4 = table.insert(10, 10, false); - - // then - assert!(r1.is_none()); - assert!(r2.is_none()); - assert!(r3.is_none()); - assert!(r4.is_some()); - assert!(!table.is_empty()); - assert_eq!(r4.unwrap(), true); - assert_eq!(table.len(), 3); - } - - #[test] - fn should_remove_element() { - // given - let mut table = Table::new(); - table.insert(5, 4, true); - assert!(!table.is_empty()); - assert_eq!(table.len(), 1); - - // when - let r = table.remove(&5, &4); - - // then - assert!(table.is_empty()); - assert_eq!(table.len() ,0); - assert_eq!(r.unwrap(), true); - } - - #[test] - fn should_return_none_if_trying_to_remove_non_existing_element() { - // given - let mut table : Table = Table::new(); - assert!(table.is_empty()); - - // when - let r = table.remove(&5, &4); - - // then - assert!(r.is_none()); - } - - #[test] - fn should_clear_row_if_removing_last_element() { - // given - let mut table = Table::new(); - table.insert(5, 4, true); - assert!(table.has_row(&5)); - - // when - let r = table.remove(&5, &4); - - // then - assert!(r.is_some()); - assert!(!table.has_row(&5)); - } - - #[test] - fn should_return_element_given_row_and_col() { - // given - let mut table = Table::new(); - table.insert(1551, 1234, 123); - - // when - let r1 = table.get(&1551, &1234); - let r2 = table.get(&5, &4); - - // then - assert!(r1.is_some()); - assert!(r2.is_none()); - assert_eq!(r1.unwrap(), &123); - } - - #[test] - fn should_clear_table() { - // given - let mut table = Table::new(); - table.insert(1, 1, true); - table.insert(1, 2, false); - table.insert(2, 2, false); - assert_eq!(table.len(), 3); - - // when - table.clear(); - - // then - assert!(table.is_empty()); - assert_eq!(table.len(), 0); - assert_eq!(table.has_row(&1), false); - assert_eq!(table.has_row(&2), false); - } - - #[test] - fn should_return_mutable_row() { - // given - let mut table = Table::new(); - table.insert(1, 1, true); - table.insert(1, 2, false); - table.insert(2, 2, false); - - // when - { - let row = table.row_mut(&1).unwrap(); - row.remove(&1); - row.remove(&2); - } - assert!(table.has_row(&1)); - table.clear_if_empty(&1); - - // then - assert!(!table.has_row(&1)); - assert_eq!(table.len(), 1); - } -} From 0f2424c9b7319a786e1565ea2a8a6d801a21b4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 22:07:34 +0100 Subject: [PATCH 19/77] Get rid of RefCell in header. --- ethcore/src/block.rs | 163 ++++++++------ ethcore/src/client/test_client.rs | 17 +- ethcore/src/engines/authority_round/mod.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 2 +- ethcore/src/ethereum/ethash.rs | 2 +- ethcore/src/header.rs | 248 +++++++++++++-------- ethcore/src/machine.rs | 16 +- ethcore/src/miner/miner.rs | 4 +- ethcore/src/snapshot/block.rs | 4 +- ethcore/src/spec/spec.rs | 3 +- machine/src/lib.rs | 1 + 11 files changed, 269 insertions(+), 193 deletions(-) diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index fc0d607f3f0..0bcd8fffb61 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -31,7 +31,7 @@ use vm::{EnvInfo, LastHashes}; use engines::EthEngine; use error::{Error, BlockError}; use factory::Factories; -use header::{Header, Seal}; +use header::{Header, HeaderMut, Seal}; use receipt::{Receipt, TransactionOutcome}; use state::State; use state_db::StateDB; @@ -318,18 +318,18 @@ impl<'x> OpenBlock<'x> { engine: engine, }; - r.block.header.set_parent_hash(parent.hash()); - r.block.header.set_number(number); - r.block.header.set_author(author); - r.block.header.set_timestamp_now(parent.timestamp()); - r.block.header.set_extra_data(extra_data); - r.block.header.note_dirty(); + r.block.header.alter(|header| { + header.set_parent_hash(parent.hash()); + header.set_number(number); + header.set_author(author); + header.set_timestamp_now(parent.timestamp()); + header.set_extra_data(extra_data); - let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit); - let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target); - - engine.machine().populate_from_parent(&mut r.block.header, parent, gas_floor_target, gas_ceil_target); - engine.populate_from_parent(&mut r.block.header, parent); + let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit); + let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target); + engine.machine().populate_from_parent(header, parent, gas_floor_target, gas_ceil_target); + engine.populate_from_parent(header.mutable_header(), parent); + }); engine.machine().on_new_block(&mut r.block)?; engine.on_new_block(&mut r.block, is_epoch_begin)?; @@ -337,39 +337,14 @@ impl<'x> OpenBlock<'x> { Ok(r) } - /// Alter the author for the block. - pub fn set_author(&mut self, author: Address) { self.block.header.set_author(author); } - - /// Alter the timestamp of the block. - pub fn set_timestamp(&mut self, timestamp: u64) { self.block.header.set_timestamp(timestamp); } - - /// Alter the difficulty for the block. - pub fn set_difficulty(&mut self, a: U256) { self.block.header.set_difficulty(a); } - - /// Alter the gas limit for the block. - pub fn set_gas_limit(&mut self, a: U256) { self.block.header.set_gas_limit(a); } - - /// Alter the gas limit for the block. - pub fn set_gas_used(&mut self, a: U256) { self.block.header.set_gas_used(a); } - - /// Alter the uncles hash the block. - pub fn set_uncles_hash(&mut self, h: H256) { self.block.header.set_uncles_hash(h); } - - /// Alter transactions root for the block. - pub fn set_transactions_root(&mut self, h: H256) { self.block.header.set_transactions_root(h); } - - /// Alter the receipts root for the block. - pub fn set_receipts_root(&mut self, h: H256) { self.block.header.set_receipts_root(h); } - - /// Alter the extra_data for the block. - pub fn set_extra_data(&mut self, extra_data: Bytes) -> Result<(), BlockError> { - if extra_data.len() > self.engine.maximum_extra_data_size() { - Err(BlockError::ExtraDataOutOfBounds(OutOfBounds{min: None, max: Some(self.engine.maximum_extra_data_size()), found: extra_data.len()})) - } else { - self.block.header.set_extra_data(extra_data); - Ok(()) - } + /// Alter header parameters and recompute hash. + pub fn alter_header(&mut self, f: F) { + self.block.header.alter(f) } + // + // /// Alter the extra_data for the block. + // pub fn set_extra_data(&mut self, extra_data: Bytes) -> Result<(), BlockError> { + // } /// Add an uncle to the block, if possible. /// @@ -450,13 +425,26 @@ impl<'x> OpenBlock<'x> { /// Populate self from a header. pub fn populate_from(&mut self, header: &Header) { - self.set_difficulty(*header.difficulty()); - self.set_gas_limit(*header.gas_limit()); - self.set_timestamp(header.timestamp()); - self.set_author(header.author().clone()); - self.set_extra_data(header.extra_data().clone()).unwrap_or_else(|e| warn!("Couldn't set extradata: {}. Ignoring.", e)); - self.set_uncles_hash(header.uncles_hash().clone()); - self.set_transactions_root(header.transactions_root().clone()); + let extra_data = if header.extra_data().len() > self.engine.maximum_extra_data_size() { + let e = BlockError::ExtraDataOutOfBounds(OutOfBounds{min: None, max: Some(self.engine.maximum_extra_data_size()), found: header.extra_data().len()}); + warn!("Couldn't set extradata: {}. Ignoring.", e); + None + } else { + Some(header.extra_data().clone()) + }; + + self.alter_header(|h| { + h.set_difficulty(*header.difficulty()); + h.set_gas_limit(*header.gas_limit()); + h.set_timestamp(header.timestamp()); + h.set_author(header.author().clone()); + h.set_uncles_hash(header.uncles_hash().clone()); + h.set_transactions_root(header.transactions_root().clone()); + + if let Some(extra_data) = extra_data { + h.set_extra_data(extra_data); + } + }); } /// Turn this into a `ClosedBlock`. @@ -472,13 +460,22 @@ impl<'x> OpenBlock<'x> { if let Err(e) = s.block.state.commit() { warn!("Encountered error on state commit: {}", e); } - s.block.header.set_transactions_root(ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes()))); + let transactions_root = ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes())); let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| {s.append_raw(&u.rlp(Seal::With), 1); s} ).out(); - s.block.header.set_uncles_hash(keccak(&uncle_bytes)); - s.block.header.set_state_root(s.block.state.root().clone()); - s.block.header.set_receipts_root(ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes()))); - s.block.header.set_log_bloom(s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b})); //TODO: use |= operator - s.block.header.set_gas_used(s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used)); + let uncle_hash = keccak(&uncle_bytes); + let state_root = *s.block.state.root(); + let receipts_root = ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes())); + let log_bloom = s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b}); //TODO: use |= operator + let gas_used = s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used); + + s.block.header.alter(|header| { + header.set_transactions_root(transactions_root); + header.set_uncles_hash(uncle_hash); + header.set_state_root(state_root); + header.set_receipts_root(receipts_root); + header.set_log_bloom(log_bloom); + header.set_gas_used(gas_used); + }); ClosedBlock { block: s.block, @@ -498,20 +495,39 @@ impl<'x> OpenBlock<'x> { if let Err(e) = s.block.state.commit() { warn!("Encountered error on state commit: {}", e); } - if s.block.header.transactions_root().is_zero() || s.block.header.transactions_root() == &KECCAK_NULL_RLP { - s.block.header.set_transactions_root(ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes()))); - } - let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| {s.append_raw(&u.rlp(Seal::With), 1); s} ).out(); - if s.block.header.uncles_hash().is_zero() || s.block.header.uncles_hash() == &KECCAK_EMPTY_LIST_RLP { - s.block.header.set_uncles_hash(keccak(&uncle_bytes)); - } - if s.block.header.receipts_root().is_zero() || s.block.header.receipts_root() == &KECCAK_NULL_RLP { - s.block.header.set_receipts_root(ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes()))); - } - s.block.header.set_state_root(s.block.state.root().clone()); - s.block.header.set_log_bloom(s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b})); //TODO: use |= operator - s.block.header.set_gas_used(s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used)); + let transactions_root = if s.block.header.transactions_root().is_zero() || s.block.header.transactions_root() == &KECCAK_NULL_RLP { + Some(ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes()))) + } else { None }; + + let receipts_root = if s.block.header.receipts_root().is_zero() || s.block.header.receipts_root() == &KECCAK_NULL_RLP { + Some(ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes()))) + } else { None }; + + let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| { s.append_raw(&u.rlp(Seal::With), 1); s }).out(); + let uncles_hash =if s.block.header.uncles_hash().is_zero() || s.block.header.uncles_hash() == &KECCAK_EMPTY_LIST_RLP { + Some(keccak(&uncle_bytes)) + } else { None }; + + let state_root = *s.block.state.root(); + let log_bloom = s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b}); //TODO: use |= operator + let gas_used = s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used); + + s.block.header.alter(|header| { + if let Some(transactions_root) = transactions_root { + header.set_transactions_root(transactions_root); + } + if let Some(uncles_hash) = uncles_hash { + header.set_uncles_hash(uncles_hash); + } + if let Some(receipts_root) = receipts_root { + header.set_receipts_root(receipts_root); + } + + header.set_state_root(state_root); + header.set_log_bloom(log_bloom); + header.set_gas_used(gas_used); + }); LockedBlock { block: s.block, @@ -574,7 +590,7 @@ impl LockedBlock { return Err(BlockError::InvalidSealArity( Mismatch { expected: expected_seal_fields, found: seal.len() })); } - s.block.header.set_seal(seal); + s.block.header.alter(|h| h.set_seal(seal)); Ok(SealedBlock { block: s.block, uncle_bytes: s.uncle_bytes }) } @@ -587,7 +603,7 @@ impl LockedBlock { seal: Vec, ) -> Result { let mut s = self; - s.block.header.set_seal(seal); + s.block.header.alter(|h| h.set_seal(seal)); // TODO: passing state context to avoid engines owning it? match engine.verify_local_seal(&s.block.header) { @@ -602,7 +618,8 @@ impl LockedBlock { for receipt in &mut block.block.receipts { receipt.outcome = TransactionOutcome::Unknown; } - block.block.header.set_receipts_root(ordered_trie_root(block.block.receipts.iter().map(|r| r.rlp_bytes()))); + let receipts_root = ordered_trie_root(block.block.receipts.iter().map(|r| r.rlp_bytes())); + block.block.header.alter(|h| h.set_receipts_root(receipts_root)); block } } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 9746ff95416..5b5696433b1 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -233,7 +233,7 @@ impl TestBlockChainClient { pub fn add_blocks(&self, count: usize, with: EachBlockWith) { let len = self.numbers.read().len(); for n in len..(len + count) { - let mut header = BlockHeader::new(); + let mut header = BlockHeader::new().unlock(); header.set_difficulty(From::from(n)); header.set_parent_hash(self.last_hash.read().clone()); header.set_number(n as BlockNumber); @@ -242,11 +242,11 @@ impl TestBlockChainClient { let uncles = match with { EachBlockWith::Uncle | EachBlockWith::UncleAndTransaction => { let mut uncles = RlpStream::new_list(1); - let mut uncle_header = BlockHeader::new(); + let mut uncle_header = BlockHeader::new().unlock(); uncle_header.set_difficulty(From::from(n)); uncle_header.set_parent_hash(self.last_hash.read().clone()); uncle_header.set_number(n as BlockNumber); - uncles.append(&uncle_header); + uncles.append(&uncle_header.lock()); header.set_uncles_hash(keccak(uncles.as_raw())); uncles }, @@ -273,6 +273,7 @@ impl TestBlockChainClient { _ => ::rlp::EMPTY_LIST_RLP.to_vec() }; + let header = header.lock(); let mut rlp = RlpStream::new_list(3); rlp.append(&header); rlp.append_raw(&txs, 1); @@ -284,8 +285,10 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid extra data. pub fn corrupt_block(&self, n: BlockNumber) { let hash = self.block_hash(BlockId::Number(n)).unwrap(); - let mut header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); + let header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); + let mut header = header.unlock(); header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); + let header = header.lock(); let mut rlp = RlpStream::new_list(3); rlp.append(&header); rlp.append_raw(&::rlp::NULL_RLP, 1); @@ -296,8 +299,10 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid parent hash. pub fn corrupt_block_parent(&self, n: BlockNumber) { let hash = self.block_hash(BlockId::Number(n)).unwrap(); - let mut header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); + let header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); + let mut header = header.unlock(); header.set_parent_hash(H256::from(42)); + let header = header.lock(); let mut rlp = RlpStream::new_list(3); rlp.append(&header); rlp.append_raw(&::rlp::NULL_RLP, 1); @@ -387,7 +392,7 @@ impl MiningBlockChainClient for TestBlockChainClient { false, ).expect("Opening block for tests will not fail."); // TODO [todr] Override timestamp for predictability (set_timestamp_now kind of sucks) - open_block.set_timestamp(*self.latest_block_timestamp.read()); + open_block.alter_header(|h| h.set_timestamp(*self.latest_block_timestamp.read())); open_block } diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 54b9c14a8aa..749d4382fe8 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -787,7 +787,7 @@ impl Engine for AuthorityRound { }; let score = calculate_score(parent_step.into(), current_step.into(), current_empty_steps_len.into()); - header.set_difficulty(score); + header.alter(|h| h.set_difficulty(score)); } fn seals_internally(&self) -> Option { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index b408d1eee7a..1351678190a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -466,7 +466,7 @@ impl Engine for Tendermint { + consensus_view(parent).expect("Header has been verified; qed").into() - self.view.load(AtomicOrdering::SeqCst).into(); - header.set_difficulty(new_difficulty); + header.alter(|h| h.set_difficulty(new_difficulty)); } /// Should this node participate. diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index a9ee79f304d..2e566685fda 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -187,7 +187,7 @@ impl Engine for Arc { fn populate_from_parent(&self, header: &mut Header, parent: &Header) { let difficulty = self.calculate_difficulty(header, parent); - header.set_difficulty(difficulty); + header.alter(|h| h.set_difficulty(difficulty)); } fn on_new_block( diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index cfac04f3ca1..2768878e686 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -17,7 +17,6 @@ //! Block header. use std::cmp; -use std::cell::RefCell; use hash::{KECCAK_NULL_RLP, KECCAK_EMPTY_LIST_RLP, keccak}; use heapsize::HeapSizeOf; use ethereum_types::{H256, U256, Address, Bloom}; @@ -75,28 +74,19 @@ pub struct Header { /// Vector of post-RLP-encoded fields. seal: Vec, + /// The memoized RLP representation of this header *including* the seal fields. + rlp: Bytes, /// The memoized hash of the RLP representation *including* the seal fields. - hash: RefCell>, + hash: H256, + /// The memoized RLP representation of this header *without* the seal fields. + bare_rlp: Bytes, /// The memoized hash of the RLP representation *without* the seal fields. - bare_hash: RefCell>, + bare_hash: H256, } impl PartialEq for Header { fn eq(&self, c: &Header) -> bool { - self.parent_hash == c.parent_hash && - self.timestamp == c.timestamp && - self.number == c.number && - self.author == c.author && - self.transactions_root == c.transactions_root && - self.uncles_hash == c.uncles_hash && - self.extra_data == c.extra_data && - self.state_root == c.state_root && - self.receipts_root == c.receipts_root && - self.log_bloom == c.log_bloom && - self.gas_used == c.gas_used && - self.gas_limit == c.gas_limit && - self.difficulty == c.difficulty && - self.seal == c.seal + self.hash == c.hash } } @@ -120,9 +110,11 @@ impl Default for Header { difficulty: U256::default(), seal: vec![], - hash: RefCell::new(None), - bare_hash: RefCell::new(None), - } + rlp: vec![], + hash: H256::default(), + bare_rlp: vec![], + bare_hash: H256::default(), + }.recompute_hashes() } } @@ -143,8 +135,6 @@ impl Header { /// Get the extra data field of the header. pub fn extra_data(&self) -> &Bytes { &self.extra_data } - /// Get a mutable reference to extra_data - pub fn extra_data_mut(&mut self) -> &mut Bytes { self.note_dirty(); &mut self.extra_data } /// Get the state root field of the header. pub fn state_root(&self) -> &H256 { &self.state_root } @@ -172,74 +162,6 @@ impl Header { }).collect() } - // TODO: seal_at, set_seal_at &c. - - /// Set the number field of the header. - pub fn set_parent_hash(&mut self, a: H256) { self.parent_hash = a; self.note_dirty(); } - /// Set the uncles hash field of the header. - pub fn set_uncles_hash(&mut self, a: H256) { self.uncles_hash = a; self.note_dirty(); } - /// Set the state root field of the header. - pub fn set_state_root(&mut self, a: H256) { self.state_root = a; self.note_dirty(); } - /// Set the transactions root field of the header. - pub fn set_transactions_root(&mut self, a: H256) { self.transactions_root = a; self.note_dirty() } - /// Set the receipts root field of the header. - pub fn set_receipts_root(&mut self, a: H256) { self.receipts_root = a; self.note_dirty() } - /// Set the log bloom field of the header. - pub fn set_log_bloom(&mut self, a: Bloom) { self.log_bloom = a; self.note_dirty() } - /// Set the timestamp field of the header. - pub fn set_timestamp(&mut self, a: u64) { self.timestamp = a; self.note_dirty(); } - /// Set the timestamp field of the header to the current time. - pub fn set_timestamp_now(&mut self, but_later_than: u64) { self.timestamp = cmp::max(get_time().sec as u64, but_later_than + 1); self.note_dirty(); } - /// Set the number field of the header. - pub fn set_number(&mut self, a: BlockNumber) { self.number = a; self.note_dirty(); } - /// Set the author field of the header. - pub fn set_author(&mut self, a: Address) { if a != self.author { self.author = a; self.note_dirty(); } } - - /// Set the extra data field of the header. - pub fn set_extra_data(&mut self, a: Bytes) { if a != self.extra_data { self.extra_data = a; self.note_dirty(); } } - - /// Set the gas used field of the header. - pub fn set_gas_used(&mut self, a: U256) { self.gas_used = a; self.note_dirty(); } - /// Set the gas limit field of the header. - pub fn set_gas_limit(&mut self, a: U256) { self.gas_limit = a; self.note_dirty(); } - - /// Set the difficulty field of the header. - pub fn set_difficulty(&mut self, a: U256) { self.difficulty = a; self.note_dirty(); } - /// Set the seal field of the header. - pub fn set_seal(&mut self, a: Vec) { self.seal = a; self.note_dirty(); } - - /// Get the hash of this header (keccak of the RLP). - pub fn hash(&self) -> H256 { - let mut hash = self.hash.borrow_mut(); - match &mut *hash { - &mut Some(ref h) => h.clone(), - hash @ &mut None => { - let h = self.rlp_keccak(Seal::With); - *hash = Some(h.clone()); - h - } - } - } - - /// Get the hash of the header excluding the seal - pub fn bare_hash(&self) -> H256 { - let mut hash = self.bare_hash.borrow_mut(); - match &mut *hash { - &mut Some(ref h) => h.clone(), - hash @ &mut None => { - let h = self.rlp_keccak(Seal::Without); - *hash = Some(h.clone()); - h - } - } - } - - /// Note that some fields have changed. Resets the memoised hash. - pub fn note_dirty(&self) { - *self.hash.borrow_mut() = None; - *self.bare_hash.borrow_mut() = None; - } - // TODO: make these functions traity /// Place this header into an RLP stream `s`, optionally `with_seal`. pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { @@ -266,18 +188,142 @@ impl Header { /// Get the RLP of this header, optionally `with_seal`. pub fn rlp(&self, with_seal: Seal) -> Bytes { - let mut s = RlpStream::new(); - self.stream_rlp(&mut s, with_seal); - s.out() + match with_seal { + Seal::With => self.rlp.clone(), + Seal::Without => self.bare_rlp.clone(), + } } /// Get the SHA3 (Keccak) of this header, optionally `with_seal`. - pub fn rlp_keccak(&self, with_seal: Seal) -> H256 { keccak(self.rlp(with_seal)) } + pub fn rlp_keccak(&self, with_seal: Seal) -> H256 { + match with_seal { + Seal::With => self.hash, + Seal::Without => self.bare_hash, + } + } /// Encode the header, getting a type-safe wrapper around the RLP. pub fn encoded(&self) -> ::encoded::Header { ::encoded::Header::new(self.rlp(Seal::With)) } + + /// Get the hash of this header (keccak of the RLP). + pub fn hash(&self) -> H256 { + self.hash + } + + /// Get the hash of the header excluding the seal + pub fn bare_hash(&self) -> H256 { + self.bare_hash + } + + pub fn alter(&mut self, f: F) { + use std::ptr; + + let mut old_t = unsafe { ptr::read(self) }.unlock(); + f(&mut old_t); + unsafe { ptr::write(self, old_t.lock()) }; + } + + pub fn unlock(self) -> HeaderMut { + HeaderMut { + header: self, + is_dirty: false, + } + } + + fn recompute_hashes(mut self) -> Self { + Self::recompute(&mut self); + self + } + + fn recompute(header: &mut Self) { + let (rlp, bare_rlp) = { + let rlp = |with_seal| { + let mut s = RlpStream::new(); + header.stream_rlp(&mut s, with_seal); + s.out() + }; + (rlp(Seal::Without), rlp(Seal::With)) + }; + + header.rlp = rlp; + header.bare_rlp = bare_rlp; + + header.hash = keccak(&header.rlp); + header.bare_hash = keccak(&header.bare_rlp); + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HeaderMut { + header: Header, + is_dirty: bool, +} + +impl HeaderMut { + /// Lock this header and recompute hash. + pub fn lock(mut self) -> Header { + if self.is_dirty { + self.header.recompute_hashes() + } else { + self.header + } + } + + /// Get a mutable reference to header. + /// + /// NOTE It's not safe to clone such header! RLP/Hash values are most likely invalid. + pub fn mutable_header(&mut self) -> &mut Header { + self.note_dirty(); + &mut self.header + } + /// Get the number field of the header. + pub fn number(&self) -> BlockNumber { self.header.number } + /// Get the gas used field of the header. + pub fn gas_used(&self) -> &U256 { &self.header.gas_used } + + /// Set the number field of the header. + pub fn set_parent_hash(&mut self, a: H256) { self.header.parent_hash = a; self.note_dirty(); } + /// Set the uncles hash field of the header. + pub fn set_uncles_hash(&mut self, a: H256) { self.header.uncles_hash = a; self.note_dirty(); } + /// Set the state root field of the header. + pub fn set_state_root(&mut self, a: H256) { self.header.state_root = a; self.note_dirty(); } + /// Set the transactions root field of the header. + pub fn set_transactions_root(&mut self, a: H256) { self.header.transactions_root = a; self.note_dirty() } + /// Set the receipts root field of the header. + pub fn set_receipts_root(&mut self, a: H256) { self.header.receipts_root = a; self.note_dirty() } + /// Set the log bloom field of the header. + pub fn set_log_bloom(&mut self, a: Bloom) { self.header.log_bloom = a; self.note_dirty() } + /// Set the timestamp field of the header. + pub fn set_timestamp(&mut self, a: u64) { self.header.timestamp = a; self.note_dirty(); } + /// Set the timestamp field of the header to the current time. + pub fn set_timestamp_now(&mut self, but_later_than: u64) { self.header.timestamp = cmp::max(get_time().sec as u64, but_later_than + 1); self.note_dirty(); } + /// Set the number field of the header. + pub fn set_number(&mut self, a: BlockNumber) { self.header.number = a; self.note_dirty(); } + /// Set the author field of the header. + pub fn set_author(&mut self, a: Address) { if a != self.header.author { self.header.author = a; self.note_dirty(); } } + + /// Set the extra data field of the header. + pub fn set_extra_data(&mut self, a: Bytes) { if a != self.header.extra_data { self.header.extra_data = a; self.note_dirty(); } } + + /// Get a mutable reference to extra_data + pub fn extra_data_mut(&mut self) -> &mut Bytes { self.note_dirty(); &mut self.header.extra_data } + + /// Set the gas used field of the header. + pub fn set_gas_used(&mut self, a: U256) { self.header.gas_used = a; self.note_dirty(); } + /// Set the gas limit field of the header. + pub fn set_gas_limit(&mut self, a: U256) { self.header.gas_limit = a; self.note_dirty(); } + + /// Set the difficulty field of the header. + pub fn set_difficulty(&mut self, a: U256) { self.header.difficulty = a; self.note_dirty(); } + /// Set the seal field of the header. + pub fn set_seal(&mut self, a: Vec) { self.header.seal = a; self.note_dirty(); } + + /// Note that some fields have changed. Resets the memoised hash. + pub fn note_dirty(&mut self) { + self.is_dirty = true; + } } impl Decodable for Header { @@ -297,15 +343,17 @@ impl Decodable for Header { timestamp: cmp::min(r.val_at::(11)?, u64::max_value().into()).as_u64(), extra_data: r.val_at(12)?, seal: vec![], - hash: RefCell::new(Some(keccak(r.as_raw()))), - bare_hash: RefCell::new(None), + rlp: vec![], + bare_rlp: vec![], + hash: Default::default(), + bare_hash: Default::default(), }; for i in 13..r.item_count()? { blockheader.seal.push(r.at(i)?.as_raw().to_vec()) } - Ok(blockheader) + Ok(blockheader.recompute_hashes()) } } @@ -335,7 +383,11 @@ impl ::parity_machine::Header for Header { impl ::parity_machine::ScoredHeader for Header { fn score(&self) -> &U256 { self.difficulty() } - fn set_score(&mut self, score: U256) { self.set_difficulty(score) } + + fn set_score(&mut self, score: U256) { + self.difficulty = score; + Header::recompute(self) + } } #[cfg(test)] diff --git a/ethcore/src/machine.rs b/ethcore/src/machine.rs index e302a461340..66b949382b7 100644 --- a/ethcore/src/machine.rs +++ b/ethcore/src/machine.rs @@ -25,7 +25,7 @@ use builtin::Builtin; use client::BlockChainClient; use error::Error; use executive::Executive; -use header::{BlockNumber, Header}; +use header::{BlockNumber, Header, HeaderMut}; use spec::CommonParams; use state::{CleanupMode, Substate}; use trace::{NoopTracer, NoopVMTracer, Tracer, ExecutiveTracer, RewardType}; @@ -201,7 +201,7 @@ impl EthereumMachine { /// Populate a header's fields based on its parent's header. /// Usually implements the chain scoring rule based on weight. /// The gas floor target must not be lower than the engine's minimum gas limit. - pub fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, gas_ceil_target: U256) { + pub fn populate_from_parent(&self, header: &mut HeaderMut, parent: &Header, gas_floor_target: U256, gas_ceil_target: U256) { header.set_difficulty(parent.difficulty().clone()); if let Some(ref ethash_params) = self.ethash_extensions { @@ -495,7 +495,7 @@ mod tests { ); let mut parent = ::header::Header::new(); - let mut header = ::header::Header::new(); + let mut header = ::header::Header::new().unlock(); header.set_number(1); // this test will work for this constant only @@ -504,25 +504,25 @@ mod tests { // when parent.gas_limit < gas_floor_target: parent.set_gas_limit(U256::from(50_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000)); - assert_eq!(*header.gas_limit(), U256::from(50_024)); + assert_eq!(*header.clone().lock().gas_limit(), U256::from(50_024)); // when parent.gas_limit > gas_ceil_target: parent.set_gas_limit(U256::from(250_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000)); - assert_eq!(*header.gas_limit(), U256::from(249_787)); + assert_eq!(*header.clone().lock().gas_limit(), U256::from(249_787)); // when parent.gas_limit is in miner's range header.set_gas_used(U256::from(150_000)); parent.set_gas_limit(U256::from(150_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000)); - assert_eq!(*header.gas_limit(), U256::from(150_035)); + assert_eq!(*header.clone().lock().gas_limit(), U256::from(150_035)); // when parent.gas_limit is in miner's range // && we can NOT increase it to be multiple of constant header.set_gas_used(U256::from(150_000)); parent.set_gas_limit(U256::from(150_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(150_002)); - assert_eq!(*header.gas_limit(), U256::from(149_998)); + assert_eq!(*header.clone().lock().gas_limit(), U256::from(149_998)); // when parent.gas_limit is in miner's range // && we can NOT increase it to be multiple of constant @@ -530,6 +530,6 @@ mod tests { header.set_gas_used(U256::from(150_000)); parent.set_gas_limit(U256::from(150_000)); machine.populate_from_parent(&mut header, &parent, U256::from(150_000), U256::from(150_002)); - assert_eq!(*header.gas_limit(), U256::from(150_002)); + assert_eq!(*header.clone().lock().gas_limit(), U256::from(150_002)); } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 7fffe5511ef..759ce3b2941 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -360,7 +360,7 @@ impl Miner { }; if self.options.infinite_pending_block { - open_block.set_gas_limit(!U256::zero()); + open_block.alter_header(|h| h.set_gas_limit(!U256::zero())); } (open_block, last_work_hash) @@ -696,7 +696,7 @@ impl MinerService for Miner { fn set_author(&self, address: Address, password: Option) -> Result<(), AccountError> { self.params.write().author = address; - if self.engine.seals_internally().is_some() { + if self.engine.seals_internally().is_some() && password.is_some() { if let Some(ref ap) = self.accounts { let password = password.unwrap_or_default(); // Sign test message diff --git a/ethcore/src/snapshot/block.rs b/ethcore/src/snapshot/block.rs index 98215b3244d..44047bb6add 100644 --- a/ethcore/src/snapshot/block.rs +++ b/ethcore/src/snapshot/block.rs @@ -91,7 +91,7 @@ impl AbridgedBlock { pub fn to_block(&self, parent_hash: H256, number: u64, receipts_root: H256) -> Result { let rlp = UntrustedRlp::new(&self.rlp); - let mut header: Header = Default::default(); + let mut header = Header::default().unlock(); header.set_parent_hash(parent_hash); header.set_author(rlp.val_at(0)?); header.set_state_root(rlp.val_at(1)?); @@ -124,7 +124,7 @@ impl AbridgedBlock { header.set_seal(seal_fields); Ok(Block { - header: header, + header: header.lock(), transactions: transactions, uncles: uncles, }) diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index ba9e578db6d..5de648031a7 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -572,7 +572,7 @@ impl Spec { /// Get the header of the genesis block. pub fn genesis_header(&self) -> Header { - let mut header: Header = Default::default(); + let mut header = Header::default().unlock(); header.set_parent_hash(self.parent_hash.clone()); header.set_timestamp(self.timestamp); header.set_number(0); @@ -590,6 +590,7 @@ impl Spec { let r = Rlp::new(&self.seal_rlp); r.iter().map(|f| f.as_raw().to_vec()).collect() }); + let header = header.lock(); trace!(target: "spec", "Header hash is {}", header.hash()); header } diff --git a/machine/src/lib.rs b/machine/src/lib.rs index 3a45c38d2ef..c06b0904d76 100644 --- a/machine/src/lib.rs +++ b/machine/src/lib.rs @@ -77,6 +77,7 @@ pub trait Transactions: LiveBlock { pub trait Machine: for<'a> LocalizedMachine<'a> { /// The block header type. type Header: Header; + /// The live block type. type LiveBlock: LiveBlock; /// A handle to a blockchain client for this machine. From a1cb18ceddba0e2011a4c6f1b68b3ce8f0a910cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 22:32:36 +0100 Subject: [PATCH 20/77] Revert "Get rid of RefCell in header." This reverts commit 0f2424c9b7319a786e1565ea2a8a6d801a21b4fb. --- ethcore/src/block.rs | 163 ++++++-------- ethcore/src/client/test_client.rs | 17 +- ethcore/src/engines/authority_round/mod.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 2 +- ethcore/src/ethereum/ethash.rs | 2 +- ethcore/src/header.rs | 248 ++++++++------------- ethcore/src/machine.rs | 16 +- ethcore/src/miner/miner.rs | 4 +- ethcore/src/snapshot/block.rs | 4 +- ethcore/src/spec/spec.rs | 3 +- machine/src/lib.rs | 1 - 11 files changed, 193 insertions(+), 269 deletions(-) diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 0bcd8fffb61..fc0d607f3f0 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -31,7 +31,7 @@ use vm::{EnvInfo, LastHashes}; use engines::EthEngine; use error::{Error, BlockError}; use factory::Factories; -use header::{Header, HeaderMut, Seal}; +use header::{Header, Seal}; use receipt::{Receipt, TransactionOutcome}; use state::State; use state_db::StateDB; @@ -318,18 +318,18 @@ impl<'x> OpenBlock<'x> { engine: engine, }; - r.block.header.alter(|header| { - header.set_parent_hash(parent.hash()); - header.set_number(number); - header.set_author(author); - header.set_timestamp_now(parent.timestamp()); - header.set_extra_data(extra_data); + r.block.header.set_parent_hash(parent.hash()); + r.block.header.set_number(number); + r.block.header.set_author(author); + r.block.header.set_timestamp_now(parent.timestamp()); + r.block.header.set_extra_data(extra_data); + r.block.header.note_dirty(); - let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit); - let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target); - engine.machine().populate_from_parent(header, parent, gas_floor_target, gas_ceil_target); - engine.populate_from_parent(header.mutable_header(), parent); - }); + let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit); + let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target); + + engine.machine().populate_from_parent(&mut r.block.header, parent, gas_floor_target, gas_ceil_target); + engine.populate_from_parent(&mut r.block.header, parent); engine.machine().on_new_block(&mut r.block)?; engine.on_new_block(&mut r.block, is_epoch_begin)?; @@ -337,14 +337,39 @@ impl<'x> OpenBlock<'x> { Ok(r) } - /// Alter header parameters and recompute hash. - pub fn alter_header(&mut self, f: F) { - self.block.header.alter(f) + /// Alter the author for the block. + pub fn set_author(&mut self, author: Address) { self.block.header.set_author(author); } + + /// Alter the timestamp of the block. + pub fn set_timestamp(&mut self, timestamp: u64) { self.block.header.set_timestamp(timestamp); } + + /// Alter the difficulty for the block. + pub fn set_difficulty(&mut self, a: U256) { self.block.header.set_difficulty(a); } + + /// Alter the gas limit for the block. + pub fn set_gas_limit(&mut self, a: U256) { self.block.header.set_gas_limit(a); } + + /// Alter the gas limit for the block. + pub fn set_gas_used(&mut self, a: U256) { self.block.header.set_gas_used(a); } + + /// Alter the uncles hash the block. + pub fn set_uncles_hash(&mut self, h: H256) { self.block.header.set_uncles_hash(h); } + + /// Alter transactions root for the block. + pub fn set_transactions_root(&mut self, h: H256) { self.block.header.set_transactions_root(h); } + + /// Alter the receipts root for the block. + pub fn set_receipts_root(&mut self, h: H256) { self.block.header.set_receipts_root(h); } + + /// Alter the extra_data for the block. + pub fn set_extra_data(&mut self, extra_data: Bytes) -> Result<(), BlockError> { + if extra_data.len() > self.engine.maximum_extra_data_size() { + Err(BlockError::ExtraDataOutOfBounds(OutOfBounds{min: None, max: Some(self.engine.maximum_extra_data_size()), found: extra_data.len()})) + } else { + self.block.header.set_extra_data(extra_data); + Ok(()) + } } - // - // /// Alter the extra_data for the block. - // pub fn set_extra_data(&mut self, extra_data: Bytes) -> Result<(), BlockError> { - // } /// Add an uncle to the block, if possible. /// @@ -425,26 +450,13 @@ impl<'x> OpenBlock<'x> { /// Populate self from a header. pub fn populate_from(&mut self, header: &Header) { - let extra_data = if header.extra_data().len() > self.engine.maximum_extra_data_size() { - let e = BlockError::ExtraDataOutOfBounds(OutOfBounds{min: None, max: Some(self.engine.maximum_extra_data_size()), found: header.extra_data().len()}); - warn!("Couldn't set extradata: {}. Ignoring.", e); - None - } else { - Some(header.extra_data().clone()) - }; - - self.alter_header(|h| { - h.set_difficulty(*header.difficulty()); - h.set_gas_limit(*header.gas_limit()); - h.set_timestamp(header.timestamp()); - h.set_author(header.author().clone()); - h.set_uncles_hash(header.uncles_hash().clone()); - h.set_transactions_root(header.transactions_root().clone()); - - if let Some(extra_data) = extra_data { - h.set_extra_data(extra_data); - } - }); + self.set_difficulty(*header.difficulty()); + self.set_gas_limit(*header.gas_limit()); + self.set_timestamp(header.timestamp()); + self.set_author(header.author().clone()); + self.set_extra_data(header.extra_data().clone()).unwrap_or_else(|e| warn!("Couldn't set extradata: {}. Ignoring.", e)); + self.set_uncles_hash(header.uncles_hash().clone()); + self.set_transactions_root(header.transactions_root().clone()); } /// Turn this into a `ClosedBlock`. @@ -460,22 +472,13 @@ impl<'x> OpenBlock<'x> { if let Err(e) = s.block.state.commit() { warn!("Encountered error on state commit: {}", e); } - let transactions_root = ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes())); + s.block.header.set_transactions_root(ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes()))); let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| {s.append_raw(&u.rlp(Seal::With), 1); s} ).out(); - let uncle_hash = keccak(&uncle_bytes); - let state_root = *s.block.state.root(); - let receipts_root = ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes())); - let log_bloom = s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b}); //TODO: use |= operator - let gas_used = s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used); - - s.block.header.alter(|header| { - header.set_transactions_root(transactions_root); - header.set_uncles_hash(uncle_hash); - header.set_state_root(state_root); - header.set_receipts_root(receipts_root); - header.set_log_bloom(log_bloom); - header.set_gas_used(gas_used); - }); + s.block.header.set_uncles_hash(keccak(&uncle_bytes)); + s.block.header.set_state_root(s.block.state.root().clone()); + s.block.header.set_receipts_root(ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes()))); + s.block.header.set_log_bloom(s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b})); //TODO: use |= operator + s.block.header.set_gas_used(s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used)); ClosedBlock { block: s.block, @@ -495,39 +498,20 @@ impl<'x> OpenBlock<'x> { if let Err(e) = s.block.state.commit() { warn!("Encountered error on state commit: {}", e); } + if s.block.header.transactions_root().is_zero() || s.block.header.transactions_root() == &KECCAK_NULL_RLP { + s.block.header.set_transactions_root(ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes()))); + } + let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| {s.append_raw(&u.rlp(Seal::With), 1); s} ).out(); + if s.block.header.uncles_hash().is_zero() || s.block.header.uncles_hash() == &KECCAK_EMPTY_LIST_RLP { + s.block.header.set_uncles_hash(keccak(&uncle_bytes)); + } + if s.block.header.receipts_root().is_zero() || s.block.header.receipts_root() == &KECCAK_NULL_RLP { + s.block.header.set_receipts_root(ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes()))); + } - let transactions_root = if s.block.header.transactions_root().is_zero() || s.block.header.transactions_root() == &KECCAK_NULL_RLP { - Some(ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes()))) - } else { None }; - - let receipts_root = if s.block.header.receipts_root().is_zero() || s.block.header.receipts_root() == &KECCAK_NULL_RLP { - Some(ordered_trie_root(s.block.receipts.iter().map(|r| r.rlp_bytes()))) - } else { None }; - - let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| { s.append_raw(&u.rlp(Seal::With), 1); s }).out(); - let uncles_hash =if s.block.header.uncles_hash().is_zero() || s.block.header.uncles_hash() == &KECCAK_EMPTY_LIST_RLP { - Some(keccak(&uncle_bytes)) - } else { None }; - - let state_root = *s.block.state.root(); - let log_bloom = s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b}); //TODO: use |= operator - let gas_used = s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used); - - s.block.header.alter(|header| { - if let Some(transactions_root) = transactions_root { - header.set_transactions_root(transactions_root); - } - if let Some(uncles_hash) = uncles_hash { - header.set_uncles_hash(uncles_hash); - } - if let Some(receipts_root) = receipts_root { - header.set_receipts_root(receipts_root); - } - - header.set_state_root(state_root); - header.set_log_bloom(log_bloom); - header.set_gas_used(gas_used); - }); + s.block.header.set_state_root(s.block.state.root().clone()); + s.block.header.set_log_bloom(s.block.receipts.iter().fold(Bloom::zero(), |mut b, r| {b = &b | &r.log_bloom; b})); //TODO: use |= operator + s.block.header.set_gas_used(s.block.receipts.last().map_or(U256::zero(), |r| r.gas_used)); LockedBlock { block: s.block, @@ -590,7 +574,7 @@ impl LockedBlock { return Err(BlockError::InvalidSealArity( Mismatch { expected: expected_seal_fields, found: seal.len() })); } - s.block.header.alter(|h| h.set_seal(seal)); + s.block.header.set_seal(seal); Ok(SealedBlock { block: s.block, uncle_bytes: s.uncle_bytes }) } @@ -603,7 +587,7 @@ impl LockedBlock { seal: Vec, ) -> Result { let mut s = self; - s.block.header.alter(|h| h.set_seal(seal)); + s.block.header.set_seal(seal); // TODO: passing state context to avoid engines owning it? match engine.verify_local_seal(&s.block.header) { @@ -618,8 +602,7 @@ impl LockedBlock { for receipt in &mut block.block.receipts { receipt.outcome = TransactionOutcome::Unknown; } - let receipts_root = ordered_trie_root(block.block.receipts.iter().map(|r| r.rlp_bytes())); - block.block.header.alter(|h| h.set_receipts_root(receipts_root)); + block.block.header.set_receipts_root(ordered_trie_root(block.block.receipts.iter().map(|r| r.rlp_bytes()))); block } } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 5b5696433b1..9746ff95416 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -233,7 +233,7 @@ impl TestBlockChainClient { pub fn add_blocks(&self, count: usize, with: EachBlockWith) { let len = self.numbers.read().len(); for n in len..(len + count) { - let mut header = BlockHeader::new().unlock(); + let mut header = BlockHeader::new(); header.set_difficulty(From::from(n)); header.set_parent_hash(self.last_hash.read().clone()); header.set_number(n as BlockNumber); @@ -242,11 +242,11 @@ impl TestBlockChainClient { let uncles = match with { EachBlockWith::Uncle | EachBlockWith::UncleAndTransaction => { let mut uncles = RlpStream::new_list(1); - let mut uncle_header = BlockHeader::new().unlock(); + let mut uncle_header = BlockHeader::new(); uncle_header.set_difficulty(From::from(n)); uncle_header.set_parent_hash(self.last_hash.read().clone()); uncle_header.set_number(n as BlockNumber); - uncles.append(&uncle_header.lock()); + uncles.append(&uncle_header); header.set_uncles_hash(keccak(uncles.as_raw())); uncles }, @@ -273,7 +273,6 @@ impl TestBlockChainClient { _ => ::rlp::EMPTY_LIST_RLP.to_vec() }; - let header = header.lock(); let mut rlp = RlpStream::new_list(3); rlp.append(&header); rlp.append_raw(&txs, 1); @@ -285,10 +284,8 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid extra data. pub fn corrupt_block(&self, n: BlockNumber) { let hash = self.block_hash(BlockId::Number(n)).unwrap(); - let header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); - let mut header = header.unlock(); + let mut header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); - let header = header.lock(); let mut rlp = RlpStream::new_list(3); rlp.append(&header); rlp.append_raw(&::rlp::NULL_RLP, 1); @@ -299,10 +296,8 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid parent hash. pub fn corrupt_block_parent(&self, n: BlockNumber) { let hash = self.block_hash(BlockId::Number(n)).unwrap(); - let header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); - let mut header = header.unlock(); + let mut header: BlockHeader = self.block_header(BlockId::Number(n)).unwrap().decode(); header.set_parent_hash(H256::from(42)); - let header = header.lock(); let mut rlp = RlpStream::new_list(3); rlp.append(&header); rlp.append_raw(&::rlp::NULL_RLP, 1); @@ -392,7 +387,7 @@ impl MiningBlockChainClient for TestBlockChainClient { false, ).expect("Opening block for tests will not fail."); // TODO [todr] Override timestamp for predictability (set_timestamp_now kind of sucks) - open_block.alter_header(|h| h.set_timestamp(*self.latest_block_timestamp.read())); + open_block.set_timestamp(*self.latest_block_timestamp.read()); open_block } diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 749d4382fe8..54b9c14a8aa 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -787,7 +787,7 @@ impl Engine for AuthorityRound { }; let score = calculate_score(parent_step.into(), current_step.into(), current_empty_steps_len.into()); - header.alter(|h| h.set_difficulty(score)); + header.set_difficulty(score); } fn seals_internally(&self) -> Option { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1351678190a..b408d1eee7a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -466,7 +466,7 @@ impl Engine for Tendermint { + consensus_view(parent).expect("Header has been verified; qed").into() - self.view.load(AtomicOrdering::SeqCst).into(); - header.alter(|h| h.set_difficulty(new_difficulty)); + header.set_difficulty(new_difficulty); } /// Should this node participate. diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 2e566685fda..a9ee79f304d 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -187,7 +187,7 @@ impl Engine for Arc { fn populate_from_parent(&self, header: &mut Header, parent: &Header) { let difficulty = self.calculate_difficulty(header, parent); - header.alter(|h| h.set_difficulty(difficulty)); + header.set_difficulty(difficulty); } fn on_new_block( diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 2768878e686..cfac04f3ca1 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -17,6 +17,7 @@ //! Block header. use std::cmp; +use std::cell::RefCell; use hash::{KECCAK_NULL_RLP, KECCAK_EMPTY_LIST_RLP, keccak}; use heapsize::HeapSizeOf; use ethereum_types::{H256, U256, Address, Bloom}; @@ -74,19 +75,28 @@ pub struct Header { /// Vector of post-RLP-encoded fields. seal: Vec, - /// The memoized RLP representation of this header *including* the seal fields. - rlp: Bytes, /// The memoized hash of the RLP representation *including* the seal fields. - hash: H256, - /// The memoized RLP representation of this header *without* the seal fields. - bare_rlp: Bytes, + hash: RefCell>, /// The memoized hash of the RLP representation *without* the seal fields. - bare_hash: H256, + bare_hash: RefCell>, } impl PartialEq for Header { fn eq(&self, c: &Header) -> bool { - self.hash == c.hash + self.parent_hash == c.parent_hash && + self.timestamp == c.timestamp && + self.number == c.number && + self.author == c.author && + self.transactions_root == c.transactions_root && + self.uncles_hash == c.uncles_hash && + self.extra_data == c.extra_data && + self.state_root == c.state_root && + self.receipts_root == c.receipts_root && + self.log_bloom == c.log_bloom && + self.gas_used == c.gas_used && + self.gas_limit == c.gas_limit && + self.difficulty == c.difficulty && + self.seal == c.seal } } @@ -110,11 +120,9 @@ impl Default for Header { difficulty: U256::default(), seal: vec![], - rlp: vec![], - hash: H256::default(), - bare_rlp: vec![], - bare_hash: H256::default(), - }.recompute_hashes() + hash: RefCell::new(None), + bare_hash: RefCell::new(None), + } } } @@ -135,6 +143,8 @@ impl Header { /// Get the extra data field of the header. pub fn extra_data(&self) -> &Bytes { &self.extra_data } + /// Get a mutable reference to extra_data + pub fn extra_data_mut(&mut self) -> &mut Bytes { self.note_dirty(); &mut self.extra_data } /// Get the state root field of the header. pub fn state_root(&self) -> &H256 { &self.state_root } @@ -162,6 +172,74 @@ impl Header { }).collect() } + // TODO: seal_at, set_seal_at &c. + + /// Set the number field of the header. + pub fn set_parent_hash(&mut self, a: H256) { self.parent_hash = a; self.note_dirty(); } + /// Set the uncles hash field of the header. + pub fn set_uncles_hash(&mut self, a: H256) { self.uncles_hash = a; self.note_dirty(); } + /// Set the state root field of the header. + pub fn set_state_root(&mut self, a: H256) { self.state_root = a; self.note_dirty(); } + /// Set the transactions root field of the header. + pub fn set_transactions_root(&mut self, a: H256) { self.transactions_root = a; self.note_dirty() } + /// Set the receipts root field of the header. + pub fn set_receipts_root(&mut self, a: H256) { self.receipts_root = a; self.note_dirty() } + /// Set the log bloom field of the header. + pub fn set_log_bloom(&mut self, a: Bloom) { self.log_bloom = a; self.note_dirty() } + /// Set the timestamp field of the header. + pub fn set_timestamp(&mut self, a: u64) { self.timestamp = a; self.note_dirty(); } + /// Set the timestamp field of the header to the current time. + pub fn set_timestamp_now(&mut self, but_later_than: u64) { self.timestamp = cmp::max(get_time().sec as u64, but_later_than + 1); self.note_dirty(); } + /// Set the number field of the header. + pub fn set_number(&mut self, a: BlockNumber) { self.number = a; self.note_dirty(); } + /// Set the author field of the header. + pub fn set_author(&mut self, a: Address) { if a != self.author { self.author = a; self.note_dirty(); } } + + /// Set the extra data field of the header. + pub fn set_extra_data(&mut self, a: Bytes) { if a != self.extra_data { self.extra_data = a; self.note_dirty(); } } + + /// Set the gas used field of the header. + pub fn set_gas_used(&mut self, a: U256) { self.gas_used = a; self.note_dirty(); } + /// Set the gas limit field of the header. + pub fn set_gas_limit(&mut self, a: U256) { self.gas_limit = a; self.note_dirty(); } + + /// Set the difficulty field of the header. + pub fn set_difficulty(&mut self, a: U256) { self.difficulty = a; self.note_dirty(); } + /// Set the seal field of the header. + pub fn set_seal(&mut self, a: Vec) { self.seal = a; self.note_dirty(); } + + /// Get the hash of this header (keccak of the RLP). + pub fn hash(&self) -> H256 { + let mut hash = self.hash.borrow_mut(); + match &mut *hash { + &mut Some(ref h) => h.clone(), + hash @ &mut None => { + let h = self.rlp_keccak(Seal::With); + *hash = Some(h.clone()); + h + } + } + } + + /// Get the hash of the header excluding the seal + pub fn bare_hash(&self) -> H256 { + let mut hash = self.bare_hash.borrow_mut(); + match &mut *hash { + &mut Some(ref h) => h.clone(), + hash @ &mut None => { + let h = self.rlp_keccak(Seal::Without); + *hash = Some(h.clone()); + h + } + } + } + + /// Note that some fields have changed. Resets the memoised hash. + pub fn note_dirty(&self) { + *self.hash.borrow_mut() = None; + *self.bare_hash.borrow_mut() = None; + } + // TODO: make these functions traity /// Place this header into an RLP stream `s`, optionally `with_seal`. pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { @@ -188,142 +266,18 @@ impl Header { /// Get the RLP of this header, optionally `with_seal`. pub fn rlp(&self, with_seal: Seal) -> Bytes { - match with_seal { - Seal::With => self.rlp.clone(), - Seal::Without => self.bare_rlp.clone(), - } + let mut s = RlpStream::new(); + self.stream_rlp(&mut s, with_seal); + s.out() } /// Get the SHA3 (Keccak) of this header, optionally `with_seal`. - pub fn rlp_keccak(&self, with_seal: Seal) -> H256 { - match with_seal { - Seal::With => self.hash, - Seal::Without => self.bare_hash, - } - } + pub fn rlp_keccak(&self, with_seal: Seal) -> H256 { keccak(self.rlp(with_seal)) } /// Encode the header, getting a type-safe wrapper around the RLP. pub fn encoded(&self) -> ::encoded::Header { ::encoded::Header::new(self.rlp(Seal::With)) } - - /// Get the hash of this header (keccak of the RLP). - pub fn hash(&self) -> H256 { - self.hash - } - - /// Get the hash of the header excluding the seal - pub fn bare_hash(&self) -> H256 { - self.bare_hash - } - - pub fn alter(&mut self, f: F) { - use std::ptr; - - let mut old_t = unsafe { ptr::read(self) }.unlock(); - f(&mut old_t); - unsafe { ptr::write(self, old_t.lock()) }; - } - - pub fn unlock(self) -> HeaderMut { - HeaderMut { - header: self, - is_dirty: false, - } - } - - fn recompute_hashes(mut self) -> Self { - Self::recompute(&mut self); - self - } - - fn recompute(header: &mut Self) { - let (rlp, bare_rlp) = { - let rlp = |with_seal| { - let mut s = RlpStream::new(); - header.stream_rlp(&mut s, with_seal); - s.out() - }; - (rlp(Seal::Without), rlp(Seal::With)) - }; - - header.rlp = rlp; - header.bare_rlp = bare_rlp; - - header.hash = keccak(&header.rlp); - header.bare_hash = keccak(&header.bare_rlp); - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct HeaderMut { - header: Header, - is_dirty: bool, -} - -impl HeaderMut { - /// Lock this header and recompute hash. - pub fn lock(mut self) -> Header { - if self.is_dirty { - self.header.recompute_hashes() - } else { - self.header - } - } - - /// Get a mutable reference to header. - /// - /// NOTE It's not safe to clone such header! RLP/Hash values are most likely invalid. - pub fn mutable_header(&mut self) -> &mut Header { - self.note_dirty(); - &mut self.header - } - /// Get the number field of the header. - pub fn number(&self) -> BlockNumber { self.header.number } - /// Get the gas used field of the header. - pub fn gas_used(&self) -> &U256 { &self.header.gas_used } - - /// Set the number field of the header. - pub fn set_parent_hash(&mut self, a: H256) { self.header.parent_hash = a; self.note_dirty(); } - /// Set the uncles hash field of the header. - pub fn set_uncles_hash(&mut self, a: H256) { self.header.uncles_hash = a; self.note_dirty(); } - /// Set the state root field of the header. - pub fn set_state_root(&mut self, a: H256) { self.header.state_root = a; self.note_dirty(); } - /// Set the transactions root field of the header. - pub fn set_transactions_root(&mut self, a: H256) { self.header.transactions_root = a; self.note_dirty() } - /// Set the receipts root field of the header. - pub fn set_receipts_root(&mut self, a: H256) { self.header.receipts_root = a; self.note_dirty() } - /// Set the log bloom field of the header. - pub fn set_log_bloom(&mut self, a: Bloom) { self.header.log_bloom = a; self.note_dirty() } - /// Set the timestamp field of the header. - pub fn set_timestamp(&mut self, a: u64) { self.header.timestamp = a; self.note_dirty(); } - /// Set the timestamp field of the header to the current time. - pub fn set_timestamp_now(&mut self, but_later_than: u64) { self.header.timestamp = cmp::max(get_time().sec as u64, but_later_than + 1); self.note_dirty(); } - /// Set the number field of the header. - pub fn set_number(&mut self, a: BlockNumber) { self.header.number = a; self.note_dirty(); } - /// Set the author field of the header. - pub fn set_author(&mut self, a: Address) { if a != self.header.author { self.header.author = a; self.note_dirty(); } } - - /// Set the extra data field of the header. - pub fn set_extra_data(&mut self, a: Bytes) { if a != self.header.extra_data { self.header.extra_data = a; self.note_dirty(); } } - - /// Get a mutable reference to extra_data - pub fn extra_data_mut(&mut self) -> &mut Bytes { self.note_dirty(); &mut self.header.extra_data } - - /// Set the gas used field of the header. - pub fn set_gas_used(&mut self, a: U256) { self.header.gas_used = a; self.note_dirty(); } - /// Set the gas limit field of the header. - pub fn set_gas_limit(&mut self, a: U256) { self.header.gas_limit = a; self.note_dirty(); } - - /// Set the difficulty field of the header. - pub fn set_difficulty(&mut self, a: U256) { self.header.difficulty = a; self.note_dirty(); } - /// Set the seal field of the header. - pub fn set_seal(&mut self, a: Vec) { self.header.seal = a; self.note_dirty(); } - - /// Note that some fields have changed. Resets the memoised hash. - pub fn note_dirty(&mut self) { - self.is_dirty = true; - } } impl Decodable for Header { @@ -343,17 +297,15 @@ impl Decodable for Header { timestamp: cmp::min(r.val_at::(11)?, u64::max_value().into()).as_u64(), extra_data: r.val_at(12)?, seal: vec![], - rlp: vec![], - bare_rlp: vec![], - hash: Default::default(), - bare_hash: Default::default(), + hash: RefCell::new(Some(keccak(r.as_raw()))), + bare_hash: RefCell::new(None), }; for i in 13..r.item_count()? { blockheader.seal.push(r.at(i)?.as_raw().to_vec()) } - Ok(blockheader.recompute_hashes()) + Ok(blockheader) } } @@ -383,11 +335,7 @@ impl ::parity_machine::Header for Header { impl ::parity_machine::ScoredHeader for Header { fn score(&self) -> &U256 { self.difficulty() } - - fn set_score(&mut self, score: U256) { - self.difficulty = score; - Header::recompute(self) - } + fn set_score(&mut self, score: U256) { self.set_difficulty(score) } } #[cfg(test)] diff --git a/ethcore/src/machine.rs b/ethcore/src/machine.rs index 66b949382b7..e302a461340 100644 --- a/ethcore/src/machine.rs +++ b/ethcore/src/machine.rs @@ -25,7 +25,7 @@ use builtin::Builtin; use client::BlockChainClient; use error::Error; use executive::Executive; -use header::{BlockNumber, Header, HeaderMut}; +use header::{BlockNumber, Header}; use spec::CommonParams; use state::{CleanupMode, Substate}; use trace::{NoopTracer, NoopVMTracer, Tracer, ExecutiveTracer, RewardType}; @@ -201,7 +201,7 @@ impl EthereumMachine { /// Populate a header's fields based on its parent's header. /// Usually implements the chain scoring rule based on weight. /// The gas floor target must not be lower than the engine's minimum gas limit. - pub fn populate_from_parent(&self, header: &mut HeaderMut, parent: &Header, gas_floor_target: U256, gas_ceil_target: U256) { + pub fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, gas_ceil_target: U256) { header.set_difficulty(parent.difficulty().clone()); if let Some(ref ethash_params) = self.ethash_extensions { @@ -495,7 +495,7 @@ mod tests { ); let mut parent = ::header::Header::new(); - let mut header = ::header::Header::new().unlock(); + let mut header = ::header::Header::new(); header.set_number(1); // this test will work for this constant only @@ -504,25 +504,25 @@ mod tests { // when parent.gas_limit < gas_floor_target: parent.set_gas_limit(U256::from(50_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000)); - assert_eq!(*header.clone().lock().gas_limit(), U256::from(50_024)); + assert_eq!(*header.gas_limit(), U256::from(50_024)); // when parent.gas_limit > gas_ceil_target: parent.set_gas_limit(U256::from(250_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000)); - assert_eq!(*header.clone().lock().gas_limit(), U256::from(249_787)); + assert_eq!(*header.gas_limit(), U256::from(249_787)); // when parent.gas_limit is in miner's range header.set_gas_used(U256::from(150_000)); parent.set_gas_limit(U256::from(150_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000)); - assert_eq!(*header.clone().lock().gas_limit(), U256::from(150_035)); + assert_eq!(*header.gas_limit(), U256::from(150_035)); // when parent.gas_limit is in miner's range // && we can NOT increase it to be multiple of constant header.set_gas_used(U256::from(150_000)); parent.set_gas_limit(U256::from(150_000)); machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(150_002)); - assert_eq!(*header.clone().lock().gas_limit(), U256::from(149_998)); + assert_eq!(*header.gas_limit(), U256::from(149_998)); // when parent.gas_limit is in miner's range // && we can NOT increase it to be multiple of constant @@ -530,6 +530,6 @@ mod tests { header.set_gas_used(U256::from(150_000)); parent.set_gas_limit(U256::from(150_000)); machine.populate_from_parent(&mut header, &parent, U256::from(150_000), U256::from(150_002)); - assert_eq!(*header.clone().lock().gas_limit(), U256::from(150_002)); + assert_eq!(*header.gas_limit(), U256::from(150_002)); } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 759ce3b2941..7fffe5511ef 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -360,7 +360,7 @@ impl Miner { }; if self.options.infinite_pending_block { - open_block.alter_header(|h| h.set_gas_limit(!U256::zero())); + open_block.set_gas_limit(!U256::zero()); } (open_block, last_work_hash) @@ -696,7 +696,7 @@ impl MinerService for Miner { fn set_author(&self, address: Address, password: Option) -> Result<(), AccountError> { self.params.write().author = address; - if self.engine.seals_internally().is_some() && password.is_some() { + if self.engine.seals_internally().is_some() { if let Some(ref ap) = self.accounts { let password = password.unwrap_or_default(); // Sign test message diff --git a/ethcore/src/snapshot/block.rs b/ethcore/src/snapshot/block.rs index 44047bb6add..98215b3244d 100644 --- a/ethcore/src/snapshot/block.rs +++ b/ethcore/src/snapshot/block.rs @@ -91,7 +91,7 @@ impl AbridgedBlock { pub fn to_block(&self, parent_hash: H256, number: u64, receipts_root: H256) -> Result { let rlp = UntrustedRlp::new(&self.rlp); - let mut header = Header::default().unlock(); + let mut header: Header = Default::default(); header.set_parent_hash(parent_hash); header.set_author(rlp.val_at(0)?); header.set_state_root(rlp.val_at(1)?); @@ -124,7 +124,7 @@ impl AbridgedBlock { header.set_seal(seal_fields); Ok(Block { - header: header.lock(), + header: header, transactions: transactions, uncles: uncles, }) diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 5de648031a7..ba9e578db6d 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -572,7 +572,7 @@ impl Spec { /// Get the header of the genesis block. pub fn genesis_header(&self) -> Header { - let mut header = Header::default().unlock(); + let mut header: Header = Default::default(); header.set_parent_hash(self.parent_hash.clone()); header.set_timestamp(self.timestamp); header.set_number(0); @@ -590,7 +590,6 @@ impl Spec { let r = Rlp::new(&self.seal_rlp); r.iter().map(|f| f.as_raw().to_vec()).collect() }); - let header = header.lock(); trace!(target: "spec", "Header hash is {}", header.hash()); header } diff --git a/machine/src/lib.rs b/machine/src/lib.rs index c06b0904d76..3a45c38d2ef 100644 --- a/machine/src/lib.rs +++ b/machine/src/lib.rs @@ -77,7 +77,6 @@ pub trait Transactions: LiveBlock { pub trait Machine: for<'a> LocalizedMachine<'a> { /// The block header type. type Header: Header; - /// The live block type. type LiveBlock: LiveBlock; /// A handle to a blockchain client for this machine. From e902becf07c6272b1fd2673f40da94a0f8697837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 22:38:22 +0100 Subject: [PATCH 21/77] Override Sync requirement. --- ethcore/src/miner/blockchain_client.rs | 16 ++++++++++++---- ethcore/src/miner/miner.rs | 2 +- transaction-pool/src/pool.rs | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index 94613cd50d5..60d8526a0ff 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -30,12 +30,17 @@ use client::{MiningBlockChainClient, BlockId, TransactionId}; use engines::EthEngine; use header::Header; +// TODO [ToDr] Shit +#[derive(Clone)] +pub struct FakeContainer(Header); +unsafe impl Sync for FakeContainer {} + #[derive(Clone)] pub struct BlockChainClient<'a> { chain: &'a MiningBlockChainClient, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>, - best_block_header: Header, + best_block_header: FakeContainer, service_transaction_checker: Option, } @@ -47,6 +52,9 @@ impl<'a> BlockChainClient<'a> { refuse_service_transactions: bool, ) -> Self { let best_block_header = chain.best_block_header().decode(); + best_block_header.hash(); + best_block_header.bare_hash(); + let best_block_header = FakeContainer(best_block_header); BlockChainClient { chain, engine, @@ -61,7 +69,7 @@ impl<'a> BlockChainClient<'a> { } pub fn verify_signed(&self, tx: &SignedTransaction) -> Result<(), transaction::Error> { - self.engine.machine().verify_transaction(&tx, &self.best_block_header, self.chain.as_block_chain_client()) + self.engine.machine().verify_transaction(&tx, &self.best_block_header.0, self.chain.as_block_chain_client()) } } @@ -79,8 +87,8 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { fn verify_transaction(&self, tx: UnverifiedTransaction) -> Result { - self.engine.verify_transaction_basic(&tx, &self.best_block_header)?; - let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header)?; + self.engine.verify_transaction_basic(&tx, &self.best_block_header.0)?; + let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header.0)?; self.verify_signed(&tx)?; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 7fffe5511ef..89cfca7c3ed 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -696,7 +696,7 @@ impl MinerService for Miner { fn set_author(&self, address: Address, password: Option) -> Result<(), AccountError> { self.params.write().author = address; - if self.engine.seals_internally().is_some() { + if self.engine.seals_internally().is_some() && password.is_some() { if let Some(ref ap) = self.accounts { let password = password.unwrap_or_default(); // Sign test message diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index cc7e07d0aed..691e8fa8724 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -468,7 +468,7 @@ impl<'a, T, R, S, L> Iterator for PendingIterator<'a, T, R, S, L> where return Some(best.transaction) }, - state => warn!("[{:?}] Ignoring {:?} transaction.", best.transaction.hash(), state), + state => trace!("[{:?}] Ignoring {:?} transaction.", best.transaction.hash(), state), } } From fede1eaded2de481d3418233c84b8d094d8b7b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 22:55:34 +0100 Subject: [PATCH 22/77] Fix status display. --- miner/src/pool/queue.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 1c7268ccbfc..2e4059afe4c 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -48,13 +48,13 @@ impl fmt::Display for Status { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { writeln!( fmt, - "Pool: {current}/{max} ({senders} senders; {mem}/{mem_max} kB) [minGasPrice: {gp} gwei, maxGas: {max_gas}]", + "Pool: {current}/{max} ({senders} senders; {mem}/{mem_max} kB) [minGasPrice: {gp} Mwei, maxGas: {max_gas}]", current = self.status.transaction_count, max = self.limits.max_count, senders = self.status.senders, mem = self.status.mem_usage / 1024, - mem_max = self.status.mem_usage / 1024, - gp = self.options.minimal_gas_price / 1_000_000_000.into(), + mem_max = self.limits.max_mem_usage / 1024, + gp = self.options.minimal_gas_price / 1_000_000.into(), max_gas = ::std::cmp::min(self.options.block_gas_limit, self.options.tx_gas_limit), ) } From 5075cec03e45443d31b2ac8086d413416f8788a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 23:00:24 +0100 Subject: [PATCH 23/77] Unify logging. --- miner/src/pool/verifier.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index b720f5f3f3d..bff7d4b8c9e 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -112,7 +112,7 @@ impl txpool::Verifier for Verifier { let hash = tx.hash(); if self.client.transaction_already_included(&hash) { - trace!(target: "txqueue", "Rejected tx {:?}: already in the blockchain", hash); + trace!(target: "txqueue", "[{:?}] Rejected tx already in the blockchain", hash); bail!(transaction::Error::AlreadyImported) } @@ -122,7 +122,7 @@ impl txpool::Verifier for Verifier { Transaction::Retracted(tx) | Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { Ok(signed) => signed.into(), Err(err) => { - debug!(target: "txqueue", "Rejected tx {:?}: {:?}", hash, err); + debug!(target: "txqueue", "[{:?}] Rejected tx {:?}", hash, err); bail!(err) }, }, @@ -133,7 +133,7 @@ impl txpool::Verifier for Verifier { if transaction.gas > gas_limit { debug!( target: "txqueue", - "Dropping transaction above gas limit: {:?} ({} > min({}, {}))", + "[{:?}] Dropping transaction above gas limit: {} > min({}, {})", hash, transaction.gas, self.options.block_gas_limit, @@ -145,7 +145,7 @@ impl txpool::Verifier for Verifier { let minimal_gas = self.client.required_gas(&transaction); if transaction.gas < minimal_gas { trace!(target: "txqueue", - "Dropping transaction with insufficient gas: {:?} ({} > {})", + "[{:?}] Dropping transaction with insufficient gas: {} < {}", transaction.hash(), transaction.gas, minimal_gas, @@ -163,13 +163,13 @@ impl txpool::Verifier for Verifier { if transaction.gas_price < self.options.minimal_gas_price { if let TransactionType::Service = transaction_type { - trace!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); + debug!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); } else if is_own || account_details.is_local { - trace!(target: "txqueue", "Local tx {:?} below minimal gas price accepted", hash); + debug!(target: "txqueue", "Local tx {:?} below minimal gas price accepted", hash); } else { - debug!( + trace!( target: "txqueue", - "Rejected tx {:?}: below minimal gas price threshold (gp: {} < {})", + "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", hash, transaction.gas_price, self.options.minimal_gas_price, @@ -185,7 +185,7 @@ impl txpool::Verifier for Verifier { if account_details.balance < cost { debug!( target: "txqueue", - "Rejected tx {:?}: not enough balance: ({} < {})", + "[{:?}] Rejected tx with not enough balance: {} < {}", hash, account_details.balance, cost, @@ -199,7 +199,7 @@ impl txpool::Verifier for Verifier { if transaction.nonce < account_details.nonce { debug!( target: "txqueue", - "Rejected tx {:?}: old nonce ({} < {})", + "[{:?}] Rejected tx with old nonce ({} < {})", hash, transaction.nonce, account_details.nonce, From 449facf5059e25f94f6deb26073c0793fd1abb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 23 Feb 2018 23:41:41 +0100 Subject: [PATCH 24/77] Extract some cheap checks. --- Cargo.toml | 2 +- ethcore/src/miner/blockchain_client.rs | 4 +- miner/src/pool/client.rs | 2 +- miner/src/pool/verifier.rs | 94 ++++++++++++++++++------ miner/src/service_transaction_checker.rs | 15 +++- transaction-pool/src/pool.rs | 2 + 6 files changed, 90 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e89a09a597b..ca0c78a8214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,7 @@ name = "parity" panic = "abort" [profile.release] -debug = false +debug = true lto = false panic = "abort" diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index 60d8526a0ff..4f73e575d52 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -107,14 +107,14 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { self.chain.latest_nonce(address) } - fn required_gas(&self, tx: &SignedTransaction) -> U256 { + fn required_gas(&self, tx: &transaction::Transaction) -> U256 { tx.gas_required(&self.chain.latest_schedule()).into() } fn transaction_type(&self, tx: &SignedTransaction) -> pool::client::TransactionType { match self.service_transaction_checker { None => pool::client::TransactionType::Regular, - Some(ref checker) => match checker.check(self, &tx.sender(), &tx.hash()) { + Some(ref checker) => match checker.check(self, &tx) { Ok(true) => pool::client::TransactionType::Service, Ok(false) => pool::client::TransactionType::Regular, Err(e) => { diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs index eea38cd085a..932d0dc8c9f 100644 --- a/miner/src/pool/client.rs +++ b/miner/src/pool/client.rs @@ -61,7 +61,7 @@ pub trait Client: fmt::Debug + Sync { fn account_nonce(&self, address: &Address) -> U256; /// Estimate minimal gas requirurement for given transaction. - fn required_gas(&self, tx: &transaction::SignedTransaction) -> U256; + fn required_gas(&self, tx: &transaction::Transaction) -> U256; /// Classify transaction (check if transaction is filtered by some contracts). fn transaction_type(&self, tx: &transaction::SignedTransaction) -> TransactionType; diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index bff7d4b8c9e..de0863a27dd 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -81,6 +81,31 @@ impl Transaction { Transaction::Local(ref tx) => tx.hash(), } } + + fn gas(&self) -> &U256 { + match *self { + Transaction::Unverified(ref tx) => &tx.gas, + Transaction::Retracted(ref tx) => &tx.gas, + Transaction::Local(ref tx) => &tx.gas, + } + } + + + fn gas_price(&self) -> &U256 { + match *self { + Transaction::Unverified(ref tx) => &tx.gas_price, + Transaction::Retracted(ref tx) => &tx.gas_price, + Transaction::Local(ref tx) => &tx.gas_price, + } + } + + fn transaction(&self) -> &transaction::Transaction { + match *self { + Transaction::Unverified(ref tx) => &*tx, + Transaction::Retracted(ref tx) => &*tx, + Transaction::Local(ref tx) => &*tx, + } + } } /// Transaction verifier. @@ -109,6 +134,9 @@ impl txpool::Verifier for Verifier { type VerifiedTransaction = VerifiedTransaction; fn verify_transaction(&self, tx: Transaction) -> Result { + // The checks here should be ordered by cost/complexity. + // Cheap checks should be done as early as possible to discard unneeded transactions early. + let hash = tx.hash(); if self.client.transaction_already_included(&hash) { @@ -116,51 +144,73 @@ impl txpool::Verifier for Verifier { bail!(transaction::Error::AlreadyImported) } - let is_retracted = if let Transaction::Retracted(_) = tx { true } else { false }; - let is_own = if let Transaction::Local(..) = tx { true } else { false }; - let transaction = match tx { - Transaction::Retracted(tx) | Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { - Ok(signed) => signed.into(), - Err(err) => { - debug!(target: "txqueue", "[{:?}] Rejected tx {:?}", hash, err); - bail!(err) - }, - }, - Transaction::Local(tx) => tx, - }; - let gas_limit = cmp::min(self.options.tx_gas_limit, self.options.block_gas_limit); - if transaction.gas > gas_limit { + if tx.gas() > &gas_limit { debug!( target: "txqueue", "[{:?}] Dropping transaction above gas limit: {} > min({}, {})", hash, - transaction.gas, + tx.gas(), self.options.block_gas_limit, self.options.tx_gas_limit, ); - bail!(transaction::Error::GasLimitExceeded { limit: gas_limit, got: transaction.gas }); + bail!(transaction::Error::GasLimitExceeded { + limit: gas_limit, + got: *tx.gas(), + }); } - let minimal_gas = self.client.required_gas(&transaction); - if transaction.gas < minimal_gas { + let minimal_gas = self.client.required_gas(tx.transaction()); + if tx.gas() < &minimal_gas { trace!(target: "txqueue", "[{:?}] Dropping transaction with insufficient gas: {} < {}", - transaction.hash(), - transaction.gas, + hash, + tx.gas(), minimal_gas, ); bail!(transaction::Error::InsufficientGas { minimal: minimal_gas, - got: transaction.gas, + got: *tx.gas(), }) } + let is_own = if let Transaction::Local(..) = tx { true } else { false }; + // Quick exit for non-service transactions + if tx.gas_price() < &self.options.minimal_gas_price + && !tx.gas_price().is_zero() + && !is_own + { + trace!( + target: "txqueue", + "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", + hash, + tx.gas_price(), + self.options.minimal_gas_price, + ); + bail!(transaction::Error::InsufficientGasPrice { + minimal: self.options.minimal_gas_price, + got: *tx.gas_price(), + }); + } + + // Some more heavy checks below. + // Actually recover sender and verify that transaction + let is_retracted = if let Transaction::Retracted(_) = tx { true } else { false }; + let transaction = match tx { + Transaction::Retracted(tx) | Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { + Ok(signed) => signed.into(), + Err(err) => { + debug!(target: "txqueue", "[{:?}] Rejected tx {:?}", hash, err); + bail!(err) + }, + }, + Transaction::Local(tx) => tx, + }; + let transaction_type = self.client.transaction_type(&transaction); let sender = transaction.sender(); let account_details = self.client.account_details(&sender); - if transaction.gas_price < self.options.minimal_gas_price { if let TransactionType::Service = transaction_type { debug!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); diff --git a/miner/src/service_transaction_checker.rs b/miner/src/service_transaction_checker.rs index 6df2e9da8f1..d180e635293 100644 --- a/miner/src/service_transaction_checker.rs +++ b/miner/src/service_transaction_checker.rs @@ -16,7 +16,8 @@ //! A service transactions contract checker. -use ethereum_types::{H256, Address}; +use ethereum_types::Address; +use transaction::SignedTransaction; use_contract!(service_transaction, "ServiceTransaction", "res/service_transaction.json"); @@ -46,7 +47,15 @@ impl Clone for ServiceTransactionChecker { impl ServiceTransactionChecker { /// Checks if given address is whitelisted to send service transactions. - pub fn check(&self, client: &ContractCaller, sender: &Address, hash: &H256) -> Result { + pub fn check(&self, client: &ContractCaller, tx: &SignedTransaction) -> Result { + let sender = tx.sender(); + let hash = tx.hash(); + + // Skip checking the contract if the transaction does not have zero gas price + if !tx.gas_price.is_zero() { + return Ok(false) + } + let address = client.registry_address(SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME) .ok_or_else(|| "contract is not configured")?; @@ -54,7 +63,7 @@ impl ServiceTransactionChecker { self.contract.functions() .certified() - .call(*sender, &|data| client.call_contract(address, data)) + .call(sender, &|data| client.call_contract(address, data)) .map_err(|e| e.to_string()) } } diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 691e8fa8724..1c903f7f406 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -161,11 +161,13 @@ impl Pool where }, AddResult::TooCheap { new, old } => { let hash = *new.hash(); + // TODO [ToDr] Pass errors here self.listener.rejected(&Arc::new(new)); bail!(error::ErrorKind::TooCheapToReplace(*old.hash(), hash)) }, AddResult::TooCheapToEnter(new) => { let hash = *new.hash(); + // TODO [ToDr] Pass errors here self.listener.rejected(&Arc::new(new)); bail!(error::ErrorKind::TooCheapToEnter(hash)) } From e2be467a4046e50bb704708cae1a53488a880eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 27 Feb 2018 15:20:05 +0100 Subject: [PATCH 25/77] Measurements and optimizations. --- Cargo.lock | 11 +++++ Cargo.toml | 3 +- ethcore/Cargo.toml | 1 + ethcore/src/client/client.rs | 42 ++++++++++++++++++- ethcore/src/lib.rs | 19 ++++----- miner/Cargo.toml | 1 + miner/src/lib.rs | 1 + miner/src/pool/queue.rs | 2 + miner/src/pool/verifier.rs | 3 +- util/timer/Cargo.toml | 9 ++++ ethcore/src/timer.rs => util/timer/src/lib.rs | 11 +++-- 11 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 util/timer/Cargo.toml rename ethcore/src/timer.rs => util/timer/src/lib.rs (85%) diff --git a/Cargo.lock b/Cargo.lock index 3c9dde851bc..bad94f6bb5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,6 +519,7 @@ dependencies = [ "stop-guard 0.1.0", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", + "timer 0.1.0", "trie-standardmap 0.1.0", "triehash 0.1.0", "unexpected 0.1.0", @@ -637,6 +638,7 @@ dependencies = [ "price-info 1.7.0", "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "timer 0.1.0", "transaction-pool 1.9.0", ] @@ -3167,6 +3169,14 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "timer" +version = "0.1.0" +dependencies = [ + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tiny-keccak" version = "1.4.0" @@ -3291,6 +3301,7 @@ dependencies = [ "ethereum-types 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "timer 0.1.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ca0c78a8214..b34ab6ab53c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,5 +131,6 @@ members = [ "miner", "transaction-pool", "whisper", - "util/rlp_compress" + "util/rlp_compress", + "util/timer" ] diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index d857cdad145..a2133f66213 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -59,6 +59,7 @@ rust-crypto = "0.2.34" rustc-hex = "1.0" stats = { path = "../util/stats" } time = "0.1" +timer = { path = "../util/timer" } using_queue = { path = "../util/using_queue" } vm = { path = "vm" } wasm = { path = "wasm" } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ccefdc40a0a..2ee83369607 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -82,6 +82,7 @@ pub use verification::queue::QueueInfo as BlockQueueInfo; use_contract!(registry, "Registry", "res/contracts/registrar.json"); const MAX_TX_QUEUE_SIZE: usize = 4096; +const MAX_TX_TO_IMPORT: usize = 256; const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2; const MIN_HISTORY_SIZE: u64 = 8; @@ -159,6 +160,7 @@ pub struct Client { io_channel: Mutex>, notify: RwLock>>, queue_transactions: AtomicUsize, + queued_transactions: Mutex>, last_hashes: RwLock>, factories: Factories, history: u64, @@ -251,6 +253,7 @@ impl Client { io_channel: Mutex::new(message_channel), notify: RwLock::new(Vec::new()), queue_transactions: AtomicUsize::new(0), + queued_transactions: Mutex::new(vec![]), last_hashes: RwLock::new(VecDeque::new()), factories: factories, history: history, @@ -893,18 +896,52 @@ impl Client { /// Import transactions from the IO queue pub fn import_queued_transactions(&self, transactions: &[Bytes], peer_id: usize) -> usize { - trace!(target: "external_tx", "Importing queued"); let _timer = PerfTimer::new("import_queued_transactions"); self.queue_transactions.fetch_sub(transactions.len(), AtomicOrdering::SeqCst); - let txs: Vec = transactions.iter().filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()).collect(); + + let mut txs: Vec = transactions + .iter() + .filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()) + .collect(); + + // Notify sync that the transactions were received from given peer let hashes: Vec<_> = txs.iter().map(|tx| tx.hash()).collect(); self.notify(|notify| { notify.transactions_received(hashes.clone(), peer_id); }); + + // Import up to MAX_TX_TO_IMPORT transactions at once to prevent long pauses. + let len = txs.len(); + if len > MAX_TX_TO_IMPORT { + let diff = len - MAX_TX_TO_IMPORT; + self.queue_transactions.fetch_add(diff, AtomicOrdering::SeqCst); + + let mut queued = self.queued_transactions.lock(); + for tx in txs.drain(MAX_TX_TO_IMPORT .. len) { + queued.push(tx); + } + } + let results = self.miner.import_external_transactions(self, txs); results.len() } + fn check_transaction_import_queue(&self) { + let txs = { + let mut queued = self.queued_transactions.lock(); + if queued.is_empty() { + return; + } + + let len = ::std::cmp::min(queued.len(), MAX_TX_TO_IMPORT); + queued.split_off(len) + }; + + let _timer = PerfTimer::new("check_transaction_import_queue"); + self.queue_transactions.fetch_sub(txs.len(), AtomicOrdering::SeqCst); + self.miner.import_external_transactions(self, txs); + } + /// Get shared miner reference. pub fn miner(&self) -> Arc { self.miner.clone() @@ -988,6 +1025,7 @@ impl Client { // TODO: manage by real events. pub fn tick(&self, prevent_sleep: bool) { self.check_garbage(); + self.check_transaction_import_queue(); if !prevent_sleep { self.check_snooze(); } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 56e5cbfbef6..1820b4f1d43 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -95,15 +95,7 @@ extern crate kvdb_memorydb; extern crate util_error; extern crate snappy; extern crate migration; - extern crate ethabi; -#[macro_use] -extern crate ethabi_derive; -#[macro_use] -extern crate ethabi_contract; - -#[macro_use] -extern crate rlp_derive; extern crate rustc_hex; extern crate stats; extern crate stop_guard; @@ -113,13 +105,21 @@ extern crate vm; extern crate wasm; extern crate memory_cache; extern crate journaldb; +extern crate timer; #[macro_use] -extern crate macros; +extern crate ethabi_derive; +#[macro_use] +extern crate ethabi_contract; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate macros; +#[macro_use] +extern crate rlp_derive; + #[cfg_attr(test, macro_use)] extern crate evm; @@ -148,7 +148,6 @@ pub mod snapshot; pub mod spec; pub mod state; pub mod state_db; -pub mod timer; pub mod trace; pub mod verification; pub mod views; diff --git a/miner/Cargo.toml b/miner/Cargo.toml index 0286af56089..fd153789b66 100644 --- a/miner/Cargo.toml +++ b/miner/Cargo.toml @@ -26,6 +26,7 @@ log = "0.3" parking_lot = "0.5" price-info = { path = "../price-info" } rayon = "1.0" +timer = { path = "../util/timer" } transaction-pool = { path = "../transaction-pool" } [dev-dependencies] diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 917aa1aedcc..fcc54a99c63 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -30,6 +30,7 @@ extern crate linked_hash_map; extern crate parking_lot; extern crate price_info; extern crate rayon; +extern crate timer; extern crate transaction_pool as txpool; #[macro_use] diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 2e4059afe4c..726ab5bc468 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -26,6 +26,7 @@ use parking_lot::RwLock; use rayon::prelude::*; use transaction; use txpool::{self, Verifier}; +use timer::PerfTimer; use pool::{self, scoring, verifier, client, ready, listener}; use pool::local_transactions::LocalTransactionsList; @@ -100,6 +101,7 @@ impl TransactionQueue { transactions: Vec, ) -> Vec> { // Run verification + let _timer = PerfTimer::new("queue::verifyAndImport"); let options = self.options.read().clone(); let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index de0863a27dd..2e8f91f0944 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -133,6 +133,7 @@ impl txpool::Verifier for Verifier { type Error = transaction::Error; type VerifiedTransaction = VerifiedTransaction; + // TODO [ToDr] Add recently rejected. fn verify_transaction(&self, tx: Transaction) -> Result { // The checks here should be ordered by cost/complexity. // Cheap checks should be done as early as possible to discard unneeded transactions early. @@ -215,7 +216,7 @@ impl txpool::Verifier for Verifier { if let TransactionType::Service = transaction_type { debug!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); } else if is_own || account_details.is_local { - debug!(target: "txqueue", "Local tx {:?} below minimal gas price accepted", hash); + info!(target: "own_tx", "Local tx {:?} below minimal gas price accepted", hash); } else { trace!( target: "txqueue", diff --git a/util/timer/Cargo.toml b/util/timer/Cargo.toml new file mode 100644 index 00000000000..da5ebacff95 --- /dev/null +++ b/util/timer/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "timer" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Performance timer with logging" + +[dependencies] +log = "0.3" +time = "0.1" diff --git a/ethcore/src/timer.rs b/util/timer/src/lib.rs similarity index 85% rename from ethcore/src/timer.rs rename to util/timer/src/lib.rs index 791c74f152d..098239a8a8c 100644 --- a/ethcore/src/timer.rs +++ b/util/timer/src/lib.rs @@ -15,7 +15,12 @@ // along with Parity. If not, see . //! Performance timer with logging -use time::precise_time_ns; + +#![deny(missing_docs)] +extern crate time; +#[macro_use] +extern crate log; + /// Performance timer with logging. Starts measuring time in the constructor, prints /// elapsed time in the destructor or when `stop` is called. @@ -30,7 +35,7 @@ impl PerfTimer { pub fn new(name: &'static str) -> PerfTimer { PerfTimer { name: name, - start: precise_time_ns(), + start: time::precise_time_ns(), stopped: false, } } @@ -38,7 +43,7 @@ impl PerfTimer { /// Stop the timer and print elapsed time on trace level with `perf` target. pub fn stop(&mut self) { if !self.stopped { - trace!(target: "perf", "{}: {:.2}ms", self.name, (precise_time_ns() - self.start) as f32 / 1000_000.0); + trace!(target: "perf", "{}: {:.2}ms", self.name, (time::precise_time_ns() - self.start) as f32 / 1000_000.0); self.stopped = true; } } From b720077dc4535269e7c9b5a419acbeb6445ca3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 28 Feb 2018 12:12:12 +0100 Subject: [PATCH 26/77] Fix scoring bug, heap size of bug and add cache --- ethcore/src/miner/miner.rs | 3 - ethcore/transaction/src/transaction.rs | 2 +- miner/src/pool/mod.rs | 3 + miner/src/pool/queue.rs | 52 +- miner/src/pool/scoring.rs | 4 + miner/src/pool/tests/mod.rs | 1376 ++++++++++++++++++++++++ transaction-pool/src/scoring.rs | 2 +- 7 files changed, 1431 insertions(+), 11 deletions(-) create mode 100644 miner/src/pool/tests/mod.rs diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index e3548a66cef..a3fc4e863ee 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -385,8 +385,6 @@ impl Miner { client.clone(), chain_info.best_block_number, chain_info.best_block_timestamp, - // TODO [ToDr] Take only part? - |transactions| transactions.collect(), // nonce_cap, ); @@ -801,7 +799,6 @@ impl MinerService for Miner { client, chain_info.best_block_number, chain_info.best_block_timestamp, - |transactions| transactions.collect(), ) }; diff --git a/ethcore/transaction/src/transaction.rs b/ethcore/transaction/src/transaction.rs index e3d7fcde8f9..ea7119e777b 100644 --- a/ethcore/transaction/src/transaction.rs +++ b/ethcore/transaction/src/transaction.rs @@ -116,7 +116,7 @@ impl Transaction { impl HeapSizeOf for Transaction { fn heap_size_of_children(&self) -> usize { - self.data.heap_size_of_children() + self.data.len() } } diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 84630a0a39c..23349d7fed5 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -29,6 +29,9 @@ pub mod ready; pub mod scoring; pub mod verifier; +#[cfg(test)] +mod tests; + pub use self::queue::{TransactionQueue, Status as QueueStatus}; pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index f2c47ad749b..2a151e54412 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -71,6 +71,7 @@ pub struct TransactionQueue { insertion_id: Arc, pool: RwLock, options: RwLock, + cached_pending: RwLock>)>>, } impl TransactionQueue { @@ -80,6 +81,7 @@ impl TransactionQueue { insertion_id: Default::default(), pool: RwLock::new(txpool::Pool::new(Default::default(), scoring::GasPrice, limits)), options: RwLock::new(verification_options), + cached_pending: RwLock::new(None), } } @@ -104,7 +106,7 @@ impl TransactionQueue { let options = self.options.read().clone(); let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); - transactions + let results = transactions .into_par_iter() .map(|transaction| verifier.verify_transaction(transaction)) .map(|result| match result { @@ -114,14 +116,52 @@ impl TransactionQueue { }, Err(err) => Err(err), }) - .collect() + .collect::>(); + + if results.iter().any(|r| r.is_ok()) { + *self.cached_pending.write() = None; + } + results + } + + pub fn pending( + &self, + client: C, + block_number: u64, + current_timestamp: u64, + ) -> Vec> where + C: client::Client, + { + // TODO [ToDr] Check if timestamp is within limits. + let is_valid = |bn| bn == block_number; + { + let cached_pending = self.cached_pending.read(); + match *cached_pending { + Some((bn, ref pending)) if is_valid(bn) => { + return pending.clone() + }, + _ => {}, + } + } + + let mut cached_pending = self.cached_pending.write(); + match *cached_pending { + Some((bn, ref pending)) if is_valid(bn) => { + return pending.clone() + }, + _ => {}, + } + + let pending: Vec<_> = self.collect_pending(client, block_number, current_timestamp, |i| i.collect()); + *cached_pending = Some((block_number, pending.clone())); + pending } - /// Returns a queue guard that allows to get an iterator for pending transactions. + /// Collect pending transactions. /// - /// NOTE: During pending iteration importing to the queue is not allowed. - /// Make sure to drop the guard in reasonable time. - pub fn pending( + /// NOTE This is re-computing the pending set and it might be expensive to do so. + /// Prefer using cached pending set using `#pending` method. + pub fn collect_pending( &self, client: C, block_number: u64, diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs index 1f42f0e06c2..e07290b6698 100644 --- a/miner/src/pool/scoring.rs +++ b/miner/src/pool/scoring.rs @@ -56,6 +56,10 @@ impl txpool::Scoring for GasPrice { } fn choose(&self, old: &VerifiedTransaction, new: &VerifiedTransaction) -> txpool::scoring::Choice { + if old.transaction.nonce != new.transaction.nonce { + return txpool::scoring::Choice::InsertNew + } + let old_gp = old.transaction.gas_price; let new_gp = new.transaction.gas_price; diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs new file mode 100644 index 00000000000..61de6b7510d --- /dev/null +++ b/miner/src/pool/tests/mod.rs @@ -0,0 +1,1376 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +use ethereum_types::{U256, Address}; +use ethkey::{Random, Generator}; +use rustc_hex::FromHex; +use transaction::{self, Transaction, SignedTransaction Unverified}; + +use pool::VerifiedTransaction; +use pool::client::AccountDetails; + +pub struct TestClient { + account_details: AccountDetails, + gas_required: U256, + is_service_transaction: bool, +} + +impl Default for TestClient { + fn default() -> Self { + + } +} + +impl TestClient { + pub fn with_account(mut self, account_details: AccountDetails) -> Self { + self.account_details = account_details; + self + } + + pub fn with_account_nonce(mut self, nonce: U256) -> Self { + self.account_details.nonce = nonce; + self + } + + pub fn with_tx_gas_required(mut self, gas_required: U256) -> Self { + self.gas_required = gas_required; + self + } + + pub fn with_service_transaction(mut self) -> Self { + self.is_service_transaction; + self + } +} + +impl pool::client::Client for TestClient { + fn transaction_already_included(&self, _hash: &H256) -> bool { + false + } + + fn verify_transaction(&self, _tx: UnverifiedTransaction) + -> Result + { + unimplemented!() + } + + fn account_details(&self, _address: &Address) -> AccountDetails { + self.account_details.clone() + } + + fn required_gas(&self, _tx: &Transaction) -> U256 { + self.gas_required + } + + fn transaction_type(&self, _tx: &SignedTransaction) -> pool::client::TransactionType { + if is_service_transaction { + pool::client::TransactionType::Service + } else { + pool::client::TransactionType::Regular + } + } +} + +fn unwrap_tx_err(err: Result) -> transaction::Error { + err.unwrap_err() +} + +fn default_nonce() -> U256 { 123.into() } +fn default_gas_val() -> U256 { 100_000.into() } +fn default_gas_price() -> U256 { 1.into() } + +fn new_unsigned_tx(nonce: U256, gas: U256, gas_price: U256) -> Transaction { + Transaction { + action: transaction::Action::Create, + value: U256::from(100), + data: "3331600055".from_hex().unwrap(), + gas: gas, + gas_price: gas_price, + nonce: nonce + } +} + +fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + new_unsigned_tx(nonce, default_gas_val(), gas_price).sign(keypair.secret(), None) +} + +fn new_tx_with_gas(gas: U256, gas_price: U256) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + new_unsigned_tx(default_nonce(), gas, gas_price).sign(keypair.secret(), None) +} + +fn new_tx_default() -> SignedTransaction { + new_tx(default_nonce(), default_gas_price()) +} + +fn default_account_details() -> AccountDetails { + AccountDetails { + nonce: default_nonce(), + balance: !U256::zero() + } +} + +fn default_account_details_for_addr(_a: &Address) -> AccountDetails { + default_account_details() +} + +fn default_tx_provider() -> DummyTransactionDetailsProvider { + DummyTransactionDetailsProvider::default() +} + +fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { + let tx1 = new_unsigned_tx(nonce, default_gas_val(), gas_price); + let tx2 = new_unsigned_tx(nonce + nonce_increment, default_gas_val(), gas_price + gas_price_increment); + + let keypair = Random.generate().unwrap(); + let secret = &keypair.secret(); + (tx1.sign(secret, None).into(), tx2.sign(secret, None).into()) +} + +/// Returns two consecutive transactions, both with increased gas price +fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { + let gas = default_gas_price() + gas_price_increment; + let tx1 = new_unsigned_tx(default_nonce(), default_gas_val(), gas); + let tx2 = new_unsigned_tx(default_nonce() + 1.into(), default_gas_val(), gas); + + let keypair = Random.generate().unwrap(); + let secret = &keypair.secret(); + (tx1.sign(secret, None).into(), tx2.sign(secret, None).into()) +} + +fn new_tx_pair_default(nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { + new_tx_pair(default_nonce(), default_gas_price(), nonce_increment, gas_price_increment) +} + +/// Returns two transactions with identical (sender, nonce) but different gas price/hash. +fn new_similar_tx_pair() -> (SignedTransaction, SignedTransaction) { + new_tx_pair_default(0.into(), 1.into()) +} + +#[test] +fn should_return_correct_nonces_when_dropped_because_of_limit() { + // given + let mut txq = TransactionQueue::with_limits( + PrioritizationStrategy::GasPriceOnly, + 2, + usize::max_value(), + !U256::zero(), + !U256::zero(), + ); + let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); + let sender = tx1.sender(); + let nonce = tx1.nonce; + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 2); + assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into())); + + // when + let tx = new_tx(123.into(), 1.into()); + let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + // No longer the case as we don't even consider a transaction that isn't above a full + // queue's minimum gas price. + // We may want to reconsider this in the near future so leaving this code in as a + // possible alternative. + /* + assert_eq!(res.unwrap(), transaction::ImportResult::Current); + assert_eq!(txq.status().pending, 2); + assert_eq!(txq.last_nonce(&sender), Some(nonce)); + */ + assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { + minimal: 2.into(), + got: 1.into(), + }); + assert_eq!(txq.status().pending, 2); + assert_eq!(txq.last_nonce(&sender), Some(tx2.nonce)); +} + +#[test] +fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { + // given + let mut txq = TransactionQueue::default(); + let (tx, tx2) = new_similar_tx_pair(); + let prev_nonce = default_account_details().nonce - U256::one(); + + // First insert one transaction to future + let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)); + assert_eq!(res.unwrap(), transaction::ImportResult::Future); + assert_eq!(txq.status().future, 1); + + // now import second transaction to current + let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); + + // and then there should be only one transaction in current (the one with higher gas_price) + assert_eq!(res.unwrap(), transaction::ImportResult::Current); + assert_eq!(txq.status().pending, 1); + assert_eq!(txq.status().future, 0); + assert_eq!(txq.current.by_priority.len(), 1); + assert_eq!(txq.current.by_address.len(), 1); + let top = txq.top_transactions(); + assert_eq!(top[0], tx2); +} + +#[test] +fn should_move_all_transactions_from_future() { + // given + let mut txq = TransactionQueue::default(); + let (tx, tx2) = new_tx_pair_default(1.into(), 1.into()); + let prev_nonce = default_account_details().nonce - U256::one(); + + // First insert one transaction to future + let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)); + assert_eq!(res.unwrap(), transaction::ImportResult::Future); + assert_eq!(txq.status().future, 1); + + // now import second transaction to current + let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + assert_eq!(res.unwrap(), transaction::ImportResult::Current); + assert_eq!(txq.status().pending, 2); + assert_eq!(txq.status().future, 0); + assert_eq!(txq.current.by_priority.len(), 2); + assert_eq!(txq.current.by_address.len(), 2); + let top = txq.top_transactions(); + assert_eq!(top[0], tx); + assert_eq!(top[1], tx2); +} + +#[test] +fn should_import_tx() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + + // when + let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + assert_eq!(res.unwrap(), transaction::ImportResult::Current); + let stats = txq.status(); + assert_eq!(stats.pending, 1); +} + +#[test] +fn should_order_by_gas() { + // given + let mut txq = TransactionQueue::new(PrioritizationStrategy::GasAndGasPrice); + let tx1 = new_tx_with_gas(50000.into(), 40.into()); + let tx2 = new_tx_with_gas(40000.into(), 30.into()); + let tx3 = new_tx_with_gas(30000.into(), 10.into()); + let tx4 = new_tx_with_gas(50000.into(), 20.into()); + txq.set_minimal_gas_price(15.into()); + + // when + let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()); + let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); + let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()); + let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + assert_eq!(res1.unwrap(), transaction::ImportResult::Current); + assert_eq!(res2.unwrap(), transaction::ImportResult::Current); + assert_eq!(unwrap_tx_err(res3), transaction::Error::InsufficientGasPrice { + minimal: U256::from(15), + got: U256::from(10), + }); + assert_eq!(res4.unwrap(), transaction::ImportResult::Current); + let stats = txq.status(); + assert_eq!(stats.pending, 3); + assert_eq!(txq.top_transactions()[0].gas, 40000.into()); + assert_eq!(txq.top_transactions()[1].gas, 50000.into()); + assert_eq!(txq.top_transactions()[2].gas, 50000.into()); + assert_eq!(txq.top_transactions()[1].gas_price, 40.into()); + assert_eq!(txq.top_transactions()[2].gas_price, 20.into()); +} + +#[test] +fn should_order_by_gas_factor() { + // given + let mut txq = TransactionQueue::new(PrioritizationStrategy::GasFactorAndGasPrice); + + let tx1 = new_tx_with_gas(150_000.into(), 40.into()); + let tx2 = new_tx_with_gas(40_000.into(), 16.into()); + let tx3 = new_tx_with_gas(30_000.into(), 15.into()); + let tx4 = new_tx_with_gas(150_000.into(), 62.into()); + txq.set_minimal_gas_price(15.into()); + + // when + let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()); + let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); + let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()); + let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + assert_eq!(res1.unwrap(), transaction::ImportResult::Current); + assert_eq!(res2.unwrap(), transaction::ImportResult::Current); + assert_eq!(res3.unwrap(), transaction::ImportResult::Current); + assert_eq!(res4.unwrap(), transaction::ImportResult::Current); + let stats = txq.status(); + assert_eq!(stats.pending, 4); + assert_eq!(txq.top_transactions()[0].gas, 30_000.into()); + assert_eq!(txq.top_transactions()[1].gas, 150_000.into()); + assert_eq!(txq.top_transactions()[2].gas, 40_000.into()); + assert_eq!(txq.top_transactions()[3].gas, 150_000.into()); + assert_eq!(txq.top_transactions()[0].gas_price, 15.into()); + assert_eq!(txq.top_transactions()[1].gas_price, 62.into()); + assert_eq!(txq.top_transactions()[2].gas_price, 16.into()); + assert_eq!(txq.top_transactions()[3].gas_price, 40.into()); +} + +#[test] +fn tx_gas_limit_should_never_overflow() { + // given + let mut txq = TransactionQueue::default(); + txq.set_gas_limit(U256::zero()); + assert_eq!(txq.block_gas_limit, U256::zero()); + + // when + txq.set_gas_limit(!U256::zero()); + + // then + assert_eq!(txq.block_gas_limit, !U256::zero()); +} + +#[test] +fn should_not_import_transaction_above_gas_limit() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + let gas = tx.gas; + let limit = gas / U256::from(2); + txq.set_gas_limit(limit); + + // when + let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + assert_eq!(unwrap_tx_err(res), transaction::Error::GasLimitExceeded { + limit: U256::from(50_000), + got: gas, + }); + let stats = txq.status(); + assert_eq!(stats.pending, 0); + assert_eq!(stats.future, 0); +} + + +#[test] +fn should_drop_transactions_from_senders_without_balance() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + let account = AccountDetails { + nonce: default_account_details().nonce, + balance: U256::one() + }; + + // when + let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account(account)); + + // then + assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientBalance { + balance: U256::from(1), + cost: U256::from(100_100), + }); + let stats = txq.status(); + assert_eq!(stats.pending, 0); + assert_eq!(stats.future, 0); +} + +#[test] +fn should_not_import_transaction_below_min_gas_price_threshold_if_external() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + txq.set_minimal_gas_price(tx.gas_price + U256::one()); + + // when + let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { + minimal: U256::from(2), + got: U256::from(1), + }); + let stats = txq.status(); + assert_eq!(stats.pending, 0); + assert_eq!(stats.future, 0); +} + +#[test] +fn should_import_transaction_below_min_gas_price_threshold_if_local() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + txq.set_minimal_gas_price(tx.gas_price + U256::one()); + + // when + let res = txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()); + + // then + assert_eq!(res.unwrap(), transaction::ImportResult::Current); + let stats = txq.status(); + assert_eq!(stats.pending, 1); + assert_eq!(stats.future, 0); +} + +#[test] +fn should_import_txs_from_same_sender() { + // given + let mut txq = TransactionQueue::default(); + + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + + // when + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + let top = txq.top_transactions(); + assert_eq!(top[0], tx); + assert_eq!(top[1], tx2); + assert_eq!(top.len(), 2); +} + +#[test] +fn should_prioritize_local_transactions_within_same_nonce_height() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + // the second one has same nonce but higher `gas_price` + let (_, tx2) = new_similar_tx_pair(); + + // when + // first insert the one with higher gas price + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + // then the one with lower gas price, but local + txq.add(tx.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + + // then + let top = txq.top_transactions(); + assert_eq!(top[0], tx); // local should be first + assert_eq!(top[1], tx2); + assert_eq!(top.len(), 2); +} + +#[test] +fn when_importing_local_should_mark_others_from_the_same_sender_as_local() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + // the second one has same nonce but higher `gas_price` + let (_, tx0) = new_similar_tx_pair(); + + txq.add(tx0.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + // the one with higher gas price is first + let top = txq.top_transactions(); + assert_eq!(top[0], tx0); + assert_eq!(top[1], tx1); + + // when + // insert second as local + txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + + // then + // the order should be updated + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], tx2); + assert_eq!(top[2], tx0); +} + +#[test] +fn should_prioritize_reimported_transactions_within_same_nonce_height() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + // the second one has same nonce but higher `gas_price` + let (_, tx2) = new_similar_tx_pair(); + + // when + // first insert local one with higher gas price + txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + // then the one with lower gas price, but from retracted block + txq.add(tx.clone(), TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider()).unwrap(); + + // then + let top = txq.top_transactions(); + assert_eq!(top[0], tx); // retracted should be first + assert_eq!(top[1], tx2); + assert_eq!(top.len(), 2); +} + +#[test] +fn should_not_prioritize_local_transactions_with_different_nonce_height() { + // given + let mut txq = TransactionQueue::default(); + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + + // when + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + + // then + let top = txq.top_transactions(); + assert_eq!(top[0], tx); + assert_eq!(top[1], tx2); + assert_eq!(top.len(), 2); +} + +#[test] +fn should_penalize_transactions_from_sender_in_future() { + // given + let prev_nonce = default_account_details().nonce - U256::one(); + let mut txq = TransactionQueue::default(); + // txa, txb - slightly bigger gas price to have consistent ordering + let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); + let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); + + // insert everything + txq.add(txa.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); + + assert_eq!(txq.status().future, 4); + + // when + txq.penalize(&tx1.hash()); + + // then + let top: Vec<_> = txq.future_transactions().into_iter().map(|tx| tx.transaction).collect(); + assert_eq!(top[0], txa); + assert_eq!(top[1], txb); + assert_eq!(top[2], tx1); + assert_eq!(top[3], tx2); + assert_eq!(top.len(), 4); +} + +#[test] +fn should_not_penalize_local_transactions() { + // given + let mut txq = TransactionQueue::default(); + // txa, txb - slightly bigger gas price to have consistent ordering + let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); + let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); + + // insert everything + txq.add(txa.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(txb.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], txa); + assert_eq!(top[2], tx2); + assert_eq!(top[3], txb); + assert_eq!(top.len(), 4); + + // when + txq.penalize(&tx1.hash()); + + // then (order is the same) + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], txa); + assert_eq!(top[2], tx2); + assert_eq!(top[3], txb); + assert_eq!(top.len(), 4); +} + +#[test] +fn should_penalize_transactions_from_sender() { + // given + let mut txq = TransactionQueue::default(); + // txa, txb - slightly bigger gas price to have consistent ordering + let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); + let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); + + // insert everything + txq.add(txa.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + let top = txq.top_transactions(); + assert_eq!(top[0], tx1); + assert_eq!(top[1], txa); + assert_eq!(top[2], tx2); + assert_eq!(top[3], txb); + assert_eq!(top.len(), 4); + + // when + txq.penalize(&tx1.hash()); + + // then + let top = txq.top_transactions(); + assert_eq!(top[0], txa); + assert_eq!(top[1], txb); + assert_eq!(top[2], tx1); + assert_eq!(top[3], tx2); + assert_eq!(top.len(), 4); +} + +#[test] +fn should_return_pending_hashes() { + // given + let mut txq = TransactionQueue::default(); + + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + + // when + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + let top = txq.pending_hashes(); + assert_eq!(top[0], tx.hash()); + assert_eq!(top[1], tx2.hash()); + assert_eq!(top.len(), 2); +} + +#[test] +fn should_put_transaction_to_futures_if_gap_detected() { + // given + let mut txq = TransactionQueue::default(); + + let (tx, tx2) = new_tx_pair_default(2.into(), 0.into()); + + // when + let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + assert_eq!(res1, transaction::ImportResult::Current); + assert_eq!(res2, transaction::ImportResult::Future); + let stats = txq.status(); + assert_eq!(stats.pending, 1); + assert_eq!(stats.future, 1); + let top = txq.top_transactions(); + assert_eq!(top.len(), 1); + assert_eq!(top[0], tx); +} + +#[test] +fn should_handle_min_block() { + // given + let mut txq = TransactionQueue::default(); + + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + + // when + let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(transaction::Condition::Number(1)), &default_tx_provider()).unwrap(); + let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + assert_eq!(res1, transaction::ImportResult::Current); + assert_eq!(res2, transaction::ImportResult::Current); + let top = txq.top_transactions_at(0, 0, None); + assert_eq!(top.len(), 0); + let top = txq.top_transactions_at(1, 0, None); + assert_eq!(top.len(), 2); +} + +#[test] +fn should_correctly_update_futures_when_removing() { + // given + let prev_nonce = default_account_details().nonce - U256::one(); + let next2_nonce = default_nonce() + U256::from(3); + + let mut txq = TransactionQueue::default(); + + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); + assert_eq!(txq.status().future, 2); + + // when + txq.cull(tx.sender(), next2_nonce); + // should remove both transactions since they are not valid + + // then + assert_eq!(txq.status().pending, 0); + assert_eq!(txq.status().future, 0); +} + +#[test] +fn should_move_transactions_if_gap_filled() { + // given + let mut txq = TransactionQueue::default(); + let kp = Random.generate().unwrap(); + let secret = kp.secret(); + let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(secret, None).into(); + let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret, None).into(); + let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret, None).into(); + + txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 1); + txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().future, 1); + + // when + txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + let stats = txq.status(); + assert_eq!(stats.pending, 3); + assert_eq!(stats.future, 0); + assert_eq!(txq.future.by_priority.len(), 0); + assert_eq!(txq.future.by_address.len(), 0); + assert_eq!(txq.future.by_gas_price.len(), 0); +} + +#[test] +fn should_remove_transaction() { + // given + let mut txq2 = TransactionQueue::default(); + let (tx, tx2) = new_tx_pair_default(3.into(), 0.into()); + txq2.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq2.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq2.status().pending, 1); + assert_eq!(txq2.status().future, 1); + + // when + txq2.cull(tx.sender(), tx.nonce + U256::one()); + txq2.cull(tx2.sender(), tx2.nonce + U256::one()); + + // then + let stats = txq2.status(); + assert_eq!(stats.pending, 0); + assert_eq!(stats.future, 0); +} + +#[test] +fn should_move_transactions_to_future_if_gap_introduced() { + // given + let mut txq = TransactionQueue::default(); + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let tx3 = new_tx_default(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().future, 1); + txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 3); + + // when + txq.remove(&tx.hash(), &|_| default_nonce(), RemovalReason::Invalid); + + // then + let stats = txq.status(); + assert_eq!(stats.future, 1); + assert_eq!(stats.pending, 1); +} + +#[test] +fn should_clear_queue() { + // given + let mut txq = TransactionQueue::default(); + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + + // add + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + let stats = txq.status(); + assert_eq!(stats.pending, 2); + + // when + txq.clear(); + + // then + let stats = txq.status(); + assert_eq!(stats.pending, 0); +} + +#[test] +fn should_drop_old_transactions_when_hitting_the_limit() { + // given + let mut txq = TransactionQueue::with_limits( + PrioritizationStrategy::GasPriceOnly, + 1, + usize::max_value(), + !U256::zero(), + !U256::zero() + ); + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let sender = tx.sender(); + let nonce = tx.nonce; + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 1); + + // when + let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + let t = txq.top_transactions(); + assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { minimal: 2.into(), got: 1.into() }); + assert_eq!(txq.status().pending, 1); + assert_eq!(t.len(), 1); + assert_eq!(t[0], tx); + assert_eq!(txq.last_nonce(&sender), Some(nonce)); +} + +#[test] +fn should_limit_future_transactions() { + let mut txq = TransactionQueue::with_limits( + PrioritizationStrategy::GasPriceOnly, + 1, + usize::max_value(), + !U256::zero(), + !U256::zero(), + ); + txq.current.set_limit(10); + let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); + let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 2); + + // when + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().future, 1); + txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + assert_eq!(txq.status().future, 1); +} + +#[test] +fn should_limit_by_gas() { + let mut txq = TransactionQueue::with_limits( + PrioritizationStrategy::GasPriceOnly, + 100, + usize::max_value(), + default_gas_val() * U256::from(2), + !U256::zero() + ); + let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); + let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + // limited by gas + txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap_err(); + assert_eq!(txq.status().pending, 2); +} + +#[test] +fn should_keep_own_transactions_above_gas_limit() { + let mut txq = TransactionQueue::with_limits( + PrioritizationStrategy::GasPriceOnly, + 100, + usize::max_value(), + default_gas_val() * U256::from(2), + !U256::zero() + ); + let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); + let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); + let (tx5, _) = new_tx_pair_default(U256::from(1), U256::from(2)); + txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + // Not accepted because of limit + txq.add(tx5.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap_err(); + txq.add(tx3.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx4.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 4); +} + +#[test] +fn should_drop_transactions_with_old_nonces() { + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + let last_nonce = tx.nonce + U256::one(); + + // when + let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(last_nonce)); + + // then + assert_eq!(unwrap_tx_err(res), transaction::Error::Old); + let stats = txq.status(); + assert_eq!(stats.pending, 0); + assert_eq!(stats.future, 0); +} + +#[test] +fn should_not_insert_same_transaction_twice() { + // given + let nonce = default_account_details().nonce + U256::one(); + let mut txq = TransactionQueue::default(); + let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().future, 1); + assert_eq!(txq.status().pending, 0); + + // when + let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)); + + // then + assert_eq!(unwrap_tx_err(res), transaction::Error::AlreadyImported); + let stats = txq.status(); + assert_eq!(stats.future, 1); + assert_eq!(stats.pending, 0); +} + +#[test] +fn should_accept_same_transaction_twice_if_removed() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 2); + + // when + txq.remove(&tx1.hash(), &|_| default_nonce(), RemovalReason::Invalid); + assert_eq!(txq.status().pending, 0); + assert_eq!(txq.status().future, 1); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + let stats = txq.status(); + assert_eq!(stats.future, 0); + assert_eq!(stats.pending, 2); +} + +#[test] +fn should_not_move_to_future_if_state_nonce_is_higher() { + // given + let mut txq = TransactionQueue::default(); + let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let tx3 = new_tx_default(); + txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().future, 1); + txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().pending, 3); + + // when + txq.cull(tx.sender(), default_nonce() + U256::one()); + + // then + let stats = txq.status(); + assert_eq!(stats.future, 0); + assert_eq!(stats.pending, 2); +} + +#[test] +fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { + // given + let mut txq = TransactionQueue::default(); + let keypair = Random.generate().unwrap(); + let tx = new_unsigned_tx(123.into(), default_gas_val(), 20.into()).sign(keypair.secret(), None); + let tx2 = { + let mut tx2 = (**tx).clone(); + tx2.gas_price = U256::from(21); + tx2.sign(keypair.secret(), None) + }; + + // when + txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + let res = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); + + // then + assert_eq!(unwrap_tx_err(res), transaction::Error::TooCheapToReplace); + let stats = txq.status(); + assert_eq!(stats.pending, 1); + assert_eq!(stats.future, 0); + assert_eq!(txq.top_transactions()[0].gas_price, U256::from(20)); +} + +#[test] +fn should_replace_same_transaction_when_has_higher_fee() { + // given + let mut txq = TransactionQueue::default(); + let keypair = Random.generate().unwrap(); + let tx = new_unsigned_tx(123.into(), default_gas_val(), 10.into()).sign(keypair.secret(), None); + let tx2 = { + let mut tx2 = (**tx).clone(); + tx2.gas_price = U256::from(20); + tx2.sign(keypair.secret(), None) + }; + + // when + txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + let stats = txq.status(); + assert_eq!(stats.pending, 1); + assert_eq!(stats.future, 0); + assert_eq!(txq.top_transactions()[0].gas_price, U256::from(20)); +} + +#[test] +fn should_replace_same_transaction_when_importing_to_futures() { + // given + let mut txq = TransactionQueue::default(); + let keypair = Random.generate().unwrap(); + let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret(), None); + let tx1 = { + let mut tx1 = (**tx0).clone(); + tx1.nonce = U256::from(124); + tx1.sign(keypair.secret(), None) + }; + let tx2 = { + let mut tx2 = (**tx1).clone(); + tx2.gas_price = U256::from(200); + tx2.sign(keypair.secret(), None) + }; + + // when + txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.status().future, 1); + txq.add(tx0, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + let stats = txq.status(); + assert_eq!(stats.future, 0); + assert_eq!(stats.pending, 2); + assert_eq!(txq.top_transactions()[1].gas_price, U256::from(200)); +} + +#[test] +fn should_recalculate_height_when_removing_from_future() { + // given + let previous_nonce = default_account_details().nonce - U256::one(); + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); + txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); + assert_eq!(txq.status().future, 2); + + // when + txq.remove(&tx1.hash(), &|_| default_nonce() + 1.into(), RemovalReason::Invalid); + + // then + let stats = txq.status(); + assert_eq!(stats.future, 0); + assert_eq!(stats.pending, 1); +} + +#[test] +fn should_return_none_when_transaction_from_given_address_does_not_exist() { + // given + let txq = TransactionQueue::default(); + + // then + assert_eq!(txq.last_nonce(&Address::default()), None); +} + +#[test] +fn should_return_correct_nonce_when_transactions_from_given_address_exist() { + // given + let mut txq = TransactionQueue::default(); + let tx = new_tx_default(); + let from = tx.sender(); + let nonce = tx.nonce; + + // when + txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)).unwrap(); + + // then + assert_eq!(txq.last_nonce(&from), Some(nonce)); +} + +#[test] +fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); + + // Insert first transaction + txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(); + + // when + txq.cull(tx2.sender(), nonce2 + U256::one()); + + // then + assert!(txq.top_transactions().is_empty()); +} + +#[test] +fn should_return_valid_last_nonce_after_cull() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into()); + let sender = tx1.sender(); + let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); + + // when + // Insert first transaction + assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Current); + // Second should go to future + assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Future); + // Now block is imported + txq.cull(sender, nonce2 - U256::from(1)); + // tx2 should be not be promoted to current + assert_eq!(txq.status().pending, 0); + assert_eq!(txq.status().future, 1); + + // then + assert_eq!(txq.last_nonce(&sender), None); +} + +#[test] +fn should_return_true_if_there_is_local_transaction_pending() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + assert_eq!(txq.has_local_pending_transactions(), false); + + // when + assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(), transaction::ImportResult::Current); + assert_eq!(txq.has_local_pending_transactions(), false); + assert_eq!(txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(), + transaction::ImportResult::Current); + + // then + assert_eq!(txq.has_local_pending_transactions(), true); +} + +#[test] +fn should_keep_right_order_in_future() { + // given + let mut txq = TransactionQueue::with_limits( + PrioritizationStrategy::GasPriceOnly, + 1, + usize::max_value(), + !U256::zero(), + !U256::zero() + ); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let prev_nonce = default_account_details().nonce - U256::one(); + + // when + assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); + assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); + + // then + assert_eq!(txq.future.by_priority.len(), 1); + assert_eq!(txq.future.by_priority.iter().next().unwrap().hash, tx1.hash()); +} + +#[test] +fn should_return_correct_last_nonce() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2, tx2_2, tx3) = { + let keypair = Random.generate().unwrap(); + let secret = &keypair.secret(); + let nonce = 123.into(); + let gas = default_gas_val(); + let tx = new_unsigned_tx(nonce, gas, 1.into()); + let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 1.into()); + let tx2_2 = new_unsigned_tx(nonce + 1.into(), gas, 5.into()); + let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into()); + + + (tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None)) + }; + let sender = tx1.sender(); + txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx3, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.future.by_priority.len(), 0); + assert_eq!(txq.current.by_priority.len(), 3); + + // when + let res = txq.add(tx2_2, TransactionOrigin::Local, 0, None, &default_tx_provider()); + + // then + assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); + assert_eq!(res.unwrap(), transaction::ImportResult::Current); + assert_eq!(txq.current.by_priority.len(), 3); +} + +#[test] +fn should_reject_transactions_below_base_gas() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let high_gas = 100_001.into(); + + // when + let res1 = txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()); + let res2 = txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider().with_tx_gas_required(high_gas)); + + // then + assert_eq!(res1.unwrap(), transaction::ImportResult::Current); + assert_eq!(unwrap_tx_err(res2), transaction::Error::InsufficientGas { + minimal: 100_001.into(), + got: 100_000.into(), + }); + +} + +#[test] +fn should_clear_all_old_transactions() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); + let next_nonce = |_: &Address| + AccountDetails { nonce: default_nonce() + U256::one(), balance: !U256::zero() }; + + // Insert all transactions + txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.top_transactions().len(), 4); + + // when + txq.remove_old(&next_nonce, 0); + + // then + assert_eq!(txq.top_transactions().len(), 2); +} + +#[test] +fn should_remove_out_of_date_transactions_occupying_queue() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); + + // Insert all transactions + txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2, TransactionOrigin::External, 5, None, &default_tx_provider()).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_tx_provider()).unwrap(); + txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + assert_eq!(txq.top_transactions().len(), 3); + assert_eq!(txq.future_transactions().len(), 1); + + // when + txq.remove_old(&default_account_details_for_addr, 9 + super::DEFAULT_QUEUING_PERIOD); + + // then + assert_eq!(txq.top_transactions().len(), 2); + assert_eq!(txq.future_transactions().len(), 0); + assert_eq!(txq.top_transactions(), vec![tx1, tx3]); +} + +#[test] +fn should_accept_local_service_transaction() { + // given + let tx = new_tx(123.into(), 0.into()); + let mut txq = TransactionQueue::default(); + txq.set_minimal_gas_price(100.into()); + + // when + txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + + // then + assert_eq!(txq.top_transactions().len(), 1); +} + +#[test] +fn should_not_accept_external_service_transaction_if_sender_not_certified() { + // given + let tx1 = new_tx(123.into(), 0.into()); + let tx2 = new_tx(456.into(), 0.into()); + let mut txq = TransactionQueue::default(); + txq.set_minimal_gas_price(100.into()); + + // when + assert_eq!(unwrap_tx_err(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider())), + transaction::Error::InsufficientGasPrice { + minimal: 100.into(), + got: 0.into(), + }); + assert_eq!(unwrap_tx_err(txq.add(tx2, TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider())), + transaction::Error::InsufficientGasPrice { + minimal: 100.into(), + got: 0.into(), + }); + + // then + assert_eq!(txq.top_transactions().len(), 0); +} + +#[test] +fn should_not_accept_external_service_transaction_if_contract_returns_error() { + // given + let tx = new_tx(123.into(), 0.into()); + let mut txq = TransactionQueue::default(); + txq.set_minimal_gas_price(100.into()); + + // when + let details_provider = default_tx_provider().service_transaction_checker_returns_error("Contract error"); + assert_eq!(unwrap_tx_err(txq.add(tx, TransactionOrigin::External, 0, None, &details_provider)), + transaction::Error::InsufficientGasPrice { + minimal: 100.into(), + got: 0.into(), + }); + + // then + assert_eq!(txq.top_transactions().len(), 0); +} + +#[test] +fn should_accept_external_service_transaction_if_sender_is_certified() { + // given + let tx = new_tx(123.into(), 0.into()); + let mut txq = TransactionQueue::default(); + txq.set_minimal_gas_price(100.into()); + + // when + let details_provider = default_tx_provider().service_transaction_checker_accepts(true); + txq.add(tx, TransactionOrigin::External, 0, None, &details_provider).unwrap(); + + // then + assert_eq!(txq.top_transactions().len(), 1); +} + +#[test] +fn should_not_order_transactions_by_hash() { + // given + let secret1 = "0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap(); + let secret2 = "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap(); + let tx1 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret1, None); + let tx2 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret2, None); + let mut txq = TransactionQueue::default(); + + // when + txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + + // then + assert_eq!(txq.top_transactions()[0], tx1); + assert_eq!(txq.top_transactions().len(), 2); +} + +#[test] +fn should_not_return_transactions_over_nonce_cap() { + // given + let keypair = Random.generate().unwrap(); + let mut txq = TransactionQueue::default(); + // when + for nonce in 123..130 { + let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None); + txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + } + + // then + assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4); +} diff --git a/transaction-pool/src/scoring.rs b/transaction-pool/src/scoring.rs index e4f923c9b5a..d056faea651 100644 --- a/transaction-pool/src/scoring.rs +++ b/transaction-pool/src/scoring.rs @@ -69,7 +69,7 @@ pub enum Change { /// Implementation notes: /// - Returned `Score`s should match ordering of `compare` method. /// - `compare` will be called only within a context of transactions from the same sender. -/// - `choose` will be called only if `compare` returns `Ordering::Equal` +/// - `choose` may be called even if `compare` returns `Ordering::Equal` /// - `should_replace` is used to decide if new transaction should push out an old transaction already in the queue. /// - `Score`s and `compare` should align with `Ready` implementation. /// From 8312e4a09f41034efa7622e433a4a8ebbc1d9471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 28 Feb 2018 16:46:53 +0100 Subject: [PATCH 27/77] Disable tx queueing and parallel verification. --- ethcore/src/client/client.rs | 34 +--------------------------------- miner/src/pool/queue.rs | 4 ++-- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 0cc25608175..58075ca8358 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -81,7 +81,6 @@ pub use verification::queue::QueueInfo as BlockQueueInfo; use_contract!(registry, "Registry", "res/contracts/registrar.json"); const MAX_TX_QUEUE_SIZE: usize = 4096; -const MAX_TX_TO_IMPORT: usize = 256; const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2; const MIN_HISTORY_SIZE: u64 = 8; @@ -159,7 +158,6 @@ pub struct Client { io_channel: Mutex>, notify: RwLock>>, queue_transactions: AtomicUsize, - queued_transactions: Mutex>, last_hashes: RwLock>, factories: Factories, history: u64, @@ -252,7 +250,6 @@ impl Client { io_channel: Mutex::new(message_channel), notify: RwLock::new(Vec::new()), queue_transactions: AtomicUsize::new(0), - queued_transactions: Mutex::new(vec![]), last_hashes: RwLock::new(VecDeque::new()), factories: factories, history: history, @@ -895,7 +892,7 @@ impl Client { trace_time!("import_queued_transactions"); self.queue_transactions.fetch_sub(transactions.len(), AtomicOrdering::SeqCst); - let mut txs: Vec = transactions + let txs: Vec = transactions .iter() .filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()) .collect(); @@ -906,38 +903,10 @@ impl Client { notify.transactions_received(hashes.clone(), peer_id); }); - // Import up to MAX_TX_TO_IMPORT transactions at once to prevent long pauses. - let len = txs.len(); - if len > MAX_TX_TO_IMPORT { - let diff = len - MAX_TX_TO_IMPORT; - self.queue_transactions.fetch_add(diff, AtomicOrdering::SeqCst); - - let mut queued = self.queued_transactions.lock(); - for tx in txs.drain(MAX_TX_TO_IMPORT .. len) { - queued.push(tx); - } - } - let results = self.miner.import_external_transactions(self, txs); results.len() } - fn check_transaction_import_queue(&self) { - let txs = { - let mut queued = self.queued_transactions.lock(); - if queued.is_empty() { - return; - } - - let len = ::std::cmp::min(queued.len(), MAX_TX_TO_IMPORT); - queued.split_off(len) - }; - - trace_time!("check_transaction_import_queue"); - self.queue_transactions.fetch_sub(txs.len(), AtomicOrdering::SeqCst); - self.miner.import_external_transactions(self, txs); - } - /// Get shared miner reference. pub fn miner(&self) -> Arc { self.miner.clone() @@ -1021,7 +990,6 @@ impl Client { // TODO: manage by real events. pub fn tick(&self, prevent_sleep: bool) { self.check_garbage(); - self.check_transaction_import_queue(); if !prevent_sleep { self.check_snooze(); } diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 2a151e54412..35897efbfc7 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -23,7 +23,6 @@ use std::collections::BTreeMap; use ethereum_types::{H256, U256, Address}; use parking_lot::RwLock; -use rayon::prelude::*; use transaction; use txpool::{self, Verifier}; @@ -107,7 +106,7 @@ impl TransactionQueue { let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); let results = transactions - .into_par_iter() + .into_iter() .map(|transaction| verifier.verify_transaction(transaction)) .map(|result| match result { Ok(verified) => match self.pool.write().import(verified) { @@ -121,6 +120,7 @@ impl TransactionQueue { if results.iter().any(|r| r.is_ok()) { *self.cached_pending.write() = None; } + results } From 54965d645c62377d56225c81f73a81126b092b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 5 Mar 2018 13:53:38 +0100 Subject: [PATCH 28/77] Make ethcore and ethcore-miner compile again. --- Cargo.lock | 3 - ethcore/src/client/client.rs | 18 ++-- ethcore/src/client/test_client.rs | 2 + ethcore/src/miner/blockchain_client.rs | 85 +++++++++++---- ethcore/src/miner/miner.rs | 102 +++++++----------- ethcore/src/miner/mod.rs | 43 +++++--- .../src/miner/service_transaction_checker.rs | 4 +- ethcore/src/miner/stratum.rs | 2 +- miner/Cargo.toml | 3 - miner/src/lib.rs | 5 - miner/src/pool/client.rs | 17 +-- miner/src/pool/queue.rs | 29 +++-- miner/src/pool/ready.rs | 6 +- 13 files changed, 180 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 205052e0d83..8ae186da0ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,9 +620,6 @@ version = "1.11.0" dependencies = [ "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethabi 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethabi-contract 5.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethabi-derive 5.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.11.0", "ethcore-transaction 0.1.0", "ethereum-types 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 99a1308e9e6..e1808cbd9db 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -67,7 +67,6 @@ use state_db::StateDB; use state::{self, State}; use trace; use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase}; -use trace::FlatTransactionTraces; use transaction::{self, LocalizedTransaction, UnverifiedTransaction, SignedTransaction, Transaction, PendingTransaction, Action}; use types::filter::Filter; use types::mode::Mode as IpcMode; @@ -317,7 +316,7 @@ impl Importer { let route = self.commit_block(closed_block, &header, &bytes, client); import_results.push(route); - client.report.write().accrue_block(&block); + client.report.write().accrue_block(&header, transactions_len); } } else { invalid_blocks.insert(header.hash()); @@ -400,7 +399,7 @@ impl Importer { return Err(()); }; - let verify_external_result = self.verifier.verify_block_external(header, engine); + let verify_external_result = self.verifier.verify_block_external(&header, engine); if let Err(e) = verify_external_result { warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); return Err(()); @@ -996,6 +995,7 @@ impl Client { } /// Get shared miner reference. + #[cfg(test)] pub fn miner(&self) -> Arc { self.importer.miner.clone() } @@ -1901,7 +1901,7 @@ impl BlockChainClient for Client { fn ready_transactions(&self) -> Vec { // TODO [ToDr] Avoid cloning and propagate miner transaction further. - self.miner.ready_transactions(self) + self.importer.miner.ready_transactions(self) .into_iter() .map(|x| x.signed().clone()) .map(Into::into) @@ -1936,15 +1936,13 @@ impl BlockChainClient for Client { } } - fn transact_contract(&self, address: Address, data: Bytes) -> Result { + fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { let authoring_params = self.importer.miner.authoring_params(); let transaction = Transaction { nonce: self.latest_nonce(&authoring_params.author), action: Action::Call(address), // TODO [ToDr] Check that params carefuly. - gas: self.miner.sensible_gas_limit(), - gas_price: self.miner.sensible_gas_price(), - gas: self.importer.miner.gas_floor_target(), + gas: self.importer.miner.sensible_gas_limit(), gas_price: self.importer.miner.sensible_gas_price(), value: U256::zero(), data: data, @@ -2102,13 +2100,15 @@ impl MiningBlockChainClient for Client { } } +impl ::miner::TransactionImporterClient for Client {} + impl super::traits::EngineClient for Client { fn update_sealing(&self) { self.importer.miner.update_sealing(self) } fn submit_seal(&self, block_hash: H256, seal: Vec) { - let import = self.miner.submit_seal(block_hash, seal).and_then(|block| self.import_sealed_block(block)); + let import = self.importer.miner.submit_seal(block_hash, seal).and_then(|block| self.import_sealed_block(block)); if let Err(err) = import { warn!(target: "poa", "Wrong internal seal submission! {:?}", err); } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 4975e9d07dd..1a4956328bf 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -420,6 +420,8 @@ impl MiningBlockChainClient for TestBlockChainClient { } } +impl ::miner::TransactionImporterClient for TestBlockChainClient {} + impl Nonce for TestBlockChainClient { fn nonce(&self, address: &Address, id: BlockId) -> Option { match id { diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index 2f57909650f..c0224a16cfe 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -25,28 +25,42 @@ use transaction::{ }; use account_provider::AccountProvider; -use client::{MiningBlockChainClient, BlockId, TransactionId}; +use client::{TransactionId, BlockInfo, CallContract, Nonce}; use engines::EthEngine; use header::Header; -use miner::service_transaction_checker::{self, ServiceTransactionChecker}; +use miner::TransactionImporterClient; +use miner::service_transaction_checker::ServiceTransactionChecker; // TODO [ToDr] Shit #[derive(Clone)] pub struct FakeContainer(Header); unsafe impl Sync for FakeContainer {} -#[derive(Clone)] -pub struct BlockChainClient<'a> { - chain: &'a MiningBlockChainClient, +pub struct BlockChainClient<'a, C: 'a> { + chain: &'a C, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>, best_block_header: FakeContainer, service_transaction_checker: Option, } -impl<'a> BlockChainClient<'a> { +impl<'a, C: 'a> Clone for BlockChainClient<'a, C> { + fn clone(&self) -> Self { + BlockChainClient { + chain: self.chain, + engine: self.engine, + accounts: self.accounts.clone(), + best_block_header: self.best_block_header.clone(), + service_transaction_checker: self.service_transaction_checker.clone(), + } + } +} + +impl<'a, C: 'a> BlockChainClient<'a, C> where + C: BlockInfo + CallContract, +{ pub fn new( - chain: &'a MiningBlockChainClient, + chain: &'a C, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>, refuse_service_transactions: bool, @@ -69,17 +83,19 @@ impl<'a> BlockChainClient<'a> { } pub fn verify_signed(&self, tx: &SignedTransaction) -> Result<(), transaction::Error> { - self.engine.machine().verify_transaction(&tx, &self.best_block_header.0, self.chain.as_block_chain_client()) + self.engine.machine().verify_transaction(&tx, &self.best_block_header.0, self.chain) } } -impl<'a> fmt::Debug for BlockChainClient<'a> { +impl<'a, C: 'a> fmt::Debug for BlockChainClient<'a, C> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "BlockChainClient") } } -impl<'a> pool::client::Client for BlockChainClient<'a> { +impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where + C: TransactionImporterClient + Sync, +{ fn transaction_already_included(&self, hash: &H256) -> bool { self.chain.transaction_block(TransactionId::Hash(*hash)).is_some() } @@ -103,10 +119,6 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { } } - fn account_nonce(&self, address: &Address) -> U256 { - self.chain.latest_nonce(address) - } - fn required_gas(&self, tx: &transaction::Transaction) -> U256 { tx.gas_required(&self.chain.latest_schedule()).into() } @@ -114,7 +126,7 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { fn transaction_type(&self, tx: &SignedTransaction) -> pool::client::TransactionType { match self.service_transaction_checker { None => pool::client::TransactionType::Regular, - Some(ref checker) => match checker.check(self, &tx) { + Some(ref checker) => match checker.check(self.chain, &tx) { Ok(true) => pool::client::TransactionType::Service, Ok(false) => pool::client::TransactionType::Regular, Err(e) => { @@ -126,12 +138,45 @@ impl<'a> pool::client::Client for BlockChainClient<'a> { } } -impl<'a> service_transaction_checker::ContractCaller for BlockChainClient<'a> { - fn registry_address(&self, name: &str) -> Option
{ - self.chain.registry_address(name.into(), BlockId::Latest) +impl<'a, C: 'a> pool::client::StateClient for BlockChainClient<'a, C> where + C: Nonce + Sync, +{ + fn account_nonce(&self, address: &Address) -> U256 { + self.chain.latest_nonce(address) + } +} + +// TODO [ToDr] Remove! +pub struct NonceClient<'a, C: 'a> { + client: &'a C, +} + +impl<'a, C: 'a> Clone for NonceClient<'a, C> { + fn clone(&self) -> Self { + NonceClient { + client: self.client, + } + } +} + +impl<'a, C: 'a> fmt::Debug for NonceClient<'a, C> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "NonceClient") } +} - fn call_contract(&self, address: Address, data: Vec) -> Result, String> { - self.chain.call_contract(BlockId::Latest, address, data) +impl<'a, C: 'a> NonceClient<'a, C> { + pub fn new(client: &'a C) -> Self { + NonceClient { + client, + } + } +} + +impl<'a, C: 'a> pool::client::StateClient for NonceClient<'a, C> + where C: Nonce + Sync, +{ + fn account_nonce(&self, address: &Address) -> U256 { + self.client.latest_nonce(address) } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 719d73ed223..cd6afc14bde 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -40,13 +40,13 @@ use using_queue::{UsingQueue, GetAction}; use account_provider::{AccountProvider, SignError as AccountError}; use block::{ClosedBlock, IsBlock, Block, SealedBlock}; use client::{ - AccountData, BlockChain, RegistryInfo, ScheduleInfo, CallContract, BlockProducer, SealedBlockImporter + BlockChain, ChainInfo, CallContract, BlockProducer, SealedBlockImporter, Nonce }; -use client::{BlockId, TransactionId, MiningBlockChainClient}; +use client::BlockId; use executive::contract_address; use header::{Header, BlockNumber}; -use miner::MinerService; -use miner::blockchain_client::BlockChainClient; +use miner::{MinerService, TransactionImporterClient}; +use miner::blockchain_client::{BlockChainClient, NonceClient}; use receipt::{Receipt, RichReceipt}; use spec::Spec; use state::State; @@ -268,21 +268,6 @@ impl Miner { }); } - /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. - pub fn pending_state(&self, latest_block_number: BlockNumber) -> Option> { - self.map_existing_pending_block(|b| b.state().clone(), latest_block_number) - } - - /// Get `Some` `clone()` of the current pending block or `None` if we're not sealing. - pub fn pending_block(&self, latest_block_number: BlockNumber) -> Option { - self.map_existing_pending_block(|b| b.to_base(), latest_block_number) - } - - /// Get `Some` `clone()` of the current pending block header or `None` if we're not sealing. - pub fn pending_block_header(&self, latest_block_number: BlockNumber) -> Option
{ - self.map_existing_pending_block(|b| b.header().clone(), latest_block_number) - } - /// Retrieves an existing pending block iff it's not older than given block number. /// /// NOTE: This will not prepare a new pending block if it's not existing. @@ -317,7 +302,9 @@ impl Miner { ) } - fn client<'a>(&'a self, chain: &'a MiningBlockChainClient) -> BlockChainClient<'a> { + fn client<'a, C: 'a>(&'a self, chain: &'a C) -> BlockChainClient<'a, C> where + C: BlockChain + CallContract, + { BlockChainClient::new( chain, &*self.engine, @@ -327,7 +314,9 @@ impl Miner { } /// Prepares new block for sealing including top transactions from queue. - fn prepare_block(&self, chain: &C) -> (ClosedBlock, Option) { + fn prepare_block(&self, chain: &C) -> (ClosedBlock, Option) where + C: BlockChain + CallContract + BlockProducer + Nonce + Sync, + { trace_time!("prepare_block"); let chain_info = chain.chain_info(); @@ -639,18 +628,10 @@ impl Miner { } } - fn update_gas_limit(&self, client: &C) { - let gas_limit = client.best_block_header().gas_limit(); - let mut queue = self.transaction_queue.write(); - queue.set_gas_limit(gas_limit); - if let GasLimit::Auto = self.options.tx_queue_gas_limit { - // Set total tx queue gas limit to be 20x the block gas limit. - queue.set_total_gas_limit(gas_limit * 20u32); - } - } - /// Returns true if we had to prepare new pending block. - fn prepare_pending_block(&self, client: &C) -> bool { + fn prepare_pending_block(&self, client: &C) -> bool where + C: BlockChain + CallContract + BlockProducer + SealedBlockImporter + Nonce + Sync, + { trace!(target: "miner", "prepare_pending_block: entering"); let prepare_new = { let mut sealing = self.sealing.lock(); @@ -741,9 +722,9 @@ impl MinerService for Miner { self.params.read().gas_range_target.0 / 5.into() } - fn import_external_transactions( + fn import_external_transactions( &self, - client: &C, + chain: &C, transactions: Vec ) -> Vec> { trace!(target: "external_tx", "Importing external transactions"); @@ -758,13 +739,13 @@ impl MinerService for Miner { // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - self.update_sealing(client); + self.update_sealing(chain); } results } - fn import_own_transaction( + fn import_own_transaction( &self, chain: &C, pending: PendingTransaction, @@ -805,11 +786,13 @@ impl MinerService for Miner { // self.transaction_queue.read().future_transactions() } - fn ready_transactions(&self, chain: &MiningBlockChainClient) -> Vec> { + fn ready_transactions(&self, chain: &C) -> Vec> where + C: ChainInfo + Nonce + Sync, + { let chain_info = chain.chain_info(); let from_queue = || { - let client = self.client(chain); + let client = NonceClient::new(chain); self.transaction_queue.pending( client, @@ -844,10 +827,12 @@ impl MinerService for Miner { } } - fn next_nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { - let client = self.client(chain); + fn next_nonce(&self, chain: &C, address: &Address) -> U256 where + C: Nonce + Sync, + { + let client = NonceClient::new(chain); self.transaction_queue.next_nonce(client, address) - .unwrap_or_else(|| chain.nonce(address, BlockId::Latest).unwrap_or_default()) + .unwrap_or_else(|| chain.latest_nonce(address)) } fn transaction(&self, hash: &H256) -> Option> { @@ -864,14 +849,6 @@ impl MinerService for Miner { self.transaction_queue.status() } - fn pending_transactions(&self, best_block: BlockNumber) -> Option> { - self.from_pending_block( - best_block, - || None, - |pending| Some(pending.transactions().to_vec()), - ) - } - // TODO [ToDr] This is pretty inconsistent (you can get a ready_transaction, but no receipt for it) fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option { self.from_pending_block( @@ -926,9 +903,8 @@ impl MinerService for Miner { // TODO [ToDr] Pass sealing lock guard /// Update sealing if required. /// Prepare the block and work if the Engine does not seal internally. - fn update_sealing(&self, chain: &C) - where C: AccountData + BlockChain + RegistryInfo - + CallContract + BlockProducer + SealedBlockImporter + fn update_sealing(&self, chain: &C) where + C: BlockChain + CallContract + BlockProducer + SealedBlockImporter + Nonce + Sync, { trace!(target: "miner", "update_sealing"); @@ -972,8 +948,8 @@ impl MinerService for Miner { self.sealing.lock().queue.is_in_use() } - fn work_package(&self, chain: &C) -> Option<(H256, BlockNumber, u64, U256)> - where C: AccountData + BlockChain + BlockProducer + CallContract, + fn work_package(&self, chain: &C) -> Option<(H256, BlockNumber, u64, U256)> where + C: BlockChain + CallContract + BlockProducer + SealedBlockImporter + Nonce + Sync, { if self.engine.seals_internally().is_some() { return None; @@ -1017,8 +993,7 @@ impl MinerService for Miner { } fn chain_new_blocks(&self, chain: &C, imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) - where C: AccountData + BlockChain + CallContract + RegistryInfo - + BlockProducer + ScheduleInfo + SealedBlockImporter + where C: TransactionImporterClient + BlockChain + BlockProducer + SealedBlockImporter, { trace!(target: "miner", "chain_new_blocks"); @@ -1062,15 +1037,19 @@ impl MinerService for Miner { } fn pending_state(&self, latest_block_number: BlockNumber) -> Option { - Miner::pending_state(self, latest_block_number) + self.map_existing_pending_block(|b| b.state().clone(), latest_block_number) } fn pending_block_header(&self, latest_block_number: BlockNumber) -> Option
{ - Miner::pending_block_header(self, latest_block_number) + self.map_existing_pending_block(|b| b.header().clone(), latest_block_number) } fn pending_block(&self, latest_block_number: BlockNumber) -> Option { - Miner::pending_block(self, latest_block_number) + self.map_existing_pending_block(|b| b.to_base(), latest_block_number) + } + + fn pending_transactions(&self, latest_block_number: BlockNumber) -> Option> { + self.map_existing_pending_block(|b| b.transactions().into_iter().cloned().collect(), latest_block_number) } } @@ -1078,13 +1057,12 @@ impl MinerService for Miner { mod tests { use super::*; use ethkey::{Generator, Random}; - use client::{TestBlockChainClient, EachBlockWith, ChainInfo}; use hash::keccak; use header::BlockNumber; use rustc_hex::FromHex; - use transaction::Transaction; - use client::{BlockChainClient, TestBlockChainClient, EachBlockWith}; + use transaction::{Transaction}; + use client::{TestBlockChainClient, EachBlockWith, ChainInfo, ImportSealedBlock}; use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; #[test] diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 26fa0aa96d0..0ddba2f5a7f 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -34,17 +34,27 @@ use bytes::Bytes; use ethereum_types::{H256, U256, Address}; use ethcore_miner::pool::{VerifiedTransaction, QueueStatus, local_transactions}; -use block::{ClosedBlock, Block, SealedBlock}; +use block::{Block, SealedBlock}; use client::{ - MiningBlockChainClient, CallContract, RegistryInfo, ScheduleInfo, - BlockChain, AccountData, BlockProducer, SealedBlockImporter + CallContract, RegistryInfo, ScheduleInfo, + BlockChain, BlockProducer, SealedBlockImporter, ChainInfo, + AccountData, Nonce, }; use error::Error; use header::{BlockNumber, Header}; use receipt::{RichReceipt, Receipt}; -use transaction::{self, UnverifiedTransaction, PendingTransaction, SignedTransaction}; +use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction}; use state::StateInfo; +pub trait TransactionImporterClient: + Sync + + // Required for ServiceTransactionChecker + CallContract + RegistryInfo + + // Required for verifiying transactions + BlockChain + ScheduleInfo + AccountData + +{} + // TODO [ToDr] Split into smaller traits? // TODO [ToDr] get rid of from_pending_block in miner/miner.rs @@ -66,18 +76,18 @@ pub trait MinerService : Send + Sync { /// /// Returns `None` if engine seals internally. fn work_package(&self, chain: &C) -> Option<(H256, BlockNumber, u64, U256)> - where C: AccountData + BlockChain + BlockProducer + CallContract; + where C: BlockChain + CallContract + BlockProducer + SealedBlockImporter + Nonce + Sync; /// Update current pending block fn update_sealing(&self, chain: &C) - where C: AccountData + BlockChain + RegistryInfo + CallContract + BlockProducer + SealedBlockImporter; + where C: BlockChain + CallContract + BlockProducer + SealedBlockImporter + Nonce + Sync; // Notifications /// Called when blocks are imported to chain, updates transactions queue. fn chain_new_blocks(&self, chain: &C, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]) - where C: AccountData + BlockChain + CallContract + RegistryInfo + BlockProducer + ScheduleInfo + SealedBlockImporter; + where C: TransactionImporterClient + BlockProducer + SealedBlockImporter; // Pending block @@ -97,6 +107,9 @@ pub trait MinerService : Send + Sync { /// Get `Some` `clone()` of the current pending block or `None` if we're not sealing. fn pending_block(&self, latest_block_number: BlockNumber) -> Option; + /// Get `Some` `clone()` of the current pending block transactions or `None` if we're not sealing. + fn pending_transactions(&self, latest_block_number: BlockNumber) -> Option>; + // Block authoring /// Get current authoring parameters. @@ -116,12 +129,14 @@ pub trait MinerService : Send + Sync { // Transaction Pool /// Imports transactions to transaction queue. - fn import_external_transactions(&self, client: &C, transactions: Vec) - -> Vec>; + fn import_external_transactions(&self, client: &C, transactions: Vec) + -> Vec> + where C: TransactionImporterClient + BlockProducer + SealedBlockImporter; /// Imports own (node owner) transaction to queue. - fn import_own_transaction(&self, chain: &C, transaction: PendingTransaction) - -> Result<(), transaction::Error>; + fn import_own_transaction(&self, chain: &C, transaction: PendingTransaction) + -> Result<(), transaction::Error> + where C: TransactionImporterClient + BlockProducer + SealedBlockImporter; /// Removes transaction from the pool. /// @@ -139,12 +154,14 @@ pub trait MinerService : Send + Sync { /// if they are consecutive. /// NOTE: pool may contain some future transactions that will become pending after /// transaction with nonce returned from this function is signed on. - fn next_nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256; + fn next_nonce(&self, chain: &C, address: &Address) -> U256 + where C: Nonce + Sync; /// Get a list of all ready transactions. /// /// Depending on the settings may look in transaction pool or only in pending block. - fn ready_transactions(&self, chain: &MiningBlockChainClient) -> Vec>; + fn ready_transactions(&self, chain: &C) -> 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 future_transactions(&self) -> Vec>; diff --git a/ethcore/src/miner/service_transaction_checker.rs b/ethcore/src/miner/service_transaction_checker.rs index b727ab8066e..f8d5ba219e5 100644 --- a/ethcore/src/miner/service_transaction_checker.rs +++ b/ethcore/src/miner/service_transaction_checker.rs @@ -16,7 +16,7 @@ //! A service transactions contract checker. -use client::{RegistryInfo, CallContract}; +use client::{RegistryInfo, CallContract, BlockId}; use transaction::SignedTransaction; use_contract!(service_transaction, "ServiceTransaction", "res/contracts/service_transaction.json"); @@ -54,7 +54,7 @@ impl ServiceTransactionChecker { self.contract.functions() .certified() - .call(sender, &|data| client.call_contract(address, data)) + .call(sender, &|data| client.call_contract(BlockId::Latest, address, data)) .map_err(|e| e.to_string()) } } diff --git a/ethcore/src/miner/stratum.rs b/ethcore/src/miner/stratum.rs index 4cb7e63ec99..c63124dcd08 100644 --- a/ethcore/src/miner/stratum.rs +++ b/ethcore/src/miner/stratum.rs @@ -20,7 +20,7 @@ use std::sync::{Arc, Weak}; use std::net::{SocketAddr, AddrParseError}; use std::fmt; -use client::{Client, MiningBlockChainClient}; +use client::{Client, ImportSealedBlock}; use ethereum_types::{H64, H256, clean_0x, U256}; use ethereum::ethash::Ethash; use ethash::SeedHashCompute; diff --git a/miner/Cargo.toml b/miner/Cargo.toml index 37e4139f566..59b1e55667a 100644 --- a/miner/Cargo.toml +++ b/miner/Cargo.toml @@ -12,9 +12,6 @@ hyper = { git = "https://github.com/paritytech/hyper", default-features = false ansi_term = "0.10" error-chain = "0.11" -ethabi = "5.1" -ethabi-contract = "5.0" -ethabi-derive = "5.0" ethash = { path = "../ethash" } ethcore-transaction = { path = "../ethcore/transaction" } ethereum-types = "0.2" diff --git a/miner/src/lib.rs b/miner/src/lib.rs index d0eed3f3e33..628c0ba36bf 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -20,7 +20,6 @@ //! Keeps track of transactions and mined block. extern crate ansi_term; -extern crate ethabi; extern crate ethcore_transaction as transaction; extern crate ethereum_types; extern crate futures; @@ -36,10 +35,6 @@ extern crate transaction_pool as txpool; #[macro_use] extern crate error_chain; #[macro_use] -extern crate ethabi_derive; -#[macro_use] -extern crate ethabi_contract; -#[macro_use] extern crate log; #[cfg(test)] diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs index 932d0dc8c9f..4f13189f9f7 100644 --- a/miner/src/pool/client.rs +++ b/miner/src/pool/client.rs @@ -45,7 +45,7 @@ pub enum TransactionType { Service, } -/// State client. +/// Verification client. pub trait Client: fmt::Debug + Sync { /// Is transaction with given hash already in the blockchain? fn transaction_already_included(&self, hash: &H256) -> bool; @@ -54,15 +54,18 @@ pub trait Client: fmt::Debug + Sync { fn verify_transaction(&self, tx: transaction::UnverifiedTransaction) -> Result; - /// Fetch account details for given sender. - fn account_details(&self, address: &Address) -> AccountDetails; - - /// Fetch only account nonce for given sender. - fn account_nonce(&self, address: &Address) -> U256; - /// Estimate minimal gas requirurement for given transaction. fn required_gas(&self, tx: &transaction::Transaction) -> U256; + /// Fetch account details for given sender. + fn account_details(&self, address: &Address) -> AccountDetails; + /// Classify transaction (check if transaction is filtered by some contracts). fn transaction_type(&self, tx: &transaction::SignedTransaction) -> TransactionType; } + +/// State client +pub trait StateClient: fmt::Debug + Sync { + /// Fetch only account nonce for given sender. + fn account_nonce(&self, address: &Address) -> U256; +} diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 35897efbfc7..a317582d459 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -124,13 +124,18 @@ impl TransactionQueue { results } + /// Returns current pneding transactions. + /// + /// NOTE: This may return a cached version of pending transaction set. + /// Re-computing the pending set is possible with `#collet_pending` method, + /// but be aware that it's a pretty expensive operation. pub fn pending( &self, client: C, block_number: u64, current_timestamp: u64, ) -> Vec> where - C: client::Client, + C: client::StateClient, { // TODO [ToDr] Check if timestamp is within limits. let is_valid = |bn| bn == block_number; @@ -169,7 +174,7 @@ impl TransactionQueue { // TODO [ToDr] Support nonce_cap collect: F, ) -> T where - C: client::Client, + C: client::StateClient, F: FnOnce(txpool::PendingIterator< pool::VerifiedTransaction, (ready::Condition, ready::State), @@ -186,7 +191,7 @@ impl TransactionQueue { } /// Culls all stalled transactions from the pool. - pub fn cull( + pub fn cull( &self, client: C, ) { @@ -198,7 +203,7 @@ impl TransactionQueue { /// Returns next valid nonce for given sender /// or `None` if there are no pending transactions from that sender. - pub fn next_nonce( + pub fn next_nonce( &self, client: C, address: &Address, @@ -329,13 +334,8 @@ mod tests { } } - /// Fetch only account nonce for given sender. - fn account_nonce(&self, _address: &Address) -> U256 { - 0.into() - } - /// Estimate minimal gas requirurement for given transaction. - fn required_gas(&self, _tx: &transaction::SignedTransaction) -> U256 { + fn required_gas(&self, _tx: &transaction::Transaction) -> U256 { 0.into() } @@ -345,11 +345,18 @@ mod tests { } } + impl client::StateClient for TestClient { + /// Fetch only account nonce for given sender. + fn account_nonce(&self, _address: &Address) -> U256 { + 0.into() + } + } + #[test] fn should_get_pending_transactions() { let queue = TransactionQueue::new(txpool::Options::default(), verifier::Options::default()); - let pending: Vec<_> = queue.pending(TestClient, 0, 0, |x| x.collect()); + let pending: Vec<_> = queue.pending(TestClient, 0, 0); for tx in pending { assert!(tx.signed().nonce > 0.into()); diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs index 614a0326007..b126c560867 100644 --- a/miner/src/pool/ready.rs +++ b/miner/src/pool/ready.rs @@ -20,7 +20,7 @@ //! particular transaction can be included in the block. //! //! Regular transactions are ready iff the current state nonce -//! (obtained from `Client`) equals to the transaction nonce. +//! (obtained from `StateClient`) equals to the transaction nonce. //! //! Let's define `S = state nonce`. Transactions are processed //! in order, so we first include transaction with nonce `S`, @@ -45,7 +45,7 @@ use ethereum_types::{U256, H160 as Address}; use transaction; use txpool::{self, VerifiedTransaction as PoolVerifiedTransaction}; -use super::client::Client; +use super::client::StateClient; use super::VerifiedTransaction; /// Checks readiness of transactions by comparing the nonce to state nonce. @@ -65,7 +65,7 @@ impl State { } } -impl txpool::Ready for State { +impl txpool::Ready for State { fn is_ready(&mut self, tx: &VerifiedTransaction) -> txpool::Readiness { let sender = tx.sender(); let state = &self.state; From 43e93587f593f5c67a12440decad37e143b85618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 5 Mar 2018 14:38:35 +0100 Subject: [PATCH 29/77] Make RPC compile again. --- ethcore/src/client/client.rs | 11 +++-------- ethcore/src/client/mod.rs | 2 +- ethcore/src/client/test_client.rs | 16 +++------------- ethcore/src/client/traits.rs | 7 ------- ethcore/src/miner/blockchain_client.rs | 4 ++-- ethcore/src/miner/miner.rs | 11 ++++++----- ethcore/src/miner/mod.rs | 18 ++++++++++-------- parity/run.rs | 2 +- rpc/src/v1/helpers/dispatch.rs | 10 +++++----- rpc/src/v1/helpers/errors.rs | 9 +++++++++ rpc/src/v1/impls/eth.rs | 13 ++++++------- rpc/src/v1/impls/eth_filter.rs | 16 ++++++++-------- rpc/src/v1/impls/parity.rs | 8 ++++---- rpc/src/v1/impls/parity_set.rs | 6 +++--- rpc/src/v1/impls/traces.rs | 4 ++-- rpc/src/v1/tests/eth.rs | 2 +- rpc/src/v1/tests/helpers/miner_service.rs | 12 +++++------- 17 files changed, 69 insertions(+), 82 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e1808cbd9db..fb7b61f00b9 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -42,7 +42,7 @@ use client::{ }; use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, - MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, + TraceFilter, CallAnalytics, BlockImportError, Mode, ChainNotify, PruningInfo, ProvingBlockChainClient, EngineInfo }; use encoded; @@ -2094,13 +2094,8 @@ impl BroadcastProposalBlock for Client { impl SealedBlockImporter for Client {} -impl MiningBlockChainClient for Client { - fn vm_factory(&self) -> &VmFactory { - &self.factories.vm - } -} - -impl ::miner::TransactionImporterClient for Client {} +impl ::miner::TransactionVerifierClient for Client {} +impl ::miner::BlockChainClient for Client {} impl super::traits::EngineClient for Client { fn update_sealing(&self) { diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 56eb5ae1d71..8f68bcdab3c 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -35,7 +35,7 @@ pub use self::traits::{ StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter }; pub use state::StateInfo; -pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient, ProvingBlockChainClient}; +pub use self::traits::{BlockChainClient, EngineClient, ProvingBlockChainClient}; pub use types::ids::*; pub use types::trace_filter::Filter as TraceFilter; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 1a4956328bf..69a33c9b2e8 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -35,7 +35,7 @@ use transaction::{self, Transaction, LocalizedTransaction, PendingTransaction, S use blockchain::{TreeRoute, BlockReceipts}; use client::{ Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, CallContract, TransactionInfo, RegistryInfo, - PrepareOpenBlock, BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, + PrepareOpenBlock, BlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, ProvingBlockChainClient, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, StateOrBlock, Call, StateClient, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter @@ -46,8 +46,6 @@ use filter::Filter; use log_entry::LocalizedLogEntry; use receipt::{Receipt, LocalizedReceipt, TransactionOutcome}; use error::ImportResult; -use evm::VMType; -use factory::VmFactory; use vm::Schedule; use miner::{Miner, MinerService}; use spec::Spec; @@ -101,8 +99,6 @@ pub struct TestBlockChainClient { pub miner: Arc, /// Spec pub spec: Spec, - /// VM Factory - pub vm_factory: VmFactory, /// Timestamp assigned to latest sealed block pub latest_block_timestamp: RwLock, /// Ancient block info. @@ -173,7 +169,6 @@ impl TestBlockChainClient { queue_size: AtomicUsize::new(0), miner: Arc::new(Miner::new_for_tests(&spec, None)), spec: spec, - vm_factory: VmFactory::new(VMType::Interpreter, 1024 * 1024), latest_block_timestamp: RwLock::new(10_000_000), ancient_block: RwLock::new(None), first_block: RwLock::new(None), @@ -414,13 +409,8 @@ impl BroadcastProposalBlock for TestBlockChainClient { impl SealedBlockImporter for TestBlockChainClient {} -impl MiningBlockChainClient for TestBlockChainClient { - fn vm_factory(&self) -> &VmFactory { - &self.vm_factory - } -} - -impl ::miner::TransactionImporterClient for TestBlockChainClient {} +impl ::miner::TransactionVerifierClient for TestBlockChainClient {} +impl ::miner::BlockChainClient for TestBlockChainClient {} impl Nonce for TestBlockChainClient { fn nonce(&self, address: &Address, id: BlockId) -> Option { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index bf98a8923b6..826fba066d5 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -23,7 +23,6 @@ use encoded; use vm::LastHashes; use error::{ImportResult, CallError, BlockImportError}; use evm::Schedule; -use factory::VmFactory; use executive::Executed; use filter::Filter; use header::{BlockNumber}; @@ -416,12 +415,6 @@ pub trait BroadcastProposalBlock { /// Provides methods to import sealed block and broadcast a block proposal pub trait SealedBlockImporter: ImportSealedBlock + BroadcastProposalBlock {} -/// Extended client interface used for mining -pub trait MiningBlockChainClient: BlockChainClient + BlockProducer + ScheduleInfo + SealedBlockImporter { - /// Returns EvmFactory. - fn vm_factory(&self) -> &VmFactory; -} - /// Client facilities used by internally sealing Engines. pub trait EngineClient: Sync + Send + ChainInfo { /// Make a new block and seal it. diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index c0224a16cfe..624c29f84d5 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -28,7 +28,7 @@ use account_provider::AccountProvider; use client::{TransactionId, BlockInfo, CallContract, Nonce}; use engines::EthEngine; use header::Header; -use miner::TransactionImporterClient; +use miner; use miner::service_transaction_checker::ServiceTransactionChecker; // TODO [ToDr] Shit @@ -94,7 +94,7 @@ impl<'a, C: 'a> fmt::Debug for BlockChainClient<'a, C> { } impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where - C: TransactionImporterClient + Sync, + C: miner::TransactionVerifierClient + Sync, { fn transaction_already_included(&self, hash: &H256) -> bool { self.chain.transaction_block(TransactionId::Hash(*hash)).is_some() diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index cd6afc14bde..52db2762663 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -45,7 +45,7 @@ use client::{ use client::BlockId; use executive::contract_address; use header::{Header, BlockNumber}; -use miner::{MinerService, TransactionImporterClient}; +use miner; use miner::blockchain_client::{BlockChainClient, NonceClient}; use receipt::{Receipt, RichReceipt}; use spec::Spec; @@ -672,7 +672,7 @@ impl Miner { const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; -impl MinerService for Miner { +impl miner::MinerService for Miner { type State = State<::state_db::StateDB>; fn authoring_params(&self) -> AuthoringParams { @@ -722,7 +722,7 @@ impl MinerService for Miner { self.params.read().gas_range_target.0 / 5.into() } - fn import_external_transactions( + fn import_external_transactions( &self, chain: &C, transactions: Vec @@ -745,7 +745,7 @@ impl MinerService for Miner { results } - fn import_own_transaction( + fn import_own_transaction( &self, chain: &C, pending: PendingTransaction, @@ -993,7 +993,7 @@ impl MinerService for Miner { } fn chain_new_blocks(&self, chain: &C, imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) - where C: TransactionImporterClient + BlockChain + BlockProducer + SealedBlockImporter, + where C: miner::BlockChainClient, { trace!(target: "miner", "chain_new_blocks"); @@ -1063,6 +1063,7 @@ mod tests { use transaction::{Transaction}; use client::{TestBlockChainClient, EachBlockWith, ChainInfo, ImportSealedBlock}; + use miner::MinerService; use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; #[test] diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 0ddba2f5a7f..c0f946313f3 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -46,15 +46,17 @@ use receipt::{RichReceipt, Receipt}; use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction}; use state::StateInfo; -pub trait TransactionImporterClient: - Sync + +/// Provides methods to verify incoming external transactions +pub trait TransactionVerifierClient: Send + Sync // Required for ServiceTransactionChecker - CallContract + RegistryInfo + + + CallContract + RegistryInfo // Required for verifiying transactions - BlockChain + ScheduleInfo + AccountData - + + BlockChain + ScheduleInfo + AccountData {} +/// Extended client interface used for mining +pub trait BlockChainClient: TransactionVerifierClient + BlockProducer + SealedBlockImporter {} + // TODO [ToDr] Split into smaller traits? // TODO [ToDr] get rid of from_pending_block in miner/miner.rs @@ -87,7 +89,7 @@ pub trait MinerService : Send + Sync { /// Called when blocks are imported to chain, updates transactions queue. fn chain_new_blocks(&self, chain: &C, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]) - where C: TransactionImporterClient + BlockProducer + SealedBlockImporter; + where C: BlockChainClient; // Pending block @@ -131,12 +133,12 @@ pub trait MinerService : Send + Sync { /// Imports transactions to transaction queue. fn import_external_transactions(&self, client: &C, transactions: Vec) -> Vec> - where C: TransactionImporterClient + BlockProducer + SealedBlockImporter; + where C: BlockChainClient; /// Imports own (node owner) transaction to queue. fn import_own_transaction(&self, chain: &C, transaction: PendingTransaction) -> Result<(), transaction::Error> - where C: TransactionImporterClient + BlockProducer + SealedBlockImporter; + where C: BlockChainClient; /// Removes transaction from the pool. /// diff --git a/parity/run.rs b/parity/run.rs index 1ed7929d720..c2695699cbd 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -23,7 +23,7 @@ use std::net::{TcpListener}; use ansi_term::Colour; use ctrlc::CtrlC; use ethcore::account_provider::{AccountProvider, AccountProviderSettings}; -use ethcore::client::{Client, Mode, DatabaseCompactionProfile, VMType, BlockChainClient}; +use ethcore::client::{Client, Mode, DatabaseCompactionProfile, VMType, BlockChainClient, BlockInfo}; use ethcore::db::NUM_COLUMNS; use ethcore::ethstore::ethkey; use ethcore::miner::{stratum, Miner, MinerService, MinerOptions}; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index c6f8b9b2458..013ad917ec3 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -34,8 +34,8 @@ use stats::Corpus; use ethkey::Signature; use ethsync::LightSync; use ethcore::ids::BlockId; -use ethcore::miner::MinerService; -use ethcore::client::MiningBlockChainClient; +use ethcore::client::BlockChainClient; +use ethcore::miner::{self, MinerService}; use ethcore::account_provider::AccountProvider; use crypto::DEFAULT_MAC; use transaction::{Action, SignedTransaction, PendingTransaction, Transaction}; @@ -117,7 +117,7 @@ impl Clone for FullDispatcher { } } -impl FullDispatcher { +impl FullDispatcher { fn state_nonce(&self, from: &Address) -> U256 { self.miner.next_nonce(&*self.client, from) } @@ -132,7 +132,7 @@ impl FullDispatcher { } } -impl Dispatcher for FullDispatcher { +impl Dispatcher for FullDispatcher { fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool) -> BoxFuture { @@ -746,7 +746,7 @@ fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: S /// Extract the default gas price from a client and miner. pub fn default_gas_price(client: &C, miner: &M, percentile: usize) -> U256 where - C: MiningBlockChainClient, + C: BlockChainClient, M: MinerService, { client.gas_price_corpus(100).percentile(percentile).cloned().unwrap_or_else(|| miner.sensible_gas_price()) diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 0b7e159f9cf..9393fc53a01 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -35,6 +35,7 @@ mod codes { pub const TRANSACTION_ERROR: i64 = -32010; pub const EXECUTION_ERROR: i64 = -32015; pub const EXCEPTION_ERROR: i64 = -32016; + pub const DATABASE_ERROR: i64 = -32017; pub const ACCOUNT_LOCKED: i64 = -32020; pub const PASSWORD_INVALID: i64 = -32021; pub const ACCOUNT_ERROR: i64 = -32023; @@ -255,6 +256,14 @@ pub fn encoding(error: T) -> Error { } } +pub fn database(error: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::DATABASE_ERROR), + message: "Database error.".into(), + data: Some(Value::String(format!("{:?}", error))), + } +} + pub fn fetch(error: T) -> Error { Error { code: ErrorCode::ServerError(codes::FETCH_ERROR), diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index b3aef0c15a3..6df782362e4 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -27,13 +27,12 @@ use parking_lot::Mutex; use ethash::SeedHashCompute; use ethcore::account_provider::{AccountProvider, DappId}; -use ethcore::block::IsBlock; -use ethcore::client::{MiningBlockChainClient, BlockId, TransactionId, UncleId, StateOrBlock, StateClient, StateInfo, Call, EngineInfo}; +use ethcore::client::{BlockChainClient, BlockId, TransactionId, UncleId, StateOrBlock, StateClient, StateInfo, Call, EngineInfo}; use ethcore::ethereum::Ethash; use ethcore::filter::Filter as EthcoreFilter; use ethcore::header::{BlockNumber as EthBlockNumber, Seal}; use ethcore::log_entry::LogEntry; -use ethcore::miner::MinerService; +use ethcore::miner::{self, MinerService}; use ethcore::snapshot::SnapshotService; use ethcore::encoded; use ethsync::{SyncProvider}; @@ -93,7 +92,7 @@ impl Default for EthClientOptions { /// Eth rpc implementation. pub struct EthClient where - C: MiningBlockChainClient, + C: miner::BlockChainClient + BlockChainClient, SN: SnapshotService, S: SyncProvider, M: MinerService, @@ -143,7 +142,7 @@ enum PendingTransactionId { } impl EthClient where - C: MiningBlockChainClient + StateClient + Call + EngineInfo, + C: miner::BlockChainClient + BlockChainClient + StateClient + Call + EngineInfo, SN: SnapshotService, S: SyncProvider, M: MinerService, @@ -430,7 +429,7 @@ pub fn pending_logs(miner: &M, best_block: EthBlockNumber, filter: &EthcoreFi result } -fn check_known(client: &C, number: BlockNumber) -> Result<()> where C: MiningBlockChainClient { +fn check_known(client: &C, number: BlockNumber) -> Result<()> where C: BlockChainClient { use ethcore::block_status::BlockStatus; let id = match number { @@ -450,7 +449,7 @@ fn check_known(client: &C, number: BlockNumber) -> Result<()> where C: Mining const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. impl Eth for EthClient where - C: MiningBlockChainClient + StateClient + Call + EngineInfo + 'static, + C: miner::BlockChainClient + BlockChainClient + StateClient + Call + EngineInfo + 'static, SN: SnapshotService + 'static, S: SyncProvider + 'static, M: MinerService + 'static, diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index f9bb814cbc4..dbc4ec7747f 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -19,9 +19,9 @@ use std::sync::Arc; use std::collections::HashSet; -use ethcore::miner::MinerService; +use ethcore::miner::{self, MinerService}; use ethcore::filter::Filter as EthcoreFilter; -use ethcore::client::{MiningBlockChainClient, BlockId}; +use ethcore::client::{BlockChainClient, BlockId}; use ethereum_types::H256; use parking_lot::Mutex; @@ -55,16 +55,13 @@ pub trait Filterable { } /// Eth filter rpc implementation for a full node. -pub struct EthFilterClient where - C: MiningBlockChainClient, - M: MinerService { - +pub struct EthFilterClient { client: Arc, miner: Arc, polls: Mutex>, } -impl EthFilterClient where C: MiningBlockChainClient, M: MinerService { +impl EthFilterClient { /// Creates new Eth filter client. pub fn new(client: Arc, miner: Arc) -> Self { EthFilterClient { @@ -75,7 +72,10 @@ impl EthFilterClient where C: MiningBlockChainClient, M: MinerServic } } -impl Filterable for EthFilterClient where C: MiningBlockChainClient, M: MinerService { +impl Filterable for EthFilterClient where + C: miner::BlockChainClient + BlockChainClient, + M: MinerService, +{ fn best_block_number(&self) -> u64 { self.client.chain_info().best_block_number } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 8fae4884bb2..7f0a5ac8976 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -27,9 +27,9 @@ use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::{SyncProvider, ManageNetwork}; use ethcore::account_provider::AccountProvider; -use ethcore::client::{MiningBlockChainClient, StateClient, Call}; +use ethcore::client::{BlockChainClient, StateClient, Call}; use ethcore::ids::BlockId; -use ethcore::miner::MinerService; +use ethcore::miner::{self, MinerService}; use ethcore::mode::Mode; use ethcore::state::StateInfo; use ethcore_logger::RotatingLogger; @@ -72,7 +72,7 @@ pub struct ParityClient { } impl ParityClient where - C: MiningBlockChainClient, + C: BlockChainClient, { /// Creates new `ParityClient`. pub fn new( @@ -116,7 +116,7 @@ impl ParityClient where impl Parity for ParityClient where S: StateInfo + 'static, - C: MiningBlockChainClient + StateClient + Call + 'static, + C: miner::BlockChainClient + BlockChainClient + StateClient + Call + 'static, M: MinerService + 'static, U: UpdateService + 'static, { diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index c28b3ff7995..cad139dc915 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -18,8 +18,8 @@ use std::io; use std::sync::Arc; +use ethcore::client::BlockChainClient; use ethcore::miner::MinerService; -use ethcore::client::MiningBlockChainClient; use ethcore::mode::Mode; use ethsync::ManageNetwork; use fetch::{self, Fetch}; @@ -45,7 +45,7 @@ pub struct ParitySetClient { } impl ParitySetClient - where C: MiningBlockChainClient + 'static, + where C: BlockChainClient + 'static, { /// Creates new `ParitySetClient` with given `Fetch`. pub fn new( @@ -69,7 +69,7 @@ impl ParitySetClient } impl ParitySet for ParitySetClient where - C: MiningBlockChainClient + 'static, + C: BlockChainClient + 'static, M: MinerService + 'static, U: UpdateService + 'static, F: Fetch + 'static, diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 0ee1afd5fa7..06df83b752b 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -18,7 +18,7 @@ use std::sync::Arc; -use ethcore::client::{MiningBlockChainClient, CallAnalytics, TransactionId, TraceId, StateClient, StateInfo, Call, BlockId}; +use ethcore::client::{BlockChainClient, CallAnalytics, TransactionId, TraceId, StateClient, StateInfo, Call, BlockId}; use rlp::UntrustedRlp; use transaction::SignedTransaction; @@ -53,7 +53,7 @@ impl TracesClient { impl Traces for TracesClient where S: StateInfo + 'static, - C: MiningBlockChainClient + StateClient + Call + 'static + C: BlockChainClient + StateClient + Call + 'static { type Metadata = Metadata; diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 6606c30d538..f32f09cd727 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -24,7 +24,7 @@ use ethcore::block::Block; use ethcore::client::{BlockChainClient, Client, ClientConfig, ChainInfo, ImportBlock}; use ethcore::ethereum; use ethcore::ids::BlockId; -use ethcore::miner::{MinerService, Miner}; +use ethcore::miner::Miner; use ethcore::spec::{Genesis, Spec}; use ethcore::views::BlockView; use ethjson::blockchain::BlockChain; diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 713174ac506..173848a07ac 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -18,11 +18,10 @@ use std::sync::Arc; use std::collections::{BTreeMap, HashMap}; -use std::collections::hash_map::Entry; use bytes::Bytes; use ethcore::account_provider::SignError as AccountError; -use ethcore::block::{SealedBlock, IsBlock}; +use ethcore::block::{Block, SealedBlock, IsBlock}; use ethcore::client::{Nonce, PrepareOpenBlock, StateClient, EngineInfo}; use ethcore::engines::EthEngine; use ethcore::error::Error; @@ -31,7 +30,6 @@ use ethcore::ids::BlockId; use ethcore::miner::{MinerService, AuthoringParams}; use ethcore::receipt::{Receipt, RichReceipt}; use ethereum_types::{H256, U256, Address}; -use miner::local_transactions::Status as LocalTransactionStatus; use miner::pool::local_transactions::Status as LocalTransactionStatus; use miner::pool::{verifier, VerifiedTransaction, QueueStatus}; use parking_lot::{RwLock, Mutex}; @@ -138,7 +136,7 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_external_transactions(&self, _chain: &C, transactions: Vec) + fn import_external_transactions(&self, chain: &C, transactions: Vec) -> Vec> { // lets assume that all txs are valid @@ -157,7 +155,7 @@ impl MinerService for TestMinerService { } /// Imports transactions to transaction queue. - fn import_own_transaction(&self, chain: &C, pending: PendingTransaction) + fn import_own_transaction(&self, chain: &C, pending: PendingTransaction) -> Result<(), transaction::Error> { // keep the pending nonces up to date @@ -210,7 +208,7 @@ impl MinerService for TestMinerService { self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() } - fn ready_transactions(&self, _chain: &MiningBlockChainClient) -> Vec> { + fn ready_transactions(&self, _chain: &C) -> Vec> { self.pending_transactions.lock().values().cloned().map(|tx| { Arc::new(VerifiedTransaction::from_pending_block_transaction(tx)) }).collect() @@ -240,7 +238,7 @@ impl MinerService for TestMinerService { Some(self.pending_receipts.lock().clone()) } - fn next_nonce(&self, _chain: &MiningBlockChainClient, address: &Address) -> U256 { + fn next_nonce(&self, _chain: &C, address: &Address) -> U256 { self.next_nonces.read().get(address).cloned().unwrap_or_default() } From 236ac2475ee0ee9702558b0fbe81614222ba9ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 5 Mar 2018 19:28:56 +0100 Subject: [PATCH 30/77] Bunch of txpool tests. --- Cargo.lock | 1 + miner/Cargo.toml | 1 + miner/src/lib.rs | 2 + miner/src/pool/client.rs | 3 +- miner/src/pool/queue.rs | 18 +- miner/src/pool/scoring.rs | 10 +- miner/src/pool/tests/mod.rs | 1903 +++++++++++++----------------- miner/src/pool/verifier.rs | 2 +- transaction-pool/src/listener.rs | 4 +- transaction-pool/src/pool.rs | 2 + transaction-pool/src/scoring.rs | 2 +- 11 files changed, 879 insertions(+), 1069 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ae186da0ec..32be0f2de36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,7 @@ name = "ethcore-miner" version = "1.11.0" dependencies = [ "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.11.0", "ethcore-transaction 0.1.0", diff --git a/miner/Cargo.toml b/miner/Cargo.toml index 59b1e55667a..feb492d4358 100644 --- a/miner/Cargo.toml +++ b/miner/Cargo.toml @@ -11,6 +11,7 @@ authors = ["Parity Technologies "] hyper = { git = "https://github.com/paritytech/hyper", default-features = false } ansi_term = "0.10" +env_logger = "0.4" error-chain = "0.11" ethash = { path = "../ethash" } ethcore-transaction = { path = "../ethcore/transaction" } diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 628c0ba36bf..11c52c47d4f 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -41,6 +41,8 @@ extern crate log; extern crate rustc_hex; #[cfg(test)] extern crate ethkey; +#[cfg(test)] +extern crate env_logger; // pub mod banning_queue; pub mod external; diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs index 4f13189f9f7..4b9a952e937 100644 --- a/miner/src/pool/client.rs +++ b/miner/src/pool/client.rs @@ -26,7 +26,7 @@ use ethereum_types::{U256, H256, H160 as Address}; use transaction; /// Account Details -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct AccountDetails { /// Current account nonce pub nonce: U256, @@ -64,6 +64,7 @@ pub trait Client: fmt::Debug + Sync { fn transaction_type(&self, tx: &transaction::SignedTransaction) -> TransactionType; } +// TODO [ToDr] Rename to NonceClient /// State client pub trait StateClient: fmt::Debug + Sync { /// Fetch only account nonce for given sender. diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index a317582d459..039e61f9624 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -237,12 +237,20 @@ impl TransactionQueue { hashes: T, is_invalid: bool, ) -> Vec>> { - let mut pool = self.pool.write(); + let results = { + let mut pool = self.pool.write(); - hashes - .into_iter() - .map(|hash| pool.remove(hash, is_invalid)) - .collect() + hashes + .into_iter() + .map(|hash| pool.remove(hash, is_invalid)) + .collect::>() + }; + + if results.iter().any(Option::is_some) { + *self.cached_pending.write() = None; + } + + results } /// Clear the entire pool. diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs index e07290b6698..4ba0f259901 100644 --- a/miner/src/pool/scoring.rs +++ b/miner/src/pool/scoring.rs @@ -80,12 +80,18 @@ impl txpool::Scoring for GasPrice { super::Priority::Retracted => 5, super::Priority::Regular => 0, }; - // TODO [ToDr] overflow? - scores[i] = scores[i] + scores[i] >> boost; + scores[i] = scores[i].saturating_add(scores[i] << boost); } } fn should_replace(&self, old: &VerifiedTransaction, new: &VerifiedTransaction) -> bool { + if old.sender == new.sender { + // prefer earliest transaction + if new.transaction.nonce < old.transaction.nonce { + return true + } + } + self.choose(old, new) == txpool::scoring::Choice::ReplaceOld } } diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index 61de6b7510d..e82c696db04 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -14,44 +14,65 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethereum_types::{U256, Address}; +use ethereum_types::{U256, H256, Address}; use ethkey::{Random, Generator}; use rustc_hex::FromHex; -use transaction::{self, Transaction, SignedTransaction Unverified}; +use transaction::{self, Transaction, SignedTransaction, UnverifiedTransaction, PendingTransaction}; +use txpool; -use pool::VerifiedTransaction; +use pool::{self, verifier, VerifiedTransaction, TransactionQueue}; use pool::client::AccountDetails; +#[derive(Debug, Clone)] pub struct TestClient { account_details: AccountDetails, gas_required: U256, is_service_transaction: bool, + local_address: Address, } impl Default for TestClient { fn default() -> Self { - + TestClient { + account_details: AccountDetails { + nonce: 123.into(), + balance: 63_100.into(), + is_local: false, + }, + gas_required: 21_000.into(), + is_service_transaction: false, + local_address: Default::default(), + } } } impl TestClient { + pub fn new() -> Self { + TestClient::default() + } + pub fn with_account(mut self, account_details: AccountDetails) -> Self { self.account_details = account_details; self } - pub fn with_account_nonce(mut self, nonce: U256) -> Self { - self.account_details.nonce = nonce; + pub fn with_nonce>(mut self, nonce: T) -> Self { + self.account_details.nonce = nonce.into(); self } - pub fn with_tx_gas_required(mut self, gas_required: U256) -> Self { - self.gas_required = gas_required; + pub fn with_gas_required>(mut self, gas_required: T) -> Self { + self.gas_required = gas_required.into(); + self + } + + pub fn with_local(mut self, address: &Address) -> Self { + self.local_address = *address; self } pub fn with_service_transaction(mut self) -> Self { - self.is_service_transaction; + self.is_service_transaction = true; self } } @@ -61,14 +82,19 @@ impl pool::client::Client for TestClient { false } - fn verify_transaction(&self, _tx: UnverifiedTransaction) + fn verify_transaction(&self, tx: UnverifiedTransaction) -> Result { - unimplemented!() + Ok(SignedTransaction::new(tx)?) } - fn account_details(&self, _address: &Address) -> AccountDetails { - self.account_details.clone() + fn account_details(&self, address: &Address) -> AccountDetails { + let mut details = self.account_details.clone(); + if address == &self.local_address { + details.is_local = true; + } + + details } fn required_gas(&self, _tx: &Transaction) -> U256 { @@ -76,7 +102,7 @@ impl pool::client::Client for TestClient { } fn transaction_type(&self, _tx: &SignedTransaction) -> pool::client::TransactionType { - if is_service_transaction { + if self.is_service_transaction { pool::client::TransactionType::Service } else { pool::client::TransactionType::Regular @@ -84,1293 +110,1056 @@ impl pool::client::Client for TestClient { } } -fn unwrap_tx_err(err: Result) -> transaction::Error { - err.unwrap_err() -} - -fn default_nonce() -> U256 { 123.into() } -fn default_gas_val() -> U256 { 100_000.into() } -fn default_gas_price() -> U256 { 1.into() } - -fn new_unsigned_tx(nonce: U256, gas: U256, gas_price: U256) -> Transaction { - Transaction { - action: transaction::Action::Create, - value: U256::from(100), - data: "3331600055".from_hex().unwrap(), - gas: gas, - gas_price: gas_price, - nonce: nonce +impl pool::client::StateClient for TestClient { + fn account_nonce(&self, _address: &Address) -> U256 { + self.account_details.nonce } } -fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction { - let keypair = Random.generate().unwrap(); - new_unsigned_tx(nonce, default_gas_val(), gas_price).sign(keypair.secret(), None) -} +trait TxExt: Sized { + type Out; + type Hash; + + fn hash(&self) -> Self::Hash; + + fn local(self) -> Self::Out; -fn new_tx_with_gas(gas: U256, gas_price: U256) -> SignedTransaction { - let keypair = Random.generate().unwrap(); - new_unsigned_tx(default_nonce(), gas, gas_price).sign(keypair.secret(), None) + fn retracted(self) -> Self::Out; + + fn unverified(self) -> Self::Out; } -fn new_tx_default() -> SignedTransaction { - new_tx(default_nonce(), default_gas_price()) +impl TxExt for (A, B) where + A: TxExt, + B: TxExt, +{ + type Out = (O, O); + type Hash = (H, H); + + fn hash(&self) -> Self::Hash { (self.0.hash(), self.1.hash()) } + fn local(self) -> Self::Out { (self.0.local(), self.1.local()) } + fn retracted(self) -> Self::Out { (self.0.retracted(), self.1.retracted()) } + fn unverified(self) -> Self::Out { (self.0.unverified(), self.1.unverified()) } } -fn default_account_details() -> AccountDetails { - AccountDetails { - nonce: default_nonce(), - balance: !U256::zero() +impl TxExt for SignedTransaction { + type Out = verifier::Transaction; + type Hash = H256; + + fn hash(&self) -> Self::Hash { + UnverifiedTransaction::hash(self) } -} -fn default_account_details_for_addr(_a: &Address) -> AccountDetails { - default_account_details() -} + fn local(self) -> Self::Out { + verifier::Transaction::Local(self.into()) + } + + fn retracted(self) -> Self::Out { + verifier::Transaction::Retracted(self.into()) + } -fn default_tx_provider() -> DummyTransactionDetailsProvider { - DummyTransactionDetailsProvider::default() + fn unverified(self) -> Self::Out { + verifier::Transaction::Unverified(self.into()) + } } -fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { - let tx1 = new_unsigned_tx(nonce, default_gas_val(), gas_price); - let tx2 = new_unsigned_tx(nonce + nonce_increment, default_gas_val(), gas_price + gas_price_increment); +impl TxExt for Vec { + type Out = Vec; + type Hash = Vec; - let keypair = Random.generate().unwrap(); - let secret = &keypair.secret(); - (tx1.sign(secret, None).into(), tx2.sign(secret, None).into()) -} + fn hash(&self) -> Self::Hash { + self.iter().map(|tx| tx.hash()).collect() + } -/// Returns two consecutive transactions, both with increased gas price -fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { - let gas = default_gas_price() + gas_price_increment; - let tx1 = new_unsigned_tx(default_nonce(), default_gas_val(), gas); - let tx2 = new_unsigned_tx(default_nonce() + 1.into(), default_gas_val(), gas); + fn local(self) -> Self::Out { + self.into_iter().map(Into::into).map(verifier::Transaction::Local).collect() + } - let keypair = Random.generate().unwrap(); - let secret = &keypair.secret(); - (tx1.sign(secret, None).into(), tx2.sign(secret, None).into()) -} + fn retracted(self) -> Self::Out { + self.into_iter().map(Into::into).map(verifier::Transaction::Retracted).collect() + } -fn new_tx_pair_default(nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { - new_tx_pair(default_nonce(), default_gas_price(), nonce_increment, gas_price_increment) + fn unverified(self) -> Self::Out { + self.into_iter().map(Into::into).map(verifier::Transaction::Unverified).collect() + } } -/// Returns two transactions with identical (sender, nonce) but different gas price/hash. -fn new_similar_tx_pair() -> (SignedTransaction, SignedTransaction) { - new_tx_pair_default(0.into(), 1.into()) +trait PairExt { + type Type; + + fn into_vec(self) -> Vec; } -#[test] -fn should_return_correct_nonces_when_dropped_because_of_limit() { - // given - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 2, - usize::max_value(), - !U256::zero(), - !U256::zero(), - ); - let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); - let sender = tx1.sender(); - let nonce = tx1.nonce; - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into())); +impl PairExt for (A, A) { + type Type = A; + fn into_vec(self) -> Vec { + vec![self.0, self.1] + } +} - // when - let tx = new_tx(123.into(), 1.into()); - let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); +#[derive(Clone)] +struct Tx { + nonce: u64, + gas: u64, + gas_price: u64, +} - // then - // No longer the case as we don't even consider a transaction that isn't above a full - // queue's minimum gas price. - // We may want to reconsider this in the near future so leaving this code in as a - // possible alternative. - /* - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.last_nonce(&sender), Some(nonce)); - */ - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { - minimal: 2.into(), - got: 1.into(), - }); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.last_nonce(&sender), Some(tx2.nonce)); +impl Default for Tx { + fn default() -> Self { + Tx { + nonce: 123, + gas: 21_000, + gas_price: 1, + } + } } -#[test] -fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_similar_tx_pair(); - let prev_nonce = default_account_details().nonce - U256::one(); +impl Tx { + pub fn gas(gas: u64) -> Self { + Tx { + gas, + ..Default::default() + } + } - // First insert one transaction to future - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)); - assert_eq!(res.unwrap(), transaction::ImportResult::Future); - assert_eq!(txq.status().future, 1); + pub fn gas_price(gas_price: u64) -> Self { + Tx { + gas_price, + ..Default::default() + } + } - // now import second transaction to current - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); + pub fn nonce(nonce: u64) -> Self { + Tx { + nonce, + ..Default::default() + } + } - // and then there should be only one transaction in current (the one with higher gas_price) - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.status().pending, 1); - assert_eq!(txq.status().future, 0); - assert_eq!(txq.current.by_priority.len(), 1); - assert_eq!(txq.current.by_address.len(), 1); - let top = txq.top_transactions(); - assert_eq!(top[0], tx2); -} + pub fn signed(self) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + self.unsigned().sign(keypair.secret(), None) + } -#[test] -fn should_move_all_transactions_from_future() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 1.into()); - let prev_nonce = default_account_details().nonce - U256::one(); + pub fn signed_pair(self) -> (SignedTransaction, SignedTransaction) { + let (tx1, tx2, _) = self.signed_triple(); + (tx1, tx2) + } - // First insert one transaction to future - let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)); - assert_eq!(res.unwrap(), transaction::ImportResult::Future); - assert_eq!(txq.status().future, 1); + pub fn signed_triple(mut self) -> (SignedTransaction, SignedTransaction, SignedTransaction) { + let keypair = Random.generate().unwrap(); + let tx1 = self.clone().unsigned().sign(keypair.secret(), None); + self.nonce += 1; + let tx2 = self.clone().unsigned().sign(keypair.secret(), None); + self.nonce += 1; + let tx3 = self.unsigned().sign(keypair.secret(), None); - // now import second transaction to current - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); - // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.status().future, 0); - assert_eq!(txq.current.by_priority.len(), 2); - assert_eq!(txq.current.by_address.len(), 2); - let top = txq.top_transactions(); - assert_eq!(top[0], tx); - assert_eq!(top[1], tx2); -} + (tx1, tx2, tx3) + } -#[test] -fn should_import_tx() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); + pub fn signed_replacement(mut self) -> (SignedTransaction, SignedTransaction) { + let keypair = Random.generate().unwrap(); + let tx1 = self.clone().unsigned().sign(keypair.secret(), None); + self.gas_price += 1; + let tx2 = self.unsigned().sign(keypair.secret(), None); - // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); + (tx1, tx2) + } - // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 1); + pub fn unsigned(self) -> Transaction { + Transaction { + action: transaction::Action::Create, + value: U256::from(100), + data: "3331600055".from_hex().unwrap(), + gas: self.gas.into(), + gas_price: self.gas_price.into(), + nonce: self.nonce.into() + } + } +} + +fn new_queue() -> TransactionQueue { + TransactionQueue::new( + txpool::Options { + max_count: 3, + max_per_sender: 3, + max_mem_usage: 50 + }, + verifier::Options { + minimal_gas_price: 1.into(), + block_gas_limit: 1_000_000.into(), + tx_gas_limit: 1_000_000.into(), + }, + ) } #[test] -fn should_order_by_gas() { +fn should_return_correct_nonces_when_dropped_because_of_limit() { // given - let mut txq = TransactionQueue::new(PrioritizationStrategy::GasAndGasPrice); - let tx1 = new_tx_with_gas(50000.into(), 40.into()); - let tx2 = new_tx_with_gas(40000.into(), 30.into()); - let tx3 = new_tx_with_gas(30000.into(), 10.into()); - let tx4 = new_tx_with_gas(50000.into(), 20.into()); - txq.set_minimal_gas_price(15.into()); + let txq = TransactionQueue::new( + txpool::Options { + max_count: 3, + max_per_sender: 1, + max_mem_usage: 50 + }, + verifier::Options { + minimal_gas_price: 1.into(), + block_gas_limit: 1_000_000.into(), + tx_gas_limit: 1_000_000.into(), + }, + ); + let (tx1, tx2) = Tx::gas_price(2).signed_pair(); + let sender = tx1.sender(); + let nonce = tx1.nonce; // when - let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()); + let result = txq.import(TestClient::new(), vec![tx1, tx2].local()); + assert_eq!(result, vec![Ok(()), Err(transaction::Error::LimitReached)]); + assert_eq!(txq.status().status.transaction_count, 1); // then - assert_eq!(res1.unwrap(), transaction::ImportResult::Current); - assert_eq!(res2.unwrap(), transaction::ImportResult::Current); - assert_eq!(unwrap_tx_err(res3), transaction::Error::InsufficientGasPrice { - minimal: U256::from(15), - got: U256::from(10), - }); - assert_eq!(res4.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 3); - assert_eq!(txq.top_transactions()[0].gas, 40000.into()); - assert_eq!(txq.top_transactions()[1].gas, 50000.into()); - assert_eq!(txq.top_transactions()[2].gas, 50000.into()); - assert_eq!(txq.top_transactions()[1].gas_price, 40.into()); - assert_eq!(txq.top_transactions()[2].gas_price, 20.into()); -} - -#[test] -fn should_order_by_gas_factor() { - // given - let mut txq = TransactionQueue::new(PrioritizationStrategy::GasFactorAndGasPrice); - - let tx1 = new_tx_with_gas(150_000.into(), 40.into()); - let tx2 = new_tx_with_gas(40_000.into(), 16.into()); - let tx3 = new_tx_with_gas(30_000.into(), 15.into()); - let tx4 = new_tx_with_gas(150_000.into(), 62.into()); - txq.set_minimal_gas_price(15.into()); + assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(nonce + 1.into())); // when - let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()); + let tx1 = Tx::gas_price(2).signed(); + let tx2 = Tx::gas_price(2).signed(); + let tx3 = Tx::gas_price(1).signed(); + let tx4 = Tx::gas_price(3).signed(); + let res = txq.import(TestClient::new(), vec![tx1, tx2, tx3, tx4].local()); // then - assert_eq!(res1.unwrap(), transaction::ImportResult::Current); - assert_eq!(res2.unwrap(), transaction::ImportResult::Current); - assert_eq!(res3.unwrap(), transaction::ImportResult::Current); - assert_eq!(res4.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 4); - assert_eq!(txq.top_transactions()[0].gas, 30_000.into()); - assert_eq!(txq.top_transactions()[1].gas, 150_000.into()); - assert_eq!(txq.top_transactions()[2].gas, 40_000.into()); - assert_eq!(txq.top_transactions()[3].gas, 150_000.into()); - assert_eq!(txq.top_transactions()[0].gas_price, 15.into()); - assert_eq!(txq.top_transactions()[1].gas_price, 62.into()); - assert_eq!(txq.top_transactions()[2].gas_price, 16.into()); - assert_eq!(txq.top_transactions()[3].gas_price, 40.into()); + assert_eq!(res, vec![Ok(()), Ok(()), Err(transaction::Error::LimitReached), Ok(())]); + assert_eq!(txq.status().status.transaction_count, 3); + // First inserted transacton got dropped because of limit + assert_eq!(txq.next_nonce(TestClient::new(), &sender), None); } #[test] -fn tx_gas_limit_should_never_overflow() { +fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { // given - let mut txq = TransactionQueue::default(); - txq.set_gas_limit(U256::zero()); - assert_eq!(txq.block_gas_limit, U256::zero()); + let txq = new_queue(); + let (tx, tx2) = Tx::default().signed_replacement(); + let hash = tx2.hash(); + let client = TestClient::new().with_nonce(122); - // when - txq.set_gas_limit(!U256::zero()); + // First insert one transaction to future + let res = txq.import(client.clone(), vec![tx].local()); + assert_eq!(res, vec![Ok(())]); + // next_nonce === None -> transaction is in future + assert_eq!(txq.next_nonce(client.clone(), &tx2.sender()), None); - // then - assert_eq!(txq.block_gas_limit, !U256::zero()); + // now import second transaction to current + let res = txq.import(TestClient::new(), vec![tx2.local()]); + + // and then there should be only one transaction in current (the one with higher gas_price) + assert_eq!(res, vec![Ok(())]); + assert_eq!(txq.status().status.transaction_count, 1); + let top = txq.pending(TestClient::new(), 0, 0); + assert_eq!(top[0].hash, hash); } #[test] -fn should_not_import_transaction_above_gas_limit() { +fn should_move_all_transactions_from_future() { // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let gas = tx.gas; - let limit = gas / U256::from(2); - txq.set_gas_limit(limit); + let txq = new_queue(); + let txs = Tx::default().signed_pair(); + let (hash, hash2) = txs.hash(); + let (tx, tx2) = txs; + let client = TestClient::new().with_nonce(122); - // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); + // First insert one transaction to future + let res = txq.import(client.clone(), vec![tx.local()]); + assert_eq!(res, vec![Ok(())]); + // next_nonce === None -> transaction is in future + assert_eq!(txq.next_nonce(client.clone(), &tx2.sender()), None); + + // now import second transaction to current + let res = txq.import(client.clone(), vec![tx2.local()]); // then - assert_eq!(unwrap_tx_err(res), transaction::Error::GasLimitExceeded { - limit: U256::from(50_000), - got: gas, - }); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); + assert_eq!(res, vec![Ok(())]); + assert_eq!(txq.status().status.transaction_count, 2); + let top = txq.pending(TestClient::new(), 0, 0); + assert_eq!(top[0].hash, hash); + assert_eq!(top[1].hash, hash2); } - #[test] fn should_drop_transactions_from_senders_without_balance() { // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let account = AccountDetails { - nonce: default_account_details().nonce, - balance: U256::one() - }; + let txq = new_queue(); + let tx = Tx::default().signed(); + let client = TestClient::new().with_account(AccountDetails { + nonce: 123.into(), + balance: 1.into(), + is_local: false, + }); // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account(account)); + let res = txq.import(client, vec![tx.local()]); // then - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientBalance { + assert_eq!(res, vec![Err(transaction::Error::InsufficientBalance { balance: U256::from(1), - cost: U256::from(100_100), - }); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); + cost: U256::from(21_100), + })]); + assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_not_import_transaction_below_min_gas_price_threshold_if_external() { // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - txq.set_minimal_gas_price(tx.gas_price + U256::one()); + let txq = new_queue(); + let tx = Tx::default(); + txq.set_verifier_options(verifier::Options { + minimal_gas_price: 3.into(), + ..Default::default() + }); // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); + let res = txq.import(TestClient::new(), vec![tx.signed().unverified()]); // then - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { - minimal: U256::from(2), + assert_eq!(res, vec![Err(transaction::Error::InsufficientGasPrice { + minimal: U256::from(3), got: U256::from(1), - }); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); + })]); + assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_import_transaction_below_min_gas_price_threshold_if_local() { // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - txq.set_minimal_gas_price(tx.gas_price + U256::one()); + let txq = new_queue(); + let tx = Tx::default(); + txq.set_verifier_options(verifier::Options { + minimal_gas_price: 3.into(), + ..Default::default() + }); // when - let res = txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()); + let res = txq.import(TestClient::new(), vec![tx.signed().local()]); // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 0); + assert_eq!(res, vec![Ok(())]); + assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_import_txs_from_same_sender() { // given - let mut txq = TransactionQueue::default(); + let txq = new_queue(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let txs = Tx::default().signed_pair(); + let (hash, hash2) = txs.hash(); // when - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + txq.import(TestClient::new(), txs.local().into_vec()); // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); - assert_eq!(top[1], tx2); + let top = txq.pending(TestClient::new(), 0 ,0); + assert_eq!(top[0].hash, hash); + assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } #[test] fn should_prioritize_local_transactions_within_same_nonce_height() { // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); + let txq = new_queue(); + let tx = Tx::default().signed(); // the second one has same nonce but higher `gas_price` - let (_, tx2) = new_similar_tx_pair(); + let tx2 = Tx::gas_price(2).signed(); + let (hash, hash2) = (tx.hash(), tx2.hash()); + let client = TestClient::new().with_local(&tx.sender()); // when // first insert the one with higher gas price - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // then the one with lower gas price, but local - txq.add(tx.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + let res = txq.import(client.clone(), vec![tx.local(), tx2.unverified()]); + assert_eq!(res, vec![Ok(()), Ok(())]); // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); // local should be first - assert_eq!(top[1], tx2); + let top = txq.pending(client, 0, 0); + assert_eq!(top[0].hash, hash); // local should be first + assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } -#[test] -fn when_importing_local_should_mark_others_from_the_same_sender_as_local() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - // the second one has same nonce but higher `gas_price` - let (_, tx0) = new_similar_tx_pair(); - - txq.add(tx0.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // the one with higher gas price is first - let top = txq.top_transactions(); - assert_eq!(top[0], tx0); - assert_eq!(top[1], tx1); - - // when - // insert second as local - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - // then - // the order should be updated - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], tx2); - assert_eq!(top[2], tx0); -} - #[test] fn should_prioritize_reimported_transactions_within_same_nonce_height() { // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); + let txq = new_queue(); + let tx = Tx::default().signed(); // the second one has same nonce but higher `gas_price` - let (_, tx2) = new_similar_tx_pair(); + let tx2 = Tx::gas_price(2).signed(); + let (hash, hash2) = (tx.hash(), tx2.hash()); // when // first insert local one with higher gas price - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); // then the one with lower gas price, but from retracted block - txq.add(tx.clone(), TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider()).unwrap(); + let res = txq.import(TestClient::new(), vec![tx2.unverified(), tx.retracted()]); + assert_eq!(res, vec![Ok(()), Ok(())]); // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); // retracted should be first - assert_eq!(top[1], tx2); + let top = txq.pending(TestClient::new(), 0, 0); + assert_eq!(top[0].hash, hash); // retracted should be first + assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } #[test] fn should_not_prioritize_local_transactions_with_different_nonce_height() { // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let txq = new_queue(); + let txs = Tx::default().signed_pair(); + let (hash, hash2) = txs.hash(); + let (tx, tx2) = txs; // when - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + let res = txq.import(TestClient::new(), vec![tx.unverified(), tx2.local()]); + assert_eq!(res, vec![Ok(()), Ok(())]); // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); - assert_eq!(top[1], tx2); - assert_eq!(top.len(), 2); -} - -#[test] -fn should_penalize_transactions_from_sender_in_future() { - // given - let prev_nonce = default_account_details().nonce - U256::one(); - let mut txq = TransactionQueue::default(); - // txa, txb - slightly bigger gas price to have consistent ordering - let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); - let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); - - // insert everything - txq.add(txa.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(txb.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - - assert_eq!(txq.status().future, 4); - - // when - txq.penalize(&tx1.hash()); - - // then - let top: Vec<_> = txq.future_transactions().into_iter().map(|tx| tx.transaction).collect(); - assert_eq!(top[0], txa); - assert_eq!(top[1], txb); - assert_eq!(top[2], tx1); - assert_eq!(top[3], tx2); - assert_eq!(top.len(), 4); -} - -#[test] -fn should_not_penalize_local_transactions() { - // given - let mut txq = TransactionQueue::default(); - // txa, txb - slightly bigger gas price to have consistent ordering - let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); - let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); - - // insert everything - txq.add(txa.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(txb.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], txa); - assert_eq!(top[2], tx2); - assert_eq!(top[3], txb); - assert_eq!(top.len(), 4); - - // when - txq.penalize(&tx1.hash()); - - // then (order is the same) - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], txa); - assert_eq!(top[2], tx2); - assert_eq!(top[3], txb); - assert_eq!(top.len(), 4); -} - -#[test] -fn should_penalize_transactions_from_sender() { - // given - let mut txq = TransactionQueue::default(); - // txa, txb - slightly bigger gas price to have consistent ordering - let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); - let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); - - // insert everything - txq.add(txa.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(txb.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], txa); - assert_eq!(top[2], tx2); - assert_eq!(top[3], txb); - assert_eq!(top.len(), 4); - - // when - txq.penalize(&tx1.hash()); - - // then - let top = txq.top_transactions(); - assert_eq!(top[0], txa); - assert_eq!(top[1], txb); - assert_eq!(top[2], tx1); - assert_eq!(top[3], tx2); - assert_eq!(top.len(), 4); -} - -#[test] -fn should_return_pending_hashes() { - // given - let mut txq = TransactionQueue::default(); - - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - - // when - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let top = txq.pending_hashes(); - assert_eq!(top[0], tx.hash()); - assert_eq!(top[1], tx2.hash()); + let top = txq.pending(TestClient::new(), 0, 0); + assert_eq!(top[0].hash, hash); + assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } #[test] fn should_put_transaction_to_futures_if_gap_detected() { // given - let mut txq = TransactionQueue::default(); - - let (tx, tx2) = new_tx_pair_default(2.into(), 0.into()); + let txq = new_queue(); + let (tx, _, tx2) = Tx::default().signed_triple(); + let hash = tx.hash(); // when - let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + let res = txq.import(TestClient::new(), vec![tx, tx2].local()); // then - assert_eq!(res1, transaction::ImportResult::Current); - assert_eq!(res2, transaction::ImportResult::Future); - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 1); - let top = txq.top_transactions(); + assert_eq!(res, vec![Ok(()), Ok(())]); + let top = txq.pending(TestClient::new(), 0, 0); assert_eq!(top.len(), 1); - assert_eq!(top[0], tx); + assert_eq!(top[0].hash, hash); } #[test] fn should_handle_min_block() { // given - let mut txq = TransactionQueue::default(); + let txq = new_queue(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let (tx, tx2) = Tx::default().signed_pair(); // when - let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(transaction::Condition::Number(1)), &default_tx_provider()).unwrap(); - let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + let res = txq.import(TestClient::new(), vec![ + verifier::Transaction::Local(PendingTransaction::new(tx, transaction::Condition::Number(1).into())), + tx2.local() + ]); + assert_eq!(res, vec![Ok(()), Ok(())]); // then - assert_eq!(res1, transaction::ImportResult::Current); - assert_eq!(res2, transaction::ImportResult::Current); - let top = txq.top_transactions_at(0, 0, None); + let top = txq.pending(TestClient::new(), 0, 0); assert_eq!(top.len(), 0); - let top = txq.top_transactions_at(1, 0, None); + let top = txq.pending(TestClient::new(), 1, 0); assert_eq!(top.len(), 2); } #[test] fn should_correctly_update_futures_when_removing() { // given - let prev_nonce = default_account_details().nonce - U256::one(); - let next2_nonce = default_nonce() + U256::from(3); - - let mut txq = TransactionQueue::default(); + let txq = new_queue(); + let txs= Tx::default().signed_pair(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - assert_eq!(txq.status().future, 2); + let res = txq.import(TestClient::new().with_nonce(121), txs.local().into_vec()); + assert_eq!(res, vec![Ok(()), Ok(())]); + assert_eq!(txq.status().status.transaction_count, 2); // when - txq.cull(tx.sender(), next2_nonce); - // should remove both transactions since they are not valid + txq.cull(TestClient::new().with_nonce(125)); + // should remove both transactions since they are stalled // then - assert_eq!(txq.status().pending, 0); - assert_eq!(txq.status().future, 0); + assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_move_transactions_if_gap_filled() { // given - let mut txq = TransactionQueue::default(); - let kp = Random.generate().unwrap(); - let secret = kp.secret(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(secret, None).into(); - let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret, None).into(); - let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret, None).into(); - - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 1); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); + let txq = new_queue(); + let (tx, tx1, tx2) = Tx::default().signed_triple(); + + let res = txq.import(TestClient::new(), vec![tx, tx2].local()); + assert_eq!(res, vec![Ok(()), Ok(())]); + assert_eq!(txq.status().status.transaction_count, 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); // when - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + let res = txq.import(TestClient::new(), vec![tx1.local()]); + assert_eq!(res, vec![Ok(())]); // then - let stats = txq.status(); - assert_eq!(stats.pending, 3); - assert_eq!(stats.future, 0); - assert_eq!(txq.future.by_priority.len(), 0); - assert_eq!(txq.future.by_address.len(), 0); - assert_eq!(txq.future.by_gas_price.len(), 0); + assert_eq!(txq.status().status.transaction_count, 3); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); } #[test] fn should_remove_transaction() { // given - let mut txq2 = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(3.into(), 0.into()); - txq2.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq2.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq2.status().pending, 1); - assert_eq!(txq2.status().future, 1); + let txq = new_queue(); + let (tx, _, tx2) = Tx::default().signed_triple(); + + let res = txq.import(TestClient::default(), vec![tx, tx2].local()); + assert_eq!(res, vec![Ok(()), Ok(())]); + assert_eq!(txq.status().status.transaction_count, 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); // when - txq2.cull(tx.sender(), tx.nonce + U256::one()); - txq2.cull(tx2.sender(), tx2.nonce + U256::one()); + txq.cull(TestClient::new().with_nonce(124)); + assert_eq!(txq.status().status.transaction_count, 1); + assert_eq!(txq.pending(TestClient::new().with_nonce(125), 0, 0).len(), 1); + txq.cull(TestClient::new().with_nonce(126)); // then - let stats = txq2.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); + assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_move_transactions_to_future_if_gap_introduced() { // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - let tx3 = new_tx_default(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 3); + let txq = new_queue(); + let (tx, tx2) = Tx::default().signed_pair(); + let hash = tx.hash(); + let tx3 = Tx::default().signed(); + + let res = txq.import(TestClient::new(), vec![tx3, tx2].local()); + assert_eq!(res, vec![Ok(()), Ok(())]); + assert_eq!(txq.status().status.transaction_count, 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); + + let res = txq.import(TestClient::new(), vec![tx].local()); + assert_eq!(res, vec![Ok(())]); + assert_eq!(txq.status().status.transaction_count, 3); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); // when - txq.remove(&tx.hash(), &|_| default_nonce(), RemovalReason::Invalid); + txq.remove(vec![&hash], true); // then - let stats = txq.status(); - assert_eq!(stats.future, 1); - assert_eq!(stats.pending, 1); + assert_eq!(txq.status().status.transaction_count, 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); } #[test] fn should_clear_queue() { // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let txq = new_queue(); + let txs = Tx::default().signed_pair(); // add - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - let stats = txq.status(); - assert_eq!(stats.pending, 2); + txq.import(TestClient::new(), txs.local().into_vec()); + assert_eq!(txq.status().status.transaction_count, 2); // when txq.clear(); // then - let stats = txq.status(); - assert_eq!(stats.pending, 0); + assert_eq!(txq.status().status.transaction_count, 0); } #[test] -fn should_drop_old_transactions_when_hitting_the_limit() { +fn should_prefer_current_transactions_when_hitting_the_limit() { // given - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 1, - usize::max_value(), - !U256::zero(), - !U256::zero() + let txq = TransactionQueue::new( + txpool::Options { + max_count: 1, + max_per_sender: 2, + max_mem_usage: 50 + }, + verifier::Options { + minimal_gas_price: 1.into(), + block_gas_limit: 1_000_000.into(), + tx_gas_limit: 1_000_000.into(), + }, ); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); + let (tx, tx2) = Tx::default().signed_pair(); + let hash = tx.hash(); let sender = tx.sender(); - let nonce = tx.nonce; - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 1); - // when - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - let t = txq.top_transactions(); - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { minimal: 2.into(), got: 1.into() }); - assert_eq!(txq.status().pending, 1); - assert_eq!(t.len(), 1); - assert_eq!(t[0], tx); - assert_eq!(txq.last_nonce(&sender), Some(nonce)); -} - -#[test] -fn should_limit_future_transactions() { - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 1, - usize::max_value(), - !U256::zero(), - !U256::zero(), - ); - txq.current.set_limit(10); - let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); - let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 2); + let res = txq.import(TestClient::new(), vec![tx2.unverified()]); + assert_eq!(res, vec![Ok(())]); + assert_eq!(txq.status().status.transaction_count, 1); // when - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + let res = txq.import(TestClient::new(), vec![tx.unverified()]); // then - assert_eq!(txq.status().future, 1); -} - -#[test] -fn should_limit_by_gas() { - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 100, - usize::max_value(), - default_gas_val() * U256::from(2), - !U256::zero() - ); - let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); - let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // limited by gas - txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap_err(); - assert_eq!(txq.status().pending, 2); -} + assert_eq!(res, vec![Ok(())]); + assert_eq!(txq.status().status.transaction_count, 1); -#[test] -fn should_keep_own_transactions_above_gas_limit() { - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 100, - usize::max_value(), - default_gas_val() * U256::from(2), - !U256::zero() - ); - let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); - let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - let (tx5, _) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - // Not accepted because of limit - txq.add(tx5.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap_err(); - txq.add(tx3.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx4.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 4); + let top = txq.pending(TestClient::new(), 0, 0); + assert_eq!(top.len(), 1); + assert_eq!(top[0].hash, hash); + assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(124.into())); } #[test] fn should_drop_transactions_with_old_nonces() { - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let last_nonce = tx.nonce + U256::one(); + let txq = new_queue(); + let tx = Tx::default().signed(); // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(last_nonce)); + let res = txq.import(TestClient::new().with_nonce(125), vec![tx.unverified()]); // then - assert_eq!(unwrap_tx_err(res), transaction::Error::Old); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); + assert_eq!(res, vec![Err(transaction::Error::Old)]); + assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_not_insert_same_transaction_twice() { // given - let nonce = default_account_details().nonce + U256::one(); - let mut txq = TransactionQueue::default(); - let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - assert_eq!(txq.status().pending, 0); + let txq = new_queue(); + let (_tx1, tx2) = Tx::default().signed_pair(); + let res = txq.import(TestClient::new(), vec![tx2.clone().local()]); + assert_eq!(res, vec![Ok(())]); + assert_eq!(txq.status().status.transaction_count, 1); // when - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)); + let res = txq.import(TestClient::new(), vec![tx2.local()]); // then - assert_eq!(unwrap_tx_err(res), transaction::Error::AlreadyImported); - let stats = txq.status(); - assert_eq!(stats.future, 1); - assert_eq!(stats.pending, 0); + assert_eq!(res, vec![Err(transaction::Error::AlreadyImported)]); + assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_accept_same_transaction_twice_if_removed() { // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 2); + let txq = new_queue(); + let txs = Tx::default().signed_pair(); + let (tx1, _) = txs.clone(); + let (hash, _) = txs.hash(); - // when - txq.remove(&tx1.hash(), &|_| default_nonce(), RemovalReason::Invalid); - assert_eq!(txq.status().pending, 0); - assert_eq!(txq.status().future, 1); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 2); -} - -#[test] -fn should_not_move_to_future_if_state_nonce_is_higher() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - let tx3 = new_tx_default(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 3); - - // when - txq.cull(tx.sender(), default_nonce() + U256::one()); - - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 2); -} - -#[test] -fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { - // given - let mut txq = TransactionQueue::default(); - let keypair = Random.generate().unwrap(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 20.into()).sign(keypair.secret(), None); - let tx2 = { - let mut tx2 = (**tx).clone(); - tx2.gas_price = U256::from(21); - tx2.sign(keypair.secret(), None) - }; - - // when - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - let res = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(unwrap_tx_err(res), transaction::Error::TooCheapToReplace); - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 0); - assert_eq!(txq.top_transactions()[0].gas_price, U256::from(20)); -} - -#[test] -fn should_replace_same_transaction_when_has_higher_fee() { - // given - let mut txq = TransactionQueue::default(); - let keypair = Random.generate().unwrap(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 10.into()).sign(keypair.secret(), None); - let tx2 = { - let mut tx2 = (**tx).clone(); - tx2.gas_price = U256::from(20); - tx2.sign(keypair.secret(), None) - }; - - // when - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 0); - assert_eq!(txq.top_transactions()[0].gas_price, U256::from(20)); -} - -#[test] -fn should_replace_same_transaction_when_importing_to_futures() { - // given - let mut txq = TransactionQueue::default(); - let keypair = Random.generate().unwrap(); - let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret(), None); - let tx1 = { - let mut tx1 = (**tx0).clone(); - tx1.nonce = U256::from(124); - tx1.sign(keypair.secret(), None) - }; - let tx2 = { - let mut tx2 = (**tx1).clone(); - tx2.gas_price = U256::from(200); - tx2.sign(keypair.secret(), None) - }; - - // when - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx0, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 2); - assert_eq!(txq.top_transactions()[1].gas_price, U256::from(200)); -} - -#[test] -fn should_recalculate_height_when_removing_from_future() { - // given - let previous_nonce = default_account_details().nonce - U256::one(); - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); - assert_eq!(txq.status().future, 2); + let res = txq.import(TestClient::new(), txs.local().into_vec()); + assert_eq!(res, vec![Ok(()), Ok(())]); + assert_eq!(txq.status().status.transaction_count, 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); // when - txq.remove(&tx1.hash(), &|_| default_nonce() + 1.into(), RemovalReason::Invalid); + txq.remove(vec![&hash], true); + assert_eq!(txq.status().status.transaction_count, 1); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 0); - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 1); -} - -#[test] -fn should_return_none_when_transaction_from_given_address_does_not_exist() { - // given - let txq = TransactionQueue::default(); + let res = txq.import(TestClient::new(), vec![tx1].local()); + assert_eq!(res, vec![Ok(())]); // then - assert_eq!(txq.last_nonce(&Address::default()), None); -} - -#[test] -fn should_return_correct_nonce_when_transactions_from_given_address_exist() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let from = tx.sender(); - let nonce = tx.nonce; - - // when - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)).unwrap(); - - // then - assert_eq!(txq.last_nonce(&from), Some(nonce)); -} - -#[test] -fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); - - // Insert first transaction - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(); - - // when - txq.cull(tx2.sender(), nonce2 + U256::one()); - - // then - assert!(txq.top_transactions().is_empty()); -} - -#[test] -fn should_return_valid_last_nonce_after_cull() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into()); - let sender = tx1.sender(); - let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); - - // when - // Insert first transaction - assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Current); - // Second should go to future - assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Future); - // Now block is imported - txq.cull(sender, nonce2 - U256::from(1)); - // tx2 should be not be promoted to current - assert_eq!(txq.status().pending, 0); - assert_eq!(txq.status().future, 1); - - // then - assert_eq!(txq.last_nonce(&sender), None); -} - -#[test] -fn should_return_true_if_there_is_local_transaction_pending() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - assert_eq!(txq.has_local_pending_transactions(), false); - - // when - assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.has_local_pending_transactions(), false); - assert_eq!(txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(), - transaction::ImportResult::Current); - - // then - assert_eq!(txq.has_local_pending_transactions(), true); -} - -#[test] -fn should_keep_right_order_in_future() { - // given - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 1, - usize::max_value(), - !U256::zero(), - !U256::zero() - ); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let prev_nonce = default_account_details().nonce - U256::one(); - - // when - assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); - assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); - - // then - assert_eq!(txq.future.by_priority.len(), 1); - assert_eq!(txq.future.by_priority.iter().next().unwrap().hash, tx1.hash()); -} - -#[test] -fn should_return_correct_last_nonce() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2, tx2_2, tx3) = { - let keypair = Random.generate().unwrap(); - let secret = &keypair.secret(); - let nonce = 123.into(); - let gas = default_gas_val(); - let tx = new_unsigned_tx(nonce, gas, 1.into()); - let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 1.into()); - let tx2_2 = new_unsigned_tx(nonce + 1.into(), gas, 5.into()); - let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into()); - - - (tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None)) - }; - let sender = tx1.sender(); - txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.future.by_priority.len(), 0); - assert_eq!(txq.current.by_priority.len(), 3); - - // when - let res = txq.add(tx2_2, TransactionOrigin::Local, 0, None, &default_tx_provider()); - - // then - assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.current.by_priority.len(), 3); -} - -#[test] -fn should_reject_transactions_below_base_gas() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let high_gas = 100_001.into(); - - // when - let res1 = txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()); - let res2 = txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider().with_tx_gas_required(high_gas)); - - // then - assert_eq!(res1.unwrap(), transaction::ImportResult::Current); - assert_eq!(unwrap_tx_err(res2), transaction::Error::InsufficientGas { - minimal: 100_001.into(), - got: 100_000.into(), - }); - -} - -#[test] -fn should_clear_all_old_transactions() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); - let next_nonce = |_: &Address| - AccountDetails { nonce: default_nonce() + U256::one(), balance: !U256::zero() }; - - // Insert all transactions - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.top_transactions().len(), 4); - - // when - txq.remove_old(&next_nonce, 0); - - // then - assert_eq!(txq.top_transactions().len(), 2); -} - -#[test] -fn should_remove_out_of_date_transactions_occupying_queue() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); - - // Insert all transactions - txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 5, None, &default_tx_provider()).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_tx_provider()).unwrap(); - txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.top_transactions().len(), 3); - assert_eq!(txq.future_transactions().len(), 1); - - // when - txq.remove_old(&default_account_details_for_addr, 9 + super::DEFAULT_QUEUING_PERIOD); - - // then - assert_eq!(txq.top_transactions().len(), 2); - assert_eq!(txq.future_transactions().len(), 0); - assert_eq!(txq.top_transactions(), vec![tx1, tx3]); -} - -#[test] -fn should_accept_local_service_transaction() { - // given - let tx = new_tx(123.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - // then - assert_eq!(txq.top_transactions().len(), 1); -} - -#[test] -fn should_not_accept_external_service_transaction_if_sender_not_certified() { - // given - let tx1 = new_tx(123.into(), 0.into()); - let tx2 = new_tx(456.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - assert_eq!(unwrap_tx_err(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider())), - transaction::Error::InsufficientGasPrice { - minimal: 100.into(), - got: 0.into(), - }); - assert_eq!(unwrap_tx_err(txq.add(tx2, TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider())), - transaction::Error::InsufficientGasPrice { - minimal: 100.into(), - got: 0.into(), - }); - - // then - assert_eq!(txq.top_transactions().len(), 0); -} - -#[test] -fn should_not_accept_external_service_transaction_if_contract_returns_error() { - // given - let tx = new_tx(123.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - let details_provider = default_tx_provider().service_transaction_checker_returns_error("Contract error"); - assert_eq!(unwrap_tx_err(txq.add(tx, TransactionOrigin::External, 0, None, &details_provider)), - transaction::Error::InsufficientGasPrice { - minimal: 100.into(), - got: 0.into(), - }); - - // then - assert_eq!(txq.top_transactions().len(), 0); -} - -#[test] -fn should_accept_external_service_transaction_if_sender_is_certified() { - // given - let tx = new_tx(123.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - let details_provider = default_tx_provider().service_transaction_checker_accepts(true); - txq.add(tx, TransactionOrigin::External, 0, None, &details_provider).unwrap(); - - // then - assert_eq!(txq.top_transactions().len(), 1); -} - -#[test] -fn should_not_order_transactions_by_hash() { - // given - let secret1 = "0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap(); - let secret2 = "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap(); - let tx1 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret1, None); - let tx2 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret2, None); - let mut txq = TransactionQueue::default(); - - // when - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - assert_eq!(txq.top_transactions()[0], tx1); - assert_eq!(txq.top_transactions().len(), 2); -} - -#[test] -fn should_not_return_transactions_over_nonce_cap() { - // given - let keypair = Random.generate().unwrap(); - let mut txq = TransactionQueue::default(); - // when - for nonce in 123..130 { - let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None); - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - } - - // then - assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4); -} + assert_eq!(txq.status().status.transaction_count, 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); +} + +// #[test] +// fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { +// // given +// let txq = new_queue(); +// let keypair = Random.generate().unwrap(); +// let tx = new_unsigned_tx(123.into(), default_gas_val(), 20.into()).sign(keypair.secret(), None); +// let tx2 = { +// let mut tx2 = (**tx).clone(); +// tx2.gas_price = U256::from(21); +// tx2.sign(keypair.secret(), None) +// }; +// +// // when +// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// let res = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); +// +// // then +// assert_eq!(unwrap_tx_err(res), transaction::Error::TooCheapToReplace); +// let stats = txq.status(); +// assert_eq!(stats.pending, 1); +// assert_eq!(stats.future, 0); +// assert_eq!(txq.pending(TestClient::new(), 0, 0)[0].gas_price, U256::from(20)); +// } +// +// #[test] +// fn should_replace_same_transaction_when_has_higher_fee() { +// // given +// let txq = new_queue(); +// let keypair = Random.generate().unwrap(); +// let tx = new_unsigned_tx(123.into(), default_gas_val(), 10.into()).sign(keypair.secret(), None); +// let tx2 = { +// let mut tx2 = (**tx).clone(); +// tx2.gas_price = U256::from(20); +// tx2.sign(keypair.secret(), None) +// }; +// +// // when +// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// +// // then +// let stats = txq.status(); +// assert_eq!(stats.pending, 1); +// assert_eq!(stats.future, 0); +// assert_eq!(txq.pending(TestClient::new(), 0, 0)[0].gas_price, U256::from(20)); +// } +// +// #[test] +// fn should_replace_same_transaction_when_importing_to_futures() { +// // given +// let txq = new_queue(); +// let keypair = Random.generate().unwrap(); +// let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret(), None); +// let tx1 = { +// let mut tx1 = (**tx0).clone(); +// tx1.nonce = U256::from(124); +// tx1.sign(keypair.secret(), None) +// }; +// let tx2 = { +// let mut tx2 = (**tx1).clone(); +// tx2.gas_price = U256::from(200); +// tx2.sign(keypair.secret(), None) +// }; +// +// // when +// txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// assert_eq!(txq.status().future, 1); +// txq.add(tx0, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// +// // then +// let stats = txq.status(); +// assert_eq!(stats.future, 0); +// assert_eq!(stats.pending, 2); +// assert_eq!(txq.pending(TestClient::new(), 0, 0)[1].gas_price, U256::from(200)); +// } +// +// #[test] +// fn should_recalculate_height_when_removing_from_future() { +// // given +// let previous_nonce = default_account_details().nonce - U256::one(); +// let txq = new_queue(); +// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); +// txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); +// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); +// assert_eq!(txq.status().future, 2); +// +// // when +// txq.remove(&tx1.hash(), &|_| default_nonce() + 1.into(), RemovalReason::Invalid); +// +// // then +// let stats = txq.status(); +// assert_eq!(stats.future, 0); +// assert_eq!(stats.pending, 1); +// } +// +// #[test] +// fn should_return_none_when_transaction_from_given_address_does_not_exist() { +// // given +// let txq = TransactionQueue::default(); +// +// // then +// assert_eq!(txq.last_nonce(&Address::default()), None); +// } +// +// #[test] +// fn should_return_correct_nonce_when_transactions_from_given_address_exist() { +// // given +// let txq = new_queue(); +// let tx = Tx::default(); +// let from = tx.sender(); +// let nonce = tx.nonce; +// +// // when +// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)).unwrap(); +// +// // then +// assert_eq!(txq.last_nonce(&from), Some(nonce)); +// } +// +// #[test] +// fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() { +// // given +// let txq = new_queue(); +// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); +// let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); +// +// // Insert first transaction +// txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(); +// +// // when +// txq.cull(tx2.sender(), nonce2 + U256::one()); +// +// // then +// assert!(txq.pending(TestClient::new(), 0, 0).is_empty()); +// } +// +// #[test] +// fn should_return_valid_last_nonce_after_cull() { +// // given +// let txq = new_queue(); +// let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into()); +// let sender = tx1.sender(); +// let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); +// +// // when +// // Insert first transaction +// assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Current); +// // Second should go to future +// assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Future); +// // Now block is imported +// txq.cull(sender, nonce2 - U256::from(1)); +// // tx2 should be not be promoted to current +// assert_eq!(txq.status().pending, 0); +// assert_eq!(txq.status().future, 1); +// +// // then +// assert_eq!(txq.last_nonce(&sender), None); +// } +// +// #[test] +// fn should_return_true_if_there_is_local_transaction_pending() { +// // given +// let txq = new_queue(); +// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); +// assert_eq!(txq.has_local_pending_transactions(), false); +// +// // when +// assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(), transaction::ImportResult::Current); +// assert_eq!(txq.has_local_pending_transactions(), false); +// assert_eq!(txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(), +// transaction::ImportResult::Current); +// +// // then +// assert_eq!(txq.has_local_pending_transactions(), true); +// } +// +// #[test] +// fn should_keep_right_order_in_future() { +// // given +// let mut txq = TransactionQueue::with_limits( +// PrioritizationStrategy::GasPriceOnly, +// 1, +// usize::max_value(), +// !U256::zero(), +// !U256::zero() +// ); +// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); +// let prev_nonce = default_account_details().nonce - U256::one(); +// +// // when +// assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); +// assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); +// +// // then +// assert_eq!(txq.future.by_priority.len(), 1); +// assert_eq!(txq.future.by_priority.iter().next().unwrap().hash, tx1.hash()); +// } +// +// #[test] +// fn should_return_correct_last_nonce() { +// // given +// let txq = new_queue(); +// let (tx1, tx2, tx2_2, tx3) = { +// let keypair = Random.generate().unwrap(); +// let secret = &keypair.secret(); +// let nonce = 123.into(); +// let gas = default_gas_val(); +// let tx = new_unsigned_tx(nonce, gas, 1.into()); +// let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 1.into()); +// let tx2_2 = new_unsigned_tx(nonce + 1.into(), gas, 5.into()); +// let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into()); +// +// +// (tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None)) +// }; +// let sender = tx1.sender(); +// txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx3, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); +// assert_eq!(txq.future.by_priority.len(), 0); +// assert_eq!(txq.current.by_priority.len(), 3); +// +// // when +// let res = txq.add(tx2_2, TransactionOrigin::Local, 0, None, &default_tx_provider()); +// +// // then +// assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); +// assert_eq!(res.unwrap(), transaction::ImportResult::Current); +// assert_eq!(txq.current.by_priority.len(), 3); +// } +// +// #[test] +// fn should_reject_transactions_below_base_gas() { +// // given +// let txq = new_queue(); +// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); +// let high_gas = 100_001.into(); +// +// // when +// let res1 = txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()); +// let res2 = txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider().with_tx_gas_required(high_gas)); +// +// // then +// assert_eq!(res1.unwrap(), transaction::ImportResult::Current); +// assert_eq!(unwrap_tx_err(res2), transaction::Error::InsufficientGas { +// minimal: 100_001.into(), +// got: 100_000.into(), +// }); +// +// } +// +// #[test] +// fn should_clear_all_old_transactions() { +// // given +// let txq = new_queue(); +// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); +// let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); +// let next_nonce = |_: &Address| +// AccountDetails { nonce: default_nonce() + U256::one(), balance: !U256::zero() }; +// +// // Insert all transactions +// txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 4); +// +// // when +// txq.remove_old(&next_nonce, 0); +// +// // then +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); +// } +// +// #[test] +// fn should_remove_out_of_date_transactions_occupying_queue() { +// // given +// let txq = new_queue(); +// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); +// let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); +// +// // Insert all transactions +// txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx2, TransactionOrigin::External, 5, None, &default_tx_provider()).unwrap(); +// txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_tx_provider()).unwrap(); +// txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); +// assert_eq!(txq.future_transactions().len(), 1); +// +// // when +// txq.remove_old(&default_account_details_for_addr, 9 + super::DEFAULT_QUEUING_PERIOD); +// +// // then +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); +// assert_eq!(txq.future_transactions().len(), 0); +// assert_eq!(txq.pending(TestClient::new(), 0, 0), vec![tx1, tx3]); +// } +// +// #[test] +// fn should_accept_local_service_transaction() { +// // given +// let tx = new_tx(123.into(), 0.into()); +// let txq = new_queue(); +// txq.set_minimal_gas_price(100.into()); +// +// // when +// txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); +// +// // then +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); +// } +// +// #[test] +// fn should_not_accept_external_service_transaction_if_sender_not_certified() { +// // given +// let tx1 = new_tx(123.into(), 0.into()); +// let tx2 = new_tx(456.into(), 0.into()); +// let txq = new_queue(); +// txq.set_minimal_gas_price(100.into()); +// +// // when +// assert_eq!(unwrap_tx_err(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider())), +// transaction::Error::InsufficientGasPrice { +// minimal: 100.into(), +// got: 0.into(), +// }); +// assert_eq!(unwrap_tx_err(txq.add(tx2, TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider())), +// transaction::Error::InsufficientGasPrice { +// minimal: 100.into(), +// got: 0.into(), +// }); +// +// // then +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 0); +// } +// +// #[test] +// fn should_not_accept_external_service_transaction_if_contract_returns_error() { +// // given +// let tx = new_tx(123.into(), 0.into()); +// let txq = new_queue(); +// txq.set_minimal_gas_price(100.into()); +// +// // when +// let details_provider = default_tx_provider().service_transaction_checker_returns_error("Contract error"); +// assert_eq!(unwrap_tx_err(txq.add(tx, TransactionOrigin::External, 0, None, &details_provider)), +// transaction::Error::InsufficientGasPrice { +// minimal: 100.into(), +// got: 0.into(), +// }); +// +// // then +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 0); +// } +// +// #[test] +// fn should_accept_external_service_transaction_if_sender_is_certified() { +// // given +// let tx = new_tx(123.into(), 0.into()); +// let txq = new_queue(); +// txq.set_minimal_gas_price(100.into()); +// +// // when +// let details_provider = default_tx_provider().service_transaction_checker_accepts(true); +// txq.add(tx, TransactionOrigin::External, 0, None, &details_provider).unwrap(); +// +// // then +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); +// } +// +// #[test] +// fn should_not_order_transactions_by_hash() { +// // given +// let secret1 = "0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap(); +// let secret2 = "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap(); +// let tx1 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret1, None); +// let tx2 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret2, None); +// let txq = new_queue(); +// +// // when +// txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// +// // then +// assert_eq!(txq.pending(TestClient::new(), 0, 0)[0], tx1); +// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); +// } +// +// #[test] +// fn should_not_return_transactions_over_nonce_cap() { +// // given +// let keypair = Random.generate().unwrap(); +// let txq = new_queue(); +// // when +// for nonce in 123..130 { +// let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None); +// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); +// } +// +// // then +// assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4); +// } diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 2e8f91f0944..57e2b29e912 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -255,7 +255,7 @@ impl txpool::Verifier for Verifier { transaction.nonce, account_details.nonce, ); - bail!(transaction::Error::AlreadyImported); + bail!(transaction::Error::Old); } let priority = match (account_details.is_local, is_retracted) { diff --git a/transaction-pool/src/listener.rs b/transaction-pool/src/listener.rs index b6a9788ab5b..3bdbe2f989b 100644 --- a/transaction-pool/src/listener.rs +++ b/transaction-pool/src/listener.rs @@ -63,8 +63,8 @@ impl Listener for (A, B) where } fn dropped(&mut self, tx: &Arc) { - self.0.rejected(tx); - self.1.rejected(tx); + self.0.dropped(tx); + self.1.dropped(tx); } fn invalid(&mut self, tx: &Arc) { diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 6233ccc4fa9..f1613faff39 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -127,10 +127,12 @@ impl Pool where }; while self.by_hash.len() + 1 > self.options.max_count { + trace!("Count limit reached: {} > {}", self.by_hash.len() + 1, self.options.max_count); transaction = remove_worst(self, transaction)?; } while self.mem_usage + mem_usage > self.options.max_mem_usage { + trace!("Mem limit reached: {} > {}", self.mem_usage + mem_usage, self.options.max_mem_usage); transaction = remove_worst(self, transaction)?; } } diff --git a/transaction-pool/src/scoring.rs b/transaction-pool/src/scoring.rs index d056faea651..c2300f4235b 100644 --- a/transaction-pool/src/scoring.rs +++ b/transaction-pool/src/scoring.rs @@ -79,7 +79,7 @@ pub enum Change { /// - `update_scores`: score defined as `gasPrice` if `n==0` and `max(scores[n-1], gasPrice)` if `n>0` /// - `should_replace`: compares `gasPrice` (decides if transaction from a different sender is more valuable) /// -pub trait Scoring { +pub trait Scoring: fmt::Debug { /// A score of a transaction. type Score: cmp::Ord + Clone + Default + fmt::Debug; From d698145879f62e0d8ee17ae1daab70cfeaa36709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 6 Mar 2018 15:09:44 +0100 Subject: [PATCH 31/77] Migrate transaction queue tests. --- miner/src/pool/tests/client.rs | 114 ++ miner/src/pool/tests/mod.rs | 883 +++------- miner/src/pool/tests/tx.rs | 170 ++ miner/src/transaction_queue.rs | 2944 -------------------------------- 4 files changed, 499 insertions(+), 3612 deletions(-) create mode 100644 miner/src/pool/tests/client.rs create mode 100644 miner/src/pool/tests/tx.rs delete mode 100644 miner/src/transaction_queue.rs diff --git a/miner/src/pool/tests/client.rs b/miner/src/pool/tests/client.rs new file mode 100644 index 00000000000..ba74e2e27e1 --- /dev/null +++ b/miner/src/pool/tests/client.rs @@ -0,0 +1,114 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +use ethereum_types::{U256, H256, Address}; +use transaction::{self, Transaction, SignedTransaction, UnverifiedTransaction}; + +use pool; +use pool::client::AccountDetails; + +#[derive(Debug, Clone)] +pub struct TestClient { + account_details: AccountDetails, + gas_required: U256, + is_service_transaction: bool, + local_address: Address, +} + +impl Default for TestClient { + fn default() -> Self { + TestClient { + account_details: AccountDetails { + nonce: 123.into(), + balance: 63_100.into(), + is_local: false, + }, + gas_required: 21_000.into(), + is_service_transaction: false, + local_address: Default::default(), + } + } +} + +impl TestClient { + pub fn new() -> Self { + TestClient::default() + } + + pub fn with_balance>(mut self, balance: T) -> Self { + self.account_details.balance = balance.into(); + self + } + + pub fn with_nonce>(mut self, nonce: T) -> Self { + self.account_details.nonce = nonce.into(); + self + } + + pub fn with_gas_required>(mut self, gas_required: T) -> Self { + self.gas_required = gas_required.into(); + self + } + + pub fn with_local(mut self, address: &Address) -> Self { + self.local_address = *address; + self + } + + pub fn with_service_transaction(mut self) -> Self { + self.is_service_transaction = true; + self + } +} + +impl pool::client::Client for TestClient { + fn transaction_already_included(&self, _hash: &H256) -> bool { + false + } + + fn verify_transaction(&self, tx: UnverifiedTransaction) + -> Result + { + Ok(SignedTransaction::new(tx)?) + } + + fn account_details(&self, address: &Address) -> AccountDetails { + let mut details = self.account_details.clone(); + if address == &self.local_address { + details.is_local = true; + } + + details + } + + fn required_gas(&self, _tx: &Transaction) -> U256 { + self.gas_required + } + + fn transaction_type(&self, _tx: &SignedTransaction) -> pool::client::TransactionType { + if self.is_service_transaction { + pool::client::TransactionType::Service + } else { + pool::client::TransactionType::Regular + } + } +} + +impl pool::client::StateClient for TestClient { + fn account_nonce(&self, _address: &Address) -> U256 { + self.account_details.nonce + } +} diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index e82c696db04..26de38c9d60 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -14,270 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethereum_types::{U256, H256, Address}; -use ethkey::{Random, Generator}; -use rustc_hex::FromHex; -use transaction::{self, Transaction, SignedTransaction, UnverifiedTransaction, PendingTransaction}; +use ethereum_types::U256; +use transaction::{self, PendingTransaction}; use txpool; -use pool::{self, verifier, VerifiedTransaction, TransactionQueue}; -use pool::client::AccountDetails; +use pool::{verifier, TransactionQueue}; -#[derive(Debug, Clone)] -pub struct TestClient { - account_details: AccountDetails, - gas_required: U256, - is_service_transaction: bool, - local_address: Address, -} - -impl Default for TestClient { - fn default() -> Self { - TestClient { - account_details: AccountDetails { - nonce: 123.into(), - balance: 63_100.into(), - is_local: false, - }, - gas_required: 21_000.into(), - is_service_transaction: false, - local_address: Default::default(), - } - } -} - -impl TestClient { - pub fn new() -> Self { - TestClient::default() - } - - pub fn with_account(mut self, account_details: AccountDetails) -> Self { - self.account_details = account_details; - self - } - - pub fn with_nonce>(mut self, nonce: T) -> Self { - self.account_details.nonce = nonce.into(); - self - } - - pub fn with_gas_required>(mut self, gas_required: T) -> Self { - self.gas_required = gas_required.into(); - self - } - - pub fn with_local(mut self, address: &Address) -> Self { - self.local_address = *address; - self - } - - pub fn with_service_transaction(mut self) -> Self { - self.is_service_transaction = true; - self - } -} - -impl pool::client::Client for TestClient { - fn transaction_already_included(&self, _hash: &H256) -> bool { - false - } - - fn verify_transaction(&self, tx: UnverifiedTransaction) - -> Result - { - Ok(SignedTransaction::new(tx)?) - } - - fn account_details(&self, address: &Address) -> AccountDetails { - let mut details = self.account_details.clone(); - if address == &self.local_address { - details.is_local = true; - } - - details - } - - fn required_gas(&self, _tx: &Transaction) -> U256 { - self.gas_required - } - - fn transaction_type(&self, _tx: &SignedTransaction) -> pool::client::TransactionType { - if self.is_service_transaction { - pool::client::TransactionType::Service - } else { - pool::client::TransactionType::Regular - } - } -} - -impl pool::client::StateClient for TestClient { - fn account_nonce(&self, _address: &Address) -> U256 { - self.account_details.nonce - } -} - -trait TxExt: Sized { - type Out; - type Hash; - - fn hash(&self) -> Self::Hash; - - fn local(self) -> Self::Out; - - fn retracted(self) -> Self::Out; - - fn unverified(self) -> Self::Out; -} - -impl TxExt for (A, B) where - A: TxExt, - B: TxExt, -{ - type Out = (O, O); - type Hash = (H, H); - - fn hash(&self) -> Self::Hash { (self.0.hash(), self.1.hash()) } - fn local(self) -> Self::Out { (self.0.local(), self.1.local()) } - fn retracted(self) -> Self::Out { (self.0.retracted(), self.1.retracted()) } - fn unverified(self) -> Self::Out { (self.0.unverified(), self.1.unverified()) } -} - -impl TxExt for SignedTransaction { - type Out = verifier::Transaction; - type Hash = H256; - - fn hash(&self) -> Self::Hash { - UnverifiedTransaction::hash(self) - } - - fn local(self) -> Self::Out { - verifier::Transaction::Local(self.into()) - } - - fn retracted(self) -> Self::Out { - verifier::Transaction::Retracted(self.into()) - } - - fn unverified(self) -> Self::Out { - verifier::Transaction::Unverified(self.into()) - } -} - -impl TxExt for Vec { - type Out = Vec; - type Hash = Vec; - - fn hash(&self) -> Self::Hash { - self.iter().map(|tx| tx.hash()).collect() - } - - fn local(self) -> Self::Out { - self.into_iter().map(Into::into).map(verifier::Transaction::Local).collect() - } - - fn retracted(self) -> Self::Out { - self.into_iter().map(Into::into).map(verifier::Transaction::Retracted).collect() - } - - fn unverified(self) -> Self::Out { - self.into_iter().map(Into::into).map(verifier::Transaction::Unverified).collect() - } -} - -trait PairExt { - type Type; - - fn into_vec(self) -> Vec; -} - -impl PairExt for (A, A) { - type Type = A; - fn into_vec(self) -> Vec { - vec![self.0, self.1] - } -} - -#[derive(Clone)] -struct Tx { - nonce: u64, - gas: u64, - gas_price: u64, -} - -impl Default for Tx { - fn default() -> Self { - Tx { - nonce: 123, - gas: 21_000, - gas_price: 1, - } - } -} - -impl Tx { - pub fn gas(gas: u64) -> Self { - Tx { - gas, - ..Default::default() - } - } - - pub fn gas_price(gas_price: u64) -> Self { - Tx { - gas_price, - ..Default::default() - } - } +mod tx; +mod client; - pub fn nonce(nonce: u64) -> Self { - Tx { - nonce, - ..Default::default() - } - } - - pub fn signed(self) -> SignedTransaction { - let keypair = Random.generate().unwrap(); - self.unsigned().sign(keypair.secret(), None) - } - - pub fn signed_pair(self) -> (SignedTransaction, SignedTransaction) { - let (tx1, tx2, _) = self.signed_triple(); - (tx1, tx2) - } - - pub fn signed_triple(mut self) -> (SignedTransaction, SignedTransaction, SignedTransaction) { - let keypair = Random.generate().unwrap(); - let tx1 = self.clone().unsigned().sign(keypair.secret(), None); - self.nonce += 1; - let tx2 = self.clone().unsigned().sign(keypair.secret(), None); - self.nonce += 1; - let tx3 = self.unsigned().sign(keypair.secret(), None); - - - (tx1, tx2, tx3) - } - - pub fn signed_replacement(mut self) -> (SignedTransaction, SignedTransaction) { - let keypair = Random.generate().unwrap(); - let tx1 = self.clone().unsigned().sign(keypair.secret(), None); - self.gas_price += 1; - let tx2 = self.unsigned().sign(keypair.secret(), None); - - (tx1, tx2) - } - - pub fn unsigned(self) -> Transaction { - Transaction { - action: transaction::Action::Create, - value: U256::from(100), - data: "3331600055".from_hex().unwrap(), - gas: self.gas.into(), - gas_price: self.gas_price.into(), - nonce: self.nonce.into() - } - } -} +use self::tx::{Tx, TxExt, PairExt}; +use self::client::TestClient; fn new_queue() -> TransactionQueue { TransactionQueue::new( @@ -390,11 +137,7 @@ fn should_drop_transactions_from_senders_without_balance() { // given let txq = new_queue(); let tx = Tx::default().signed(); - let client = TestClient::new().with_account(AccountDetails { - nonce: 123.into(), - balance: 1.into(), - is_local: false, - }); + let client = TestClient::new().with_balance(1); // when let res = txq.import(client, vec![tx.local()]); @@ -760,406 +503,210 @@ fn should_accept_same_transaction_twice_if_removed() { assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); } -// #[test] -// fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { -// // given -// let txq = new_queue(); -// let keypair = Random.generate().unwrap(); -// let tx = new_unsigned_tx(123.into(), default_gas_val(), 20.into()).sign(keypair.secret(), None); -// let tx2 = { -// let mut tx2 = (**tx).clone(); -// tx2.gas_price = U256::from(21); -// tx2.sign(keypair.secret(), None) -// }; -// -// // when -// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// let res = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); -// -// // then -// assert_eq!(unwrap_tx_err(res), transaction::Error::TooCheapToReplace); -// let stats = txq.status(); -// assert_eq!(stats.pending, 1); -// assert_eq!(stats.future, 0); -// assert_eq!(txq.pending(TestClient::new(), 0, 0)[0].gas_price, U256::from(20)); -// } -// -// #[test] -// fn should_replace_same_transaction_when_has_higher_fee() { -// // given -// let txq = new_queue(); -// let keypair = Random.generate().unwrap(); -// let tx = new_unsigned_tx(123.into(), default_gas_val(), 10.into()).sign(keypair.secret(), None); -// let tx2 = { -// let mut tx2 = (**tx).clone(); -// tx2.gas_price = U256::from(20); -// tx2.sign(keypair.secret(), None) -// }; -// -// // when -// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// -// // then -// let stats = txq.status(); -// assert_eq!(stats.pending, 1); -// assert_eq!(stats.future, 0); -// assert_eq!(txq.pending(TestClient::new(), 0, 0)[0].gas_price, U256::from(20)); -// } -// -// #[test] -// fn should_replace_same_transaction_when_importing_to_futures() { -// // given -// let txq = new_queue(); -// let keypair = Random.generate().unwrap(); -// let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret(), None); -// let tx1 = { -// let mut tx1 = (**tx0).clone(); -// tx1.nonce = U256::from(124); -// tx1.sign(keypair.secret(), None) -// }; -// let tx2 = { -// let mut tx2 = (**tx1).clone(); -// tx2.gas_price = U256::from(200); -// tx2.sign(keypair.secret(), None) -// }; -// -// // when -// txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// assert_eq!(txq.status().future, 1); -// txq.add(tx0, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// -// // then -// let stats = txq.status(); -// assert_eq!(stats.future, 0); -// assert_eq!(stats.pending, 2); -// assert_eq!(txq.pending(TestClient::new(), 0, 0)[1].gas_price, U256::from(200)); -// } -// -// #[test] -// fn should_recalculate_height_when_removing_from_future() { -// // given -// let previous_nonce = default_account_details().nonce - U256::one(); -// let txq = new_queue(); -// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); -// txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); -// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); -// assert_eq!(txq.status().future, 2); -// -// // when -// txq.remove(&tx1.hash(), &|_| default_nonce() + 1.into(), RemovalReason::Invalid); -// -// // then -// let stats = txq.status(); -// assert_eq!(stats.future, 0); -// assert_eq!(stats.pending, 1); -// } -// -// #[test] -// fn should_return_none_when_transaction_from_given_address_does_not_exist() { -// // given -// let txq = TransactionQueue::default(); -// -// // then -// assert_eq!(txq.last_nonce(&Address::default()), None); -// } -// -// #[test] -// fn should_return_correct_nonce_when_transactions_from_given_address_exist() { -// // given -// let txq = new_queue(); -// let tx = Tx::default(); -// let from = tx.sender(); -// let nonce = tx.nonce; -// -// // when -// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)).unwrap(); -// -// // then -// assert_eq!(txq.last_nonce(&from), Some(nonce)); -// } -// -// #[test] -// fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() { -// // given -// let txq = new_queue(); -// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); -// let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); -// -// // Insert first transaction -// txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(); -// -// // when -// txq.cull(tx2.sender(), nonce2 + U256::one()); -// -// // then -// assert!(txq.pending(TestClient::new(), 0, 0).is_empty()); -// } -// -// #[test] -// fn should_return_valid_last_nonce_after_cull() { -// // given -// let txq = new_queue(); -// let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into()); -// let sender = tx1.sender(); -// let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); -// -// // when -// // Insert first transaction -// assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Current); -// // Second should go to future -// assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Future); -// // Now block is imported -// txq.cull(sender, nonce2 - U256::from(1)); -// // tx2 should be not be promoted to current -// assert_eq!(txq.status().pending, 0); -// assert_eq!(txq.status().future, 1); -// -// // then -// assert_eq!(txq.last_nonce(&sender), None); -// } -// -// #[test] -// fn should_return_true_if_there_is_local_transaction_pending() { -// // given -// let txq = new_queue(); -// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); -// assert_eq!(txq.has_local_pending_transactions(), false); -// -// // when -// assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(), transaction::ImportResult::Current); -// assert_eq!(txq.has_local_pending_transactions(), false); -// assert_eq!(txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(), -// transaction::ImportResult::Current); -// -// // then -// assert_eq!(txq.has_local_pending_transactions(), true); -// } -// -// #[test] -// fn should_keep_right_order_in_future() { -// // given -// let mut txq = TransactionQueue::with_limits( -// PrioritizationStrategy::GasPriceOnly, -// 1, -// usize::max_value(), -// !U256::zero(), -// !U256::zero() -// ); -// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); -// let prev_nonce = default_account_details().nonce - U256::one(); -// -// // when -// assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); -// assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); -// -// // then -// assert_eq!(txq.future.by_priority.len(), 1); -// assert_eq!(txq.future.by_priority.iter().next().unwrap().hash, tx1.hash()); -// } -// -// #[test] -// fn should_return_correct_last_nonce() { -// // given -// let txq = new_queue(); -// let (tx1, tx2, tx2_2, tx3) = { -// let keypair = Random.generate().unwrap(); -// let secret = &keypair.secret(); -// let nonce = 123.into(); -// let gas = default_gas_val(); -// let tx = new_unsigned_tx(nonce, gas, 1.into()); -// let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 1.into()); -// let tx2_2 = new_unsigned_tx(nonce + 1.into(), gas, 5.into()); -// let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into()); -// -// -// (tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None)) -// }; -// let sender = tx1.sender(); -// txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx3, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); -// assert_eq!(txq.future.by_priority.len(), 0); -// assert_eq!(txq.current.by_priority.len(), 3); -// -// // when -// let res = txq.add(tx2_2, TransactionOrigin::Local, 0, None, &default_tx_provider()); -// -// // then -// assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); -// assert_eq!(res.unwrap(), transaction::ImportResult::Current); -// assert_eq!(txq.current.by_priority.len(), 3); -// } -// -// #[test] -// fn should_reject_transactions_below_base_gas() { -// // given -// let txq = new_queue(); -// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); -// let high_gas = 100_001.into(); -// -// // when -// let res1 = txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()); -// let res2 = txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider().with_tx_gas_required(high_gas)); -// -// // then -// assert_eq!(res1.unwrap(), transaction::ImportResult::Current); -// assert_eq!(unwrap_tx_err(res2), transaction::Error::InsufficientGas { -// minimal: 100_001.into(), -// got: 100_000.into(), -// }); -// -// } -// -// #[test] -// fn should_clear_all_old_transactions() { -// // given -// let txq = new_queue(); -// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); -// let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); -// let next_nonce = |_: &Address| -// AccountDetails { nonce: default_nonce() + U256::one(), balance: !U256::zero() }; -// -// // Insert all transactions -// txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 4); -// -// // when -// txq.remove_old(&next_nonce, 0); -// -// // then -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); -// } -// -// #[test] -// fn should_remove_out_of_date_transactions_occupying_queue() { -// // given -// let txq = new_queue(); -// let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); -// let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); -// -// // Insert all transactions -// txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx2, TransactionOrigin::External, 5, None, &default_tx_provider()).unwrap(); -// txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_tx_provider()).unwrap(); -// txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); -// assert_eq!(txq.future_transactions().len(), 1); -// -// // when -// txq.remove_old(&default_account_details_for_addr, 9 + super::DEFAULT_QUEUING_PERIOD); -// -// // then -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); -// assert_eq!(txq.future_transactions().len(), 0); -// assert_eq!(txq.pending(TestClient::new(), 0, 0), vec![tx1, tx3]); -// } -// -// #[test] -// fn should_accept_local_service_transaction() { -// // given -// let tx = new_tx(123.into(), 0.into()); -// let txq = new_queue(); -// txq.set_minimal_gas_price(100.into()); -// -// // when -// txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); -// -// // then -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); -// } -// -// #[test] -// fn should_not_accept_external_service_transaction_if_sender_not_certified() { -// // given -// let tx1 = new_tx(123.into(), 0.into()); -// let tx2 = new_tx(456.into(), 0.into()); -// let txq = new_queue(); -// txq.set_minimal_gas_price(100.into()); -// -// // when -// assert_eq!(unwrap_tx_err(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider())), -// transaction::Error::InsufficientGasPrice { -// minimal: 100.into(), -// got: 0.into(), -// }); -// assert_eq!(unwrap_tx_err(txq.add(tx2, TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider())), -// transaction::Error::InsufficientGasPrice { -// minimal: 100.into(), -// got: 0.into(), -// }); -// -// // then -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 0); -// } -// -// #[test] -// fn should_not_accept_external_service_transaction_if_contract_returns_error() { -// // given -// let tx = new_tx(123.into(), 0.into()); -// let txq = new_queue(); -// txq.set_minimal_gas_price(100.into()); -// -// // when -// let details_provider = default_tx_provider().service_transaction_checker_returns_error("Contract error"); -// assert_eq!(unwrap_tx_err(txq.add(tx, TransactionOrigin::External, 0, None, &details_provider)), -// transaction::Error::InsufficientGasPrice { -// minimal: 100.into(), -// got: 0.into(), -// }); -// -// // then -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 0); -// } -// -// #[test] -// fn should_accept_external_service_transaction_if_sender_is_certified() { -// // given -// let tx = new_tx(123.into(), 0.into()); -// let txq = new_queue(); -// txq.set_minimal_gas_price(100.into()); -// -// // when -// let details_provider = default_tx_provider().service_transaction_checker_accepts(true); -// txq.add(tx, TransactionOrigin::External, 0, None, &details_provider).unwrap(); -// -// // then -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); -// } -// -// #[test] -// fn should_not_order_transactions_by_hash() { -// // given -// let secret1 = "0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap(); -// let secret2 = "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap(); -// let tx1 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret1, None); -// let tx2 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret2, None); -// let txq = new_queue(); -// -// // when -// txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// -// // then -// assert_eq!(txq.pending(TestClient::new(), 0, 0)[0], tx1); -// assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); -// } -// -// #[test] -// fn should_not_return_transactions_over_nonce_cap() { -// // given -// let keypair = Random.generate().unwrap(); -// let txq = new_queue(); -// // when -// for nonce in 123..130 { -// let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None); -// txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); -// } -// -// // then -// assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4); -// } +#[test] +fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { + // given + let txq = new_queue(); + let (tx, tx2) = Tx::gas_price(20).signed_replacement(); + let (tx3, tx4) = Tx::gas_price(1).signed_replacement(); + let client = TestClient::new().with_balance(1_000_000); + + // when + let res = txq.import(client.clone(), vec![tx, tx3].local()); + assert_eq!(res, vec![Ok(()), Ok(())]); + + let res = txq.import(client.clone(), vec![tx2, tx4].local()); + + // then + assert_eq!(res, vec![Err(transaction::Error::TooCheapToReplace), Ok(())]); + assert_eq!(txq.status().status.transaction_count, 2); + assert_eq!(txq.pending(client.clone(), 0, 0)[0].signed().gas_price, U256::from(20)); + assert_eq!(txq.pending(client.clone(), 0, 0)[1].signed().gas_price, U256::from(2)); +} + +#[test] +fn should_return_none_when_transaction_from_given_address_does_not_exist() { + // given + let txq = new_queue(); + + // then + assert_eq!(txq.next_nonce(TestClient::new(), &Default::default()), None); +} + +#[test] +fn should_return_correct_nonce_when_transactions_from_given_address_exist() { + // given + let txq = new_queue(); + let tx = Tx::default().signed(); + let from = tx.sender(); + let nonce = tx.nonce; + + // when + txq.import(TestClient::new(), vec![tx.local()]); + + // then + assert_eq!(txq.next_nonce(TestClient::new(), &from), Some(nonce + 1.into())); +} + +#[test] +fn should_return_valid_last_nonce_after_cull() { + // given + let txq = new_queue(); + let (tx1, _, tx2) = Tx::default().signed_triple(); + let sender = tx1.sender(); + + // when + // Second should go to future + let res = txq.import(TestClient::new(), vec![tx1, tx2].local()); + assert_eq!(res, vec![Ok(()), Ok(())]); + // Now block is imported + let client = TestClient::new().with_nonce(124); + txq.cull(client.clone()); + // tx2 should be not be promoted to current + assert_eq!(txq.pending(client.clone(), 0, 0).len(), 0); + + // then + assert_eq!(txq.next_nonce(client.clone(), &sender), None); + assert_eq!(txq.next_nonce(client.with_nonce(125), &sender), Some(126.into())); +} + +#[test] +fn should_return_true_if_there_is_local_transaction_pending() { + // given + let txq = new_queue(); + let (tx1, tx2) = Tx::default().signed_pair(); + assert_eq!(txq.has_local_pending_transactions(), false); + let client = TestClient::new().with_local(&tx1.sender()); + + // when + let res = txq.import(client.clone(), vec![tx1.unverified(), tx2.local()]); + assert_eq!(res, vec![Ok(()), Ok(())]); + + // then + assert_eq!(txq.has_local_pending_transactions(), true); +} + +#[test] +fn should_reject_transactions_below_base_gas() { + // given + let txq = new_queue(); + let tx = Tx::default().signed(); + + // when + let res = txq.import(TestClient::new().with_gas_required(100_001), vec![tx].local()); + + // then + assert_eq!(res, vec![Err(transaction::Error::InsufficientGas { + minimal: 100_001.into(), + got: 21_000.into(), + })]); +} + +#[test] +fn should_remove_out_of_date_transactions_occupying_queue() { + assert_eq!(true, false); + // given + // let txq = new_queue(); + // let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + // let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); + // + // // Insert all transactions + // txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); + // txq.add(tx2, TransactionOrigin::External, 5, None, &default_tx_provider()).unwrap(); + // txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_tx_provider()).unwrap(); + // txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + // assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); + // assert_eq!(txq.future_transactions().len(), 1); + // + // // when + // txq.remove_old(&default_account_details_for_addr, 9 + super::DEFAULT_QUEUING_PERIOD); + // + // // then + // assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); + // assert_eq!(txq.future_transactions().len(), 0); + // assert_eq!(txq.pending(TestClient::new(), 0, 0), vec![tx1, tx3]); +} + +#[test] +fn should_accept_local_transactions_below_min_gas_price() { + // given + let txq = TransactionQueue::new( + txpool::Options { + max_count: 3, + max_per_sender: 3, + max_mem_usage: 50 + }, + verifier::Options { + minimal_gas_price: 10.into(), + ..Default::default() + }, + ); + let tx = Tx::gas_price(1).signed(); + + // when + let res = txq.import(TestClient::new(), vec![tx.local()]); + assert_eq!(res, vec![Ok(())]); + + // then + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); +} + +#[test] +fn should_accept_local_service_transaction() { + // given + let txq = new_queue(); + let tx = Tx::gas_price(0).signed(); + + // when + let res = txq.import( + TestClient::new() + .with_local(&tx.sender()), + vec![tx.local()] + ); + assert_eq!(res, vec![Ok(())]); + + // then + assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); +} + +#[test] +fn should_not_accept_external_service_transaction_if_sender_not_certified() { + // given + let txq = new_queue(); + let tx1 = Tx::gas_price(0).signed().unverified(); + let tx2 = Tx::gas_price(0).signed().retracted(); + let tx3 = Tx::gas_price(0).signed().unverified(); + + // when + let res = txq.import(TestClient::new(), vec![tx1, tx2]); + assert_eq!(res, vec![ + Err(transaction::Error::InsufficientGasPrice { + minimal: 1.into(), + got: 0.into(), + }), + Err(transaction::Error::InsufficientGasPrice { + minimal: 1.into(), + got: 0.into(), + }), + ]); + + // then + let res = txq.import(TestClient::new().with_service_transaction(), vec![tx3]); + assert_eq!(res, vec![Ok(())]); +} + +#[test] +fn should_not_return_transactions_over_nonce_cap() { + assert_eq!(true, false); + // // given + // let keypair = Random.generate().unwrap(); + // let txq = new_queue(); + // // when + // for nonce in 123..130 { + // let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None); + // txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); + // } + // + // // then + // assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4); +} diff --git a/miner/src/pool/tests/tx.rs b/miner/src/pool/tests/tx.rs new file mode 100644 index 00000000000..35bf4296e6a --- /dev/null +++ b/miner/src/pool/tests/tx.rs @@ -0,0 +1,170 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +use ethereum_types::{U256, H256}; +use ethkey::{Random, Generator}; +use rustc_hex::FromHex; +use transaction::{self, Transaction, SignedTransaction, UnverifiedTransaction}; + +use pool::verifier; + +#[derive(Clone)] +pub struct Tx { + nonce: u64, + gas: u64, + gas_price: u64, +} + +impl Default for Tx { + fn default() -> Self { + Tx { + nonce: 123, + gas: 21_000, + gas_price: 1, + } + } +} + +impl Tx { + pub fn gas_price(gas_price: u64) -> Self { + Tx { + gas_price, + ..Default::default() + } + } + + pub fn signed(self) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + self.unsigned().sign(keypair.secret(), None) + } + + pub fn signed_pair(self) -> (SignedTransaction, SignedTransaction) { + let (tx1, tx2, _) = self.signed_triple(); + (tx1, tx2) + } + + pub fn signed_triple(mut self) -> (SignedTransaction, SignedTransaction, SignedTransaction) { + let keypair = Random.generate().unwrap(); + let tx1 = self.clone().unsigned().sign(keypair.secret(), None); + self.nonce += 1; + let tx2 = self.clone().unsigned().sign(keypair.secret(), None); + self.nonce += 1; + let tx3 = self.unsigned().sign(keypair.secret(), None); + + + (tx1, tx2, tx3) + } + + pub fn signed_replacement(mut self) -> (SignedTransaction, SignedTransaction) { + let keypair = Random.generate().unwrap(); + let tx1 = self.clone().unsigned().sign(keypair.secret(), None); + self.gas_price += 1; + let tx2 = self.unsigned().sign(keypair.secret(), None); + + (tx1, tx2) + } + + pub fn unsigned(self) -> Transaction { + Transaction { + action: transaction::Action::Create, + value: U256::from(100), + data: "3331600055".from_hex().unwrap(), + gas: self.gas.into(), + gas_price: self.gas_price.into(), + nonce: self.nonce.into() + } + } +} +pub trait TxExt: Sized { + type Out; + type Hash; + + fn hash(&self) -> Self::Hash; + + fn local(self) -> Self::Out; + + fn retracted(self) -> Self::Out; + + fn unverified(self) -> Self::Out; +} + +impl TxExt for (A, B) where + A: TxExt, + B: TxExt, +{ + type Out = (O, O); + type Hash = (H, H); + + fn hash(&self) -> Self::Hash { (self.0.hash(), self.1.hash()) } + fn local(self) -> Self::Out { (self.0.local(), self.1.local()) } + fn retracted(self) -> Self::Out { (self.0.retracted(), self.1.retracted()) } + fn unverified(self) -> Self::Out { (self.0.unverified(), self.1.unverified()) } +} + +impl TxExt for SignedTransaction { + type Out = verifier::Transaction; + type Hash = H256; + + fn hash(&self) -> Self::Hash { + UnverifiedTransaction::hash(self) + } + + fn local(self) -> Self::Out { + verifier::Transaction::Local(self.into()) + } + + fn retracted(self) -> Self::Out { + verifier::Transaction::Retracted(self.into()) + } + + fn unverified(self) -> Self::Out { + verifier::Transaction::Unverified(self.into()) + } +} + +impl TxExt for Vec { + type Out = Vec; + type Hash = Vec; + + fn hash(&self) -> Self::Hash { + self.iter().map(|tx| tx.hash()).collect() + } + + fn local(self) -> Self::Out { + self.into_iter().map(Into::into).map(verifier::Transaction::Local).collect() + } + + fn retracted(self) -> Self::Out { + self.into_iter().map(Into::into).map(verifier::Transaction::Retracted).collect() + } + + fn unverified(self) -> Self::Out { + self.into_iter().map(Into::into).map(verifier::Transaction::Unverified).collect() + } +} + +pub trait PairExt { + type Type; + + fn into_vec(self) -> Vec; +} + +impl PairExt for (A, A) { + type Type = A; + fn into_vec(self) -> Vec { + vec![self.0, self.1] + } +} diff --git a/miner/src/transaction_queue.rs b/miner/src/transaction_queue.rs deleted file mode 100644 index 2be7fc8a3b8..00000000000 --- a/miner/src/transaction_queue.rs +++ /dev/null @@ -1,2944 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity 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 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. If not, see . - -//! Transaction Queue -//! -//! `TransactionQueue` keeps track of all transactions seen by the node (received from other peers) and own transactions -//! and orders them by priority. Top priority transactions are those with low nonce height (difference between -//! transaction's nonce and next nonce expected from this sender). If nonces are equal transaction's gas price is used -//! for comparison (higher gas price = higher priority). -//! -//! # Usage Example -//! -//! ```rust -//! extern crate ethereum_types; -//! extern crate ethcore_miner as miner; -//! extern crate ethcore_transaction as transaction; -//! extern crate ethkey; -//! extern crate rustc_hex; -//! -//! use ethereum_types::{U256, Address}; -//! use ethkey::{Random, Generator}; -//! use miner::transaction_queue::{TransactionQueue, TransactionDetailsProvider, AccountDetails, TransactionOrigin, RemovalReason}; -//! use transaction::*; -//! use rustc_hex::FromHex; -//! -//! #[derive(Default)] -//! struct DummyTransactionDetailsProvider; -//! -//! impl TransactionDetailsProvider for DummyTransactionDetailsProvider { -//! fn fetch_account(&self, _address: &Address) -> AccountDetails { -//! AccountDetails { -//! nonce: U256::from(10), -//! balance: U256::from(1_000_000) -//! } -//! } -//! -//! fn estimate_gas_required(&self, _tx: &SignedTransaction) -> U256 { -//! 2.into() -//! } -//! -//! fn is_service_transaction_acceptable(&self, _tx: &SignedTransaction) -> Result { -//! Ok(true) -//! } -//! } -//! -//! fn main() { -//! let key = Random.generate().unwrap(); -//! let t1 = Transaction { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), -//! gas: U256::from(100_000), gas_price: U256::one(), nonce: U256::from(10) }; -//! let t2 = Transaction { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), -//! gas: U256::from(100_000), gas_price: U256::one(), nonce: U256::from(11) }; -//! -//! let st1 = t1.sign(&key.secret(), None); -//! let st2 = t2.sign(&key.secret(), None); -//! let details_provider = DummyTransactionDetailsProvider::default(); -//! -//! let mut txq = TransactionQueue::default(); -//! txq.add(st2.clone(), TransactionOrigin::External, 0, None, &details_provider).unwrap(); -//! txq.add(st1.clone(), TransactionOrigin::External, 0, None, &details_provider).unwrap(); -//! -//! // Check status -//! assert_eq!(txq.status().pending, 2); -//! // Check top transactions -//! let top = txq.top_transactions(); -//! assert_eq!(top.len(), 2); -//! assert_eq!(top[0], st1); -//! assert_eq!(top[1], st2); -//! -//! // And when transaction is removed (but nonce haven't changed) -//! // it will move subsequent transactions to future -//! txq.remove(&st1.hash(), &|_| 10.into(), RemovalReason::Invalid); -//! assert_eq!(txq.status().pending, 0); -//! assert_eq!(txq.status().future, 1); -//! assert_eq!(txq.top_transactions().len(), 0); -//! } -//! ``` -//! -//! # Maintaing valid state -//! -//! 1. Whenever transaction is imported to queue (to queue) all other transactions from this sender are revalidated in current. It means that they are moved to future and back again (height recalculation & gap filling). -//! 2. Whenever invalid transaction is removed: -//! - When it's removed from `future` - all `future` transactions heights are recalculated and then -//! we check if the transactions should go to `current` (comparing state nonce) -//! - When it's removed from `current` - all transactions from this sender (`current` & `future`) are recalculated. -//! 3. `cull` is used to inform the queue about client (state) nonce changes. -//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce -//! - It moves matching `future` transactions to `current` -//! 4. `remove_old` is used as convenient method to update the state nonce for all senders in the queue. -//! - Invokes `cull` with latest state nonce for all senders. - -use std::cmp::Ordering; -use std::cmp; -use std::collections::{HashSet, HashMap, BTreeSet, BTreeMap}; -use std::ops::Deref; - -use ethereum_types::{H256, U256, Address}; -use heapsize::HeapSizeOf; -use linked_hash_map::LinkedHashMap; -use local_transactions::{LocalTransactionsList, Status as LocalTransactionStatus}; -use table::Table; -use transaction::{self, SignedTransaction, PendingTransaction}; - -type BlockNumber = u64; - -/// Transaction origin -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum TransactionOrigin { - /// Transaction coming from local RPC - Local, - /// External transaction received from network - External, - /// Transactions from retracted blocks - RetractedBlock, -} - -impl PartialOrd for TransactionOrigin { - fn partial_cmp(&self, other: &TransactionOrigin) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for TransactionOrigin { - fn cmp(&self, other: &TransactionOrigin) -> Ordering { - if *other == *self { - return Ordering::Equal; - } - - match (*self, *other) { - (TransactionOrigin::RetractedBlock, _) => Ordering::Less, - (_, TransactionOrigin::RetractedBlock) => Ordering::Greater, - (TransactionOrigin::Local, _) => Ordering::Less, - _ => Ordering::Greater, - } - } -} - -impl TransactionOrigin { - fn is_local(&self) -> bool { - *self == TransactionOrigin::Local - } -} - -#[derive(Clone, Debug)] -/// Light structure used to identify transaction and its order -struct TransactionOrder { - /// Primary ordering factory. Difference between transaction nonce and expected nonce in state - /// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5) - /// High nonce_height = Low priority (processed later) - nonce_height: U256, - /// Gas Price of the transaction. - /// Low gas price = Low priority (processed later) - gas_price: U256, - /// Gas usage priority factor. Usage depends on strategy. - /// Represents the linear increment in required gas price for heavy transactions. - /// - /// High gas limit + Low gas price = Very Low priority - /// High gas limit + High gas price = High priority - gas_factor: U256, - /// Gas (limit) of the transaction. Usage depends on strategy. - /// Low gas limit = High priority (processed earlier) - gas: U256, - /// Heap usage of this transaction. - mem_usage: usize, - /// Transaction ordering strategy - strategy: PrioritizationStrategy, - /// Hash to identify associated transaction - hash: H256, - /// Incremental id assigned when transaction is inserted to the queue. - insertion_id: u64, - /// Origin of the transaction - origin: TransactionOrigin, - /// Penalties - penalties: usize, -} - - -impl TransactionOrder { - - fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256, min_gas_price: U256, strategy: PrioritizationStrategy) -> Self { - let factor = (tx.transaction.gas >> 15) * min_gas_price; - TransactionOrder { - nonce_height: tx.nonce() - base_nonce, - gas_price: tx.transaction.gas_price, - gas_factor: factor, - gas: tx.transaction.gas, - mem_usage: tx.transaction.heap_size_of_children(), - strategy: strategy, - hash: tx.hash(), - insertion_id: tx.insertion_id, - origin: tx.origin, - penalties: 0, - } - } - - fn update_height(mut self, nonce: U256, base_nonce: U256) -> Self { - self.nonce_height = nonce - base_nonce; - self - } - - fn penalize(mut self) -> Self { - self.penalties = self.penalties.saturating_add(1); - self - } -} - -impl Eq for TransactionOrder {} -impl PartialEq for TransactionOrder { - fn eq(&self, other: &TransactionOrder) -> bool { - self.cmp(other) == Ordering::Equal - } -} -impl PartialOrd for TransactionOrder { - fn partial_cmp(&self, other: &TransactionOrder) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for TransactionOrder { - fn cmp(&self, b: &TransactionOrder) -> Ordering { - // First check number of penalties - if self.penalties != b.penalties { - return self.penalties.cmp(&b.penalties); - } - - // Local transactions should always have priority - if self.origin != b.origin { - return self.origin.cmp(&b.origin); - } - - // Check nonce_height - if self.nonce_height != b.nonce_height { - return self.nonce_height.cmp(&b.nonce_height); - } - - match self.strategy { - PrioritizationStrategy::GasAndGasPrice => { - if self.gas != b.gas { - return self.gas.cmp(&b.gas); - } - }, - PrioritizationStrategy::GasFactorAndGasPrice => { - // avoiding overflows - // (gp1 - g1) > (gp2 - g2) <=> - // (gp1 + g2) > (gp2 + g1) - let f_a = self.gas_price + b.gas_factor; - let f_b = b.gas_price + self.gas_factor; - if f_a != f_b { - return f_b.cmp(&f_a); - } - }, - PrioritizationStrategy::GasPriceOnly => {}, - } - - // Then compare gas_prices - if self.gas_price != b.gas_price { - return b.gas_price.cmp(&self.gas_price); - } - - // Lastly compare insertion_id - self.insertion_id.cmp(&b.insertion_id) - } -} - -/// Verified transaction -#[derive(Debug)] -struct VerifiedTransaction { - /// Transaction. - transaction: SignedTransaction, - /// Transaction origin. - origin: TransactionOrigin, - /// Delay until specified condition is met. - condition: Option, - /// Insertion time - insertion_time: QueuingInstant, - /// ID assigned upon insertion, should be unique. - insertion_id: u64, -} - -impl VerifiedTransaction { - fn new( - transaction: SignedTransaction, - origin: TransactionOrigin, - condition: Option, - insertion_time: QueuingInstant, - insertion_id: u64, - ) -> Self { - VerifiedTransaction { - transaction, - origin, - condition, - insertion_time, - insertion_id, - } - } - - fn hash(&self) -> H256 { - self.transaction.hash() - } - - fn nonce(&self) -> U256 { - self.transaction.nonce - } - - fn sender(&self) -> Address { - self.transaction.sender() - } - - fn cost(&self) -> U256 { - self.transaction.value + self.transaction.gas_price * self.transaction.gas - } -} - -#[derive(Debug, Default)] -struct GasPriceQueue { - backing: BTreeMap>, -} - -impl GasPriceQueue { - /// Insert an item into a BTreeMap/HashSet "multimap". - pub fn insert(&mut self, gas_price: U256, hash: H256) -> bool { - self.backing.entry(gas_price).or_insert_with(Default::default).insert(hash) - } - - /// Remove an item from a BTreeMap/HashSet "multimap". - /// Returns true if the item was removed successfully. - pub fn remove(&mut self, gas_price: &U256, hash: &H256) -> bool { - if let Some(hashes) = self.backing.get_mut(gas_price) { - let only_one_left = hashes.len() == 1; - if !only_one_left { - // Operation may be ok: only if hash is in gas-price's Set. - return hashes.remove(hash); - } - if hash != hashes.iter().next().expect("We know there is only one element in collection, tested above; qed") { - // Operation failed: hash not the single item in gas-price's Set. - return false; - } - } else { - // Operation failed: gas-price not found in Map. - return false; - } - // Operation maybe ok: only if hash not found in gas-price Set. - self.backing.remove(gas_price).is_some() - } -} - -impl Deref for GasPriceQueue { - type Target=BTreeMap>; - - fn deref(&self) -> &Self::Target { - &self.backing - } -} - -/// Holds transactions accessible by (address, nonce) and by priority -/// -/// `TransactionSet` keeps number of entries below limit, but it doesn't -/// automatically happen during `insert/remove` operations. -/// You have to call `enforce_limit` to remove lowest priority transactions from set. -struct TransactionSet { - by_priority: BTreeSet, - by_address: Table, - by_gas_price: GasPriceQueue, - limit: usize, - total_gas_limit: U256, - memory_limit: usize, -} - -impl TransactionSet { - /// Inserts `TransactionOrder` to this set. Transaction does not need to be unique - - /// the same transaction may be validly inserted twice. Any previous transaction that - /// it replaces (i.e. with the same `sender` and `nonce`) should be returned. - fn insert(&mut self, sender: Address, nonce: U256, order: TransactionOrder) -> Option { - if !self.by_priority.insert(order.clone()) { - return Some(order.clone()); - } - let order_hash = order.hash.clone(); - let order_gas_price = order.gas_price.clone(); - let by_address_replaced = self.by_address.insert(sender, nonce, order); - // If transaction was replaced remove it from priority queue - if let Some(ref old_order) = by_address_replaced { - assert!(self.by_priority.remove(old_order), "hash is in `by_address`; all transactions in `by_address` must be in `by_priority`; qed"); - assert!(self.by_gas_price.remove(&old_order.gas_price, &old_order.hash), - "hash is in `by_address`; all transactions' gas_prices in `by_address` must be in `by_gas_limit`; qed"); - } - self.by_gas_price.insert(order_gas_price, order_hash); - assert_eq!(self.by_priority.len(), self.by_address.len()); - assert_eq!(self.by_gas_price.values().map(|v| v.len()).fold(0, |a, b| a + b), self.by_address.len()); - by_address_replaced - } - - /// Remove low priority transactions if there is more than specified by given `limit`. - /// - /// It drops transactions from this set but also removes associated `VerifiedTransaction`. - /// Returns addresses and lowest nonces of transactions removed because of limit. - fn enforce_limit(&mut self, by_hash: &mut HashMap, local: &mut LocalTransactionsList) -> Option> { - let mut count = 0; - let mut mem_usage = 0; - let mut gas: U256 = 0.into(); - let to_drop : Vec<(Address, U256)> = { - self.by_priority - .iter() - .filter(|order| { - // update transaction count and mem usage - count += 1; - mem_usage += order.mem_usage; - - // calculate current gas usage - let r = gas.overflowing_add(order.gas); - if r.1 { return false } - gas = r.0; - - let is_own_or_retracted = order.origin.is_local() || order.origin == TransactionOrigin::RetractedBlock; - // Own and retracted transactions are allowed to go above all limits. - !is_own_or_retracted && (mem_usage > self.memory_limit || count > self.limit || gas > self.total_gas_limit) - }) - .map(|order| by_hash.get(&order.hash) - .expect("All transactions in `self.by_priority` and `self.by_address` are kept in sync with `by_hash`.")) - .map(|tx| (tx.sender(), tx.nonce())) - .collect() - }; - - Some(to_drop.into_iter() - .fold(HashMap::new(), |mut removed, (sender, nonce)| { - let order = self.drop(&sender, &nonce) - .expect("Transaction has just been found in `by_priority`; so it is in `by_address` also."); - trace!(target: "txqueue", "Dropped out of limit transaction: {:?}", order.hash); - - let order = by_hash.remove(&order.hash) - .expect("hash is in `by_priorty`; all hashes in `by_priority` must be in `by_hash`; qed"); - - if order.origin.is_local() { - local.mark_dropped(order.transaction); - } - - let min = removed.get(&sender).map_or(nonce, |val| cmp::min(*val, nonce)); - removed.insert(sender, min); - removed - })) - } - - /// Drop transaction from this set (remove from `by_priority` and `by_address`) - fn drop(&mut self, sender: &Address, nonce: &U256) -> Option { - if let Some(tx_order) = self.by_address.remove(sender, nonce) { - assert!(self.by_gas_price.remove(&tx_order.gas_price, &tx_order.hash), - "hash is in `by_address`; all transactions' gas_prices in `by_address` must be in `by_gas_limit`; qed"); - assert!(self.by_priority.remove(&tx_order), - "hash is in `by_address`; all transactions' gas_prices in `by_address` must be in `by_priority`; qed"); - assert_eq!(self.by_priority.len(), self.by_address.len()); - assert_eq!(self.by_gas_price.values().map(|v| v.len()).fold(0, |a, b| a + b), self.by_address.len()); - return Some(tx_order); - } - assert_eq!(self.by_priority.len(), self.by_address.len()); - assert_eq!(self.by_gas_price.values().map(|v| v.len()).fold(0, |a, b| a + b), self.by_address.len()); - None - } - - /// Drop all transactions. - fn clear(&mut self) { - self.by_priority.clear(); - self.by_address.clear(); - self.by_gas_price.backing.clear(); - } - - /// Sets new limit for number of transactions in this `TransactionSet`. - /// Note the limit is not applied (no transactions are removed) by calling this method. - fn set_limit(&mut self, limit: usize) { - self.limit = limit; - } - - /// Get the minimum gas price that we can accept into this queue that wouldn't cause the transaction to - /// immediately be dropped. 0 if the queue isn't at capacity; 1 plus the lowest if it is. - fn gas_price_entry_limit(&self) -> U256 { - match self.by_gas_price.keys().next() { - Some(k) if self.by_priority.len() >= self.limit => *k + 1.into(), - _ => U256::default(), - } - } -} - -#[derive(Debug)] -/// Current status of the queue -pub struct TransactionQueueStatus { - /// Number of pending transactions (ready to go to block) - pub pending: usize, - /// Number of future transactions (waiting for transactions with lower nonces first) - pub future: usize, -} - -/// Details of account -pub struct AccountDetails { - /// Most recent account nonce - pub nonce: U256, - /// Current account balance - pub balance: U256, -} - -/// Transaction with the same (sender, nonce) can be replaced only if -/// `new_gas_price > old_gas_price + old_gas_price >> SHIFT` -const GAS_PRICE_BUMP_SHIFT: usize = 3; // 2 = 25%, 3 = 12.5%, 4 = 6.25% - -/// Describes the strategy used to prioritize transactions in the queue. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum PrioritizationStrategy { - /// Use only gas price. Disregards the actual computation cost of the transaction. - /// i.e. Higher gas price = Higher priority - GasPriceOnly, - /// Use gas limit and then gas price. - /// i.e. Higher gas limit = Lower priority - GasAndGasPrice, - /// Calculate and use priority based on gas and gas price. - /// PRIORITY = GAS_PRICE - GAS/2^15 * MIN_GAS_PRICE - /// - /// Rationale: - /// Heavy transactions are paying linear cost (GAS * GAS_PRICE) - /// while the computation might be more expensive. - /// - /// i.e. - /// 1M gas tx with `gas_price=30*min` has the same priority - /// as 32k gas tx with `gas_price=min` - GasFactorAndGasPrice, -} - -/// Reason to remove single transaction from the queue. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum RemovalReason { - /// Transaction is invalid - Invalid, - /// Transaction was canceled - Canceled, - /// Transaction is not allowed, - NotAllowed, -} - -/// Point in time when transaction was inserted. -pub type QueuingInstant = BlockNumber; -const DEFAULT_QUEUING_PERIOD: BlockNumber = 128; - -/// `TransactionQueue` transaction details provider. -pub trait TransactionDetailsProvider { - /// Fetch transaction-related account details. - fn fetch_account(&self, address: &Address) -> AccountDetails; - /// Estimate gas required for transaction. - fn estimate_gas_required(&self, tx: &SignedTransaction) -> U256; - /// Check if this service transaction can be accepted by `TransactionQueue`. - fn is_service_transaction_acceptable(&self, tx: &SignedTransaction) -> Result; -} - -/// `TransactionQueue` implementation -pub struct TransactionQueue { - /// Prioritization strategy for this queue - strategy: PrioritizationStrategy, - /// Gas Price threshold for transactions that can be imported to this queue (defaults to 0) - minimal_gas_price: U256, - /// The maximum amount of gas any individual transaction may use. - tx_gas_limit: U256, - /// Current gas limit (block gas limit). Transactions above the limit will not be accepted (default to !0) - block_gas_limit: U256, - /// Maximal time transaction may occupy the queue. - /// When we reach `max_time_in_queue / 2^3` we re-validate - /// account balance. - max_time_in_queue: QueuingInstant, - /// Priority queue for transactions that can go to block - current: TransactionSet, - /// Priority queue for transactions that has been received but are not yet valid to go to block - future: TransactionSet, - /// All transactions managed by queue indexed by hash - by_hash: HashMap, - /// Last nonce of transaction in current (to quickly check next expected transaction) - last_nonces: HashMap, - /// List of local transactions and their statuses. - local_transactions: LocalTransactionsList, - /// Next id that should be assigned to a transaction imported to the queue. - next_transaction_id: u64, -} - -impl Default for TransactionQueue { - fn default() -> Self { - TransactionQueue::new(PrioritizationStrategy::GasPriceOnly) - } -} - -impl TransactionQueue { - /// Creates new instance of this Queue - pub fn new(strategy: PrioritizationStrategy) -> Self { - Self::with_limits(strategy, 8192, usize::max_value(), !U256::zero(), !U256::zero()) - } - - /// Create new instance of this Queue with specified limits - pub fn with_limits( - strategy: PrioritizationStrategy, - limit: usize, - memory_limit: usize, - total_gas_limit: U256, - tx_gas_limit: U256, - ) -> Self { - let current = TransactionSet { - by_priority: BTreeSet::new(), - by_address: Table::new(), - by_gas_price: Default::default(), - limit, - total_gas_limit, - memory_limit, - }; - - let future = TransactionSet { - by_priority: BTreeSet::new(), - by_address: Table::new(), - by_gas_price: Default::default(), - total_gas_limit, - limit, - memory_limit, - }; - - TransactionQueue { - strategy, - minimal_gas_price: U256::zero(), - block_gas_limit: !U256::zero(), - tx_gas_limit, - max_time_in_queue: DEFAULT_QUEUING_PERIOD, - current, - future, - by_hash: HashMap::new(), - last_nonces: HashMap::new(), - local_transactions: LocalTransactionsList::default(), - next_transaction_id: 0, - } - } - - /// Set the new limit for `current` and `future` queue. - pub fn set_limit(&mut self, limit: usize) { - self.current.set_limit(limit); - self.future.set_limit(limit); - // And ensure the limits - self.current.enforce_limit(&mut self.by_hash, &mut self.local_transactions); - self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); - } - - /// Returns current limit of transactions in the queue. - pub fn limit(&self) -> usize { - self.current.limit - } - - /// Get the minimal gas price. - pub fn minimal_gas_price(&self) -> &U256 { - &self.minimal_gas_price - } - - /// Sets new gas price threshold for incoming transactions. - /// Any transaction already imported to the queue is not affected. - pub fn set_minimal_gas_price(&mut self, min_gas_price: U256) { - self.minimal_gas_price = min_gas_price; - } - - /// Get one more than the lowest gas price in the queue iff the pool is - /// full, otherwise 0. - pub fn effective_minimum_gas_price(&self) -> U256 { - self.current.gas_price_entry_limit() - } - - /// Sets new gas limit. Transactions with gas over the limit will not be accepted. - /// Any transaction already imported to the queue is not affected. - pub fn set_gas_limit(&mut self, gas_limit: U256) { - self.block_gas_limit = gas_limit; - } - - /// Sets new total gas limit. - pub fn set_total_gas_limit(&mut self, total_gas_limit: U256) { - self.current.total_gas_limit = total_gas_limit; - self.future.total_gas_limit = total_gas_limit; - self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); - } - - /// Set the new limit for the amount of gas any individual transaction may have. - /// Any transaction already imported to the queue is not affected. - pub fn set_tx_gas_limit(&mut self, limit: U256) { - self.tx_gas_limit = limit; - } - - /// Returns current status for this queue - pub fn status(&self) -> TransactionQueueStatus { - TransactionQueueStatus { - pending: self.current.by_priority.len(), - future: self.future.by_priority.len(), - } - } - - /// Add signed transaction to queue to be verified and imported. - /// - /// NOTE details_provider methods should be cheap to compute - /// otherwise it might open up an attack vector. - pub fn add( - &mut self, - tx: SignedTransaction, - origin: TransactionOrigin, - time: QueuingInstant, - condition: Option, - details_provider: &TransactionDetailsProvider, - ) -> Result { - if origin == TransactionOrigin::Local { - let hash = tx.hash(); - let cloned_tx = tx.clone(); - - let result = self.add_internal(tx, origin, time, condition, details_provider); - match result { - Ok(transaction::ImportResult::Current) => { - self.local_transactions.mark_pending(hash); - }, - Ok(transaction::ImportResult::Future) => { - self.local_transactions.mark_future(hash); - }, - Err(ref err) => { - // Sometimes transactions are re-imported, so - // don't overwrite transactions if they are already on the list - if !self.local_transactions.contains(&hash) { - self.local_transactions.mark_rejected(cloned_tx, err.clone()); - } - }, - } - result - } else { - self.add_internal(tx, origin, time, condition, details_provider) - } - } - - /// Adds signed transaction to the queue. - fn add_internal( - &mut self, - tx: SignedTransaction, - origin: TransactionOrigin, - time: QueuingInstant, - condition: Option, - details_provider: &TransactionDetailsProvider, - ) -> Result { - if origin != TransactionOrigin::Local && tx.gas_price < self.minimal_gas_price { - // if it is non-service-transaction => drop - let is_service_transaction = tx.gas_price.is_zero(); - if !is_service_transaction { - trace!(target: "txqueue", - "Dropping transaction below minimal gas price threshold: {:?} (gp: {} < {})", - tx.hash(), - tx.gas_price, - self.minimal_gas_price - ); - - return Err(transaction::Error::InsufficientGasPrice { - minimal: self.minimal_gas_price, - got: tx.gas_price, - }); - } - - let is_service_transaction_accepted = match details_provider.is_service_transaction_acceptable(&tx) { - Ok(true) => true, - Ok(false) => { - trace!(target: "txqueue", - "Dropping service transaction as sender is not certified to send service transactions: {:?} (sender: {:?})", - tx.hash(), - tx.sender(), - ); - - false - }, - Err(contract_err) => { - trace!(target: "txqueue", - "Dropping service transaction as service contract returned error: {:?} (error: {:?})", - tx.hash(), - contract_err, - ); - - false - }, - }; - - if !is_service_transaction_accepted { - return Err(transaction::Error::InsufficientGasPrice { - minimal: self.minimal_gas_price, - got: tx.gas_price, - }); - } - } - - let full_queues_lowest = self.effective_minimum_gas_price(); - if tx.gas_price < full_queues_lowest && origin != TransactionOrigin::Local { - trace!(target: "txqueue", - "Dropping transaction below lowest gas price in a full queue: {:?} (gp: {} < {})", - tx.hash(), - tx.gas_price, - full_queues_lowest - ); - - return Err(transaction::Error::InsufficientGasPrice { - minimal: full_queues_lowest, - got: tx.gas_price, - }); - } - - let gas_limit = cmp::min(self.tx_gas_limit, self.block_gas_limit); - if tx.gas > gas_limit { - trace!(target: "txqueue", - "Dropping transaction above gas limit: {:?} ({} > min({}, {}))", - tx.hash(), - tx.gas, - self.block_gas_limit, - self.tx_gas_limit - ); - return Err(transaction::Error::GasLimitExceeded { - limit: gas_limit, - got: tx.gas, - }); - } - - let minimal_gas = details_provider.estimate_gas_required(&tx); - if tx.gas < minimal_gas { - trace!(target: "txqueue", - "Dropping transaction with insufficient gas: {:?} ({} > {})", - tx.hash(), - tx.gas, - minimal_gas, - ); - - return Err(transaction::Error::InsufficientGas { - minimal: minimal_gas, - got: tx.gas, - }); - } - - let client_account = details_provider.fetch_account(&tx.sender()); - let cost = tx.value + tx.gas_price * tx.gas; - if client_account.balance < cost { - trace!(target: "txqueue", - "Dropping transaction without sufficient balance: {:?} ({} < {})", - tx.hash(), - client_account.balance, - cost - ); - - return Err(transaction::Error::InsufficientBalance { - cost: cost, - balance: client_account.balance - }); - } - tx.check_low_s()?; - // No invalid transactions beyond this point. - let id = self.next_transaction_id; - self.next_transaction_id += 1; - let vtx = VerifiedTransaction::new(tx, origin, condition, time, id); - let r = self.import_tx(vtx, client_account.nonce); - assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); - r - } - - /// Removes all transactions from particular sender up to (excluding) given client (state) nonce. - /// Client (State) Nonce = next valid nonce for this sender. - pub fn cull(&mut self, sender: Address, client_nonce: U256) { - // Check if there is anything in current... - let should_check_in_current = self.current.by_address.row(&sender) - // If nonce == client_nonce nothing is changed - .and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce < &client_nonce)) - .map(|_| ()); - // ... or future - let should_check_in_future = self.future.by_address.row(&sender) - // if nonce == client_nonce we need to promote to current - .and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce <= &client_nonce)) - .map(|_| ()); - - if should_check_in_current.or(should_check_in_future).is_none() { - return; - } - - self.cull_internal(sender, client_nonce); - } - - /// Always updates future and moves transactions from current to future. - fn cull_internal(&mut self, sender: Address, client_nonce: U256) { - // We will either move transaction to future or remove it completely - // so there will be no transactions from this sender in current - self.last_nonces.remove(&sender); - // First update height of transactions in future to avoid collisions - self.update_future(&sender, client_nonce); - // This should move all current transactions to future and remove old transactions - self.move_all_to_future(&sender, client_nonce); - // And now lets check if there is some batch of transactions in future - // that should be placed in current. It should also update last_nonces. - self.move_matching_future_to_current(sender, client_nonce, client_nonce); - assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); - } - - /// Checks the current nonce for all transactions' senders in the queue and removes the old transactions. - pub fn remove_old(&mut self, fetch_account: &F, current_time: QueuingInstant) where - F: Fn(&Address) -> AccountDetails, - { - let senders = self.current.by_address.keys() - .chain(self.future.by_address.keys()) - .map(|sender| (*sender, fetch_account(sender))) - .collect::>(); - - for (sender, details) in senders.iter() { - self.cull(*sender, details.nonce); - } - - let max_time = self.max_time_in_queue; - let balance_check = max_time >> 3; - // Clear transactions occupying the queue too long - let invalid = self.by_hash.iter() - .filter(|&(_, ref tx)| !tx.origin.is_local()) - .map(|(hash, tx)| (hash, tx, current_time.saturating_sub(tx.insertion_time))) - .filter_map(|(hash, tx, time_diff)| { - if time_diff > max_time { - return Some(*hash); - } - - if time_diff > balance_check { - return match senders.get(&tx.sender()) { - Some(details) if tx.cost() > details.balance => { - Some(*hash) - }, - _ => None, - }; - } - - None - }) - .collect::>(); - let fetch_nonce = |a: &Address| senders.get(a) - .expect("We fetch details for all senders from both current and future") - .nonce; - for hash in invalid { - self.remove(&hash, &fetch_nonce, RemovalReason::Invalid); - } - } - - /// Penalize transactions from sender of transaction with given hash. - /// I.e. it should change the priority of the transaction in the queue. - /// - /// NOTE: We need to penalize all transactions from particular sender - /// to avoid breaking invariants in queue (ordered by nonces). - /// Consecutive transactions from this sender would fail otherwise (because of invalid nonce). - pub fn penalize(&mut self, transaction_hash: &H256) { - let transaction = match self.by_hash.get(transaction_hash) { - None => return, - Some(t) => t, - }; - - // Never penalize local transactions - if transaction.origin.is_local() { - return; - } - - let sender = transaction.sender(); - - // Penalize all transactions from this sender - let nonces_from_sender = match self.current.by_address.row(&sender) { - Some(row_map) => row_map.keys().cloned().collect::>(), - None => vec![], - }; - for k in nonces_from_sender { - let order = self.current.drop(&sender, &k).expect("transaction known to be in self.current; qed"); - self.current.insert(sender, k, order.penalize()); - } - // Same thing for future - let nonces_from_sender = match self.future.by_address.row(&sender) { - Some(row_map) => row_map.keys().cloned().collect::>(), - None => vec![], - }; - for k in nonces_from_sender { - let order = self.future.drop(&sender, &k).expect("transaction known to be in self.future; qed"); - self.future.insert(sender, k, order.penalize()); - } - } - - /// Removes invalid transaction identified by hash from queue. - /// Assumption is that this transaction nonce is not related to client nonce, - /// so transactions left in queue are processed according to client nonce. - /// - /// If gap is introduced marks subsequent transactions as future - pub fn remove(&mut self, transaction_hash: &H256, fetch_nonce: &F, reason: RemovalReason) - where F: Fn(&Address) -> U256 { - - assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); - let transaction = self.by_hash.remove(transaction_hash); - if transaction.is_none() { - // We don't know this transaction - return; - } - - let transaction = transaction.expect("None is tested in early-exit condition above; qed"); - let sender = transaction.sender(); - let nonce = transaction.nonce(); - let current_nonce = fetch_nonce(&sender); - - trace!(target: "txqueue", "Removing invalid transaction: {:?}", transaction.hash()); - - // Mark in locals - if self.local_transactions.contains(transaction_hash) { - match reason { - RemovalReason::Invalid => self.local_transactions.mark_invalid( - transaction.transaction.into() - ), - RemovalReason::NotAllowed => self.local_transactions.mark_invalid( - transaction.transaction.into() - ), - RemovalReason::Canceled => self.local_transactions.mark_canceled( - PendingTransaction::new(transaction.transaction, transaction.condition) - ), - } - } - - // Remove from future - let order = self.future.drop(&sender, &nonce); - if order.is_some() { - self.update_future(&sender, current_nonce); - // And now lets check if there is some chain of transactions in future - // that should be placed in current - self.move_matching_future_to_current(sender, current_nonce, current_nonce); - assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); - return; - } - - // Remove from current - let order = self.current.drop(&sender, &nonce); - if order.is_some() { - // This will keep consistency in queue - // Moves all to future and then promotes a batch from current: - self.cull_internal(sender, current_nonce); - assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); - return; - } - } - - /// Marks all transactions from particular sender as local transactions - fn mark_transactions_local(&mut self, sender: &Address) { - fn mark_local(sender: &Address, set: &mut TransactionSet, mut mark: F) { - // Mark all transactions from this sender as local - let nonces_from_sender = set.by_address.row(sender) - .map(|row_map| { - row_map.iter().filter_map(|(nonce, order)| if order.origin.is_local() { - None - } else { - Some(*nonce) - }).collect::>() - }) - .unwrap_or_else(Vec::new); - - for k in nonces_from_sender { - let mut order = set.drop(sender, &k).expect("transaction known to be in self.current/self.future; qed"); - order.origin = TransactionOrigin::Local; - mark(order.hash); - set.insert(*sender, k, order); - } - } - - let local = &mut self.local_transactions; - mark_local(sender, &mut self.current, |hash| local.mark_pending(hash)); - mark_local(sender, &mut self.future, |hash| local.mark_future(hash)); - } - - /// Update height of all transactions in future transactions set. - fn update_future(&mut self, sender: &Address, current_nonce: U256) { - // We need to drain all transactions for current sender from future and reinsert them with updated height - let all_nonces_from_sender = match self.future.by_address.row(sender) { - Some(row_map) => row_map.keys().cloned().collect::>(), - None => vec![], - }; - for k in all_nonces_from_sender { - let order = self.future.drop(sender, &k).expect("iterating over a collection that has been retrieved above; qed"); - if k >= current_nonce { - self.future.insert(*sender, k, order.update_height(k, current_nonce)); - } else { - trace!(target: "txqueue", "Removing old transaction: {:?} (nonce: {} < {})", order.hash, k, current_nonce); - // Remove the transaction completely - self.by_hash.remove(&order.hash).expect("All transactions in `future` are also in `by_hash`"); - } - } - } - - /// Drop all transactions from given sender from `current`. - /// Either moves them to `future` or removes them from queue completely. - fn move_all_to_future(&mut self, sender: &Address, current_nonce: U256) { - let all_nonces_from_sender = match self.current.by_address.row(sender) { - Some(row_map) => row_map.keys().cloned().collect::>(), - None => vec![], - }; - - for k in all_nonces_from_sender { - // Goes to future or is removed - let order = self.current.drop(sender, &k).expect("iterating over a collection that has been retrieved above; - qed"); - if k >= current_nonce { - let order = order.update_height(k, current_nonce); - if order.origin.is_local() { - self.local_transactions.mark_future(order.hash); - } - if let Some(old) = self.future.insert(*sender, k, order.clone()) { - Self::replace_orders(*sender, k, old, order, &mut self.future, &mut self.by_hash, &mut self.local_transactions); - } - } else { - trace!(target: "txqueue", "Removing old transaction: {:?} (nonce: {} < {})", order.hash, k, current_nonce); - let tx = self.by_hash.remove(&order.hash).expect("All transactions in `future` are also in `by_hash`"); - if tx.origin.is_local() { - self.local_transactions.mark_mined(tx.transaction); - } - } - } - self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); - } - - /// Returns top transactions from the queue ordered by priority. - pub fn top_transactions(&self) -> Vec { - self.top_transactions_at(BlockNumber::max_value(), u64::max_value(), None) - - } - - fn filter_pending_transaction(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option, mut f: F) - where F: FnMut(&VerifiedTransaction) { - - let mut delayed = HashSet::new(); - for t in self.current.by_priority.iter() { - let tx = self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`"); - let sender = tx.sender(); - if delayed.contains(&sender) { - continue; - } - if let Some(max_nonce) = nonce_cap { - if tx.nonce() >= max_nonce { - continue; - } - } - let delay = match tx.condition { - Some(transaction::Condition::Number(n)) => n > best_block, - Some(transaction::Condition::Timestamp(t)) => t > best_timestamp, - None => false, - }; - if delay { - delayed.insert(sender); - continue; - } - f(&tx); - } - } - - /// Returns top transactions from the queue ordered by priority. - pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option) -> Vec { - let mut r = Vec::new(); - self.filter_pending_transaction(best_block, best_timestamp, nonce_cap, |tx| r.push(tx.transaction.clone())); - r - } - - /// Return all ready transactions. - pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec { - let mut r = Vec::new(); - self.filter_pending_transaction(best_block, best_timestamp, None, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone()))); - r - } - - /// Return all future transactions. - pub fn future_transactions(&self) -> Vec { - self.future.by_priority - .iter() - .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) - .map(|t| PendingTransaction { transaction: t.transaction.clone(), condition: t.condition.clone() }) - .collect() - } - - /// Returns local transactions (some of them might not be part of the queue anymore). - pub fn local_transactions(&self) -> &LinkedHashMap { - self.local_transactions.all_transactions() - } - - /// Returns hashes of all transactions from current, ordered by priority. - pub fn pending_hashes(&self) -> Vec { - self.current.by_priority - .iter() - .map(|t| t.hash) - .collect() - } - - /// Returns true if there is at least one local transaction pending - pub fn has_local_pending_transactions(&self) -> bool { - self.current.by_priority.iter().any(|tx| tx.origin == TransactionOrigin::Local) - } - - /// Finds transaction in the queue by hash (if any) - pub fn find(&self, hash: &H256) -> Option { - self.by_hash.get(hash).map(|tx| PendingTransaction { transaction: tx.transaction.clone(), condition: tx.condition.clone() }) - } - - /// Removes all elements (in any state) from the queue - pub fn clear(&mut self) { - self.current.clear(); - self.future.clear(); - self.by_hash.clear(); - self.last_nonces.clear(); - } - - /// Returns highest transaction nonce for given address. - pub fn last_nonce(&self, address: &Address) -> Option { - self.last_nonces.get(address).cloned() - } - - /// Checks if there are any transactions in `future` that should actually be promoted to `current` - /// (because nonce matches). - fn move_matching_future_to_current(&mut self, address: Address, mut current_nonce: U256, first_nonce: U256) { - let mut update_last_nonce_to = None; - { - let by_nonce = self.future.by_address.row_mut(&address); - if by_nonce.is_none() { - return; - } - let by_nonce = by_nonce.expect("None is tested in early-exit condition above; qed"); - while let Some(order) = by_nonce.remove(¤t_nonce) { - // remove also from priority and gas_price - self.future.by_priority.remove(&order); - self.future.by_gas_price.remove(&order.gas_price, &order.hash); - // Put to current - let order = order.update_height(current_nonce, first_nonce); - if order.origin.is_local() { - self.local_transactions.mark_pending(order.hash); - } - if let Some(old) = self.current.insert(address, current_nonce, order.clone()) { - Self::replace_orders(address, current_nonce, old, order, &mut self.current, &mut self.by_hash, &mut self.local_transactions); - } - update_last_nonce_to = Some(current_nonce); - current_nonce = current_nonce + U256::one(); - } - } - self.future.by_address.clear_if_empty(&address); - if let Some(x) = update_last_nonce_to { - // Update last inserted nonce - self.last_nonces.insert(address, x); - } - } - - /// Adds VerifiedTransaction to this queue. - /// - /// Determines if it should be placed in current or future. When transaction is - /// imported to `current` also checks if there are any `future` transactions that should be promoted because of - /// this. - /// - /// It ignores transactions that has already been imported (same `hash`) and replaces the transaction - /// iff `(address, nonce)` is the same but `gas_price` is higher. - /// - /// Returns `true` when transaction was imported successfuly - fn import_tx(&mut self, tx: VerifiedTransaction, state_nonce: U256) -> Result { - - if self.by_hash.get(&tx.hash()).is_some() { - // Transaction is already imported. - trace!(target: "txqueue", "Dropping already imported transaction: {:?}", tx.hash()); - return Err(transaction::Error::AlreadyImported); - } - - let min_gas_price = (self.minimal_gas_price, self.strategy); - let address = tx.sender(); - let nonce = tx.nonce(); - let hash = tx.hash(); - - // The transaction might be old, let's check that. - // This has to be the first test, otherwise calculating - // nonce height would result in overflow. - if nonce < state_nonce { - // Droping transaction - trace!(target: "txqueue", "Dropping old transaction: {:?} (nonce: {} < {})", tx.hash(), nonce, state_nonce); - return Err(transaction::Error::Old); - } - - // Update nonces of transactions in future (remove old transactions) - self.update_future(&address, state_nonce); - // State nonce could be updated. Maybe there are some more items waiting in future? - self.move_matching_future_to_current(address, state_nonce, state_nonce); - // Check the next expected nonce (might be updated by move above) - let next_nonce = self.last_nonces - .get(&address) - .cloned() - .map_or(state_nonce, |n| n + U256::one()); - - if tx.origin.is_local() { - self.mark_transactions_local(&address); - } - - // Future transaction - if nonce > next_nonce { - // We have a gap - put to future. - // Insert transaction (or replace old one with lower gas price) - check_too_cheap( - Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash, &mut self.local_transactions) - )?; - // Enforce limit in Future - let removed = self.future.enforce_limit(&mut self.by_hash, &mut self.local_transactions); - // Return an error if this transaction was not imported because of limit. - check_if_removed(&address, &nonce, removed)?; - - debug!(target: "txqueue", "Importing transaction to future: {:?}", hash); - debug!(target: "txqueue", "status: {:?}", self.status()); - return Ok(transaction::ImportResult::Future); - } - - // We might have filled a gap - move some more transactions from future - self.move_matching_future_to_current(address, nonce, state_nonce); - self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce); - - // Replace transaction if any - check_too_cheap( - Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash, &mut self.local_transactions) - )?; - // Keep track of highest nonce stored in current - let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n)); - self.last_nonces.insert(address, new_max); - - // Also enforce the limit - let removed = self.current.enforce_limit(&mut self.by_hash, &mut self.local_transactions); - // If some transaction were removed because of limit we need to update last_nonces also. - self.update_last_nonces(&removed); - // Trigger error if the transaction we are importing was removed. - check_if_removed(&address, &nonce, removed)?; - - debug!(target: "txqueue", "Imported transaction to current: {:?}", hash); - debug!(target: "txqueue", "status: {:?}", self.status()); - Ok(transaction::ImportResult::Current) - } - - /// Updates - fn update_last_nonces(&mut self, removed_min_nonces: &Option>) { - if let Some(ref min_nonces) = *removed_min_nonces { - for (sender, nonce) in min_nonces.iter() { - if *nonce == U256::zero() { - self.last_nonces.remove(sender); - } else { - self.last_nonces.insert(*sender, *nonce - U256::one()); - } - } - } - } - - /// Replaces transaction in given set (could be `future` or `current`). - /// - /// If there is already transaction with same `(sender, nonce)` it will be replaced iff `gas_price` is higher. - /// One of the transactions is dropped from set and also removed from queue entirely (from `by_hash`). - /// - /// Returns `true` if transaction actually got to the queue (`false` if there was already a transaction with higher - /// gas_price) - fn replace_transaction( - tx: VerifiedTransaction, - base_nonce: U256, - min_gas_price: (U256, PrioritizationStrategy), - set: &mut TransactionSet, - by_hash: &mut HashMap, - local: &mut LocalTransactionsList, - ) -> bool { - let order = TransactionOrder::for_transaction(&tx, base_nonce, min_gas_price.0, min_gas_price.1); - let hash = tx.hash(); - let address = tx.sender(); - let nonce = tx.nonce(); - - let old_hash = by_hash.insert(hash, tx); - assert!(old_hash.is_none(), "Each hash has to be inserted exactly once."); - - trace!(target: "txqueue", "Inserting: {:?}", order); - - if let Some(old) = set.insert(address, nonce, order.clone()) { - Self::replace_orders(address, nonce, old, order, set, by_hash, local) - } else { - true - } - } - - fn replace_orders( - address: Address, - nonce: U256, - old: TransactionOrder, - order: TransactionOrder, - set: &mut TransactionSet, - by_hash: &mut HashMap, - local: &mut LocalTransactionsList, - ) -> bool { - // There was already transaction in queue. Let's check which one should stay - let old_hash = old.hash; - let new_hash = order.hash; - - let old_gas_price = old.gas_price; - let new_gas_price = order.gas_price; - let min_required_gas_price = old_gas_price + (old_gas_price >> GAS_PRICE_BUMP_SHIFT); - - if min_required_gas_price > new_gas_price { - trace!(target: "txqueue", "Didn't insert transaction because gas price was too low: {:?} ({:?} stays in the queue)", order.hash, old.hash); - // Put back old transaction since it has greater priority (higher gas_price) - set.insert(address, nonce, old); - // and remove new one - let order = by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`."); - if order.origin.is_local() { - local.mark_replaced(order.transaction, old_gas_price, old_hash); - } - false - } else { - trace!(target: "txqueue", "Replaced transaction: {:?} with transaction with higher gas price: {:?}", old.hash, order.hash); - // Make sure we remove old transaction entirely - let old = by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`."); - if old.origin.is_local() { - local.mark_replaced(old.transaction, new_gas_price, new_hash); - } - true - } - } -} - -fn check_too_cheap(is_in: bool) -> Result<(), transaction::Error> { - if is_in { - Ok(()) - } else { - Err(transaction::Error::TooCheapToReplace) - } -} - -fn check_if_removed(sender: &Address, nonce: &U256, dropped: Option>) -> Result<(), - transaction::Error> { - match dropped { - Some(ref dropped) => match dropped.get(sender) { - Some(min) if nonce >= min => { - Err(transaction::Error::LimitReached) - }, - _ => Ok(()), - }, - _ => Ok(()), - } -} - - -#[cfg(test)] -pub mod test { - use ethereum_types::{U256, Address}; - use super::*; - use ethkey::{Random, Generator}; - use rustc_hex::FromHex; - use transaction::Transaction; - - pub struct DummyTransactionDetailsProvider { - account_details: AccountDetails, - gas_required: U256, - service_transactions_check_result: Result, - } - - impl Default for DummyTransactionDetailsProvider { - fn default() -> Self { - DummyTransactionDetailsProvider { - account_details: default_account_details(), - gas_required: U256::zero(), - service_transactions_check_result: Ok(false), - } - } - } - - impl DummyTransactionDetailsProvider { - pub fn with_account(mut self, account_details: AccountDetails) -> Self { - self.account_details = account_details; - self - } - - pub fn with_account_nonce(mut self, nonce: U256) -> Self { - self.account_details.nonce = nonce; - self - } - - pub fn with_tx_gas_required(mut self, gas_required: U256) -> Self { - self.gas_required = gas_required; - self - } - - pub fn service_transaction_checker_returns_error(mut self, error: &str) -> Self { - self.service_transactions_check_result = Err(error.to_owned()); - self - } - - pub fn service_transaction_checker_accepts(mut self, accepts: bool) -> Self { - self.service_transactions_check_result = Ok(accepts); - self - } - } - - impl TransactionDetailsProvider for DummyTransactionDetailsProvider { - fn fetch_account(&self, _address: &Address) -> AccountDetails { - AccountDetails { - nonce: self.account_details.nonce, - balance: self.account_details.balance, - } - } - - fn estimate_gas_required(&self, _tx: &SignedTransaction) -> U256 { - self.gas_required - } - - fn is_service_transaction_acceptable(&self, _tx: &SignedTransaction) -> Result { - self.service_transactions_check_result.clone() - } - } - - fn unwrap_tx_err(err: Result) -> transaction::Error { - err.unwrap_err() - } - - fn default_nonce() -> U256 { 123.into() } - fn default_gas_val() -> U256 { 100_000.into() } - fn default_gas_price() -> U256 { 1.into() } - - fn new_unsigned_tx(nonce: U256, gas: U256, gas_price: U256) -> Transaction { - Transaction { - action: transaction::Action::Create, - value: U256::from(100), - data: "3331600055".from_hex().unwrap(), - gas: gas, - gas_price: gas_price, - nonce: nonce - } - } - - fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction { - let keypair = Random.generate().unwrap(); - new_unsigned_tx(nonce, default_gas_val(), gas_price).sign(keypair.secret(), None) - } - - fn new_tx_with_gas(gas: U256, gas_price: U256) -> SignedTransaction { - let keypair = Random.generate().unwrap(); - new_unsigned_tx(default_nonce(), gas, gas_price).sign(keypair.secret(), None) - } - - fn new_tx_default() -> SignedTransaction { - new_tx(default_nonce(), default_gas_price()) - } - - fn default_account_details() -> AccountDetails { - AccountDetails { - nonce: default_nonce(), - balance: !U256::zero() - } - } - - fn default_account_details_for_addr(_a: &Address) -> AccountDetails { - default_account_details() - } - - fn default_tx_provider() -> DummyTransactionDetailsProvider { - DummyTransactionDetailsProvider::default() - } - - fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { - let tx1 = new_unsigned_tx(nonce, default_gas_val(), gas_price); - let tx2 = new_unsigned_tx(nonce + nonce_increment, default_gas_val(), gas_price + gas_price_increment); - - let keypair = Random.generate().unwrap(); - let secret = &keypair.secret(); - (tx1.sign(secret, None).into(), tx2.sign(secret, None).into()) - } - - /// Returns two consecutive transactions, both with increased gas price - fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { - let gas = default_gas_price() + gas_price_increment; - let tx1 = new_unsigned_tx(default_nonce(), default_gas_val(), gas); - let tx2 = new_unsigned_tx(default_nonce() + 1.into(), default_gas_val(), gas); - - let keypair = Random.generate().unwrap(); - let secret = &keypair.secret(); - (tx1.sign(secret, None).into(), tx2.sign(secret, None).into()) - } - - fn new_tx_pair_default(nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { - new_tx_pair(default_nonce(), default_gas_price(), nonce_increment, gas_price_increment) - } - - /// Returns two transactions with identical (sender, nonce) but different gas price/hash. - fn new_similar_tx_pair() -> (SignedTransaction, SignedTransaction) { - new_tx_pair_default(0.into(), 1.into()) - } - - #[test] - fn test_ordering() { - assert_eq!(TransactionOrigin::Local.cmp(&TransactionOrigin::External), Ordering::Less); - assert_eq!(TransactionOrigin::RetractedBlock.cmp(&TransactionOrigin::Local), Ordering::Less); - assert_eq!(TransactionOrigin::RetractedBlock.cmp(&TransactionOrigin::External), Ordering::Less); - - assert_eq!(TransactionOrigin::External.cmp(&TransactionOrigin::Local), Ordering::Greater); - assert_eq!(TransactionOrigin::Local.cmp(&TransactionOrigin::RetractedBlock), Ordering::Greater); - assert_eq!(TransactionOrigin::External.cmp(&TransactionOrigin::RetractedBlock), Ordering::Greater); - } - - fn transaction_order(tx: &VerifiedTransaction, nonce: U256) -> TransactionOrder { - TransactionOrder::for_transaction(tx, nonce, 0.into(), PrioritizationStrategy::GasPriceOnly) - } - - #[test] - fn should_return_correct_nonces_when_dropped_because_of_limit() { - // given - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 2, - usize::max_value(), - !U256::zero(), - !U256::zero(), - ); - let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); - let sender = tx1.sender(); - let nonce = tx1.nonce; - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into())); - - // when - let tx = new_tx(123.into(), 1.into()); - let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - // No longer the case as we don't even consider a transaction that isn't above a full - // queue's minimum gas price. - // We may want to reconsider this in the near future so leaving this code in as a - // possible alternative. - /* - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.last_nonce(&sender), Some(nonce)); - */ - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { - minimal: 2.into(), - got: 1.into(), - }); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.last_nonce(&sender), Some(tx2.nonce)); - } - - #[test] - fn should_create_transaction_set() { - // given - let mut local = LocalTransactionsList::default(); - let mut set = TransactionSet { - by_priority: BTreeSet::new(), - by_address: Table::new(), - by_gas_price: Default::default(), - limit: 1, - total_gas_limit: !U256::zero(), - memory_limit: usize::max_value(), - }; - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, None, 0, 0); - let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, None, 0, 1); - let mut by_hash = { - let mut x = HashMap::new(); - let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, None, 0, 0); - let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, None, 0, 1); - x.insert(tx1.hash(), tx1); - x.insert(tx2.hash(), tx2); - x - }; - // Insert both transactions - let order1 = transaction_order(&tx1, U256::zero()); - set.insert(tx1.sender(), tx1.nonce(), order1.clone()); - let order2 = transaction_order(&tx2, U256::zero()); - set.insert(tx2.sender(), tx2.nonce(), order2.clone()); - assert_eq!(set.by_priority.len(), 2); - assert_eq!(set.by_address.len(), 2); - - // when - set.enforce_limit(&mut by_hash, &mut local); - - // then - assert_eq!(by_hash.len(), 1); - assert_eq!(set.by_priority.len(), 1); - assert_eq!(set.by_address.len(), 1); - assert_eq!(set.by_priority.iter().next().unwrap().clone(), order1); - set.clear(); - assert_eq!(set.by_priority.len(), 0); - assert_eq!(set.by_address.len(), 0); - } - - #[test] - fn should_replace_transaction_in_set() { - let mut set = TransactionSet { - by_priority: BTreeSet::new(), - by_address: Table::new(), - by_gas_price: Default::default(), - limit: 1, - total_gas_limit: !U256::zero(), - memory_limit: 0, - }; - // Create two transactions with same nonce - // (same hash) - let (tx1, tx2) = new_tx_pair_default(0.into(), 0.into()); - let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External, None, 0, 0); - let tx2 = VerifiedTransaction::new(tx2, TransactionOrigin::External, None, 0, 1); - let by_hash = { - let mut x = HashMap::new(); - let tx1 = VerifiedTransaction::new(tx1.transaction.clone(), TransactionOrigin::External, None, 0, 0); - let tx2 = VerifiedTransaction::new(tx2.transaction.clone(), TransactionOrigin::External, None, 0, 1); - x.insert(tx1.hash(), tx1); - x.insert(tx2.hash(), tx2); - x - }; - // Insert both transactions - let order1 = transaction_order(&tx1, U256::zero()); - set.insert(tx1.sender(), tx1.nonce(), order1.clone()); - assert_eq!(set.by_priority.len(), 1); - assert_eq!(set.by_address.len(), 1); - assert_eq!(set.by_gas_price.len(), 1); - assert_eq!(*set.by_gas_price.iter().next().unwrap().0, 1.into()); - assert_eq!(set.by_gas_price.iter().next().unwrap().1.len(), 1); - // Two different orders (imagine nonce changed in the meantime) - let order2 = transaction_order(&tx2, U256::one()); - set.insert(tx2.sender(), tx2.nonce(), order2.clone()); - assert_eq!(set.by_priority.len(), 1); - assert_eq!(set.by_address.len(), 1); - assert_eq!(set.by_gas_price.len(), 1); - assert_eq!(*set.by_gas_price.iter().next().unwrap().0, 1.into()); - assert_eq!(set.by_gas_price.iter().next().unwrap().1.len(), 1); - - // then - assert_eq!(by_hash.len(), 1); - assert_eq!(set.by_priority.len(), 1); - assert_eq!(set.by_address.len(), 1); - assert_eq!(set.by_gas_price.len(), 1); - assert_eq!(*set.by_gas_price.iter().next().unwrap().0, 1.into()); - assert_eq!(set.by_gas_price.iter().next().unwrap().1.len(), 1); - assert_eq!(set.by_priority.iter().next().unwrap().clone(), order2); - } - - #[test] - fn should_not_insert_same_transaction_twice_into_set() { - let mut set = TransactionSet { - by_priority: BTreeSet::new(), - by_address: Table::new(), - by_gas_price: Default::default(), - limit: 2, - total_gas_limit: !U256::zero(), - memory_limit: 0, - }; - let tx = new_tx_default(); - let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, None, 0, 0); - let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly); - assert!(set.insert(tx1.sender(), tx1.nonce(), order1).is_none()); - let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External, None, 0, 1); - let order2 = TransactionOrder::for_transaction(&tx2, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly); - assert!(set.insert(tx2.sender(), tx2.nonce(), order2).is_some()); - } - - #[test] - fn should_give_correct_gas_price_entry_limit() { - let mut set = TransactionSet { - by_priority: BTreeSet::new(), - by_address: Table::new(), - by_gas_price: Default::default(), - limit: 1, - total_gas_limit: !U256::zero(), - memory_limit: 0, - }; - - assert_eq!(set.gas_price_entry_limit(), 0.into()); - let tx = new_tx_default(); - let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External, None, 0, 0); - let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly); - assert!(set.insert(tx1.sender(), tx1.nonce(), order1.clone()).is_none()); - assert_eq!(set.gas_price_entry_limit(), 2.into()); - } - - #[test] - fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_similar_tx_pair(); - let prev_nonce = default_account_details().nonce - U256::one(); - - // First insert one transaction to future - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)); - assert_eq!(res.unwrap(), transaction::ImportResult::Future); - assert_eq!(txq.status().future, 1); - - // now import second transaction to current - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); - - // and then there should be only one transaction in current (the one with higher gas_price) - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.status().pending, 1); - assert_eq!(txq.status().future, 0); - assert_eq!(txq.current.by_priority.len(), 1); - assert_eq!(txq.current.by_address.len(), 1); - let top = txq.top_transactions(); - assert_eq!(top[0], tx2); - } - - #[test] - fn should_move_all_transactions_from_future() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 1.into()); - let prev_nonce = default_account_details().nonce - U256::one(); - - // First insert one transaction to future - let res = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)); - assert_eq!(res.unwrap(), transaction::ImportResult::Future); - assert_eq!(txq.status().future, 1); - - // now import second transaction to current - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.status().pending, 2); - assert_eq!(txq.status().future, 0); - assert_eq!(txq.current.by_priority.len(), 2); - assert_eq!(txq.current.by_address.len(), 2); - let top = txq.top_transactions(); - assert_eq!(top[0], tx); - assert_eq!(top[1], tx2); - } - - #[test] - fn should_import_tx() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - - // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 1); - } - - #[test] - fn should_order_by_gas() { - // given - let mut txq = TransactionQueue::new(PrioritizationStrategy::GasAndGasPrice); - let tx1 = new_tx_with_gas(50000.into(), 40.into()); - let tx2 = new_tx_with_gas(40000.into(), 30.into()); - let tx3 = new_tx_with_gas(30000.into(), 10.into()); - let tx4 = new_tx_with_gas(50000.into(), 20.into()); - txq.set_minimal_gas_price(15.into()); - - // when - let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(res1.unwrap(), transaction::ImportResult::Current); - assert_eq!(res2.unwrap(), transaction::ImportResult::Current); - assert_eq!(unwrap_tx_err(res3), transaction::Error::InsufficientGasPrice { - minimal: U256::from(15), - got: U256::from(10), - }); - assert_eq!(res4.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 3); - assert_eq!(txq.top_transactions()[0].gas, 40000.into()); - assert_eq!(txq.top_transactions()[1].gas, 50000.into()); - assert_eq!(txq.top_transactions()[2].gas, 50000.into()); - assert_eq!(txq.top_transactions()[1].gas_price, 40.into()); - assert_eq!(txq.top_transactions()[2].gas_price, 20.into()); - } - - #[test] - fn should_order_by_gas_factor() { - // given - let mut txq = TransactionQueue::new(PrioritizationStrategy::GasFactorAndGasPrice); - - let tx1 = new_tx_with_gas(150_000.into(), 40.into()); - let tx2 = new_tx_with_gas(40_000.into(), 16.into()); - let tx3 = new_tx_with_gas(30_000.into(), 15.into()); - let tx4 = new_tx_with_gas(150_000.into(), 62.into()); - txq.set_minimal_gas_price(15.into()); - - // when - let res1 = txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res2 = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res3 = txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()); - let res4 = txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(res1.unwrap(), transaction::ImportResult::Current); - assert_eq!(res2.unwrap(), transaction::ImportResult::Current); - assert_eq!(res3.unwrap(), transaction::ImportResult::Current); - assert_eq!(res4.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 4); - assert_eq!(txq.top_transactions()[0].gas, 30_000.into()); - assert_eq!(txq.top_transactions()[1].gas, 150_000.into()); - assert_eq!(txq.top_transactions()[2].gas, 40_000.into()); - assert_eq!(txq.top_transactions()[3].gas, 150_000.into()); - assert_eq!(txq.top_transactions()[0].gas_price, 15.into()); - assert_eq!(txq.top_transactions()[1].gas_price, 62.into()); - assert_eq!(txq.top_transactions()[2].gas_price, 16.into()); - assert_eq!(txq.top_transactions()[3].gas_price, 40.into()); - } - - #[test] - fn tx_gas_limit_should_never_overflow() { - // given - let mut txq = TransactionQueue::default(); - txq.set_gas_limit(U256::zero()); - assert_eq!(txq.block_gas_limit, U256::zero()); - - // when - txq.set_gas_limit(!U256::zero()); - - // then - assert_eq!(txq.block_gas_limit, !U256::zero()); - } - - #[test] - fn should_not_import_transaction_above_gas_limit() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let gas = tx.gas; - let limit = gas / U256::from(2); - txq.set_gas_limit(limit); - - // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(unwrap_tx_err(res), transaction::Error::GasLimitExceeded { - limit: U256::from(50_000), - got: gas, - }); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); - } - - - #[test] - fn should_drop_transactions_from_senders_without_balance() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let account = AccountDetails { - nonce: default_account_details().nonce, - balance: U256::one() - }; - - // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account(account)); - - // then - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientBalance { - balance: U256::from(1), - cost: U256::from(100_100), - }); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); - } - - #[test] - fn should_not_import_transaction_below_min_gas_price_threshold_if_external() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - txq.set_minimal_gas_price(tx.gas_price + U256::one()); - - // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { - minimal: U256::from(2), - got: U256::from(1), - }); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); - } - - #[test] - fn should_import_transaction_below_min_gas_price_threshold_if_local() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - txq.set_minimal_gas_price(tx.gas_price + U256::one()); - - // when - let res = txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()); - - // then - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 0); - } - - #[test] - fn should_import_txs_from_same_sender() { - // given - let mut txq = TransactionQueue::default(); - - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - - // when - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); - assert_eq!(top[1], tx2); - assert_eq!(top.len(), 2); - } - - #[test] - fn should_prioritize_local_transactions_within_same_nonce_height() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - // the second one has same nonce but higher `gas_price` - let (_, tx2) = new_similar_tx_pair(); - - // when - // first insert the one with higher gas price - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // then the one with lower gas price, but local - txq.add(tx.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); // local should be first - assert_eq!(top[1], tx2); - assert_eq!(top.len(), 2); - } - - #[test] - fn when_importing_local_should_mark_others_from_the_same_sender_as_local() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - // the second one has same nonce but higher `gas_price` - let (_, tx0) = new_similar_tx_pair(); - - txq.add(tx0.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // the one with higher gas price is first - let top = txq.top_transactions(); - assert_eq!(top[0], tx0); - assert_eq!(top[1], tx1); - - // when - // insert second as local - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - // then - // the order should be updated - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], tx2); - assert_eq!(top[2], tx0); - } - - #[test] - fn should_prioritize_reimported_transactions_within_same_nonce_height() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - // the second one has same nonce but higher `gas_price` - let (_, tx2) = new_similar_tx_pair(); - - // when - // first insert local one with higher gas price - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - // then the one with lower gas price, but from retracted block - txq.add(tx.clone(), TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider()).unwrap(); - - // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); // retracted should be first - assert_eq!(top[1], tx2); - assert_eq!(top.len(), 2); - } - - #[test] - fn should_not_prioritize_local_transactions_with_different_nonce_height() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - - // when - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - // then - let top = txq.top_transactions(); - assert_eq!(top[0], tx); - assert_eq!(top[1], tx2); - assert_eq!(top.len(), 2); - } - - #[test] - fn should_penalize_transactions_from_sender_in_future() { - // given - let prev_nonce = default_account_details().nonce - U256::one(); - let mut txq = TransactionQueue::default(); - // txa, txb - slightly bigger gas price to have consistent ordering - let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); - let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); - - // insert everything - txq.add(txa.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(txb.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - - assert_eq!(txq.status().future, 4); - - // when - txq.penalize(&tx1.hash()); - - // then - let top: Vec<_> = txq.future_transactions().into_iter().map(|tx| tx.transaction).collect(); - assert_eq!(top[0], txa); - assert_eq!(top[1], txb); - assert_eq!(top[2], tx1); - assert_eq!(top[3], tx2); - assert_eq!(top.len(), 4); - } - - #[test] - fn should_not_penalize_local_transactions() { - // given - let mut txq = TransactionQueue::default(); - // txa, txb - slightly bigger gas price to have consistent ordering - let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); - let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); - - // insert everything - txq.add(txa.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(txb.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], txa); - assert_eq!(top[2], tx2); - assert_eq!(top[3], txb); - assert_eq!(top.len(), 4); - - // when - txq.penalize(&tx1.hash()); - - // then (order is the same) - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], txa); - assert_eq!(top[2], tx2); - assert_eq!(top[3], txb); - assert_eq!(top.len(), 4); - } - - #[test] - fn should_penalize_transactions_from_sender() { - // given - let mut txq = TransactionQueue::default(); - // txa, txb - slightly bigger gas price to have consistent ordering - let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); - let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); - - // insert everything - txq.add(txa.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(txb.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - let top = txq.top_transactions(); - assert_eq!(top[0], tx1); - assert_eq!(top[1], txa); - assert_eq!(top[2], tx2); - assert_eq!(top[3], txb); - assert_eq!(top.len(), 4); - - // when - txq.penalize(&tx1.hash()); - - // then - let top = txq.top_transactions(); - assert_eq!(top[0], txa); - assert_eq!(top[1], txb); - assert_eq!(top[2], tx1); - assert_eq!(top[3], tx2); - assert_eq!(top.len(), 4); - } - - #[test] - fn should_return_pending_hashes() { - // given - let mut txq = TransactionQueue::default(); - - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - - // when - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let top = txq.pending_hashes(); - assert_eq!(top[0], tx.hash()); - assert_eq!(top[1], tx2.hash()); - assert_eq!(top.len(), 2); - } - - #[test] - fn should_put_transaction_to_futures_if_gap_detected() { - // given - let mut txq = TransactionQueue::default(); - - let (tx, tx2) = new_tx_pair_default(2.into(), 0.into()); - - // when - let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - assert_eq!(res1, transaction::ImportResult::Current); - assert_eq!(res2, transaction::ImportResult::Future); - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 1); - let top = txq.top_transactions(); - assert_eq!(top.len(), 1); - assert_eq!(top[0], tx); - } - - #[test] - fn should_handle_min_block() { - // given - let mut txq = TransactionQueue::default(); - - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - - // when - let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(transaction::Condition::Number(1)), &default_tx_provider()).unwrap(); - let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - assert_eq!(res1, transaction::ImportResult::Current); - assert_eq!(res2, transaction::ImportResult::Current); - let top = txq.top_transactions_at(0, 0, None); - assert_eq!(top.len(), 0); - let top = txq.top_transactions_at(1, 0, None); - assert_eq!(top.len(), 2); - } - - #[test] - fn should_correctly_update_futures_when_removing() { - // given - let prev_nonce = default_account_details().nonce - U256::one(); - let next2_nonce = default_nonce() + U256::from(3); - - let mut txq = TransactionQueue::default(); - - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(); - assert_eq!(txq.status().future, 2); - - // when - txq.cull(tx.sender(), next2_nonce); - // should remove both transactions since they are not valid - - // then - assert_eq!(txq.status().pending, 0); - assert_eq!(txq.status().future, 0); - } - - #[test] - fn should_move_transactions_if_gap_filled() { - // given - let mut txq = TransactionQueue::default(); - let kp = Random.generate().unwrap(); - let secret = kp.secret(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(secret, None).into(); - let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret, None).into(); - let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret, None).into(); - - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 1); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - - // when - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let stats = txq.status(); - assert_eq!(stats.pending, 3); - assert_eq!(stats.future, 0); - assert_eq!(txq.future.by_priority.len(), 0); - assert_eq!(txq.future.by_address.len(), 0); - assert_eq!(txq.future.by_gas_price.len(), 0); - } - - #[test] - fn should_remove_transaction() { - // given - let mut txq2 = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(3.into(), 0.into()); - txq2.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq2.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq2.status().pending, 1); - assert_eq!(txq2.status().future, 1); - - // when - txq2.cull(tx.sender(), tx.nonce + U256::one()); - txq2.cull(tx2.sender(), tx2.nonce + U256::one()); - - // then - let stats = txq2.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); - } - - #[test] - fn should_move_transactions_to_future_if_gap_introduced() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - let tx3 = new_tx_default(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 3); - - // when - txq.remove(&tx.hash(), &|_| default_nonce(), RemovalReason::Invalid); - - // then - let stats = txq.status(); - assert_eq!(stats.future, 1); - assert_eq!(stats.pending, 1); - } - - #[test] - fn should_clear_queue() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - - // add - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - let stats = txq.status(); - assert_eq!(stats.pending, 2); - - // when - txq.clear(); - - // then - let stats = txq.status(); - assert_eq!(stats.pending, 0); - } - - #[test] - fn should_drop_old_transactions_when_hitting_the_limit() { - // given - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 1, - usize::max_value(), - !U256::zero(), - !U256::zero() - ); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - let sender = tx.sender(); - let nonce = tx.nonce; - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 1); - - // when - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - let t = txq.top_transactions(); - assert_eq!(unwrap_tx_err(res), transaction::Error::InsufficientGasPrice { minimal: 2.into(), got: 1.into() }); - assert_eq!(txq.status().pending, 1); - assert_eq!(t.len(), 1); - assert_eq!(t[0], tx); - assert_eq!(txq.last_nonce(&sender), Some(nonce)); - } - - #[test] - fn should_limit_future_transactions() { - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 1, - usize::max_value(), - !U256::zero(), - !U256::zero(), - ); - txq.current.set_limit(10); - let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); - let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 2); - - // when - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - assert_eq!(txq.status().future, 1); - } - - #[test] - fn should_limit_by_gas() { - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 100, - usize::max_value(), - default_gas_val() * U256::from(2), - !U256::zero() - ); - let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); - let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // limited by gas - txq.add(tx4.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap_err(); - assert_eq!(txq.status().pending, 2); - } - - #[test] - fn should_keep_own_transactions_above_gas_limit() { - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 100, - usize::max_value(), - default_gas_val() * U256::from(2), - !U256::zero() - ); - let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); - let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - let (tx5, _) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - // Not accepted because of limit - txq.add(tx5.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap_err(); - txq.add(tx3.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx4.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 4); - } - - #[test] - fn should_drop_transactions_with_old_nonces() { - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let last_nonce = tx.nonce + U256::one(); - - // when - let res = txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(last_nonce)); - - // then - assert_eq!(unwrap_tx_err(res), transaction::Error::Old); - let stats = txq.status(); - assert_eq!(stats.pending, 0); - assert_eq!(stats.future, 0); - } - - #[test] - fn should_not_insert_same_transaction_twice() { - // given - let nonce = default_account_details().nonce + U256::one(); - let mut txq = TransactionQueue::default(); - let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - assert_eq!(txq.status().pending, 0); - - // when - let res = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)); - - // then - assert_eq!(unwrap_tx_err(res), transaction::Error::AlreadyImported); - let stats = txq.status(); - assert_eq!(stats.future, 1); - assert_eq!(stats.pending, 0); - } - - #[test] - fn should_accept_same_transaction_twice_if_removed() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 2); - - // when - txq.remove(&tx1.hash(), &|_| default_nonce(), RemovalReason::Invalid); - assert_eq!(txq.status().pending, 0); - assert_eq!(txq.status().future, 1); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 2); - } - - #[test] - fn should_not_move_to_future_if_state_nonce_is_higher() { - // given - let mut txq = TransactionQueue::default(); - let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - let tx3 = new_tx_default(); - txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().pending, 3); - - // when - txq.cull(tx.sender(), default_nonce() + U256::one()); - - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 2); - } - - #[test] - fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { - // given - let mut txq = TransactionQueue::default(); - let keypair = Random.generate().unwrap(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 20.into()).sign(keypair.secret(), None); - let tx2 = { - let mut tx2 = (**tx).clone(); - tx2.gas_price = U256::from(21); - tx2.sign(keypair.secret(), None) - }; - - // when - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - let res = txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()); - - // then - assert_eq!(unwrap_tx_err(res), transaction::Error::TooCheapToReplace); - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 0); - assert_eq!(txq.top_transactions()[0].gas_price, U256::from(20)); - } - - #[test] - fn should_replace_same_transaction_when_has_higher_fee() { - // given - let mut txq = TransactionQueue::default(); - let keypair = Random.generate().unwrap(); - let tx = new_unsigned_tx(123.into(), default_gas_val(), 10.into()).sign(keypair.secret(), None); - let tx2 = { - let mut tx2 = (**tx).clone(); - tx2.gas_price = U256::from(20); - tx2.sign(keypair.secret(), None) - }; - - // when - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let stats = txq.status(); - assert_eq!(stats.pending, 1); - assert_eq!(stats.future, 0); - assert_eq!(txq.top_transactions()[0].gas_price, U256::from(20)); - } - - #[test] - fn should_replace_same_transaction_when_importing_to_futures() { - // given - let mut txq = TransactionQueue::default(); - let keypair = Random.generate().unwrap(); - let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret(), None); - let tx1 = { - let mut tx1 = (**tx0).clone(); - tx1.nonce = U256::from(124); - tx1.sign(keypair.secret(), None) - }; - let tx2 = { - let mut tx2 = (**tx1).clone(); - tx2.gas_price = U256::from(200); - tx2.sign(keypair.secret(), None) - }; - - // when - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.status().future, 1); - txq.add(tx0, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 2); - assert_eq!(txq.top_transactions()[1].gas_price, U256::from(200)); - } - - #[test] - fn should_recalculate_height_when_removing_from_future() { - // given - let previous_nonce = default_account_details().nonce - U256::one(); - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(previous_nonce)).unwrap(); - assert_eq!(txq.status().future, 2); - - // when - txq.remove(&tx1.hash(), &|_| default_nonce() + 1.into(), RemovalReason::Invalid); - - // then - let stats = txq.status(); - assert_eq!(stats.future, 0); - assert_eq!(stats.pending, 1); - } - - #[test] - fn should_return_none_when_transaction_from_given_address_does_not_exist() { - // given - let txq = TransactionQueue::default(); - - // then - assert_eq!(txq.last_nonce(&Address::default()), None); - } - - #[test] - fn should_return_correct_nonce_when_transactions_from_given_address_exist() { - // given - let mut txq = TransactionQueue::default(); - let tx = new_tx_default(); - let from = tx.sender(); - let nonce = tx.nonce; - - // when - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce)).unwrap(); - - // then - assert_eq!(txq.last_nonce(&from), Some(nonce)); - } - - #[test] - fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); - - // Insert first transaction - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(); - - // when - txq.cull(tx2.sender(), nonce2 + U256::one()); - - // then - assert!(txq.top_transactions().is_empty()); - } - - #[test] - fn should_return_valid_last_nonce_after_cull() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into()); - let sender = tx1.sender(); - let (nonce1, nonce2) = (tx1.nonce, tx2.nonce); - - // when - // Insert first transaction - assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Current); - // Second should go to future - assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(nonce1)).unwrap(), transaction::ImportResult::Future); - // Now block is imported - txq.cull(sender, nonce2 - U256::from(1)); - // tx2 should be not be promoted to current - assert_eq!(txq.status().pending, 0); - assert_eq!(txq.status().future, 1); - - // then - assert_eq!(txq.last_nonce(&sender), None); - } - - #[test] - fn should_return_true_if_there_is_local_transaction_pending() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - assert_eq!(txq.has_local_pending_transactions(), false); - - // when - assert_eq!(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.has_local_pending_transactions(), false); - assert_eq!(txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(), - transaction::ImportResult::Current); - - // then - assert_eq!(txq.has_local_pending_transactions(), true); - } - - #[test] - fn should_keep_right_order_in_future() { - // given - let mut txq = TransactionQueue::with_limits( - PrioritizationStrategy::GasPriceOnly, - 1, - usize::max_value(), - !U256::zero(), - !U256::zero() - ); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let prev_nonce = default_account_details().nonce - U256::one(); - - // when - assert_eq!(txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); - assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider().with_account_nonce(prev_nonce)).unwrap(), transaction::ImportResult::Future); - - // then - assert_eq!(txq.future.by_priority.len(), 1); - assert_eq!(txq.future.by_priority.iter().next().unwrap().hash, tx1.hash()); - } - - #[test] - fn should_return_correct_last_nonce() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2, tx2_2, tx3) = { - let keypair = Random.generate().unwrap(); - let secret = &keypair.secret(); - let nonce = 123.into(); - let gas = default_gas_val(); - let tx = new_unsigned_tx(nonce, gas, 1.into()); - let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 1.into()); - let tx2_2 = new_unsigned_tx(nonce + 1.into(), gas, 5.into()); - let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into()); - - - (tx.sign(secret, None), tx2.sign(secret, None), tx2_2.sign(secret, None), tx3.sign(secret, None)) - }; - let sender = tx1.sender(); - txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.future.by_priority.len(), 0); - assert_eq!(txq.current.by_priority.len(), 3); - - // when - let res = txq.add(tx2_2, TransactionOrigin::Local, 0, None, &default_tx_provider()); - - // then - assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); - assert_eq!(res.unwrap(), transaction::ImportResult::Current); - assert_eq!(txq.current.by_priority.len(), 3); - } - - #[test] - fn should_reject_transactions_below_base_gas() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let high_gas = 100_001.into(); - - // when - let res1 = txq.add(tx1, TransactionOrigin::Local, 0, None, &default_tx_provider()); - let res2 = txq.add(tx2, TransactionOrigin::Local, 0, None, &default_tx_provider().with_tx_gas_required(high_gas)); - - // then - assert_eq!(res1.unwrap(), transaction::ImportResult::Current); - assert_eq!(unwrap_tx_err(res2), transaction::Error::InsufficientGas { - minimal: 100_001.into(), - got: 100_000.into(), - }); - - } - - #[test] - fn should_clear_all_old_transactions() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); - let next_nonce = |_: &Address| - AccountDetails { nonce: default_nonce() + U256::one(), balance: !U256::zero() }; - - // Insert all transactions - txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx3, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.top_transactions().len(), 4); - - // when - txq.remove_old(&next_nonce, 0); - - // then - assert_eq!(txq.top_transactions().len(), 2); - } - - #[test] - fn should_remove_out_of_date_transactions_occupying_queue() { - // given - let mut txq = TransactionQueue::default(); - let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); - - // Insert all transactions - txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 5, None, &default_tx_provider()).unwrap(); - txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_tx_provider()).unwrap(); - txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - assert_eq!(txq.top_transactions().len(), 3); - assert_eq!(txq.future_transactions().len(), 1); - - // when - txq.remove_old(&default_account_details_for_addr, 9 + super::DEFAULT_QUEUING_PERIOD); - - // then - assert_eq!(txq.top_transactions().len(), 2); - assert_eq!(txq.future_transactions().len(), 0); - assert_eq!(txq.top_transactions(), vec![tx1, tx3]); - } - - #[test] - fn should_accept_local_service_transaction() { - // given - let tx = new_tx(123.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - txq.add(tx, TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - - // then - assert_eq!(txq.top_transactions().len(), 1); - } - - #[test] - fn should_not_accept_external_service_transaction_if_sender_not_certified() { - // given - let tx1 = new_tx(123.into(), 0.into()); - let tx2 = new_tx(456.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - assert_eq!(unwrap_tx_err(txq.add(tx1, TransactionOrigin::External, 0, None, &default_tx_provider())), - transaction::Error::InsufficientGasPrice { - minimal: 100.into(), - got: 0.into(), - }); - assert_eq!(unwrap_tx_err(txq.add(tx2, TransactionOrigin::RetractedBlock, 0, None, &default_tx_provider())), - transaction::Error::InsufficientGasPrice { - minimal: 100.into(), - got: 0.into(), - }); - - // then - assert_eq!(txq.top_transactions().len(), 0); - } - - #[test] - fn should_not_accept_external_service_transaction_if_contract_returns_error() { - // given - let tx = new_tx(123.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - let details_provider = default_tx_provider().service_transaction_checker_returns_error("Contract error"); - assert_eq!(unwrap_tx_err(txq.add(tx, TransactionOrigin::External, 0, None, &details_provider)), - transaction::Error::InsufficientGasPrice { - minimal: 100.into(), - got: 0.into(), - }); - - // then - assert_eq!(txq.top_transactions().len(), 0); - } - - #[test] - fn should_accept_external_service_transaction_if_sender_is_certified() { - // given - let tx = new_tx(123.into(), 0.into()); - let mut txq = TransactionQueue::default(); - txq.set_minimal_gas_price(100.into()); - - // when - let details_provider = default_tx_provider().service_transaction_checker_accepts(true); - txq.add(tx, TransactionOrigin::External, 0, None, &details_provider).unwrap(); - - // then - assert_eq!(txq.top_transactions().len(), 1); - } - - #[test] - fn should_not_order_transactions_by_hash() { - // given - let secret1 = "0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap(); - let secret2 = "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap(); - let tx1 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret1, None); - let tx2 = new_unsigned_tx(123.into(), default_gas_val(), 0.into()).sign(&secret2, None); - let mut txq = TransactionQueue::default(); - - // when - txq.add(tx1.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - txq.add(tx2, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - assert_eq!(txq.top_transactions()[0], tx1); - assert_eq!(txq.top_transactions().len(), 2); - } - - #[test] - fn should_not_return_transactions_over_nonce_cap() { - // given - let keypair = Random.generate().unwrap(); - let mut txq = TransactionQueue::default(); - // when - for nonce in 123..130 { - let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None); - txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - } - - // then - assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4); - } -} From e97e45340555ef042379195599e179c116605a16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 6 Mar 2018 16:35:23 +0100 Subject: [PATCH 32/77] Nonce Cap --- ethcore/src/miner/blockchain_client.rs | 4 +- ethcore/src/miner/miner.rs | 13 ++-- miner/src/pool/client.rs | 5 +- miner/src/pool/mod.rs | 2 +- miner/src/pool/queue.rs | 25 ++++---- miner/src/pool/ready.rs | 85 ++++++++++++++++++++++++-- miner/src/pool/tests/client.rs | 13 +++- miner/src/pool/tests/mod.rs | 54 ++++++++-------- miner/src/pool/tests/tx.rs | 23 +++++-- miner/src/pool/verifier.rs | 1 - 10 files changed, 165 insertions(+), 60 deletions(-) diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index 624c29f84d5..5aba3c94e68 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -138,7 +138,7 @@ impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where } } -impl<'a, C: 'a> pool::client::StateClient for BlockChainClient<'a, C> where +impl<'a, C: 'a> pool::client::NonceClient for BlockChainClient<'a, C> where C: Nonce + Sync, { fn account_nonce(&self, address: &Address) -> U256 { @@ -173,7 +173,7 @@ impl<'a, C: 'a> NonceClient<'a, C> { } } -impl<'a, C: 'a> pool::client::StateClient for NonceClient<'a, C> +impl<'a, C: 'a> pool::client::NonceClient for NonceClient<'a, C> where C: Nonce + Sync, { fn account_nonce(&self, address: &Address) -> U256 { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 52db2762663..726372da101 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -377,7 +377,7 @@ impl Miner { client.clone(), chain_info.best_block_number, chain_info.best_block_timestamp, - // nonce_cap, + nonce_cap, ); for tx in pending { @@ -792,12 +792,14 @@ impl miner::MinerService for Miner { let chain_info = chain.chain_info(); let from_queue = || { - let client = NonceClient::new(chain); - self.transaction_queue.pending( - client, + NonceClient::new(chain), chain_info.best_block_number, chain_info.best_block_timestamp, + // We propagate transactions over the nonce cap. + // The mechanism is only to limit number of transactions in pending block + // those transactions are valid and will just be ready to be included in next block. + None, ) }; @@ -830,8 +832,7 @@ impl miner::MinerService for Miner { fn next_nonce(&self, chain: &C, address: &Address) -> U256 where C: Nonce + Sync, { - let client = NonceClient::new(chain); - self.transaction_queue.next_nonce(client, address) + self.transaction_queue.next_nonce(NonceClient::new(chain), address) .unwrap_or_else(|| chain.latest_nonce(address)) } diff --git a/miner/src/pool/client.rs b/miner/src/pool/client.rs index 4b9a952e937..4243e8d26bf 100644 --- a/miner/src/pool/client.rs +++ b/miner/src/pool/client.rs @@ -64,9 +64,8 @@ pub trait Client: fmt::Debug + Sync { fn transaction_type(&self, tx: &transaction::SignedTransaction) -> TransactionType; } -// TODO [ToDr] Rename to NonceClient -/// State client -pub trait StateClient: fmt::Debug + Sync { +/// State nonce client +pub trait NonceClient: fmt::Debug + Sync { /// Fetch only account nonce for given sender. fn account_nonce(&self, address: &Address) -> U256; } diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 23349d7fed5..f06e7b8f88b 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -30,7 +30,7 @@ pub mod scoring; pub mod verifier; #[cfg(test)] -mod tests; +pub mod tests; pub use self::queue::{TransactionQueue, Status as QueueStatus}; pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 039e61f9624..099d79b2b5a 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -134,8 +134,9 @@ impl TransactionQueue { client: C, block_number: u64, current_timestamp: u64, + nonce_cap: Option, ) -> Vec> where - C: client::StateClient, + C: client::NonceClient, { // TODO [ToDr] Check if timestamp is within limits. let is_valid = |bn| bn == block_number; @@ -157,7 +158,7 @@ impl TransactionQueue { _ => {}, } - let pending: Vec<_> = self.collect_pending(client, block_number, current_timestamp, |i| i.collect()); + let pending: Vec<_> = self.collect_pending(client, block_number, current_timestamp, nonce_cap, |i| i.collect()); *cached_pending = Some((block_number, pending.clone())); pending } @@ -171,10 +172,10 @@ impl TransactionQueue { client: C, block_number: u64, current_timestamp: u64, - // TODO [ToDr] Support nonce_cap + nonce_cap: Option, collect: F, ) -> T where - C: client::StateClient, + C: client::NonceClient, F: FnOnce(txpool::PendingIterator< pool::VerifiedTransaction, (ready::Condition, ready::State), @@ -183,7 +184,7 @@ impl TransactionQueue { >) -> T, { let pending_readiness = ready::Condition::new(block_number, current_timestamp); - let state_readiness = ready::State::new(client); + let state_readiness = ready::State::new(client, nonce_cap); let ready = (pending_readiness, state_readiness); @@ -191,11 +192,12 @@ impl TransactionQueue { } /// Culls all stalled transactions from the pool. - pub fn cull( + pub fn cull( &self, client: C, ) { - let state_readiness = ready::State::new(client); + // We don't care about future transactions, so nonce_cap is not important. + let state_readiness = ready::State::new(client, None); let removed = self.pool.write().cull(None, state_readiness); debug!(target: "txqueue", "Removed {} stalled transactions. {}", removed, self.status()); @@ -203,12 +205,13 @@ impl TransactionQueue { /// Returns next valid nonce for given sender /// or `None` if there are no pending transactions from that sender. - pub fn next_nonce( + pub fn next_nonce( &self, client: C, address: &Address, ) -> Option { - let state_readiness = ready::State::new(client); + // Do not take nonce_cap into account when determining next nonce. + let state_readiness = ready::State::new(client, None); self.pool.read().pending_from_sender(state_readiness, address) .last() @@ -353,7 +356,7 @@ mod tests { } } - impl client::StateClient for TestClient { + impl client::NonceClient for TestClient { /// Fetch only account nonce for given sender. fn account_nonce(&self, _address: &Address) -> U256 { 0.into() @@ -364,7 +367,7 @@ mod tests { fn should_get_pending_transactions() { let queue = TransactionQueue::new(txpool::Options::default(), verifier::Options::default()); - let pending: Vec<_> = queue.pending(TestClient, 0, 0); + let pending: Vec<_> = queue.pending(TestClient, 0, 0, None); for tx in pending { assert!(tx.signed().nonce > 0.into()); diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs index b126c560867..01dc9e93362 100644 --- a/miner/src/pool/ready.rs +++ b/miner/src/pool/ready.rs @@ -20,7 +20,7 @@ //! particular transaction can be included in the block. //! //! Regular transactions are ready iff the current state nonce -//! (obtained from `StateClient`) equals to the transaction nonce. +//! (obtained from `NonceClient`) equals to the transaction nonce. //! //! Let's define `S = state nonce`. Transactions are processed //! in order, so we first include transaction with nonce `S`, @@ -45,7 +45,7 @@ use ethereum_types::{U256, H160 as Address}; use transaction; use txpool::{self, VerifiedTransaction as PoolVerifiedTransaction}; -use super::client::StateClient; +use super::client::NonceClient; use super::VerifiedTransaction; /// Checks readiness of transactions by comparing the nonce to state nonce. @@ -53,20 +53,30 @@ use super::VerifiedTransaction; pub struct State { nonces: HashMap, state: C, + max_nonce: Option, } impl State { /// Create new State checker, given client interface. - pub fn new(state: C) -> Self { + pub fn new(state: C, max_nonce: Option) -> Self { State { nonces: Default::default(), state, + max_nonce, } } } -impl txpool::Ready for State { +impl txpool::Ready for State { fn is_ready(&mut self, tx: &VerifiedTransaction) -> txpool::Readiness { + // Check max nonce + match self.max_nonce { + Some(nonce) if tx.transaction.nonce > nonce => { + return txpool::Readiness::Future; + }, + _ => {}, + } + let sender = tx.sender(); let state = &self.state; // TODO [ToDr] Handle null-sender transactions @@ -110,4 +120,71 @@ impl txpool::Ready for Condition { } } +#[cfg(test)] +mod tests { + use super::*; + use txpool::Ready; + use pool::tests::client::TestClient; + use pool::tests::tx::{Tx, TxExt}; + + #[test] + fn should_return_correct_state_readiness() { + // given + let (tx1, tx2, tx3) = Tx::default().signed_triple(); + let (tx1, tx2, tx3) = (tx1.verified(), tx2.verified(), tx3.verified()); + + // when + assert_eq!(State::new(TestClient::new(), None).is_ready(&tx3), txpool::Readiness::Future); + assert_eq!(State::new(TestClient::new(), None).is_ready(&tx2), txpool::Readiness::Future); + + let mut ready = State::new(TestClient::new(), None); + + // then + assert_eq!(ready.is_ready(&tx1), txpool::Readiness::Ready); + assert_eq!(ready.is_ready(&tx2), txpool::Readiness::Ready); + assert_eq!(ready.is_ready(&tx3), txpool::Readiness::Ready); + } + + #[test] + fn should_return_future_if_nonce_cap_reached() { + // given + let tx = Tx::default().signed().verified(); + + // when + let res1 = State::new(TestClient::new(), Some(10.into())).is_ready(&tx); + let res2 = State::new(TestClient::new(), Some(124.into())).is_ready(&tx); + // then + assert_eq!(res1, txpool::Readiness::Future); + assert_eq!(res2, txpool::Readiness::Ready); + } + + #[test] + fn should_return_stale_if_nonce_does_not_match() { + // given + let tx = Tx::default().signed().verified(); + + // when + let res = State::new(TestClient::new().with_nonce(125), None).is_ready(&tx); + + // then + assert_eq!(res, txpool::Readiness::Stalled); + } + + #[test] + fn should_check_readiness_of_condition() { + // given + let tx = Tx::default().signed(); + let v = |tx: transaction::PendingTransaction| TestClient::new().verify(tx); + let tx1 = v(transaction::PendingTransaction::new(tx.clone(), transaction::Condition::Number(5).into())); + let tx2 = v(transaction::PendingTransaction::new(tx.clone(), transaction::Condition::Timestamp(3).into())); + let tx3 = v(transaction::PendingTransaction::new(tx.clone(), None)); + + // when/then + assert_eq!(Condition::new(0, 0).is_ready(&tx1), txpool::Readiness::Future); + assert_eq!(Condition::new(0, 0).is_ready(&tx2), txpool::Readiness::Future); + assert_eq!(Condition::new(0, 0).is_ready(&tx3), txpool::Readiness::Ready); + assert_eq!(Condition::new(5, 0).is_ready(&tx1), txpool::Readiness::Ready); + assert_eq!(Condition::new(0, 3).is_ready(&tx2), txpool::Readiness::Ready); + } +} diff --git a/miner/src/pool/tests/client.rs b/miner/src/pool/tests/client.rs index ba74e2e27e1..7f7be64cc8d 100644 --- a/miner/src/pool/tests/client.rs +++ b/miner/src/pool/tests/client.rs @@ -72,6 +72,17 @@ impl TestClient { self.is_service_transaction = true; self } + + pub fn verify>(&self, tx: T) -> pool::VerifiedTransaction { + let tx = tx.into(); + pool::VerifiedTransaction { + hash: tx.hash(), + sender: tx.sender(), + priority: pool::Priority::Regular, + transaction: tx, + insertion_id: 1, + } + } } impl pool::client::Client for TestClient { @@ -107,7 +118,7 @@ impl pool::client::Client for TestClient { } } -impl pool::client::StateClient for TestClient { +impl pool::client::NonceClient for TestClient { fn account_nonce(&self, _address: &Address) -> U256 { self.account_details.nonce } diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index 26de38c9d60..895f961ee7c 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -20,8 +20,8 @@ use txpool; use pool::{verifier, TransactionQueue}; -mod tx; -mod client; +pub mod tx; +pub mod client; use self::tx::{Tx, TxExt, PairExt}; use self::client::TestClient; @@ -102,7 +102,7 @@ fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { // and then there should be only one transaction in current (the one with higher gas_price) assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); - let top = txq.pending(TestClient::new(), 0, 0); + let top = txq.pending(TestClient::new(), 0, 0, None); assert_eq!(top[0].hash, hash); } @@ -127,7 +127,7 @@ fn should_move_all_transactions_from_future() { // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); - let top = txq.pending(TestClient::new(), 0, 0); + let top = txq.pending(TestClient::new(), 0, 0, None); assert_eq!(top[0].hash, hash); assert_eq!(top[1].hash, hash2); } @@ -201,7 +201,7 @@ fn should_import_txs_from_same_sender() { txq.import(TestClient::new(), txs.local().into_vec()); // then - let top = txq.pending(TestClient::new(), 0 ,0); + let top = txq.pending(TestClient::new(), 0 ,0, None); assert_eq!(top[0].hash, hash); assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); @@ -223,7 +223,7 @@ fn should_prioritize_local_transactions_within_same_nonce_height() { assert_eq!(res, vec![Ok(()), Ok(())]); // then - let top = txq.pending(client, 0, 0); + let top = txq.pending(client, 0, 0, None); assert_eq!(top[0].hash, hash); // local should be first assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); @@ -245,7 +245,7 @@ fn should_prioritize_reimported_transactions_within_same_nonce_height() { assert_eq!(res, vec![Ok(()), Ok(())]); // then - let top = txq.pending(TestClient::new(), 0, 0); + let top = txq.pending(TestClient::new(), 0, 0, None); assert_eq!(top[0].hash, hash); // retracted should be first assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); @@ -264,7 +264,7 @@ fn should_not_prioritize_local_transactions_with_different_nonce_height() { assert_eq!(res, vec![Ok(()), Ok(())]); // then - let top = txq.pending(TestClient::new(), 0, 0); + let top = txq.pending(TestClient::new(), 0, 0, None); assert_eq!(top[0].hash, hash); assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); @@ -282,7 +282,7 @@ fn should_put_transaction_to_futures_if_gap_detected() { // then assert_eq!(res, vec![Ok(()), Ok(())]); - let top = txq.pending(TestClient::new(), 0, 0); + let top = txq.pending(TestClient::new(), 0, 0, None); assert_eq!(top.len(), 1); assert_eq!(top[0].hash, hash); } @@ -302,9 +302,9 @@ fn should_handle_min_block() { assert_eq!(res, vec![Ok(()), Ok(())]); // then - let top = txq.pending(TestClient::new(), 0, 0); + let top = txq.pending(TestClient::new(), 0, 0, None); assert_eq!(top.len(), 0); - let top = txq.pending(TestClient::new(), 1, 0); + let top = txq.pending(TestClient::new(), 1, 0, None); assert_eq!(top.len(), 2); } @@ -335,7 +335,7 @@ fn should_move_transactions_if_gap_filled() { let res = txq.import(TestClient::new(), vec![tx, tx2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1); // when let res = txq.import(TestClient::new(), vec![tx1.local()]); @@ -343,7 +343,7 @@ fn should_move_transactions_if_gap_filled() { // then assert_eq!(txq.status().status.transaction_count, 3); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 3); } #[test] @@ -355,12 +355,12 @@ fn should_remove_transaction() { let res = txq.import(TestClient::default(), vec![tx, tx2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1); // when txq.cull(TestClient::new().with_nonce(124)); assert_eq!(txq.status().status.transaction_count, 1); - assert_eq!(txq.pending(TestClient::new().with_nonce(125), 0, 0).len(), 1); + assert_eq!(txq.pending(TestClient::new().with_nonce(125), 0, 0, None).len(), 1); txq.cull(TestClient::new().with_nonce(126)); // then @@ -378,19 +378,19 @@ fn should_move_transactions_to_future_if_gap_introduced() { let res = txq.import(TestClient::new(), vec![tx3, tx2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1); let res = txq.import(TestClient::new(), vec![tx].local()); assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 3); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 3); // when txq.remove(vec![&hash], true); // then assert_eq!(txq.status().status.transaction_count, 2); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1); } #[test] @@ -440,7 +440,7 @@ fn should_prefer_current_transactions_when_hitting_the_limit() { assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); - let top = txq.pending(TestClient::new(), 0, 0); + let top = txq.pending(TestClient::new(), 0, 0, None); assert_eq!(top.len(), 1); assert_eq!(top[0].hash, hash); assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(124.into())); @@ -488,19 +488,19 @@ fn should_accept_same_transaction_twice_if_removed() { let res = txq.import(TestClient::new(), txs.local().into_vec()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 2); // when txq.remove(vec![&hash], true); assert_eq!(txq.status().status.transaction_count, 1); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 0); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 0); let res = txq.import(TestClient::new(), vec![tx1].local()); assert_eq!(res, vec![Ok(())]); // then assert_eq!(txq.status().status.transaction_count, 2); - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 2); } #[test] @@ -520,8 +520,8 @@ fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { // then assert_eq!(res, vec![Err(transaction::Error::TooCheapToReplace), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); - assert_eq!(txq.pending(client.clone(), 0, 0)[0].signed().gas_price, U256::from(20)); - assert_eq!(txq.pending(client.clone(), 0, 0)[1].signed().gas_price, U256::from(2)); + assert_eq!(txq.pending(client.clone(), 0, 0, None)[0].signed().gas_price, U256::from(20)); + assert_eq!(txq.pending(client.clone(), 0, 0, None)[1].signed().gas_price, U256::from(2)); } #[test] @@ -563,7 +563,7 @@ fn should_return_valid_last_nonce_after_cull() { let client = TestClient::new().with_nonce(124); txq.cull(client.clone()); // tx2 should be not be promoted to current - assert_eq!(txq.pending(client.clone(), 0, 0).len(), 0); + assert_eq!(txq.pending(client.clone(), 0, 0, None).len(), 0); // then assert_eq!(txq.next_nonce(client.clone(), &sender), None); @@ -648,7 +648,7 @@ fn should_accept_local_transactions_below_min_gas_price() { assert_eq!(res, vec![Ok(())]); // then - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1); } #[test] @@ -666,7 +666,7 @@ fn should_accept_local_service_transaction() { assert_eq!(res, vec![Ok(())]); // then - assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 1); + assert_eq!(txq.pending(TestClient::new(), 0, 0, None).len(), 1); } #[test] diff --git a/miner/src/pool/tests/tx.rs b/miner/src/pool/tests/tx.rs index 35bf4296e6a..ee0a7390eed 100644 --- a/miner/src/pool/tests/tx.rs +++ b/miner/src/pool/tests/tx.rs @@ -19,7 +19,7 @@ use ethkey::{Random, Generator}; use rustc_hex::FromHex; use transaction::{self, Transaction, SignedTransaction, UnverifiedTransaction}; -use pool::verifier; +use pool::{verifier, VerifiedTransaction}; #[derive(Clone)] pub struct Tx { @@ -90,6 +90,7 @@ impl Tx { } pub trait TxExt: Sized { type Out; + type Verified; type Hash; fn hash(&self) -> Self::Hash; @@ -99,23 +100,28 @@ pub trait TxExt: Sized { fn retracted(self) -> Self::Out; fn unverified(self) -> Self::Out; + + fn verified(self) -> Self::Verified; } -impl TxExt for (A, B) where - A: TxExt, - B: TxExt, +impl TxExt for (A, B) where + A: TxExt, + B: TxExt, { type Out = (O, O); + type Verified = (V, V); type Hash = (H, H); fn hash(&self) -> Self::Hash { (self.0.hash(), self.1.hash()) } fn local(self) -> Self::Out { (self.0.local(), self.1.local()) } fn retracted(self) -> Self::Out { (self.0.retracted(), self.1.retracted()) } fn unverified(self) -> Self::Out { (self.0.unverified(), self.1.unverified()) } + fn verified(self) -> Self::Verified { (self.0.verified(), self.1.verified()) } } impl TxExt for SignedTransaction { type Out = verifier::Transaction; + type Verified = VerifiedTransaction; type Hash = H256; fn hash(&self) -> Self::Hash { @@ -133,10 +139,15 @@ impl TxExt for SignedTransaction { fn unverified(self) -> Self::Out { verifier::Transaction::Unverified(self.into()) } + + fn verified(self) -> Self::Verified { + VerifiedTransaction::from_pending_block_transaction(self) + } } impl TxExt for Vec { type Out = Vec; + type Verified = Vec; type Hash = Vec; fn hash(&self) -> Self::Hash { @@ -154,6 +165,10 @@ impl TxExt for Vec { fn unverified(self) -> Self::Out { self.into_iter().map(Into::into).map(verifier::Transaction::Unverified).collect() } + + fn verified(self) -> Self::Verified { + self.into_iter().map(VerifiedTransaction::from_pending_block_transaction).collect() + } } pub trait PairExt { diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 57e2b29e912..5d73ca18bef 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -133,7 +133,6 @@ impl txpool::Verifier for Verifier { type Error = transaction::Error; type VerifiedTransaction = VerifiedTransaction; - // TODO [ToDr] Add recently rejected. fn verify_transaction(&self, tx: Transaction) -> Result { // The checks here should be ordered by cost/complexity. // Cheap checks should be done as early as possible to discard unneeded transactions early. From f079b91eea86df6fdc1d80b0da3cb0232d900635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 7 Mar 2018 14:10:56 +0100 Subject: [PATCH 33/77] Nonce cap cache and tests. --- miner/src/pool/queue.rs | 107 +++++++++++++++++++++++++++++------- miner/src/pool/tests/mod.rs | 51 +++++++++++++---- miner/src/pool/verifier.rs | 2 +- 3 files changed, 127 insertions(+), 33 deletions(-) diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 099d79b2b5a..61c0e6663bc 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -32,6 +32,14 @@ use pool::local_transactions::LocalTransactionsList; type Listener = (LocalTransactionsList, (listener::Notifier, listener::Logger)); type Pool = txpool::Pool; +/// Max cache time in milliseconds for pending transactions. +/// +/// Pending transactions are cached and will only be computed again +/// if last cache has been created earler than `TIMESTAMP_CACHE` ms ago. +/// This timeout applies only if there are local pending transactions +/// since it only affects transaction Condition. +const TIMESTAMP_CACHE: u64 = 1000; + /// Transaction queue status #[derive(Debug, Clone, PartialEq)] pub struct Status { @@ -59,6 +67,66 @@ impl fmt::Display for Status { } } +#[derive(Debug)] +struct CachedPending { + block_number: u64, + current_timestamp: u64, + nonce_cap: Option, + has_local_pending: bool, + pending: Option>>, +} + +impl CachedPending { + /// Creates new `CachedPending` without cached set. + pub fn none() -> Self { + CachedPending { + block_number: 0, + current_timestamp: 0, + has_local_pending: false, + pending: None, + nonce_cap: None, + } + } + + /// Remove cached pending set. + pub fn clear(&mut self) { + self.pending = None; + } + + /// Returns cached pending set (if any) if it's valid. + pub fn pending( + &self, + block_number: u64, + current_timestamp: u64, + nonce_cap: Option<&U256>, + ) -> Option>> { + // First check if we have anything in cache. + let pending = self.pending.as_ref()?; + + if block_number != self.block_number { + return None; + } + + // In case we don't have any local pending transactions + // there is no need to invalidate the cache because of timestamp. + // Timestamp only affects local `PendingTransactions` with `Condition::Timestamp`. + if self.has_local_pending && current_timestamp > self.current_timestamp + TIMESTAMP_CACHE { + return None; + } + + // It's fine to return limited set even if `nonce_cap` is `None`. + // The worst thing that may happen is that some transactions won't get propagated in current round, + // but they are not really valid in current block anyway. We will propagate them in the next round. + // Also there is no way to have both `Some` with different numbers since it depends on the block number + // and a constant parameter in schedule (`nonce_cap_increment`) + if self.nonce_cap.is_none() && nonce_cap.is_some() { + return None; + } + + Some(pending.clone()) + } +} + /// Ethereum Transaction Queue /// /// Responsible for: @@ -70,7 +138,7 @@ pub struct TransactionQueue { insertion_id: Arc, pool: RwLock, options: RwLock, - cached_pending: RwLock>)>>, + cached_pending: RwLock, } impl TransactionQueue { @@ -80,7 +148,7 @@ impl TransactionQueue { insertion_id: Default::default(), pool: RwLock::new(txpool::Pool::new(Default::default(), scoring::GasPrice, limits)), options: RwLock::new(verification_options), - cached_pending: RwLock::new(None), + cached_pending: RwLock::new(CachedPending::none()), } } @@ -118,7 +186,7 @@ impl TransactionQueue { .collect::>(); if results.iter().any(|r| r.is_ok()) { - *self.cached_pending.write() = None; + self.cached_pending.write().clear(); } results @@ -138,28 +206,27 @@ impl TransactionQueue { ) -> Vec> where C: client::NonceClient, { - // TODO [ToDr] Check if timestamp is within limits. - let is_valid = |bn| bn == block_number; - { - let cached_pending = self.cached_pending.read(); - match *cached_pending { - Some((bn, ref pending)) if is_valid(bn) => { - return pending.clone() - }, - _ => {}, - } + + if let Some(pending) = self.cached_pending.read().pending(block_number, current_timestamp, nonce_cap.as_ref()) { + return pending; } + // Double check after acquiring write lock let mut cached_pending = self.cached_pending.write(); - match *cached_pending { - Some((bn, ref pending)) if is_valid(bn) => { - return pending.clone() - }, - _ => {}, + if let Some(pending) = cached_pending.pending(block_number, current_timestamp, nonce_cap.as_ref()) { + return pending; } let pending: Vec<_> = self.collect_pending(client, block_number, current_timestamp, nonce_cap, |i| i.collect()); - *cached_pending = Some((block_number, pending.clone())); + + *cached_pending = CachedPending { + block_number, + current_timestamp, + nonce_cap, + has_local_pending: self.has_local_pending_transactions(), + pending: Some(pending.clone()), + }; + pending } @@ -250,7 +317,7 @@ impl TransactionQueue { }; if results.iter().any(Option::is_some) { - *self.cached_pending.write() = None; + self.cached_pending.write().clear(); } results diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index 895f961ee7c..674172bc86a 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -697,16 +697,43 @@ fn should_not_accept_external_service_transaction_if_sender_not_certified() { #[test] fn should_not_return_transactions_over_nonce_cap() { - assert_eq!(true, false); - // // given - // let keypair = Random.generate().unwrap(); - // let txq = new_queue(); - // // when - // for nonce in 123..130 { - // let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None); - // txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // } - // - // // then - // assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4); + // given + let txq = new_queue(); + let (tx1, tx2, tx3) = Tx::default().signed_triple(); + let res = txq.import( + TestClient::new(), + vec![tx1, tx2, tx3].local() + ); + assert_eq!(res, vec![Ok(()), Ok(()), Ok(())]); + + // when + let all = txq.pending(TestClient::new(), 0, 0, None); + // This should invalidate the cache! + let limited = txq.pending(TestClient::new(), 0, 0, Some(123.into())); + + + // then + assert_eq!(all.len(), 3); + assert_eq!(limited.len(), 1); +} + +#[test] +fn should_clear_cache_after_timeout_for_local() { + // given + let txq = new_queue(); + let (tx, tx2) = Tx::default().signed_pair(); + let res = txq.import(TestClient::new(), vec![ + verifier::Transaction::Local(PendingTransaction::new(tx, transaction::Condition::Timestamp(1000).into())), + tx2.local() + ]); + assert_eq!(res, vec![Ok(()), Ok(())]); + + // This should populate cache and set timestamp to 1 + // when + assert_eq!(txq.pending(TestClient::new(), 0, 1, None).len(), 0); + assert_eq!(txq.pending(TestClient::new(), 0, 1000, None).len(), 0); + + // This should invalidate the cache and trigger transaction ready. + // then + assert_eq!(txq.pending(TestClient::new(), 0, 1002, None).len(), 2); } diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 5d73ca18bef..530ca720b37 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -257,7 +257,7 @@ impl txpool::Verifier for Verifier { bail!(transaction::Error::Old); } - let priority = match (account_details.is_local, is_retracted) { + let priority = match (is_own || account_details.is_local, is_retracted) { (true, _) => super::Priority::Local, (false, false) => super::Priority::Regular, (false, true) => super::Priority::Retracted, From 41a5acc7cb89e9765d349721681ad8ea042102d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 7 Mar 2018 15:17:35 +0100 Subject: [PATCH 34/77] Remove stale future transactions from the queue. --- miner/src/pool/mod.rs | 4 +-- miner/src/pool/queue.rs | 31 +++++++++++++++++----- miner/src/pool/ready.rs | 40 +++++++++++++++++++++------- miner/src/pool/scoring.rs | 2 -- miner/src/pool/tests/mod.rs | 53 ++++++++++++++++++++++--------------- 5 files changed, 90 insertions(+), 40 deletions(-) diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index f06e7b8f88b..aa22d4ab73b 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -63,7 +63,7 @@ impl Priority { #[derive(Debug, PartialEq, Eq)] pub struct VerifiedTransaction { transaction: transaction::PendingTransaction, - // TODO [ToDr] hash and sender should go directly from transaction + // TODO [ToDr] hash and sender should go directly from the transaction hash: H256, sender: Address, priority: Priority, @@ -75,7 +75,7 @@ impl VerifiedTransaction { /// /// This method should be used only: /// 1. for tests - /// 2. In case we are converting pending block transactions that are already in the queue to match function signature. + /// 2. In case we are converting pending block transactions that are already in the queue to match the function signature. pub fn from_pending_block_transaction(tx: transaction::SignedTransaction) -> Self { let hash = tx.hash(); let sender = tx.sender(); diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 61c0e6663bc..b3976b5c971 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -16,9 +16,9 @@ //! Ethereum Transaction Queue -use std::fmt; +use std::{cmp, fmt}; use std::sync::Arc; -use std::sync::atomic::AtomicUsize; +use std::sync::atomic::{self, AtomicUsize}; use std::collections::BTreeMap; use ethereum_types::{H256, U256, Address}; @@ -62,7 +62,7 @@ impl fmt::Display for Status { mem = self.status.mem_usage / 1024, mem_max = self.limits.max_mem_usage / 1024, gp = self.options.minimal_gas_price / 1_000_000.into(), - max_gas = ::std::cmp::min(self.options.block_gas_limit, self.options.tx_gas_limit), + max_gas = cmp::min(self.options.block_gas_limit, self.options.tx_gas_limit), ) } } @@ -251,7 +251,9 @@ impl TransactionQueue { >) -> T, { let pending_readiness = ready::Condition::new(block_number, current_timestamp); - let state_readiness = ready::State::new(client, nonce_cap); + // don't mark any transactions as stale at this point. + let stale_id = None; + let state_readiness = ready::State::new(client, stale_id, nonce_cap); let ready = (pending_readiness, state_readiness); @@ -264,7 +266,20 @@ impl TransactionQueue { client: C, ) { // We don't care about future transactions, so nonce_cap is not important. - let state_readiness = ready::State::new(client, None); + let nonce_cap = None; + // We want to clear stale transactions from the queue as well. + // (Transactions that are occuping the queue for a long time without being included) + let stale_id = { + let current_id = self.insertion_id.load(atomic::Ordering::Relaxed) as u64; + // wait at least for half of the queue to be replaced + let gap = self.pool.read().options().max_count / 2; + // but never less than 100 transactions + let gap = cmp::max(100, gap) as u64; + + current_id.checked_sub(gap) + }; + + let state_readiness = ready::State::new(client, stale_id, nonce_cap); let removed = self.pool.write().cull(None, state_readiness); debug!(target: "txqueue", "Removed {} stalled transactions. {}", removed, self.status()); @@ -278,7 +293,11 @@ impl TransactionQueue { address: &Address, ) -> Option { // Do not take nonce_cap into account when determining next nonce. - let state_readiness = ready::State::new(client, None); + let nonce_cap = None; + // Also we ignore stale transactions in the queue. + let stale_id = None; + + let state_readiness = ready::State::new(client, stale_id, nonce_cap); self.pool.read().pending_from_sender(state_readiness, address) .last() diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs index 01dc9e93362..367c9e48eee 100644 --- a/miner/src/pool/ready.rs +++ b/miner/src/pool/ready.rs @@ -54,15 +54,21 @@ pub struct State { nonces: HashMap, state: C, max_nonce: Option, + stale_id: Option, } impl State { /// Create new State checker, given client interface. - pub fn new(state: C, max_nonce: Option) -> Self { + pub fn new( + state: C, + stale_id: Option, + max_nonce: Option, + ) -> Self { State { nonces: Default::default(), state, max_nonce, + stale_id, } } } @@ -77,13 +83,17 @@ impl txpool::Ready for State { _ => {}, } + let sender = tx.sender(); let state = &self.state; - // TODO [ToDr] Handle null-sender transactions let state_nonce = || state.account_nonce(sender); let nonce = self.nonces.entry(*sender).or_insert_with(state_nonce); match tx.transaction.nonce.cmp(nonce) { - cmp::Ordering::Greater => txpool::Readiness::Future, + // Before marking as future check for stale ids + cmp::Ordering::Greater => match self.stale_id { + Some(id) if tx.insertion_id() < id => txpool::Readiness::Stalled, + _ => txpool::Readiness::Future, + }, cmp::Ordering::Less => txpool::Readiness::Stalled, cmp::Ordering::Equal => { *nonce = *nonce + 1.into(); @@ -134,10 +144,10 @@ mod tests { let (tx1, tx2, tx3) = (tx1.verified(), tx2.verified(), tx3.verified()); // when - assert_eq!(State::new(TestClient::new(), None).is_ready(&tx3), txpool::Readiness::Future); - assert_eq!(State::new(TestClient::new(), None).is_ready(&tx2), txpool::Readiness::Future); + assert_eq!(State::new(TestClient::new(), None, None).is_ready(&tx3), txpool::Readiness::Future); + assert_eq!(State::new(TestClient::new(), None, None).is_ready(&tx2), txpool::Readiness::Future); - let mut ready = State::new(TestClient::new(), None); + let mut ready = State::new(TestClient::new(), None, None); // then assert_eq!(ready.is_ready(&tx1), txpool::Readiness::Ready); @@ -151,8 +161,8 @@ mod tests { let tx = Tx::default().signed().verified(); // when - let res1 = State::new(TestClient::new(), Some(10.into())).is_ready(&tx); - let res2 = State::new(TestClient::new(), Some(124.into())).is_ready(&tx); + let res1 = State::new(TestClient::new(), None, Some(10.into())).is_ready(&tx); + let res2 = State::new(TestClient::new(), None, Some(124.into())).is_ready(&tx); // then assert_eq!(res1, txpool::Readiness::Future); @@ -165,7 +175,19 @@ mod tests { let tx = Tx::default().signed().verified(); // when - let res = State::new(TestClient::new().with_nonce(125), None).is_ready(&tx); + let res = State::new(TestClient::new().with_nonce(125), None, None).is_ready(&tx); + + // then + assert_eq!(res, txpool::Readiness::Stalled); + } + + #[test] + fn should_return_stale_for_old_transactions() { + // given + let (_, tx) = Tx::default().signed_pair().verified(); + + // when + let res = State::new(TestClient::new(), Some(1), None).is_ready(&tx); // then assert_eq!(res, txpool::Readiness::Stalled); diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs index 4ba0f259901..964443a8889 100644 --- a/miner/src/pool/scoring.rs +++ b/miner/src/pool/scoring.rs @@ -51,7 +51,6 @@ impl txpool::Scoring for GasPrice { type Score = U256; fn compare(&self, old: &VerifiedTransaction, other: &VerifiedTransaction) -> cmp::Ordering { - // TODO [ToDr] Handle null-sender transactions old.transaction.nonce.cmp(&other.transaction.nonce) } @@ -64,7 +63,6 @@ impl txpool::Scoring for GasPrice { let new_gp = new.transaction.gas_price; let min_required_gp = old_gp + (old_gp >> GAS_PRICE_BUMP_SHIFT); - // TODO [ToDr] Handle null-sender transactions match min_required_gp.cmp(&new_gp) { cmp::Ordering::Greater => txpool::scoring::Choice::RejectNew, _ => txpool::scoring::Choice::ReplaceOld, diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index 674172bc86a..779e8c58d73 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -604,27 +604,38 @@ fn should_reject_transactions_below_base_gas() { #[test] fn should_remove_out_of_date_transactions_occupying_queue() { - assert_eq!(true, false); - // given - // let txq = new_queue(); - // let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - // let (tx3, tx4) = new_tx_pair_default(2.into(), 0.into()); - // - // // Insert all transactions - // txq.add(tx1.clone(), TransactionOrigin::Local, 0, None, &default_tx_provider()).unwrap(); - // txq.add(tx2, TransactionOrigin::External, 5, None, &default_tx_provider()).unwrap(); - // txq.add(tx3.clone(), TransactionOrigin::External, 10, None, &default_tx_provider()).unwrap(); - // txq.add(tx4, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - // assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 3); - // assert_eq!(txq.future_transactions().len(), 1); - // - // // when - // txq.remove_old(&default_account_details_for_addr, 9 + super::DEFAULT_QUEUING_PERIOD); - // - // // then - // assert_eq!(txq.pending(TestClient::new(), 0, 0).len(), 2); - // assert_eq!(txq.future_transactions().len(), 0); - // assert_eq!(txq.pending(TestClient::new(), 0, 0), vec![tx1, tx3]); + // given + let txq = TransactionQueue::new( + txpool::Options { + max_count: 105, + max_per_sender: 3, + max_mem_usage: 5_000_000, + }, + verifier::Options { + minimal_gas_price: 10.into(), + ..Default::default() + }, + ); + // that transaction will be occupying the queue + let (_, tx) = Tx::default().signed_pair(); + let res = txq.import(TestClient::new(), vec![tx.local()]); + assert_eq!(res, vec![Ok(())]); + // This should not clear the transaction (yet) + txq.cull(TestClient::new()); + assert_eq!(txq.status().status.transaction_count, 1); + + // Now insert at least 100 transactions to have the other one marked as future. + for _ in 0..34 { + let (tx1, tx2, tx3) = Tx::default().signed_triple(); + txq.import(TestClient::new(), vec![tx1, tx2, tx3].local()); + } + assert_eq!(txq.status().status.transaction_count, 103); + + // when + txq.cull(TestClient::new()); + + // then + assert_eq!(txq.status().status.transaction_count, 102); } #[test] From 35c77832ff8e6c8cdcd00b95bb413b0ac68324c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 7 Mar 2018 20:43:33 +0100 Subject: [PATCH 35/77] Optimize scoring and write some tests. --- miner/src/pool/scoring.rs | 80 ++++++++++++++++++++++++---- transaction-pool/src/scoring.rs | 3 +- transaction-pool/src/transactions.rs | 4 ++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs index 964443a8889..33c714b226e 100644 --- a/miner/src/pool/scoring.rs +++ b/miner/src/pool/scoring.rs @@ -63,22 +63,28 @@ impl txpool::Scoring for GasPrice { let new_gp = new.transaction.gas_price; let min_required_gp = old_gp + (old_gp >> GAS_PRICE_BUMP_SHIFT); + match min_required_gp.cmp(&new_gp) { cmp::Ordering::Greater => txpool::scoring::Choice::RejectNew, _ => txpool::scoring::Choice::ReplaceOld, } } - fn update_scores(&self, txs: &[Arc], scores: &mut [U256], _change: txpool::scoring::Change) { - // TODO [ToDr] Optimize - for i in 0..txs.len() { - scores[i] = txs[i].transaction.gas_price; - let boost = match txs[i].priority() { - super::Priority::Local => 10, - super::Priority::Retracted => 5, - super::Priority::Regular => 0, - }; - scores[i] = scores[i].saturating_add(scores[i] << boost); + fn update_scores(&self, txs: &[Arc], scores: &mut [U256], change: txpool::scoring::Change) { + use self::txpool::scoring::Change; + + match change { + Change::Culled(_) => {}, + Change::RemovedAt(_) => {} + Change::InsertedAt(i) | Change::ReplacedAt(i) => { + scores[i] = txs[i].transaction.gas_price; + let boost = match txs[i].priority() { + super::Priority::Local => 15, + super::Priority::Retracted => 10, + super::Priority::Regular => 0, + }; + scores[i] = scores[i] << boost; + }, } } @@ -93,3 +99,57 @@ impl txpool::Scoring for GasPrice { self.choose(old, new) == txpool::scoring::Choice::ReplaceOld } } + +#[cfg(test)] +mod tests { + use super::*; + + use pool::tests::tx::{Tx, TxExt}; + use txpool::Scoring; + + #[test] + fn should_calculate_score_correctly() { + // given + let scoring = GasPrice; + let (tx1, tx2, tx3) = Tx::default().signed_triple(); + let transactions = vec![tx1, tx2, tx3].into_iter().enumerate().map(|(i, tx)| { + let mut verified = tx.verified(); + verified.priority = match i { + 0 => ::pool::Priority::Local, + 1 => ::pool::Priority::Retracted, + _ => ::pool::Priority::Regular, + }; + Arc::new(verified) + }).collect::>(); + let initial_scores = vec![U256::from(0), 0.into(), 0.into()]; + + // No update required + let mut scores = initial_scores.clone(); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::Culled(0)); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::Culled(1)); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::Culled(2)); + assert_eq!(scores, initial_scores); + let mut scores = initial_scores.clone(); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::RemovedAt(0)); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::RemovedAt(1)); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::RemovedAt(2)); + assert_eq!(scores, initial_scores); + + // Compute score at given index + let mut scores = initial_scores.clone(); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::InsertedAt(0)); + assert_eq!(scores, vec![32768.into(), 0.into(), 0.into()]); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::InsertedAt(1)); + assert_eq!(scores, vec![32768.into(), 1024.into(), 0.into()]); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::InsertedAt(2)); + assert_eq!(scores, vec![32768.into(), 1024.into(), 1.into()]); + + let mut scores = initial_scores.clone(); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::ReplacedAt(0)); + assert_eq!(scores, vec![32768.into(), 0.into(), 0.into()]); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::ReplacedAt(1)); + assert_eq!(scores, vec![32768.into(), 1024.into(), 0.into()]); + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::ReplacedAt(2)); + assert_eq!(scores, vec![32768.into(), 1024.into(), 1.into()]); + } +} diff --git a/transaction-pool/src/scoring.rs b/transaction-pool/src/scoring.rs index c2300f4235b..7046381f20e 100644 --- a/transaction-pool/src/scoring.rs +++ b/transaction-pool/src/scoring.rs @@ -56,7 +56,8 @@ pub enum Change { /// The score at that index needs to be update (it contains value from previous transaction). ReplacedAt(usize), /// Given number of stalled transactions has been culled from the beginning. - /// Usually the score will have to be re-computed from scratch. + /// The scores has been removed from the beginning as well. + /// For simple scoring algorithms no action is required here. Culled(usize), } diff --git a/transaction-pool/src/transactions.rs b/transaction-pool/src/transactions.rs index 39fd08e931c..8e641591671 100644 --- a/transaction-pool/src/transactions.rs +++ b/transaction-pool/src/transactions.rs @@ -192,6 +192,10 @@ impl> Transactions { } } + if first_non_stalled == 0 { + return result; + } + // reverse the vectors to easily remove first elements. self.transactions.reverse(); self.scores.reverse(); From 886224a530101becc2175430336011512af79854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 8 Mar 2018 12:31:54 +0100 Subject: [PATCH 36/77] Simple penalization. --- ethcore/src/miner/miner.rs | 94 +++++++++++++++------------ miner/src/pool/queue.rs | 5 ++ miner/src/pool/scoring.rs | 24 +++++-- transaction-pool/src/pool.rs | 17 +++++ transaction-pool/src/scoring.rs | 9 ++- transaction-pool/src/tests/helpers.rs | 17 +++-- transaction-pool/src/tests/mod.rs | 62 +++++++++++++++++- transaction-pool/src/transactions.rs | 4 ++ 8 files changed, 179 insertions(+), 53 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 726372da101..8cbc9dfcd1e 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -63,25 +63,38 @@ pub enum PendingSet { SealingOrElseQueue, } -// /// Transaction queue banning settings. -// #[derive(Debug, PartialEq, Clone)] -// pub enum Banning { -// /// Banning in transaction queue is disabled -// Disabled, -// /// Banning in transaction queue is enabled -// Enabled { -// /// Upper limit of transaction processing time before banning. -// offend_threshold: Duration, -// /// Number of similar offending transactions before banning. -// min_offends: u16, -// /// Number of seconds the offender is banned for. -// ban_duration: Duration, -// }, -// } -// -// +/// Transaction queue penalization settings. +/// +/// Senders of long-running transactions (above defined threshold) +/// will get lower priority. +#[derive(Debug, PartialEq, Clone)] +pub enum Penalization { + /// Penalization in transaction queue is disabled + Disabled, + /// Penalization in transaction queue is enabled + Enabled { + /// Upper limit of transaction processing time before penalizing. + offend_threshold: Duration, + }, +} + +/// Initial minimal gas price. +/// +/// Gas price should be later overwritten externally +/// for instance by a dynamic gas price mechanism or CLI parameter. +/// This constant controls the initial value. const DEFAULT_MINIMAL_GAS_PRICE: u64 = 20_000_000_000; +/// Allowed number of skipped transactions when constructing pending block. +/// +/// When we push transactions to pending block, some of the transactions might +/// get skipped because of block gas limit being reached. +/// This constant controls how many transactions we can skip because of that +/// before stopping attempts to push more transactions to the block. +/// This is an optimization that prevents traversing the entire pool +/// in case we have only a fraction of available block gas limit left. +const MAX_SKIPPED_TRANSACTIONS: usize = 8; + /// Configures the behaviour of the miner. #[derive(Debug, PartialEq)] pub struct MinerOptions { @@ -111,6 +124,8 @@ pub struct MinerOptions { // / Strategy to use for prioritizing transactions in the queue. // pub tx_queue_strategy: PrioritizationStrategy, + /// Simple senders penalization. + pub tx_queue_penalization: Penalization, /// Do we refuse to accept service transactions even if sender is certified. pub refuse_service_transactions: bool, /// Transaction pool limits. @@ -126,14 +141,14 @@ impl Default for MinerOptions { reseal_on_external_tx: false, reseal_on_own_tx: true, reseal_on_uncle: false, - pending_set: PendingSet::AlwaysQueue, reseal_min_period: Duration::from_secs(2), reseal_max_period: Duration::from_secs(120), + pending_set: PendingSet::AlwaysQueue, work_queue_size: 20, enable_resubmission: true, infinite_pending_block: false, // tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, - // tx_queue_banning: Banning::Disabled, + tx_queue_penalization: Penalization::Disabled, refuse_service_transactions: false, pool_limits: pool::Options { max_count: 16_384, @@ -359,7 +374,7 @@ impl Miner { let mut invalid_transactions = HashSet::new(); let mut not_allowed_transactions = HashSet::new(); - // let mut transactions_to_penalize = HashSet::new(); + let mut senders_to_penalize = HashSet::new(); let block_number = open_block.block().header().number(); let mut tx_count = 0usize; @@ -367,6 +382,7 @@ impl Miner { let client = self.client(chain); let engine_params = self.engine.params(); + let min_tx_gas = self.engine.schedule(chain_info.best_block_number).tx_gas.into(); let nonce_cap: Option = if chain_info.best_block_number + 1 >= engine_params.dust_protection_transition { Some((engine_params.nonce_cap_increment * (chain_info.best_block_number + 1)).into()) } else { @@ -385,6 +401,7 @@ impl Miner { let transaction = tx.signed().clone(); let hash = transaction.hash(); + let sender = transaction.sender(); // Re-verify transaction again vs current state. let result = client.verify_signed(&transaction) @@ -394,23 +411,18 @@ impl Miner { }); let took = start.elapsed(); + let took_ms = || took.as_secs() * 1000 + took.subsec_nanos() as u64 / 1_000_000; // Check for heavy transactions - // match self.options.tx_queue_banning { - // Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { - // match self.transaction_queue.write().ban_transaction(&hash) { - // true => { - // warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); - // }, - // false => { - // transactions_to_penalize.insert(hash); - // debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") - // } - // } - // }, - // _ => {}, - // } - trace!(target: "miner", "Adding tx {:?} took {:?}", hash, took); + match self.options.tx_queue_penalization { + Penalization::Enabled { ref offend_threshold } if &took > offend_threshold => { + senders_to_penalize.insert(sender); + debug!(target: "miner", "Detected heavy transaction ({} ms). Penalizing sender.", took_ms()); + }, + _ => {}, + } + + debug!(target: "miner", "Adding tx {:?} took {} ms", hash, took_ms()); match result { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); @@ -422,15 +434,16 @@ impl Miner { } // Exit early if gas left is smaller then min_tx_gas - let min_tx_gas: U256 = 21000.into(); // TODO: figure this out properly. let gas_left = gas_limit - gas_used; if gas_left < min_tx_gas { + debug!(target: "miner", "Remaining gas is lower than minimal gas for a transaction. Block is full."); break; } // Avoid iterating over the entire queue in case block is almost full. skipped_transactions += 1; - if skipped_transactions > 8 { + if skipped_transactions > MAX_SKIPPED_TRANSACTIONS { + debug!(target: "miner", "Reached skipped transactions threshold. Assuming block is full."); break; } }, @@ -463,11 +476,7 @@ impl Miner { { self.transaction_queue.remove(invalid_transactions.iter(), true); self.transaction_queue.remove(not_allowed_transactions.iter(), false); - - // TODO [ToDr] Penalize - // for hash in transactions_to_penalize { - // queue.penalize(&hash); - // } + self.transaction_queue.penalize(senders_to_penalize.iter()); } (block, original_work_hash) @@ -1113,6 +1122,7 @@ mod tests { work_queue_size: 5, enable_resubmission: true, infinite_pending_block: false, + tx_queue_penalization: Penalization::Disabled, refuse_service_transactions: false, pool_limits: Default::default(), pool_verification_options: pool::verifier::Options { diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index b3976b5c971..93ad5b6f38a 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -347,6 +347,11 @@ impl TransactionQueue { self.pool.write().clear(); } + /// Penalize given senders. + pub fn penalize<'a, T: IntoIterator>(&self, senders: T) { + + } + /// Returns gas price of currently the worst transaction in the pool. pub fn current_worst_gas_price(&self) -> U256 { match self.pool.read().worst_transaction() { diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs index 33c714b226e..57d1ad9c366 100644 --- a/miner/src/pool/scoring.rs +++ b/miner/src/pool/scoring.rs @@ -38,17 +38,16 @@ use super::VerifiedTransaction; /// `new_gas_price > old_gas_price + old_gas_price >> SHIFT` const GAS_PRICE_BUMP_SHIFT: usize = 3; // 2 = 25%, 3 = 12.5%, 4 = 6.25% - /// Simple, gas-price based scoring for transactions. /// -/// TODO [ToDr] Consider including: -/// - Penalization -/// - Score of first transaction = Max/Avg(gas price of all transactions) +/// NOTE: Currently penalization does not apply to new transactions that enter the pool. +/// We might want to store penalization status in some persistent state. #[derive(Debug)] pub struct GasPrice; impl txpool::Scoring for GasPrice { type Score = U256; + type Event = (); fn compare(&self, old: &VerifiedTransaction, other: &VerifiedTransaction) -> cmp::Ordering { old.transaction.nonce.cmp(&other.transaction.nonce) @@ -77,6 +76,9 @@ impl txpool::Scoring for GasPrice { Change::Culled(_) => {}, Change::RemovedAt(_) => {} Change::InsertedAt(i) | Change::ReplacedAt(i) => { + assert!(i < txs.len()); + assert!(i < scores.len()); + scores[i] = txs[i].transaction.gas_price; let boost = match txs[i].priority() { super::Priority::Local => 15, @@ -85,6 +87,16 @@ impl txpool::Scoring for GasPrice { }; scores[i] = scores[i] << boost; }, + // We are only sending an event in case of penalization. + // So just lower the priority of all non-local transactions. + Change::Event(_) => { + for (score, tx) in scores.iter_mut().zip(txs) { + // Never penalize local transactions. + if !tx.priority().is_local() { + *score = *score >> 3; + } + } + }, } } @@ -151,5 +163,9 @@ mod tests { assert_eq!(scores, vec![32768.into(), 1024.into(), 0.into()]); scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::ReplacedAt(2)); assert_eq!(scores, vec![32768.into(), 1024.into(), 1.into()]); + + // Check penalization + scoring.update_scores(&transactions, &mut *scores, txpool::scoring::Change::Event(())); + assert_eq!(scores, vec![32768.into(), 128.into(), 0.into()]); } } diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index f1613faff39..ad1d7d3b8b5 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -260,6 +260,7 @@ impl Pool where self.remove_from_set(to_remove.transaction.sender(), |set, scoring| { set.remove(&to_remove.transaction, scoring) }); + Ok(to_remove.transaction) } @@ -387,6 +388,22 @@ impl Pool where } } + /// Update score of transactions of a particular sender. + pub fn update_score(&mut self, sender: &Sender, event: S::Event) { + let res = if let Some(set) = self.transactions.get_mut(sender) { + let prev = set.worst_and_best(); + set.update_scores(&self.scoring, event); + let current = set.worst_and_best(); + Some((prev, current)) + } else { + None + }; + + if let Some((prev, current)) = res { + self.update_senders_worst_and_best(prev, current); + } + } + /// Computes the full status of the pool (including readiness). pub fn status>(&self, mut ready: R) -> Status { let mut status = Status::default(); diff --git a/transaction-pool/src/scoring.rs b/transaction-pool/src/scoring.rs index 7046381f20e..4e7a9833a16 100644 --- a/transaction-pool/src/scoring.rs +++ b/transaction-pool/src/scoring.rs @@ -42,7 +42,7 @@ pub enum Choice { /// The `Scoring` implementations can use this information /// to update the `Score` table more efficiently. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Change { +pub enum Change { /// New transaction has been inserted at given index. /// The Score at that index is initialized with default value /// and needs to be filled in. @@ -59,6 +59,9 @@ pub enum Change { /// The scores has been removed from the beginning as well. /// For simple scoring algorithms no action is required here. Culled(usize), + /// Custom event to update the score triggered outside of the pool. + /// Handling this event is up to scoring implementation. + Event(T), } /// A transaction ordering. @@ -83,6 +86,8 @@ pub enum Change { pub trait Scoring: fmt::Debug { /// A score of a transaction. type Score: cmp::Ord + Clone + Default + fmt::Debug; + /// Custom scoring update event type. + type Event: fmt::Debug; /// Decides on ordering of `T`s from a particular sender. fn compare(&self, old: &T, other: &T) -> cmp::Ordering; @@ -93,7 +98,7 @@ pub trait Scoring: fmt::Debug { /// Updates the transaction scores given a list of transactions and a change to previous scoring. /// NOTE: you can safely assume that both slices have the same length. /// (i.e. score at index `i` represents transaction at the same index) - fn update_scores(&self, txs: &[Arc], scores: &mut [Self::Score], change: Change); + fn update_scores(&self, txs: &[Arc], scores: &mut [Self::Score], change: Change); /// Decides if `new` should push out `old` transaction from the pool. fn should_replace(&self, old: &T, new: &T) -> bool; diff --git a/transaction-pool/src/tests/helpers.rs b/transaction-pool/src/tests/helpers.rs index 4e03cc89612..ab5b2a334b0 100644 --- a/transaction-pool/src/tests/helpers.rs +++ b/transaction-pool/src/tests/helpers.rs @@ -21,11 +21,12 @@ use ethereum_types::U256; use {scoring, Scoring, Ready, Readiness, Address as Sender}; use super::{Transaction, SharedTransaction}; -#[derive(Default)] +#[derive(Debug, Default)] pub struct DummyScoring; impl Scoring for DummyScoring { type Score = U256; + type Event = (); fn compare(&self, old: &Transaction, new: &Transaction) -> cmp::Ordering { old.nonce.cmp(&new.nonce) @@ -43,9 +44,17 @@ impl Scoring for DummyScoring { } } - fn update_scores(&self, txs: &[SharedTransaction], scores: &mut [Self::Score], _change: scoring::Change) { - for i in 0..txs.len() { - scores[i] = txs[i].gas_price; + fn update_scores(&self, txs: &[SharedTransaction], scores: &mut [Self::Score], change: scoring::Change) { + if let scoring::Change::Event(_) = change { + // In case of event reset all scores to 0 + for i in 0..txs.len() { + scores[i] = 0.into(); + } + } else { + // Set to a gas price otherwise + for i in 0..txs.len() { + scores[i] = txs[i].gas_price; + } } } diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index 55f39a2b6ce..f1f96d24ebb 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -249,6 +249,66 @@ fn should_construct_pending() { assert_eq!(pending.next(), None); } +#[test] +fn should_update_scoring_correctly() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + let tx0 = txq.import(b.tx().nonce(0).gas_price(5).new()).unwrap(); + let tx1 = txq.import(b.tx().nonce(1).gas_price(5).new()).unwrap(); + let tx2 = txq.import(b.tx().nonce(2).new()).unwrap(); + // this transaction doesn't get to the block despite high gas price + // because of block gas limit and simplistic ordering algorithm. + txq.import(b.tx().nonce(3).gas_price(4).new()).unwrap(); + //gap + txq.import(b.tx().nonce(5).new()).unwrap(); + + let tx5 = txq.import(b.tx().sender(1).nonce(0).new()).unwrap(); + let tx6 = txq.import(b.tx().sender(1).nonce(1).new()).unwrap(); + let tx7 = txq.import(b.tx().sender(1).nonce(2).new()).unwrap(); + let tx8 = txq.import(b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap(); + // gap + txq.import(b.tx().sender(1).nonce(5).new()).unwrap(); + + let tx9 = txq.import(b.tx().sender(2).nonce(0).new()).unwrap(); + assert_eq!(txq.light_status().transaction_count, 11); + assert_eq!(txq.status(NonceReady::default()), Status { + stalled: 0, + pending: 9, + future: 2, + }); + assert_eq!(txq.status(NonceReady::new(1)), Status { + stalled: 3, + pending: 6, + future: 2, + }); + + txq.update_score(&0.into(), ()); + + // when + let mut current_gas = U256::zero(); + let limit = (21_000 * 8).into(); + let mut pending = txq.pending(NonceReady::default()).take_while(|tx| { + let should_take = tx.gas + current_gas <= limit; + if should_take { + current_gas = current_gas + tx.gas + } + should_take + }); + + assert_eq!(pending.next(), Some(tx9)); + assert_eq!(pending.next(), Some(tx5)); + assert_eq!(pending.next(), Some(tx6)); + assert_eq!(pending.next(), Some(tx7)); + assert_eq!(pending.next(), Some(tx8)); + // penalized transactions + assert_eq!(pending.next(), Some(tx0)); + assert_eq!(pending.next(), Some(tx1)); + assert_eq!(pending.next(), Some(tx2)); + assert_eq!(pending.next(), None); +} + #[test] fn should_remove_transaction() { // given @@ -403,7 +463,7 @@ mod listener { self.0.borrow_mut().push(if old.is_some() { "replaced" } else { "added" }); } - fn rejected(&mut self, _tx: Transaction) { + fn rejected(&mut self, _tx: &SharedTransaction) { self.0.borrow_mut().push("rejected".into()); } diff --git a/transaction-pool/src/transactions.rs b/transaction-pool/src/transactions.rs index 8e641591671..d7d824e8e3c 100644 --- a/transaction-pool/src/transactions.rs +++ b/transaction-pool/src/transactions.rs @@ -107,6 +107,10 @@ impl> Transactions { } } + pub fn update_scores(&mut self, scoring: &S, event: S::Event) { + scoring.update_scores(&self.transactions, &mut self.scores, scoring::Change::Event(event)); + } + pub fn add(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult { let index = match self.transactions.binary_search_by(|old| scoring.compare(old, &tx)) { Ok(index) => index, From b4d9341d30a4480c71038c8d345dda321ac1ee6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 8 Mar 2018 12:55:50 +0100 Subject: [PATCH 37/77] Clean up and support for different scoring algorithms. --- ethcore/src/miner/miner.rs | 11 +++--- ethcore/src/miner/mod.rs | 3 +- miner/src/pool/mod.rs | 3 +- miner/src/pool/queue.rs | 58 +++++++------------------------ miner/src/pool/scoring.rs | 8 ++--- miner/src/pool/tests/mod.rs | 7 +++- parity/cli/mod.rs | 19 +++++----- parity/configuration.rs | 8 ++--- parity/helpers.rs | 13 +++++-- transaction-pool/src/pool.rs | 2 +- transaction-pool/src/tests/mod.rs | 2 +- 11 files changed, 57 insertions(+), 77 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 8cbc9dfcd1e..1e40ab77622 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -23,7 +23,7 @@ use bytes::Bytes; use engines::{EthEngine, Seal}; use error::{Error, ExecutionError}; use ethcore_miner::gas_pricer::GasPricer; -use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction, QueueStatus}; +use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction, QueueStatus, PrioritizationStrategy}; use ethcore_miner::work_notify::{WorkPoster, NotifyWork}; use ethereum_types::{H256, U256, Address}; use parking_lot::{Mutex, RwLock}; @@ -122,8 +122,8 @@ pub struct MinerOptions { pub infinite_pending_block: bool, - // / Strategy to use for prioritizing transactions in the queue. - // pub tx_queue_strategy: PrioritizationStrategy, + /// Strategy to use for prioritizing transactions in the queue. + pub tx_queue_strategy: PrioritizationStrategy, /// Simple senders penalization. pub tx_queue_penalization: Penalization, /// Do we refuse to accept service transactions even if sender is certified. @@ -147,7 +147,7 @@ impl Default for MinerOptions { work_queue_size: 20, enable_resubmission: true, infinite_pending_block: false, - // tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, + tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, tx_queue_penalization: Penalization::Disabled, refuse_service_transactions: false, pool_limits: pool::Options { @@ -228,6 +228,7 @@ impl Miner { pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { let limits = options.pool_limits.clone(); let verifier_options = options.pool_verification_options.clone(); + let tx_queue_strategy = options.tx_queue_strategy; Miner { sealing: Mutex::new(SealingWork { @@ -242,7 +243,7 @@ impl Miner { listeners: RwLock::new(vec![]), gas_pricer: Mutex::new(gas_pricer), options, - transaction_queue: Arc::new(TransactionQueue::new(limits, verifier_options)), + transaction_queue: Arc::new(TransactionQueue::new(limits, verifier_options, tx_queue_strategy)), accounts, engine: spec.engine.clone(), } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index c0f946313f3..80aa3e5232a 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -25,7 +25,8 @@ mod service_transaction_checker; pub mod stratum; -pub use self::miner::{Miner, MinerOptions, PendingSet, AuthoringParams}; +pub use self::miner::{Miner, MinerOptions, Penalization, PendingSet, AuthoringParams}; +pub use ethcore_miner::pool::PrioritizationStrategy; use std::sync::Arc; use std::collections::BTreeMap; diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index aa22d4ab73b..6fc69674c85 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -38,9 +38,10 @@ pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; // TODO [ToDr] Actually use that parameter and implement more strategies. /// How to prioritize transactions in the pool +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum PrioritizationStrategy { /// Simple gas-price based prioritization. - GasPrice, + GasPriceOnly, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 93ad5b6f38a..5105df779eb 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -26,11 +26,11 @@ use parking_lot::RwLock; use transaction; use txpool::{self, Verifier}; -use pool::{self, scoring, verifier, client, ready, listener}; +use pool::{self, scoring, verifier, client, ready, listener, PrioritizationStrategy}; use pool::local_transactions::LocalTransactionsList; type Listener = (LocalTransactionsList, (listener::Notifier, listener::Logger)); -type Pool = txpool::Pool; +type Pool = txpool::Pool; /// Max cache time in milliseconds for pending transactions. /// @@ -143,10 +143,10 @@ pub struct TransactionQueue { impl TransactionQueue { /// Create new queue with given pool limits and initial verification options. - pub fn new(limits: txpool::Options, verification_options: verifier::Options) -> Self { + pub fn new(limits: txpool::Options, verification_options: verifier::Options, strategy: PrioritizationStrategy) -> Self { TransactionQueue { insertion_id: Default::default(), - pool: RwLock::new(txpool::Pool::new(Default::default(), scoring::GasPrice, limits)), + pool: RwLock::new(txpool::Pool::new(Default::default(), scoring::NonceAndGasPrice(strategy), limits)), options: RwLock::new(verification_options), cached_pending: RwLock::new(CachedPending::none()), } @@ -246,7 +246,7 @@ impl TransactionQueue { F: FnOnce(txpool::PendingIterator< pool::VerifiedTransaction, (ready::Condition, ready::State), - scoring::GasPrice, + scoring::NonceAndGasPrice, Listener, >) -> T, { @@ -349,7 +349,10 @@ impl TransactionQueue { /// Penalize given senders. pub fn penalize<'a, T: IntoIterator>(&self, senders: T) { - + let mut pool = self.pool.write(); + for sender in senders { + pool.update_scores(sender, ()); + } } /// Returns gas price of currently the worst transaction in the pool. @@ -415,50 +418,13 @@ fn convert_error(err: txpool::Error) -> transaction::Error { #[cfg(test)] mod tests { use super::*; - - #[derive(Debug)] - struct TestClient; - impl client::Client for TestClient { - fn transaction_already_included(&self, _hash: &H256) -> bool { - false - } - - fn verify_transaction(&self, tx: transaction::UnverifiedTransaction) -> Result { - Ok(transaction::SignedTransaction::new(tx)?) - } - - /// Fetch account details for given sender. - fn account_details(&self, _address: &Address) -> client::AccountDetails { - client::AccountDetails { - balance: 5_000_000.into(), - nonce: 0.into(), - is_local: false, - } - } - - /// Estimate minimal gas requirurement for given transaction. - fn required_gas(&self, _tx: &transaction::Transaction) -> U256 { - 0.into() - } - - /// Classify transaction (check if transaction is filtered by some contracts). - fn transaction_type(&self, _tx: &transaction::SignedTransaction) -> client::TransactionType { - client::TransactionType::Regular - } - } - - impl client::NonceClient for TestClient { - /// Fetch only account nonce for given sender. - fn account_nonce(&self, _address: &Address) -> U256 { - 0.into() - } - } + use pool::tests::client::TestClient; #[test] fn should_get_pending_transactions() { - let queue = TransactionQueue::new(txpool::Options::default(), verifier::Options::default()); + let queue = TransactionQueue::new(txpool::Options::default(), verifier::Options::default(), PrioritizationStrategy::GasPriceOnly); - let pending: Vec<_> = queue.pending(TestClient, 0, 0, None); + let pending: Vec<_> = queue.pending(TestClient::default(), 0, 0, None); for tx in pending { assert!(tx.signed().nonce > 0.into()); diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs index 57d1ad9c366..b9f074ecb0f 100644 --- a/miner/src/pool/scoring.rs +++ b/miner/src/pool/scoring.rs @@ -32,7 +32,7 @@ use std::sync::Arc; use ethereum_types::U256; use txpool; -use super::VerifiedTransaction; +use super::{PrioritizationStrategy, VerifiedTransaction}; /// Transaction with the same (sender, nonce) can be replaced only if /// `new_gas_price > old_gas_price + old_gas_price >> SHIFT` @@ -43,9 +43,9 @@ const GAS_PRICE_BUMP_SHIFT: usize = 3; // 2 = 25%, 3 = 12.5%, 4 = 6.25% /// NOTE: Currently penalization does not apply to new transactions that enter the pool. /// We might want to store penalization status in some persistent state. #[derive(Debug)] -pub struct GasPrice; +pub struct NonceAndGasPrice(pub PrioritizationStrategy); -impl txpool::Scoring for GasPrice { +impl txpool::Scoring for NonceAndGasPrice { type Score = U256; type Event = (); @@ -122,7 +122,7 @@ mod tests { #[test] fn should_calculate_score_correctly() { // given - let scoring = GasPrice; + let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); let (tx1, tx2, tx3) = Tx::default().signed_triple(); let transactions = vec![tx1, tx2, tx3].into_iter().enumerate().map(|(i, tx)| { let mut verified = tx.verified(); diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index 779e8c58d73..32db76a5c71 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -18,7 +18,7 @@ use ethereum_types::U256; use transaction::{self, PendingTransaction}; use txpool; -use pool::{verifier, TransactionQueue}; +use pool::{verifier, TransactionQueue, PrioritizationStrategy}; pub mod tx; pub mod client; @@ -38,6 +38,7 @@ fn new_queue() -> TransactionQueue { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), }, + PrioritizationStrategy::GasPriceOnly, ) } @@ -55,6 +56,7 @@ fn should_return_correct_nonces_when_dropped_because_of_limit() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), }, + PrioritizationStrategy::GasPriceOnly, ); let (tx1, tx2) = Tx::gas_price(2).signed_pair(); let sender = tx1.sender(); @@ -424,6 +426,7 @@ fn should_prefer_current_transactions_when_hitting_the_limit() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), }, + PrioritizationStrategy::GasPriceOnly, ); let (tx, tx2) = Tx::default().signed_pair(); let hash = tx.hash(); @@ -615,6 +618,7 @@ fn should_remove_out_of_date_transactions_occupying_queue() { minimal_gas_price: 10.into(), ..Default::default() }, + PrioritizationStrategy::GasPriceOnly, ); // that transaction will be occupying the queue let (_, tx) = Tx::default().signed_pair(); @@ -651,6 +655,7 @@ fn should_accept_local_transactions_below_min_gas_price() { minimal_gas_price: 10.into(), ..Default::default() }, + PrioritizationStrategy::GasPriceOnly, ); let tx = Tx::gas_price(1).signed(); diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index c41b595983f..9e985640120 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -665,15 +665,7 @@ usage! { ARG arg_tx_queue_strategy: (String) = "gas_price", or |c: &Config| c.mining.as_ref()?.tx_queue_strategy.clone(), "--tx-queue-strategy=[S]", - "Prioritization strategy used to order transactions in the queue. S may be: gas - Prioritize txs with low gas limit; gas_price - Prioritize txs with high gas price; gas_factor - Prioritize txs using gas price and gas limit ratio.", - - ARG arg_tx_queue_ban_count: (u16) = 1u16, or |c: &Config| c.mining.as_ref()?.tx_queue_ban_count.clone(), - "--tx-queue-ban-count=[C]", - "Number of times maximal time for execution (--tx-time-limit) can be exceeded before banning sender/recipient/code.", - - ARG arg_tx_queue_ban_time: (u16) = 180u16, or |c: &Config| c.mining.as_ref()?.tx_queue_ban_time.clone(), - "--tx-queue-ban-time=[SEC]", - "Banning time (in seconds) for offenders of specified execution time limit. Also number of offending actions have to reach the threshold within that time.", + "Prioritization strategy used to order transactions in the queue. S may be: gas_price - Prioritize txs with high gas price", ARG arg_stratum_interface: (String) = "local", or |c: &Config| c.stratum.as_ref()?.interface.clone(), "--stratum-interface=[IP]", @@ -705,7 +697,7 @@ usage! { ARG arg_tx_time_limit: (Option) = None, or |c: &Config| c.mining.as_ref()?.tx_time_limit.clone(), "--tx-time-limit=[MS]", - "Maximal time for processing single transaction. If enabled senders/recipients/code of transactions offending the limit will be banned from being included in transaction queue for 180 seconds.", + "Maximal time for processing single transaction. If enabled senders of transactions offending the limit will get other transactions penalized.", ARG arg_extra_data: (Option) = None, or |c: &Config| c.mining.as_ref()?.extra_data.clone(), "--extra-data=[STRING]", @@ -963,6 +955,13 @@ usage! { "--cache=[MB]", "Equivalent to --cache-size MB.", + ARG arg_tx_queue_ban_count: (u16) = 1u16, or |c: &Config| c.mining.as_ref()?.tx_queue_ban_count.clone(), + "--tx-queue-ban-count=[C]", + "Not supported.", + + ARG arg_tx_queue_ban_time: (u16) = 180u16, or |c: &Config| c.mining.as_ref()?.tx_queue_ban_time.clone(), + "--tx-queue-ban-time=[SEC]", + "Not supported.", } } diff --git a/parity/configuration.rs b/parity/configuration.rs index 40e0c07fe16..5eea1c00c21 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -39,7 +39,7 @@ use rpc_apis::ApiSet; use parity_rpc::NetworkSettings; use cache::CacheConfig; use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, geth_ipc_path, parity_ipc_path, -to_bootnodes, to_addresses, to_address, to_queue_strategy}; +to_bootnodes, to_addresses, to_address, to_queue_strategy, to_queue_penalization}; use dir::helpers::{replace_home, replace_home_and_local}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, SpecType}; use ethcore_logger::Config as LogConfig; @@ -516,10 +516,6 @@ impl Configuration { return Err("Force sealing can't be used with reseal_min_period = 0".into()); } - if let Some(_) = self.args.arg_tx_time_limit { - warn!("Banning is not available in this version."); - } - let reseal = self.args.arg_reseal_on_txs.parse::()?; let options = MinerOptions { @@ -535,6 +531,8 @@ impl Configuration { enable_resubmission: !self.args.flag_remove_solved, infinite_pending_block: self.args.flag_infinite_pending_block, + tx_queue_penalization: to_queue_penalization(self.args.arg_tx_time_limit)?, + tx_queue_strategy: to_queue_strategy(&self.args.arg_tx_queue_strategy)?, refuse_service_transactions: self.args.flag_refuse_service_transactions, pool_limits: self.pool_limits()?, diff --git a/parity/helpers.rs b/parity/helpers.rs index 7e7d11c813c..4e29e003e08 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -22,7 +22,7 @@ use ethereum_types::{U256, clean_0x, Address}; use kvdb_rocksdb::CompactionProfile; use journaldb::Algorithm; use ethcore::client::{Mode, BlockId, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; -use ethcore::miner::PendingSet; +use ethcore::miner::{PendingSet, Penalization}; use miner::pool::PrioritizationStrategy; use cache::CacheConfig; use dir::DatabaseDirectories; @@ -99,11 +99,20 @@ pub fn to_pending_set(s: &str) -> Result { pub fn to_queue_strategy(s: &str) -> Result { match s { - "gas_price" => Ok(PrioritizationStrategy::GasPrice), + "gas_price" => Ok(PrioritizationStrategy::GasPriceOnly), other => Err(format!("Invalid queue strategy: {}", other)), } } +pub fn to_queue_penalization(time: Option) -> Result { + Ok(match time { + Some(threshold_ms) => Penalization::Enabled { + offend_threshold: Duration::from_millis(threshold_ms), + }, + None => Penalization::Disabled, + }) +} + pub fn to_address(s: Option) -> Result { match s { Some(ref a) => clean_0x(a).parse().map_err(|_| format!("Invalid address: {:?}", a)), diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index ad1d7d3b8b5..4a3159a92b3 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -389,7 +389,7 @@ impl Pool where } /// Update score of transactions of a particular sender. - pub fn update_score(&mut self, sender: &Sender, event: S::Event) { + pub fn update_scores(&mut self, sender: &Sender, event: S::Event) { let res = if let Some(set) = self.transactions.get_mut(sender) { let prev = set.worst_and_best(); set.update_scores(&self.scoring, event); diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index f1f96d24ebb..85a0dece36b 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -284,7 +284,7 @@ fn should_update_scoring_correctly() { future: 2, }); - txq.update_score(&0.into(), ()); + txq.update_scores(&0.into(), ()); // when let mut current_gas = U256::zero(); From 03e89ec0acaa7977ae0b60ca11ead97dd0c89679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 8 Mar 2018 14:38:07 +0100 Subject: [PATCH 38/77] Add CLI parameters for the new queue. --- ethcore/src/miner/miner.rs | 4 ++-- ethcore/src/miner/mod.rs | 1 - miner/src/pool/mod.rs | 4 ++-- parity/cli/mod.rs | 13 ++++++++++--- parity/cli/presets/config.mining.toml | 9 +++++---- parity/configuration.rs | 25 ++++++++++--------------- 6 files changed, 29 insertions(+), 27 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 1e40ab77622..50d968a6ab3 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -151,8 +151,8 @@ impl Default for MinerOptions { tx_queue_penalization: Penalization::Disabled, refuse_service_transactions: false, pool_limits: pool::Options { - max_count: 16_384, - max_per_sender: 64, + max_count: 8_192, + max_per_sender: 409, max_mem_usage: 8 * 1024 * 1024, }, pool_verification_options: pool::verifier::Options { diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 80aa3e5232a..3ddc46f8237 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -26,7 +26,6 @@ mod service_transaction_checker; pub mod stratum; pub use self::miner::{Miner, MinerOptions, Penalization, PendingSet, AuthoringParams}; -pub use ethcore_miner::pool::PrioritizationStrategy; use std::sync::Arc; use std::collections::BTreeMap; diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 6fc69674c85..8a4855b825d 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -35,9 +35,9 @@ pub mod tests; pub use self::queue::{TransactionQueue, Status as QueueStatus}; pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; -// TODO [ToDr] Actually use that parameter and implement more strategies. - /// How to prioritize transactions in the pool +/// +/// TODO [ToDr] Implement more strategies. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum PrioritizationStrategy { /// Simple gas-price based prioritization. diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 9e985640120..f703a52afcf 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -651,14 +651,18 @@ usage! { "--gas-cap=[GAS]", "A cap on how large we will raise the gas limit per block due to transaction volume.", - ARG arg_tx_queue_mem_limit: (u32) = 2u32, or |c: &Config| c.mining.as_ref()?.tx_queue_mem_limit.clone(), + ARG arg_tx_queue_mem_limit: (u32) = 8u32, or |c: &Config| c.mining.as_ref()?.tx_queue_mem_limit.clone(), "--tx-queue-mem-limit=[MB]", "Maximum amount of memory that can be used by the transaction queue. Setting this parameter to 0 disables limiting.", - ARG arg_tx_queue_size: (usize) = 8192usize, or |c: &Config| c.mining.as_ref()?.tx_queue_size.clone(), + ARG arg_tx_queue_size: (usize) = 8_192usize, or |c: &Config| c.mining.as_ref()?.tx_queue_size.clone(), "--tx-queue-size=[LIMIT]", "Maximum amount of transactions in the queue (waiting to be included in next block).", + ARG arg_tx_queue_per_sender: (Option) = None, or |c: &Config| c.mining.as_ref()?.tx_queue_per_sender.clone(), + "--tx-queue-per-sender=[LIMIT]", + "Maximum number of transactions per sender in the queue. By default it's 5% of the entire queue, but not less than 16.", + ARG arg_tx_queue_gas: (String) = "off", or |c: &Config| c.mining.as_ref()?.tx_queue_gas.clone(), "--tx-queue-gas=[LIMIT]", "Maximum amount of total gas for external transactions in the queue. LIMIT can be either an amount of gas or 'auto' or 'off'. 'auto' sets the limit to be 20x the current block gas limit.", @@ -1145,6 +1149,7 @@ struct Mining { gas_cap: Option, extra_data: Option, tx_queue_size: Option, + tx_queue_per_sender: Option, tx_queue_mem_limit: Option, tx_queue_gas: Option, tx_queue_strategy: Option, @@ -1555,7 +1560,8 @@ mod tests { arg_gas_cap: "6283184".into(), arg_extra_data: Some("Parity".into()), arg_tx_queue_size: 8192usize, - arg_tx_queue_mem_limit: 2u32, + arg_tx_queue_per_sender: None, + arg_tx_queue_mem_limit: 8u32, arg_tx_queue_gas: "off".into(), arg_tx_queue_strategy: "gas_factor".into(), arg_tx_queue_ban_count: 1u16, @@ -1804,6 +1810,7 @@ mod tests { gas_floor_target: None, gas_cap: None, tx_queue_size: Some(8192), + tx_queue_per_sender: None, tx_queue_mem_limit: None, tx_queue_gas: Some("off".into()), tx_queue_strategy: None, diff --git a/parity/cli/presets/config.mining.toml b/parity/cli/presets/config.mining.toml index f6c39bdd6df..6d22a0f0878 100644 --- a/parity/cli/presets/config.mining.toml +++ b/parity/cli/presets/config.mining.toml @@ -19,12 +19,13 @@ force_sealing = true reseal_on_txs = "all" # New pending block will be created only once per 4000 milliseconds. reseal_min_period = 4000 -# Parity will keep/relay at most 2048 transactions in queue. -tx_queue_size = 2048 +# Parity will keep/relay at most 8192 transactions in queue. +tx_queue_size = 8192 +tx_queue_per_sender = 128 [footprint] -# If defined will never use more then 256MB for all caches. (Overrides other cache settings). -cache_size = 256 +# If defined will never use more then 1024MB for all caches. (Overrides other cache settings). +cache_size = 1024 [misc] # Logging pattern (`=`, e.g. `own_tx=trace`). diff --git a/parity/configuration.rs b/parity/configuration.rs index 5eea1c00c21..31f97f509fa 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -19,7 +19,7 @@ use std::io::Read; use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::collections::BTreeMap; -use std::cmp::max; +use std::cmp; use std::str::FromStr; use cli::{Args, ArgsError}; use hash::keccak; @@ -459,7 +459,7 @@ impl Configuration { fn max_peers(&self) -> u32 { let peers = self.args.arg_max_peers as u32; - max(self.min_peers(), peers) + cmp::max(self.min_peers(), peers) } fn ip_filter(&self) -> Result { @@ -543,10 +543,11 @@ impl Configuration { } fn pool_limits(&self) -> Result { + let max_count = self.args.arg_tx_queue_size; + Ok(pool::Options { - max_count: self.args.arg_tx_queue_size, - // TODO [ToDr] Add seperate parameter for that! - max_per_sender: self.args.arg_tx_queue_size, + max_count, + max_per_sender: self.args.arg_tx_queue_per_sender.unwrap_or_else(|| cmp::max(16, max_count / 20)), max_mem_usage: if self.args.arg_tx_queue_mem_limit > 0 { self.args.arg_tx_queue_mem_limit as usize * 1024 * 1024 } else { @@ -559,7 +560,7 @@ impl Configuration { Ok(pool::verifier::Options { // NOTE min_gas_price and block_gas_limit will be overwritten right after start. minimal_gas_price: U256::from(20_000_000) * 1_000u32, - block_gas_limit: to_u256(&self.args.arg_gas_floor_target)?, + block_gas_limit: U256::max_value(), tx_gas_limit: match self.args.arg_tx_gas_limit { Some(ref d) => to_u256(d)?, None => U256::max_value(), @@ -1154,7 +1155,7 @@ mod tests { use tempdir::TempDir; use ethcore::client::{VMType, BlockId}; use ethcore::miner::MinerOptions; - use miner::transaction_queue::PrioritizationStrategy; + use miner::pool::PrioritizationStrategy; use parity_rpc::NetworkSettings; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; @@ -1440,18 +1441,12 @@ mod tests { // when let conf0 = parse(&["parity"]); - let conf1 = parse(&["parity", "--tx-queue-strategy", "gas_factor"]); let conf2 = parse(&["parity", "--tx-queue-strategy", "gas_price"]); - let conf3 = parse(&["parity", "--tx-queue-strategy", "gas"]); // then assert_eq!(conf0.miner_options().unwrap(), mining_options); - mining_options.tx_queue_strategy = PrioritizationStrategy::GasFactorAndGasPrice; - assert_eq!(conf1.miner_options().unwrap(), mining_options); mining_options.tx_queue_strategy = PrioritizationStrategy::GasPriceOnly; assert_eq!(conf2.miner_options().unwrap(), mining_options); - mining_options.tx_queue_strategy = PrioritizationStrategy::GasAndGasPrice; - assert_eq!(conf3.miner_options().unwrap(), mining_options); } #[test] @@ -1708,8 +1703,8 @@ mod tests { assert_eq!(c.miner_options.reseal_on_external_tx, true); assert_eq!(c.miner_options.reseal_on_own_tx, true); assert_eq!(c.miner_options.reseal_min_period, Duration::from_millis(4000)); - assert_eq!(c.miner_options.tx_queue_size, 2048); - assert_eq!(c.cache_config, CacheConfig::new_with_total_cache_size(256)); + assert_eq!(c.miner_options.pool_limits.max_count, 8192); + assert_eq!(c.cache_config, CacheConfig::new_with_total_cache_size(1024)); assert_eq!(c.logger_config.mode.unwrap(), "miner=trace,own_tx=trace"); }, _ => panic!("Should be Cmd::Run"), From 900b4464470fc0b4468c7a8fc3c5541d68bbe23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 8 Mar 2018 14:55:49 +0100 Subject: [PATCH 39/77] Remove banning queue. --- miner/src/banning_queue.rs | 321 ------------------------------------- 1 file changed, 321 deletions(-) delete mode 100644 miner/src/banning_queue.rs diff --git a/miner/src/banning_queue.rs b/miner/src/banning_queue.rs deleted file mode 100644 index 388ae5e0015..00000000000 --- a/miner/src/banning_queue.rs +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity 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 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. If not, see . - -//! Banning Queue -//! Transacton Queue wrapper maintaining additional list of banned senders and contract hashes. - -use std::time::Duration; -use std::ops::{Deref, DerefMut}; -use ethereum_types::{H256, U256, Address}; -use hash::keccak; -use transaction::{self, SignedTransaction, Action}; -use transient_hashmap::TransientHashMap; - -use transaction_queue::{TransactionQueue, TransactionDetailsProvider, TransactionOrigin, QueuingInstant}; - -type Count = u16; - -/// Auto-Banning threshold -pub enum Threshold { - /// Should ban after given number of misbehaves reported. - BanAfter(Count), - /// Should never ban anything - NeverBan -} - -impl Default for Threshold { - fn default() -> Self { - Threshold::NeverBan - } -} - -/// Transaction queue with banlist. -pub struct BanningTransactionQueue { - queue: TransactionQueue, - ban_threshold: Threshold, - senders_bans: TransientHashMap, - recipients_bans: TransientHashMap, - codes_bans: TransientHashMap, -} - -impl BanningTransactionQueue { - /// Creates new banlisting transaction queue - pub fn new(queue: TransactionQueue, ban_threshold: Threshold, ban_lifetime: Duration) -> Self { - let ban_lifetime_sec = ban_lifetime.as_secs() as u32; - assert!(ban_lifetime_sec > 0, "Lifetime has to be specified in seconds."); - BanningTransactionQueue { - queue: queue, - ban_threshold: ban_threshold, - senders_bans: TransientHashMap::new(ban_lifetime_sec), - recipients_bans: TransientHashMap::new(ban_lifetime_sec), - codes_bans: TransientHashMap::new(ban_lifetime_sec), - } - } - - /// Borrows internal queue. - /// NOTE: you can insert transactions to the queue even - /// if they would be rejected because of ban otherwise. - /// But probably you shouldn't. - pub fn queue(&mut self) -> &mut TransactionQueue { - &mut self.queue - } - - /// Add to the queue taking bans into consideration. - /// May reject transaction because of the banlist. - pub fn add_with_banlist( - &mut self, - transaction: SignedTransaction, - time: QueuingInstant, - details_provider: &TransactionDetailsProvider, - ) -> Result { - if let Threshold::BanAfter(threshold) = self.ban_threshold { - // NOTE In all checks use direct query to avoid increasing ban timeout. - - // Check sender - let sender = transaction.sender(); - let count = self.senders_bans.direct().get(&sender).cloned().unwrap_or(0); - if count > threshold { - debug!(target: "txqueue", "Ignoring transaction {:?} because sender is banned.", transaction.hash()); - return Err(transaction::Error::SenderBanned); - } - - // Check recipient - if let Action::Call(recipient) = transaction.action { - let count = self.recipients_bans.direct().get(&recipient).cloned().unwrap_or(0); - if count > threshold { - debug!(target: "txqueue", "Ignoring transaction {:?} because recipient is banned.", transaction.hash()); - return Err(transaction::Error::RecipientBanned); - } - } - - // Check code - if let Action::Create = transaction.action { - let code_hash = keccak(&transaction.data); - let count = self.codes_bans.direct().get(&code_hash).cloned().unwrap_or(0); - if count > threshold { - debug!(target: "txqueue", "Ignoring transaction {:?} because code is banned.", transaction.hash()); - return Err(transaction::Error::CodeBanned); - } - } - } - self.queue.add(transaction, TransactionOrigin::External, time, None, details_provider) - } - - /// Ban transaction with given hash. - /// Transaction has to be in the queue. - /// - /// Bans sender and recipient/code and returns `true` when any ban has reached threshold. - pub fn ban_transaction(&mut self, hash: &H256) -> bool { - let transaction = self.queue.find(hash); - match transaction { - Some(transaction) => { - let sender = transaction.sender(); - // Ban sender - let sender_banned = self.ban_sender(sender); - // Ban recipient and codehash - let recipient_or_code_banned = match transaction.action { - Action::Call(recipient) => { - self.ban_recipient(recipient) - }, - Action::Create => { - self.ban_codehash(keccak(&transaction.data)) - }, - }; - sender_banned || recipient_or_code_banned - }, - None => false, - } - } - - /// Ban given sender. - /// If bans threshold is reached all subsequent transactions from this sender will be rejected. - /// Reaching bans threshold also removes all existsing transaction from this sender that are already in the - /// queue. - fn ban_sender(&mut self, address: Address) -> bool { - let count = { - let count = self.senders_bans.entry(address).or_insert_with(|| 0); - *count = count.saturating_add(1); - *count - }; - match self.ban_threshold { - Threshold::BanAfter(threshold) if count > threshold => { - // Banlist the sender. - // Remove all transactions from the queue. - self.cull(address, !U256::zero()); - true - }, - _ => false - } - } - - /// Ban given recipient. - /// If bans threshold is reached all subsequent transactions to this address will be rejected. - /// Returns true if bans threshold has been reached. - fn ban_recipient(&mut self, address: Address) -> bool { - let count = { - let count = self.recipients_bans.entry(address).or_insert_with(|| 0); - *count = count.saturating_add(1); - *count - }; - match self.ban_threshold { - // TODO [ToDr] Consider removing other transactions to the same recipient from the queue? - Threshold::BanAfter(threshold) if count > threshold => true, - _ => false - } - } - - - /// Ban given codehash. - /// If bans threshold is reached all subsequent transactions to contracts with this codehash will be rejected. - /// Returns true if bans threshold has been reached. - fn ban_codehash(&mut self, code_hash: H256) -> bool { - let count = self.codes_bans.entry(code_hash).or_insert_with(|| 0); - *count = count.saturating_add(1); - - match self.ban_threshold { - // TODO [ToDr] Consider removing other transactions with the same code from the queue? - Threshold::BanAfter(threshold) if *count > threshold => true, - _ => false, - } - } -} - -impl Deref for BanningTransactionQueue { - type Target = TransactionQueue; - - fn deref(&self) -> &Self::Target { - &self.queue - } -} -impl DerefMut for BanningTransactionQueue { - fn deref_mut(&mut self) -> &mut Self::Target { - self.queue() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ethkey::{Random, Generator}; - use rustc_hex::FromHex; - use transaction_queue::test::DummyTransactionDetailsProvider; - use ethereum_types::{U256, Address}; - - fn queue() -> BanningTransactionQueue { - BanningTransactionQueue::new(TransactionQueue::default(), Threshold::BanAfter(1), Duration::from_secs(180)) - } - - fn default_tx_provider() -> DummyTransactionDetailsProvider { - DummyTransactionDetailsProvider::default().with_account_nonce(U256::zero()) - } - - fn transaction(action: Action) -> SignedTransaction { - let keypair = Random.generate().unwrap(); - transaction::Transaction { - action: action, - value: U256::from(100), - data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), - gas_price: U256::from(10), - nonce: U256::from(0), - }.sign(keypair.secret(), None) - } - - fn unwrap_err(res: Result) -> transaction::Error { - res.unwrap_err() - } - - #[test] - fn should_allow_to_borrow_the_queue() { - // given - let tx = transaction(Action::Create); - let mut txq = queue(); - - // when - txq.queue().add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); - - // then - // should also deref to queue - assert_eq!(txq.status().pending, 1); - } - - #[test] - fn should_not_accept_transactions_from_banned_sender() { - // given - let tx = transaction(Action::Create); - let mut txq = queue(); - // Banlist once (threshold not reached) - let banlist1 = txq.ban_sender(tx.sender()); - assert!(!banlist1, "Threshold not reached yet."); - // Insert once - let import1 = txq.add_with_banlist(tx.clone(), 0, &default_tx_provider()).unwrap(); - assert_eq!(import1, transaction::ImportResult::Current); - - // when - let banlist2 = txq.ban_sender(tx.sender()); - let import2 = txq.add_with_banlist(tx.clone(), 0, &default_tx_provider()); - - // then - assert!(banlist2, "Threshold should be reached - banned."); - assert_eq!(unwrap_err(import2), transaction::Error::SenderBanned); - // Should also remove transacion from the queue - assert_eq!(txq.find(&tx.hash()), None); - } - - #[test] - fn should_not_accept_transactions_to_banned_recipient() { - // given - let recipient = Address::default(); - let tx = transaction(Action::Call(recipient)); - let mut txq = queue(); - // Banlist once (threshold not reached) - let banlist1 = txq.ban_recipient(recipient); - assert!(!banlist1, "Threshold not reached yet."); - // Insert once - let import1 = txq.add_with_banlist(tx.clone(), 0, &default_tx_provider()).unwrap(); - assert_eq!(import1, transaction::ImportResult::Current); - - // when - let banlist2 = txq.ban_recipient(recipient); - let import2 = txq.add_with_banlist(tx.clone(), 0, &default_tx_provider()); - - // then - assert!(banlist2, "Threshold should be reached - banned."); - assert_eq!(unwrap_err(import2), transaction::Error::RecipientBanned); - } - - #[test] - fn should_not_accept_transactions_with_banned_code() { - // given - let tx = transaction(Action::Create); - let codehash = keccak(&tx.data); - let mut txq = queue(); - // Banlist once (threshold not reached) - let banlist1 = txq.ban_codehash(codehash); - assert!(!banlist1, "Threshold not reached yet."); - // Insert once - let import1 = txq.add_with_banlist(tx.clone(), 0, &default_tx_provider()).unwrap(); - assert_eq!(import1, transaction::ImportResult::Current); - - // when - let banlist2 = txq.ban_codehash(codehash); - let import2 = txq.add_with_banlist(tx.clone(), 0, &default_tx_provider()); - - // then - assert!(banlist2, "Threshold should be reached - banned."); - assert_eq!(unwrap_err(import2), transaction::Error::CodeBanned); - } -} From 8046ef96b201c48ae44d2cb0e851c47561938b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 8 Mar 2018 15:21:08 +0100 Subject: [PATCH 40/77] Disable debug build. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c1d9b116804..b670df21cae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,7 @@ name = "parity" panic = "abort" [profile.release] -debug = true +debug = false lto = false panic = "abort" From 83c92bc15ebadffcb19716ccfe55d4005a55cbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 8 Mar 2018 16:12:50 +0100 Subject: [PATCH 41/77] Change per_sender limit to be 1% instead of 5% --- parity/cli/mod.rs | 2 +- parity/configuration.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index f703a52afcf..d74e740ee04 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -661,7 +661,7 @@ usage! { ARG arg_tx_queue_per_sender: (Option) = None, or |c: &Config| c.mining.as_ref()?.tx_queue_per_sender.clone(), "--tx-queue-per-sender=[LIMIT]", - "Maximum number of transactions per sender in the queue. By default it's 5% of the entire queue, but not less than 16.", + "Maximum number of transactions per sender in the queue. By default it's 1% of the entire queue, but not less than 16.", ARG arg_tx_queue_gas: (String) = "off", or |c: &Config| c.mining.as_ref()?.tx_queue_gas.clone(), "--tx-queue-gas=[LIMIT]", diff --git a/parity/configuration.rs b/parity/configuration.rs index 31f97f509fa..586b0e444a5 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -547,7 +547,7 @@ impl Configuration { Ok(pool::Options { max_count, - max_per_sender: self.args.arg_tx_queue_per_sender.unwrap_or_else(|| cmp::max(16, max_count / 20)), + max_per_sender: self.args.arg_tx_queue_per_sender.unwrap_or_else(|| cmp::max(16, max_count / 100)), max_mem_usage: if self.args.arg_tx_queue_mem_limit > 0 { self.args.arg_tx_queue_mem_limit as usize * 1024 * 1024 } else { From eb025db94352d5410c132b26cc1031afd61cf4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Mar 2018 14:28:51 +0100 Subject: [PATCH 42/77] Avoid cloning when propagating transactions. --- ethcore/light/src/provider.rs | 3 ++- ethcore/src/client/client.rs | 10 +++------- ethcore/src/client/test_client.rs | 10 +++------- ethcore/src/client/traits.rs | 5 ++++- ethcore/src/miner/miner.rs | 2 +- sync/src/chain.rs | 17 +++++++++-------- 6 files changed, 22 insertions(+), 25 deletions(-) diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 1d9af0ac19b..e9adb080b1f 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -281,7 +281,8 @@ impl Provider for T { } fn ready_transactions(&self) -> Vec { - BlockChainClient::ready_transactions(self) + unimplemented!() + // BlockChainClient::ready_transactions(self) } fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option { diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index fb7b61f00b9..16c562747a0 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -56,6 +56,7 @@ use header::{BlockNumber, Header}; use io::IoChannel; use log_entry::LocalizedLogEntry; use miner::{Miner, MinerService}; +use ethcore_miner::pool::VerifiedTransaction; use parking_lot::{Mutex, RwLock}; use rand::OsRng; use receipt::{Receipt, LocalizedReceipt}; @@ -67,7 +68,7 @@ use state_db::StateDB; use state::{self, State}; use trace; use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase}; -use transaction::{self, LocalizedTransaction, UnverifiedTransaction, SignedTransaction, Transaction, PendingTransaction, Action}; +use transaction::{self, LocalizedTransaction, UnverifiedTransaction, SignedTransaction, Transaction, Action}; use types::filter::Filter; use types::mode::Mode as IpcMode; use verification; @@ -1899,13 +1900,8 @@ impl BlockChainClient for Client { } } - fn ready_transactions(&self) -> Vec { - // TODO [ToDr] Avoid cloning and propagate miner transaction further. + fn ready_transactions(&self) -> Vec> { self.importer.miner.ready_transactions(self) - .into_iter() - .map(|x| x.signed().clone()) - .map(Into::into) - .collect() } fn queue_consensus_message(&self, message: Bytes) { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 69a33c9b2e8..b67c51644b3 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -31,7 +31,8 @@ use kvdb_memorydb; use bytes::Bytes; use rlp::*; use ethkey::{Generator, Random}; -use transaction::{self, Transaction, LocalizedTransaction, PendingTransaction, SignedTransaction, Action}; +use ethcore_miner::pool::VerifiedTransaction; +use transaction::{self, Transaction, LocalizedTransaction, SignedTransaction, Action}; use blockchain::{TreeRoute, BlockReceipts}; use client::{ Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, CallContract, TransactionInfo, RegistryInfo, @@ -801,13 +802,8 @@ impl BlockChainClient for TestBlockChainClient { self.spec.engine.handle_message(&message).unwrap(); } - // TODO [ToDr] Avoid cloning - fn ready_transactions(&self) -> Vec { + fn ready_transactions(&self) -> Vec> { self.miner.ready_transactions(self) - .into_iter() - .map(|tx| tx.signed().clone()) - .map(Into::into) - .collect() } fn signing_chain_id(&self) -> Option { None } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 826fba066d5..92489013f92 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -15,6 +15,8 @@ // along with Parity. If not, see . use std::collections::BTreeMap; +use std::sync::Arc; + use itertools::Itertools; use block::{OpenBlock, SealedBlock, ClosedBlock}; @@ -36,6 +38,7 @@ use header::Header; use engines::EthEngine; use ethereum_types::{H256, U256, Address}; +use ethcore_miner::pool::VerifiedTransaction; use bytes::Bytes; use hashdb::DBValue; @@ -314,7 +317,7 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra fn queue_consensus_message(&self, message: Bytes); /// List all transactions that are allowed into the next block. - fn ready_transactions(&self) -> Vec; + fn ready_transactions(&self) -> Vec>; /// Sorted list of transaction gas prices from at least last sample_size blocks. fn gas_price_corpus(&self, sample_size: usize) -> ::stats::Corpus { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 50d968a6ab3..0c006279bd9 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -792,6 +792,7 @@ impl miner::MinerService for Miner { } fn future_transactions(&self) -> Vec> { + // TODO [ToDr] Implement! unimplemented!() // self.transaction_queue.read().future_transactions() } @@ -911,7 +912,6 @@ impl miner::MinerService for Miner { ) } - // TODO [ToDr] Pass sealing lock guard /// Update sealing if required. /// Prepare the block and work if the Engine does not seal internally. fn update_sealing(&self, chain: &C) where diff --git a/sync/src/chain.rs b/sync/src/chain.rs index d569991a579..e203281d518 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -102,7 +102,7 @@ use ethcore::header::{BlockNumber, Header as BlockHeader}; use ethcore::client::{BlockChainClient, BlockStatus, BlockId, BlockChainInfo, BlockImportError, BlockQueueInfo}; use ethcore::error::*; use ethcore::snapshot::{ManifestData, RestorationStatus}; -use transaction::PendingTransaction; +use transaction::SignedTransaction; use sync_io::SyncIo; use time; use super::SyncConfig; @@ -1979,8 +1979,9 @@ impl ChainSync { return 0; } - let (transactions, service_transactions): (Vec<_>, Vec<_>) = transactions.into_iter() - .partition(|tx| !tx.transaction.gas_price.is_zero()); + let (transactions, service_transactions): (Vec<_>, Vec<_>) = transactions.iter() + .map(|tx| tx.signed()) + .partition(|tx| !tx.gas_price.is_zero()); // usual transactions could be propagated to all peers let mut affected_peers = HashSet::new(); @@ -2015,13 +2016,13 @@ impl ChainSync { .collect() } - fn propagate_transactions_to_peers(&mut self, io: &mut SyncIo, peers: Vec, transactions: Vec) -> HashSet { + fn propagate_transactions_to_peers(&mut self, io: &mut SyncIo, peers: Vec, transactions: Vec<&SignedTransaction>) -> HashSet { let all_transactions_hashes = transactions.iter() - .map(|tx| tx.transaction.hash()) + .map(|tx| tx.hash()) .collect::>(); let all_transactions_rlp = { let mut packet = RlpStream::new_list(transactions.len()); - for tx in &transactions { packet.append(&tx.transaction); } + for tx in &transactions { packet.append(&**tx); } packet.out() }; @@ -2065,10 +2066,10 @@ impl ChainSync { packet.begin_unbounded_list(); let mut pushed = 0; for tx in &transactions { - let hash = tx.transaction.hash(); + let hash = tx.hash(); if to_send.contains(&hash) { let mut transaction = RlpStream::new(); - tx.transaction.rlp_append(&mut transaction); + tx.rlp_append(&mut transaction); let appended = packet.append_raw_checked(&transaction.drain(), 1, MAX_TRANSACTION_PACKET_SIZE); if !appended { // Maximal packet size reached just proceed with sending From 1f1694b444592857bfd9811e72d4e40c0d835b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Mar 2018 14:29:58 +0100 Subject: [PATCH 43/77] Remove old todo. --- ethcore/src/client/client.rs | 1 - ethcore/src/client/traits.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 16c562747a0..af79966f408 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1937,7 +1937,6 @@ impl BlockChainClient for Client { let transaction = Transaction { nonce: self.latest_nonce(&authoring_params.author), action: Action::Call(address), - // TODO [ToDr] Check that params carefuly. gas: self.importer.miner.sensible_gas_limit(), gas_price: self.importer.miner.sensible_gas_price(), value: U256::zero(), diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 92489013f92..6018ff75351 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -31,7 +31,7 @@ use header::{BlockNumber}; use log_entry::LocalizedLogEntry; use receipt::LocalizedReceipt; use trace::LocalizedTrace; -use transaction::{self, LocalizedTransaction, PendingTransaction, SignedTransaction}; +use transaction::{self, LocalizedTransaction, SignedTransaction}; use verification::queue::QueueInfo as BlockQueueInfo; use state::StateInfo; use header::Header; From 660b6b0969cfd04c24c6e21b050e4a734c763131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Mar 2018 10:59:45 +0100 Subject: [PATCH 44/77] Post-review fixes. --- ethcore/Cargo.toml | 13 ++++++-- ethcore/light/src/net/tests/mod.rs | 2 +- ethcore/light/src/transaction_queue.rs | 16 +++++----- ethcore/node_filter/src/lib.rs | 2 +- ethcore/src/client/traits.rs | 2 +- ethcore/src/engines/mod.rs | 20 ++++++++++-- ethcore/src/miner/miner.rs | 12 ++++--- ethcore/src/miner/mod.rs | 2 +- miner/src/lib.rs | 1 - miner/src/pool/listener.rs | 4 +-- miner/src/pool/local_transactions.rs | 20 ++++++------ miner/src/pool/mod.rs | 10 ++++++ miner/src/pool/queue.rs | 10 ++++-- miner/src/pool/verifier.rs | 18 +++++++++-- rpc/src/v1/helpers/errors.rs | 4 +-- rpc/src/v1/impls/light/parity.rs | 15 +++++++++ rpc/src/v1/impls/parity.rs | 10 ++++-- rpc/src/v1/tests/helpers/miner_service.rs | 8 ++--- rpc/src/v1/traits/parity.rs | 8 ++++- rpc/src/v1/types/transaction.rs | 8 ++--- sync/src/chain.rs | 15 +++------ sync/src/tests/consensus.rs | 38 +++++++++++------------ sync/src/tests/helpers.rs | 9 ++++-- transaction-pool/src/listener.rs | 10 +++--- transaction-pool/src/pool.rs | 2 +- transaction-pool/src/tests/mod.rs | 8 ++--- 26 files changed, 174 insertions(+), 93 deletions(-) diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index e45b6d90c93..cddc0cdb10e 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -73,11 +73,20 @@ tempdir = "0.3" trie-standardmap = { path = "../util/trie-standardmap" } [features] +# Enable JIT EVM interpreter. jit = ["evm/jit"] +# Display EVM debug traces. evm-debug = ["slow-blocks"] +# Display EVM debug traces when running tests. evm-debug-tests = ["evm-debug", "evm/evm-debug-tests"] -slow-blocks = [] # Use SLOW_TX_DURATION="50" (compile time!) to track transactions over 50ms +# Measure time of transaction execution. +# Whenever the transaction execution time (in millis) exceeds the value of +# SLOW_TX_DURATION env variable (provided compile time!) +# EVM debug traces are printed. +slow-blocks = [] +# Run JSON consensus tests. json-tests = ["ethcore-transaction/json-tests"] +# Run memory/cpu heavy tests. test-heavy = [] -default = [] +# Compile benches benches = [] diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 2d689fd8c70..721aee13fed 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -172,7 +172,7 @@ impl Provider for TestProvider { }) } - fn ready_transactions(&self) -> Vec { + fn ready_transactions(&self) -> Vec { self.0.client.ready_transactions() } } diff --git a/ethcore/light/src/transaction_queue.rs b/ethcore/light/src/transaction_queue.rs index caef364574f..ae3dc269156 100644 --- a/ethcore/light/src/transaction_queue.rs +++ b/ethcore/light/src/transaction_queue.rs @@ -121,7 +121,7 @@ impl AccountTransactions { } /// Transaction import result. -pub enum ImportResult { +pub enum ImportDestination { /// Transaction has been imported to the current queue. /// /// It's going to be propagated to peers. @@ -129,7 +129,7 @@ pub enum ImportResult { /// Transaction has been imported to future queue. /// /// It means it won't be propagated until the gap is filled. - Future + Future, } type Listener = Box; @@ -154,7 +154,7 @@ impl fmt::Debug for TransactionQueue { impl TransactionQueue { /// Import a pending transaction to be queued. - pub fn import(&mut self, tx: PendingTransaction) -> Result { + pub fn import(&mut self, tx: PendingTransaction) -> Result { let sender = tx.sender(); let hash = tx.hash(); let nonce = tx.nonce; @@ -170,7 +170,7 @@ impl TransactionQueue { future: BTreeMap::new(), }); - (ImportResult::Current, vec![hash]) + (ImportDestination::Current, vec![hash]) } Entry::Occupied(mut entry) => { let acct_txs = entry.get_mut(); @@ -192,7 +192,7 @@ impl TransactionQueue { let old = ::std::mem::replace(&mut acct_txs.current[idx], tx_info); self.by_hash.remove(&old.hash); - (ImportResult::Current, vec![hash]) + (ImportDestination::Current, vec![hash]) } Err(idx) => { let cur_len = acct_txs.current.len(); @@ -214,13 +214,13 @@ impl TransactionQueue { acct_txs.future.insert(future_nonce, future); } - (ImportResult::Current, vec![hash]) + (ImportDestination::Current, vec![hash]) } else if idx == cur_len && acct_txs.current.last().map_or(false, |f| f.nonce + 1.into() != nonce) { trace!(target: "txqueue", "Queued future transaction for {}, nonce={}", sender, nonce); let future_nonce = nonce; acct_txs.future.insert(future_nonce, tx_info); - (ImportResult::Future, vec![]) + (ImportDestination::Future, vec![]) } else { trace!(target: "txqueue", "Queued current transaction for {}, nonce={}", sender, nonce); @@ -229,7 +229,7 @@ impl TransactionQueue { let mut promoted = acct_txs.adjust_future(); promoted.insert(0, hash); - (ImportResult::Current, promoted) + (ImportDestination::Current, promoted) } } } diff --git a/ethcore/node_filter/src/lib.rs b/ethcore/node_filter/src/lib.rs index 1fa955464ae..9329d2ed64d 100644 --- a/ethcore/node_filter/src/lib.rs +++ b/ethcore/node_filter/src/lib.rs @@ -139,7 +139,7 @@ mod test { ClientConfig::default(), &spec, client_db, - Arc::new(Miner::with_spec(&spec)), + Arc::new(Miner::new_for_tests(&spec, None)), IoChannel::disconnected(), ).unwrap(); let filter = NodeFilter::new(Arc::downgrade(&client) as Weak, contract_addr); diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 6018ff75351..f6e3de6a3a5 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -368,7 +368,7 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra /// Returns information about pruning/data availability. fn pruning_info(&self) -> PruningInfo; - /// Import a transaction: used for misbehaviour reporting. + /// Schedule state-altering transaction to be executed on the next pending block. fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error>; /// Get the address of the registry itself. diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index d6cae45b71a..a00641caad6 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -374,13 +374,27 @@ pub trait EthEngine: Engine<::machine::EthereumMachine> { } /// Verify a particular transaction is valid. + /// + /// Unordered verification doesn't rely on the transaction execution order, + /// i.e. it should only verify stuff that doesn't assume any previous transactions + /// has already been verified and executed. + /// + /// NOTE This function consumuses an `UnverifiedTransaction` and produces `SignedTransaction` + /// which implies that a heavy check of the signature is performed here. fn verify_transaction_unordered(&self, t: UnverifiedTransaction, header: &Header) -> Result { self.machine().verify_transaction_unordered(t, header) } - /// Additional verification for transactions in blocks. - // TODO: Add flags for which bits of the transaction to check. - // TODO: consider including State in the params. + /// Perform basic/cheap transaction verification. + /// + /// This should include all cheap checks that can be done before + /// actually checking the signature, like chain-replay protection. + /// + /// NOTE This is done before the signature is recovered so avoid + /// doing any state-touching checks that might be expensive. + /// + /// TODO: Add flags for which bits of the transaction to check. + /// TODO: consider including State in the params. fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> Result<(), transaction::Error> { self.machine().verify_transaction_basic(t, header) } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 0c006279bd9..01cf2fa0ce5 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -483,6 +483,11 @@ impl Miner { (block, original_work_hash) } + /// Returns `true` if we should create pending block even if some other conditions are not met. + /// + /// In general we always seal iff: + /// 1. --force-sealing CLI parameter is provided + /// 2. There are listeners awaiting new work packages (e.g. remote work notifications or stratum). fn forced_sealing(&self) -> bool { self.options.force_sealing || !self.listeners.read().is_empty() } @@ -791,10 +796,8 @@ impl miner::MinerService for Miner { self.transaction_queue.local_transactions() } - fn future_transactions(&self) -> Vec> { - // TODO [ToDr] Implement! - unimplemented!() - // self.transaction_queue.read().future_transactions() + fn queued_transactions(&self) -> Vec> { + self.transaction_queue.all_transactions() } fn ready_transactions(&self, chain: &C) -> Vec> where @@ -1124,6 +1127,7 @@ mod tests { enable_resubmission: true, infinite_pending_block: false, tx_queue_penalization: Penalization::Disabled, + tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, refuse_service_transactions: false, pool_limits: Default::default(), pool_verification_options: pool::verifier::Options { diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 3ddc46f8237..b3485f11ccb 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -166,7 +166,7 @@ pub trait MinerService : Send + Sync { 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 future_transactions(&self) -> Vec>; + fn queued_transactions(&self) -> Vec>; /// Get a list of local transactions with statuses. fn local_transactions(&self) -> BTreeMap; diff --git a/miner/src/lib.rs b/miner/src/lib.rs index 11c52c47d4f..a59a48a3086 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -44,7 +44,6 @@ extern crate ethkey; #[cfg(test)] extern crate env_logger; -// pub mod banning_queue; pub mod external; pub mod gas_pricer; pub mod pool; diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs index 697957b7698..4a27710011b 100644 --- a/miner/src/pool/listener.rs +++ b/miner/src/pool/listener.rs @@ -90,8 +90,8 @@ impl txpool::Listener for Logger { debug!(target: "txqueue", "[{:?}] Marked as invalid by executor.", tx.hash()); } - fn cancelled(&mut self, tx: &Arc) { - debug!(target: "txqueue", "[{:?}] Cancelled by the user.", tx.hash()); + fn canceled(&mut self, tx: &Arc) { + debug!(target: "txqueue", "[{:?}] Canceled by the user.", tx.hash()); } fn mined(&mut self, tx: &Arc) { diff --git a/miner/src/pool/local_transactions.rs b/miner/src/pool/local_transactions.rs index b3ad30d1e03..b071790439e 100644 --- a/miner/src/pool/local_transactions.rs +++ b/miner/src/pool/local_transactions.rs @@ -36,10 +36,10 @@ pub enum Status { Dropped(Arc), /// Replaced because of higher gas price of another transaction. Replaced { - /// Transaction object - tx: Arc, + /// Replaced transaction + old: Arc, /// Transaction that replaced this one. - by: Arc, + new: Arc, }, /// Transaction was never accepted to the queue. /// It means that it was too cheap to replace any transaction already in the pool. @@ -108,12 +108,12 @@ impl LocalTransactionsList { return; } - let to_remove = self.transactions + let to_remove: Vec<_> = self.transactions .iter() .filter(|&(_, status)| !status.is_pending()) .map(|(hash, _)| *hash) .take(number_of_old - self.max_old) - .collect::>(); + .collect(); for hash in to_remove { self.transactions.remove(&hash); @@ -134,8 +134,8 @@ impl txpool::Listener for LocalTransactionsList { if let Some(old) = old { if self.transactions.contains_key(old.hash()) { self.transactions.insert(*old.hash(), Status::Replaced { - tx: old.clone(), - by: tx.clone(), + old: old.clone(), + new: tx.clone(), }); } } @@ -171,7 +171,7 @@ impl txpool::Listener for LocalTransactionsList { self.clear_old(); } - fn cancelled(&mut self, tx: &Arc) { + fn canceled(&mut self, tx: &Arc) { if !tx.priority().is_local() { return; } @@ -216,8 +216,8 @@ mod tests { list.added(&tx2, None); // then - assert!(list.contains(tx1.hash()), "Should contain the transaction."); - assert!(list.contains(tx2.hash()), "Should contain the transaction."); + assert!(list.contains(tx1.hash())); + assert!(list.contains(tx2.hash())); let statuses = list.all_transactions().values().cloned().collect::>(); assert_eq!(statuses, vec![Status::Pending(tx1), Status::Pending(tx2)]); } diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 8a4855b825d..045e14e8326 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -44,10 +44,20 @@ pub enum PrioritizationStrategy { GasPriceOnly, } +/// Transaction priority. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(crate) enum Priority { + /// Local transactions (high priority) + /// + /// Transactions either from a local account or + /// submitted over local RPC connection via `eth_sendRawTransaction` Local, + /// Transactions from retracted blocks (medium priority) + /// + /// When block becomes non-canonical we re-import the transactions it contains + /// to the queue and boost their priority. Retracted, + /// Regular transactions received over the network. (no priority boost) Regular, } diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 5105df779eb..24190629b3c 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -40,10 +40,10 @@ type Pool = txpool::Pool Vec> { + let ready = |_tx: &pool::VerifiedTransaction| txpool::Readiness::Ready; + self.pool.read().pending(ready).collect() + } + /// Returns current pneding transactions. /// /// NOTE: This may return a cached version of pending transaction set. diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 530ca720b37..93195f6ae54 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -106,6 +106,20 @@ impl Transaction { Transaction::Local(ref tx) => &*tx, } } + + fn is_local(&self) -> bool { + match *self { + Transaction::Local(..) => true, + _ => false, + } + } + + fn is_retracted(&self) -> bool { + match *self { + Transaction::Retracted(..) => true, + _ => false, + } + } } /// Transaction verifier. @@ -175,7 +189,7 @@ impl txpool::Verifier for Verifier { }) } - let is_own = if let Transaction::Local(..) = tx { true } else { false }; + let is_own = tx.is_local(); // Quick exit for non-service transactions if tx.gas_price() < &self.options.minimal_gas_price && !tx.gas_price().is_zero() @@ -196,7 +210,7 @@ impl txpool::Verifier for Verifier { // Some more heavy checks below. // Actually recover sender and verify that transaction - let is_retracted = if let Transaction::Retracted(_) = tx { true } else { false }; + let is_retracted = tx.is_retracted(); let transaction = match tx { Transaction::Retracted(tx) | Transaction::Unverified(tx) => match self.client.verify_transaction(tx) { Ok(signed) => signed.into(), diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 9393fc53a01..47058e739fc 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -373,11 +373,11 @@ pub fn no_light_peers() -> Error { } } -pub fn deprecated>>(message: T) -> Error { +pub fn deprecated, T: Into>>(message: T) -> Error { Error { code: ErrorCode::ServerError(codes::DEPRECATED), message: "Method deprecated".into(), - data: message.into().map(Value::String), + data: message.into().map(Into::into).map(Value::String), } } diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index 3b2fab9a3cb..574343ddb1a 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -275,6 +275,21 @@ impl Parity for ParityClient { ) } + fn all_transactions(&self) -> Result> { + let txq = self.light_dispatch.transaction_queue.read(); + let chain_info = self.light_dispatch.client.chain_info(); + + let current = txq.ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp); + let future = txq.future_transactions(chain_info.best_block_number, chain_info.best_block_timestamp); + Ok( + current + .into_iter() + .chain(future.into_iter()) + .map(|tx| Transaction::from_pending(tx, chain_info.best_block_number, self.eip86_transition)) + .collect::>() + ) + } + fn future_transactions(&self) -> Result> { let txq = self.light_dispatch.transaction_queue.read(); let chain_info = self.light_dispatch.client.chain_info(); diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 7f0a5ac8976..306d75033d9 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -324,17 +324,21 @@ impl Parity for ParityClient where ) } - fn future_transactions(&self) -> Result> { + fn all_transactions(&self) -> Result> { let block_number = self.client.chain_info().best_block_number; - let future_transactions = self.miner.future_transactions(); + let all_transactions = self.miner.queued_transactions(); - Ok(future_transactions + Ok(all_transactions .into_iter() .map(|t| Transaction::from_pending(t.pending().clone(), block_number, self.eip86_transition)) .collect() ) } + fn future_transactions(&self) -> Result> { + Err(errors::deprecated("Use `parity_allTransaction` instead.")) + } + fn pending_transactions_stats(&self) -> Result> { let stats = self.sync.transactions_stats(); Ok(stats.into_iter() diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 173848a07ac..0bd2f80832a 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -209,15 +209,15 @@ impl MinerService for TestMinerService { } fn ready_transactions(&self, _chain: &C) -> Vec> { + self.queued_transactions() + } + + fn queued_transactions(&self) -> Vec> { self.pending_transactions.lock().values().cloned().map(|tx| { Arc::new(VerifiedTransaction::from_pending_block_transaction(tx)) }).collect() } - fn future_transactions(&self) -> Vec> { - vec![] - } - fn pending_receipt(&self, _best_block: BlockNumber, hash: &H256) -> Option { // Not much point implementing this since the logic is complex and the only thing it relies on is pending_receipts, which is already tested. self.pending_receipts(0).unwrap().get(hash).map(|r| diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 81c406fb3a3..165cf63d677 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -143,7 +143,13 @@ build_rpc_trait! { #[rpc(name = "parity_pendingTransactions")] fn pending_transactions(&self) -> Result>; - /// Returns all future transactions from transaction queue. + /// Returns all transactions from transaction queue. + /// + /// Some of them might not be ready to be included in a block yet. + #[rpc(name = "parity_allTransactions")] + fn all_transactions(&self) -> Result>; + + /// Returns all future transactions from transaction queue (deprecated) #[rpc(name = "parity_futureTransactions")] fn future_transactions(&self) -> Result>; diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 3329b944a18..3ab955b7993 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -260,10 +260,10 @@ impl LocalTransactionStatus { Rejected(tx) => LocalTransactionStatus::Rejected(convert(tx)), Invalid(tx) => LocalTransactionStatus::Invalid(convert(tx)), Canceled(tx) => LocalTransactionStatus::Canceled(convert(tx)), - Replaced { tx, by } => LocalTransactionStatus::Replaced( - convert(tx), - by.signed().gas_price.into(), - by.signed().hash().into(), + Replaced { old, new } => LocalTransactionStatus::Replaced( + convert(old), + new.signed().gas_price.into(), + new.signed().hash().into(), ), } } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index e203281d518..cf229b671b3 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -2971,8 +2971,7 @@ mod tests { let mut io = TestIo::new(&mut client, &ss, &queue, None); io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks); sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[], &[]); - assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); - assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 1); + assert_eq!(io.chain.miner.ready_transactions(io.chain).len(), 1); } // We need to update nonce status (because we say that the block has been imported) for h in &[good_blocks[0]] { @@ -2988,9 +2987,7 @@ mod tests { } // then - let status = client.miner.status(); - assert_eq!(status.transactions_in_pending_queue, 1); - assert_eq!(status.transactions_in_future_queue, 0); + assert_eq!(client.miner.ready_transactions(&client).len(), 1); } #[test] @@ -3011,13 +3008,11 @@ mod tests { // when sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[], &[]); - assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); - assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 0); + assert_eq!(io.chain.miner.queue_status().status.transaction_count, 0); sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[], &[]); // then - let status = io.chain.miner.status(); - assert_eq!(status.transactions_in_pending_queue, 0); - assert_eq!(status.transactions_in_future_queue, 0); + let status = io.chain.miner.queue_status(); + assert_eq!(status.status.transaction_count, 0); } } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 33ecbb7f4f6..872dd374c06 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -68,8 +68,8 @@ fn authority_round() { let io_handler0: Arc> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); let io_handler1: Arc> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); // Push transaction to both clients. Only one of them gets lucky to produce a block. - net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); - net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); + net.peer(0).miner.set_author(s0.address(), Some("".into())).unwrap(); + net.peer(1).miner.set_author(s1.address(), Some("".to_owned())).unwrap(); net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain) as _); net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain) as _); net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); @@ -77,15 +77,15 @@ fn authority_round() { // exchange statuses net.sync(); // Trigger block proposal - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into(), chain_id)).unwrap(); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into(), chain_id)).unwrap(); + net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into(), chain_id)).unwrap(); // Sync a block net.sync(); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into(), chain_id)).unwrap(); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into(), chain_id)).unwrap(); + net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into(), chain_id)).unwrap(); // Move to next proposer step. net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); @@ -94,8 +94,8 @@ fn authority_round() { assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); // Fork the network with equal height. - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into(), chain_id)).unwrap(); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into(), chain_id)).unwrap(); + net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into(), chain_id)).unwrap(); // Let both nodes build one block. net.peer(0).chain.engine().step(); let early_hash = net.peer(0).chain.chain_info().best_block_hash; @@ -117,8 +117,8 @@ fn authority_round() { assert_eq!(ci1.best_block_hash, early_hash); // Selfish miner - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 3.into(), chain_id)).unwrap(); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 3.into(), chain_id)).unwrap(); + net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 3.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 3.into(), chain_id)).unwrap(); // Node 0 is an earlier primary. net.peer(0).chain.engine().step(); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 4); @@ -129,7 +129,7 @@ fn authority_round() { // Node 1 makes 2 blocks, but is a later primary on the first one. net.peer(1).chain.engine().step(); net.peer(1).chain.engine().step(); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 4.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 4.into(), chain_id)).unwrap(); net.peer(1).chain.engine().step(); net.peer(1).chain.engine().step(); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 5); @@ -155,9 +155,9 @@ fn tendermint() { let io_handler0: Arc> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); let io_handler1: Arc> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); // Push transaction to both clients. Only one of them issues a proposal. - net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); + net.peer(0).miner.set_author(s0.address(), Some("".into())).unwrap(); trace!(target: "poa", "Peer 0 is {}.", s0.address()); - net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); + net.peer(1).miner.set_author(s1.address(), Some("".into())).unwrap(); trace!(target: "poa", "Peer 1 is {}.", s1.address()); net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain) as _); net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain) as _); @@ -166,7 +166,7 @@ fn tendermint() { // Exhange statuses net.sync(); // Propose - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into(), chain_id)).unwrap(); + net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into(), chain_id)).unwrap(); net.sync(); // Propose timeout, synchronous for now net.peer(0).chain.engine().step(); @@ -177,7 +177,7 @@ fn tendermint() { assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into(), chain_id)).unwrap(); // Commit timeout net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); @@ -191,8 +191,8 @@ fn tendermint() { assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into(), chain_id)).unwrap(); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into(), chain_id)).unwrap(); + net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into(), chain_id)).unwrap(); // Peers get disconnected. // Commit net.peer(0).chain.engine().step(); @@ -200,8 +200,8 @@ fn tendermint() { // Propose net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); - net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into(), chain_id)).unwrap(); - net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into(), chain_id)).unwrap(); + net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into(), chain_id)).unwrap(); + net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into(), chain_id)).unwrap(); // Send different prevotes net.sync(); // Prevote timeout diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 31417e1aa38..cacdc29e11a 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -191,6 +191,7 @@ pub struct EthPeer where C: FlushingBlockChainClient { pub snapshot_service: Arc, pub sync: RwLock, pub queue: RwLock>, + pub miner: Arc, } impl Peer for EthPeer { @@ -259,6 +260,7 @@ impl TestNet> { }; for _ in 0..n { let chain = TestBlockChainClient::new(); + let miner = chain.miner.clone(); let ss = Arc::new(TestSnapshotService::new()); let sync = ChainSync::new(config.clone(), &chain); net.peers.push(Arc::new(EthPeer { @@ -266,6 +268,7 @@ impl TestNet> { snapshot_service: ss, chain: Arc::new(chain), queue: RwLock::new(VecDeque::new()), + miner, })); } net @@ -288,11 +291,12 @@ impl TestNet> { } pub fn add_peer(&mut self, config: SyncConfig, spec: Spec, accounts: Option>) { + let miner = Arc::new(Miner::new_for_tests(&spec, accounts)); let client = EthcoreClient::new( ClientConfig::default(), &spec, Arc::new(::kvdb_memorydb::create(::ethcore::db::NUM_COLUMNS.unwrap_or(0))), - Arc::new(Miner::new_for_tests(&spec, accounts)), + miner.clone(), IoChannel::disconnected(), ).unwrap(); @@ -301,8 +305,9 @@ impl TestNet> { let peer = Arc::new(EthPeer { sync: RwLock::new(sync), snapshot_service: ss, - chain: client, queue: RwLock::new(VecDeque::new()), + chain: client, + miner, }); peer.chain.add_notify(peer.clone()); self.peers.push(peer); diff --git a/transaction-pool/src/listener.rs b/transaction-pool/src/listener.rs index 3bdbe2f989b..428550b8c29 100644 --- a/transaction-pool/src/listener.rs +++ b/transaction-pool/src/listener.rs @@ -36,8 +36,8 @@ pub trait Listener { /// The transaction was marked as invalid by executor. fn invalid(&mut self, _tx: &Arc) {} - /// The transaction has been cancelled. - fn cancelled(&mut self, _tx: &Arc) {} + /// The transaction has been canceled. + fn canceled(&mut self, _tx: &Arc) {} /// The transaction has been mined. fn mined(&mut self, _tx: &Arc) {} @@ -72,9 +72,9 @@ impl Listener for (A, B) where self.1.invalid(tx); } - fn cancelled(&mut self, tx: &Arc) { - self.0.cancelled(tx); - self.1.cancelled(tx); + fn canceled(&mut self, tx: &Arc) { + self.0.canceled(tx); + self.1.canceled(tx); } fn mined(&mut self, tx: &Arc) { diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 4a3159a92b3..f80989eea0c 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -303,7 +303,7 @@ impl Pool where if is_invalid { self.listener.invalid(&tx); } else { - self.listener.cancelled(&tx); + self.listener.canceled(&tx); } Some(tx) } else { diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index 85a0dece36b..88fa1f1dacc 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -475,8 +475,8 @@ mod listener { self.0.borrow_mut().push("invalid".into()); } - fn cancelled(&mut self, _tx: &SharedTransaction) { - self.0.borrow_mut().push("cancelled".into()); + fn canceled(&mut self, _tx: &SharedTransaction) { + self.0.borrow_mut().push("canceled".into()); } fn mined(&mut self, _tx: &SharedTransaction) { @@ -535,9 +535,9 @@ mod listener { // then txq.remove(&tx1.hash(), false); - assert_eq!(*results.borrow(), &["added", "added", "cancelled"]); + assert_eq!(*results.borrow(), &["added", "added", "canceled"]); txq.remove(&tx2.hash(), true); - assert_eq!(*results.borrow(), &["added", "added", "cancelled", "invalid"]); + assert_eq!(*results.borrow(), &["added", "added", "canceled", "invalid"]); assert_eq!(txq.light_status().transaction_count, 0); } From 006d63591df34941fd6316710500fb529b8c9710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Mar 2018 12:48:36 +0100 Subject: [PATCH 45/77] Fix miner options default. --- ethcore/src/miner/miner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 01cf2fa0ce5..3d6f0f51453 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -152,7 +152,7 @@ impl Default for MinerOptions { refuse_service_transactions: false, pool_limits: pool::Options { max_count: 8_192, - max_per_sender: 409, + max_per_sender: 81, max_mem_usage: 8 * 1024 * 1024, }, pool_verification_options: pool::verifier::Options { From a6b93243ca4733f20d96225af62e3825fc05121f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Mar 2018 12:59:07 +0100 Subject: [PATCH 46/77] Implement back ready transactions for light client. --- ethcore/light/src/provider.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index e9adb080b1f..aaa6f5858ae 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -281,8 +281,10 @@ impl Provider for T { } fn ready_transactions(&self) -> Vec { - unimplemented!() - // BlockChainClient::ready_transactions(self) + BlockChainClient::ready_transactions(self) + .into_iter() + .map(|tx| tx.pending().clone()) + .collect() } fn epoch_signal(&self, req: request::CompleteSignalRequest) -> Option { From cfc2200e4ea1ea0b2125f763f717bb91f20bbf2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Mar 2018 13:15:33 +0100 Subject: [PATCH 47/77] Get rid of from_pending_block --- ethcore/src/miner/miner.rs | 120 ++++++++++++++----------------------- ethcore/src/miner/mod.rs | 7 +-- 2 files changed, 46 insertions(+), 81 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 3d6f0f51453..4e00803ec23 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -291,31 +291,13 @@ impl Miner { fn map_existing_pending_block(&self, f: F, latest_block_number: BlockNumber) -> Option where F: FnOnce(&ClosedBlock) -> T, { - self.from_pending_block( - latest_block_number, - || None, - |block| Some(f(block)), - ) - } - - // TODO [ToDr] Get rid of this method. - // - // We should never fall back to client, this can be handled on RPC level by returning Option<> - fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H where - F: Fn() -> H, - G: FnOnce(&ClosedBlock) -> H, - { - let sealing = self.sealing.lock(); - sealing.queue.peek_last_ref().map_or_else( - || from_chain(), - |b| { - if b.block().header().number() > latest_block_number { - map_block(b) - } else { - from_chain() - } - } - ) + self.sealing.lock().queue + .peek_last_ref() + .and_then(|b| if b.block().header().number() > latest_block_number { + Some(f(b)) + } else { + None + }) } fn client<'a, C: 'a>(&'a self, chain: &'a C) -> BlockChainClient<'a, C> where @@ -818,16 +800,13 @@ impl miner::MinerService for Miner { }; let from_pending = || { - self.from_pending_block( - chain_info.best_block_number, - || None, - |sealing| Some(sealing.transactions() + self.map_existing_pending_block(|sealing| { + sealing.transactions() .iter() .map(|signed| pool::VerifiedTransaction::from_pending_block_transaction(signed.clone())) .map(Arc::new) .collect() - ) - ) + }, chain_info.best_block_number) }; match self.options.pending_set { @@ -864,55 +843,44 @@ impl miner::MinerService for Miner { self.transaction_queue.status() } - // TODO [ToDr] This is pretty inconsistent (you can get a ready_transaction, but no receipt for it) fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option { - self.from_pending_block( - best_block, - // TODO [ToDr] Should try to find transaction in best block! - || None, - |pending| { - let txs = pending.transactions(); - txs.iter() - .map(|t| t.hash()) - .position(|t| t == *hash) - .map(|index| { - let receipts = pending.receipts(); - let prev_gas = if index == 0 { Default::default() } else { receipts[index - 1].gas_used }; - let tx = &txs[index]; - let receipt = &receipts[index]; - RichReceipt { - transaction_hash: hash.clone(), - transaction_index: index, - cumulative_gas_used: receipt.gas_used, - gas_used: receipt.gas_used - prev_gas, - contract_address: match tx.action { - Action::Call(_) => None, - Action::Create => { - let sender = tx.sender(); - Some(contract_address(self.engine.create_address_scheme(pending.header().number()), &sender, &tx.nonce, &tx.data).0) - } - }, - logs: receipt.logs.clone(), - log_bloom: receipt.log_bloom, - outcome: receipt.outcome.clone(), - } - }) - } - ) + self.map_existing_pending_block(|pending| { + let txs = pending.transactions(); + txs.iter() + .map(|t| t.hash()) + .position(|t| t == *hash) + .map(|index| { + let receipts = pending.receipts(); + let prev_gas = if index == 0 { Default::default() } else { receipts[index - 1].gas_used }; + let tx = &txs[index]; + let receipt = &receipts[index]; + RichReceipt { + transaction_hash: hash.clone(), + transaction_index: index, + cumulative_gas_used: receipt.gas_used, + gas_used: receipt.gas_used - prev_gas, + contract_address: match tx.action { + Action::Call(_) => None, + Action::Create => { + let sender = tx.sender(); + Some(contract_address(self.engine.create_address_scheme(pending.header().number()), &sender, &tx.nonce, &tx.data).0) + } + }, + logs: receipt.logs.clone(), + log_bloom: receipt.log_bloom, + outcome: receipt.outcome.clone(), + } + }) + }, best_block).and_then(|x| x) } fn pending_receipts(&self, best_block: BlockNumber) -> Option> { - self.from_pending_block( - best_block, - // TODO [ToDr] This is wrong should look in latest block! - || None, - |pending| { - let hashes = pending.transactions().iter().map(|t| t.hash()); - let receipts = pending.receipts().iter().cloned(); - - Some(hashes.zip(receipts).collect()) - } - ) + self.map_existing_pending_block(|pending| { + let hashes = pending.transactions().iter().map(|t| t.hash()); + let receipts = pending.receipts().iter().cloned(); + + hashes.zip(receipts).collect() + }, best_block) } /// Update sealing if required. diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index b3485f11ccb..bbd645f1d9d 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -57,9 +57,6 @@ pub trait TransactionVerifierClient: Send + Sync /// Extended client interface used for mining pub trait BlockChainClient: TransactionVerifierClient + BlockProducer + SealedBlockImporter {} -// TODO [ToDr] Split into smaller traits? -// TODO [ToDr] get rid of from_pending_block in miner/miner.rs - /// Miner client API pub trait MinerService : Send + Sync { /// Type representing chain state @@ -109,8 +106,8 @@ pub trait MinerService : Send + Sync { /// Get `Some` `clone()` of the current pending block or `None` if we're not sealing. fn pending_block(&self, latest_block_number: BlockNumber) -> Option; - /// Get `Some` `clone()` of the current pending block transactions or `None` if we're not sealing. - fn pending_transactions(&self, latest_block_number: BlockNumber) -> Option>; + // / Get `Some` `clone()` of the current pending block transactions or `None` if we're not sealing. + // fn pending_transactions(&self, latest_block_number: BlockNumber) -> Option>; // Block authoring From ce024949f625038a0df07d0ef8b3391679bd5568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Mar 2018 13:23:40 +0100 Subject: [PATCH 48/77] Pass rejection reason. --- ethcore/src/miner/mod.rs | 4 ++-- miner/src/pool/listener.rs | 4 ++-- miner/src/pool/local_transactions.rs | 8 ++++---- rpc/src/v1/types/transaction.rs | 7 ++++--- transaction-pool/src/listener.rs | 9 +++++---- transaction-pool/src/pool.rs | 16 +++++++--------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index bbd645f1d9d..281d04dae3b 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -106,8 +106,8 @@ pub trait MinerService : Send + Sync { /// Get `Some` `clone()` of the current pending block or `None` if we're not sealing. fn pending_block(&self, latest_block_number: BlockNumber) -> Option; - // / Get `Some` `clone()` of the current pending block transactions or `None` if we're not sealing. - // fn pending_transactions(&self, latest_block_number: BlockNumber) -> Option>; + /// Get `Some` `clone()` of the current pending block transactions or `None` if we're not sealing. + fn pending_transactions(&self, latest_block_number: BlockNumber) -> Option>; // Block authoring diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs index 4a27710011b..a26f35eaf30 100644 --- a/miner/src/pool/listener.rs +++ b/miner/src/pool/listener.rs @@ -78,8 +78,8 @@ impl txpool::Listener for Logger { } } - fn rejected(&mut self, tx: &Arc) { - trace!(target: "txqueue", "[{:?}] Rejected. Too cheap to enter.", tx.hash()); + fn rejected(&mut self, tx: &Arc, reason: &txpool::ErrorKind) { + trace!(target: "txqueue", "[{:?}] Rejected. {}.", tx.hash(), reason); } fn dropped(&mut self, tx: &Arc) { diff --git a/miner/src/pool/local_transactions.rs b/miner/src/pool/local_transactions.rs index b071790439e..6488b26ec55 100644 --- a/miner/src/pool/local_transactions.rs +++ b/miner/src/pool/local_transactions.rs @@ -43,7 +43,7 @@ pub enum Status { }, /// Transaction was never accepted to the queue. /// It means that it was too cheap to replace any transaction already in the pool. - Rejected(Arc), + Rejected(Arc, String), /// Transaction is invalid. Invalid(Arc), /// Transaction was canceled. @@ -141,13 +141,13 @@ impl txpool::Listener for LocalTransactionsList { } } - fn rejected(&mut self, tx: &Arc) { + fn rejected(&mut self, tx: &Arc, reason: &txpool::ErrorKind) { if !tx.priority().is_local() { return; } - debug!(target: "own_tx", "Transaction rejected because it was too cheap (hash {:?})", tx.hash()); - self.transactions.insert(*tx.hash(), Status::Rejected(tx.clone())); + debug!(target: "own_tx", "Transaction rejected (hash {:?}). {}", tx.hash(), reason); + self.transactions.insert(*tx.hash(), Status::Rejected(tx.clone(), format!("{}", reason))); self.clear_old(); } diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 3ab955b7993..461bea007ef 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -89,7 +89,7 @@ pub enum LocalTransactionStatus { /// Transaction was replaced by transaction with higher gas price. Replaced(Transaction, U256, H256), /// Transaction never got into the queue. - Rejected(Transaction), + Rejected(Transaction, String), /// Transaction is invalid. Invalid(Transaction), /// Transaction was canceled. @@ -132,9 +132,10 @@ impl Serialize for LocalTransactionStatus { struc.serialize_field(status, "invalid")?; struc.serialize_field(transaction, tx)?; }, - Rejected(ref tx) => { + Rejected(ref tx, ref reason) => { struc.serialize_field(status, "rejected")?; struc.serialize_field(transaction, tx)?; + struc.serialize_field("error", reason)?; }, Replaced(ref tx, ref gas_price, ref hash) => { struc.serialize_field(status, "replaced")?; @@ -257,7 +258,7 @@ impl LocalTransactionStatus { Pending(_) => LocalTransactionStatus::Pending, Mined(tx) => LocalTransactionStatus::Mined(convert(tx)), Dropped(tx) => LocalTransactionStatus::Dropped(convert(tx)), - Rejected(tx) => LocalTransactionStatus::Rejected(convert(tx)), + Rejected(tx, reason) => LocalTransactionStatus::Rejected(convert(tx), reason), Invalid(tx) => LocalTransactionStatus::Invalid(convert(tx)), Canceled(tx) => LocalTransactionStatus::Canceled(convert(tx)), Replaced { old, new } => LocalTransactionStatus::Replaced( diff --git a/transaction-pool/src/listener.rs b/transaction-pool/src/listener.rs index 428550b8c29..5f99619d5da 100644 --- a/transaction-pool/src/listener.rs +++ b/transaction-pool/src/listener.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::sync::Arc; +use error::ErrorKind; /// Transaction pool listener. /// @@ -28,7 +29,7 @@ pub trait Listener { /// The transaction was rejected from the pool. /// It means that it was too cheap to replace any transaction already in the pool. - fn rejected(&mut self, _tx: &Arc) {} + fn rejected(&mut self, _tx: &Arc, _reason: &ErrorKind) {} /// The transaction was dropped from the pool because of a limit. fn dropped(&mut self, _tx: &Arc) {} @@ -57,9 +58,9 @@ impl Listener for (A, B) where self.1.added(tx, old); } - fn rejected(&mut self, tx: &Arc) { - self.0.rejected(tx); - self.1.rejected(tx); + fn rejected(&mut self, tx: &Arc, reason: &ErrorKind) { + self.0.rejected(tx, reason); + self.1.rejected(tx, reason); } fn dropped(&mut self, tx: &Arc) { diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index f80989eea0c..95c3728cdfd 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -115,7 +115,7 @@ impl Pool where let remove_worst = |s: &mut Self, transaction| { match s.remove_worst(&transaction) { Err(err) => { - s.listener.rejected(&Arc::new(transaction)); + s.listener.rejected(&Arc::new(transaction), err.kind()); Err(err) }, Ok(removed) => { @@ -162,16 +162,14 @@ impl Pool where Ok(new) }, AddResult::TooCheap { new, old } => { - let hash = *new.hash(); - // TODO [ToDr] Pass errors here - self.listener.rejected(&Arc::new(new)); - bail!(error::ErrorKind::TooCheapToReplace(*old.hash(), hash)) + let error = error::ErrorKind::TooCheapToReplace(*old.hash(), *new.hash()); + self.listener.rejected(&Arc::new(new), &error); + bail!(error) }, AddResult::TooCheapToEnter(new) => { - let hash = *new.hash(); - // TODO [ToDr] Pass errors here - self.listener.rejected(&Arc::new(new)); - bail!(error::ErrorKind::TooCheapToEnter(hash)) + let error = error::ErrorKind::TooCheapToEnter(*new.hash()); + self.listener.rejected(&Arc::new(new), &error); + bail!(error) } } } From 68b3f2a7eec32346da45ae5abcd319406bf5cf0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Mar 2018 17:55:30 +0100 Subject: [PATCH 49/77] Add more details to drop. --- miner/src/pool/listener.rs | 11 +++++++---- miner/src/pool/local_transactions.rs | 7 +++++-- transaction-pool/src/listener.rs | 10 +++++----- transaction-pool/src/pool.rs | 4 ++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs index a26f35eaf30..ecaa9f05ae6 100644 --- a/miner/src/pool/listener.rs +++ b/miner/src/pool/listener.rs @@ -78,12 +78,15 @@ impl txpool::Listener for Logger { } } - fn rejected(&mut self, tx: &Arc, reason: &txpool::ErrorKind) { - trace!(target: "txqueue", "[{:?}] Rejected. {}.", tx.hash(), reason); + fn rejected(&mut self, _tx: &Arc, reason: &txpool::ErrorKind) { + trace!(target: "txqueue", "Rejected {}.", reason); } - fn dropped(&mut self, tx: &Arc) { - debug!(target: "txqueue", "[{:?}] Dropped because of limit.", tx.hash()); + fn dropped(&mut self, tx: &Arc, new: Option<&Transaction>) { + match new { + Some(new) => debug!(target: "txqueue", "[{:?}] Pushed out by [{:?}]", tx.hash(), new.hash()), + None => debug!(target: "txqueue", "[{:?}] Dropped.", tx.hash()), + } } fn invalid(&mut self, tx: &Arc) { diff --git a/miner/src/pool/local_transactions.rs b/miner/src/pool/local_transactions.rs index 6488b26ec55..552a6239600 100644 --- a/miner/src/pool/local_transactions.rs +++ b/miner/src/pool/local_transactions.rs @@ -151,12 +151,15 @@ impl txpool::Listener for LocalTransactionsList { self.clear_old(); } - fn dropped(&mut self, tx: &Arc) { + fn dropped(&mut self, tx: &Arc, new: Option<&Transaction>) { if !tx.priority().is_local() { return; } - warn!(target: "own_tx", "Transaction dropped because of limit (hash {:?})", tx.hash()); + match new { + Some(new) => warn!(target: "own_tx", "Transaction pushed out because of limit (hash {:?}, replacement: {:?})", tx.hash(), new.hash()), + None => warn!(target: "own_tx", "Transaction dropped because of limit (hash: {:?})", tx.hash()), + } self.transactions.insert(*tx.hash(), Status::Dropped(tx.clone())); self.clear_old(); } diff --git a/transaction-pool/src/listener.rs b/transaction-pool/src/listener.rs index 5f99619d5da..728a035e314 100644 --- a/transaction-pool/src/listener.rs +++ b/transaction-pool/src/listener.rs @@ -31,8 +31,8 @@ pub trait Listener { /// It means that it was too cheap to replace any transaction already in the pool. fn rejected(&mut self, _tx: &Arc, _reason: &ErrorKind) {} - /// The transaction was dropped from the pool because of a limit. - fn dropped(&mut self, _tx: &Arc) {} + /// The transaction was pushed out from the pool because of the limit. + fn dropped(&mut self, _tx: &Arc, _by: Option<&T>) {} /// The transaction was marked as invalid by executor. fn invalid(&mut self, _tx: &Arc) {} @@ -63,9 +63,9 @@ impl Listener for (A, B) where self.1.rejected(tx, reason); } - fn dropped(&mut self, tx: &Arc) { - self.0.dropped(tx); - self.1.dropped(tx); + fn dropped(&mut self, tx: &Arc, by: Option<&T>) { + self.0.dropped(tx, by); + self.1.dropped(tx, by); } fn invalid(&mut self, tx: &Arc) { diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 95c3728cdfd..7a8624c6739 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -119,7 +119,7 @@ impl Pool where Err(err) }, Ok(removed) => { - s.listener.dropped(&removed); + s.listener.dropped(&removed, Some(&transaction)); s.finalize_remove(removed.hash()); Ok(transaction) }, @@ -286,7 +286,7 @@ impl Pool where self.worst_transactions.clear(); for (_hash, tx) in self.by_hash.drain() { - self.listener.dropped(&tx) + self.listener.dropped(&tx, None) } } From 72f49d4820755cf7da7b6e47f09ff0f20ff9a9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 12 Mar 2018 18:01:18 +0100 Subject: [PATCH 50/77] Rollback heap size of. --- ethcore/transaction/src/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/transaction/src/transaction.rs b/ethcore/transaction/src/transaction.rs index ea7119e777b..e3d7fcde8f9 100644 --- a/ethcore/transaction/src/transaction.rs +++ b/ethcore/transaction/src/transaction.rs @@ -116,7 +116,7 @@ impl Transaction { impl HeapSizeOf for Transaction { fn heap_size_of_children(&self) -> usize { - self.data.len() + self.data.heap_size_of_children() } } From 8ab2912aac758f1e504303f9e108612c8b704092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 13 Mar 2018 10:28:57 +0100 Subject: [PATCH 51/77] Avoid cloning hashes when propagating and include more details on rejection. --- ethcore/src/client/chain_notify.rs | 2 +- ethcore/src/client/client.rs | 2 +- sync/src/api.rs | 2 +- sync/src/chain.rs | 4 ++-- transaction-pool/src/error.rs | 8 ++++---- transaction-pool/src/pool.rs | 8 ++++---- transaction-pool/src/transactions.rs | 11 ++++++----- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index d05c95b8c85..0dbeb31bc1a 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -49,7 +49,7 @@ pub trait ChainNotify : Send + Sync { /// fires when new transactions are received from a peer fn transactions_received(&self, - _hashes: Vec, + _hashes: &[H256], _peer_id: usize, ) { // does nothing by default diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index af79966f408..a4309357ff1 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -988,7 +988,7 @@ impl Client { // Notify sync that the transactions were received from given peer let hashes: Vec<_> = txs.iter().map(|tx| tx.hash()).collect(); self.notify(|notify| { - notify.transactions_received(hashes.clone(), peer_id); + notify.transactions_received(&hashes, peer_id); }); let results = self.importer.miner.import_external_transactions(self, txs); diff --git a/sync/src/api.rs b/sync/src/api.rs index 38543de15c3..07603c59b68 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -441,7 +441,7 @@ impl ChainNotify for EthSync { }); } - fn transactions_received(&self, hashes: Vec, peer_id: PeerId) { + fn transactions_received(&self, hashes: &[H256], peer_id: PeerId) { let mut sync = self.eth_handler.sync.write(); sync.transactions_received(hashes, peer_id); } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index cf229b671b3..d6ed6d63df6 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -457,9 +457,9 @@ impl ChainSync { } /// Updates transactions were received by a peer - pub fn transactions_received(&mut self, hashes: Vec, peer_id: PeerId) { + pub fn transactions_received(&mut self, hashes: &[H256], peer_id: PeerId) { if let Some(peer_info) = self.peers.get_mut(&peer_id) { - peer_info.last_sent_transactions.extend(&hashes); + peer_info.last_sent_transactions.extend(hashes); } } diff --git a/transaction-pool/src/error.rs b/transaction-pool/src/error.rs index 706a5b77b19..d7591eff7f0 100644 --- a/transaction-pool/src/error.rs +++ b/transaction-pool/src/error.rs @@ -21,17 +21,17 @@ error_chain! { /// Transaction is already imported AlreadyImported(hash: H256) { description("transaction is already in the pool"), - display("[{:?}] transaction already imported", hash) + display("[{:?}] already imported", hash) } /// Transaction is too cheap to enter the queue - TooCheapToEnter(hash: H256) { + TooCheapToEnter(hash: H256, min_score: String) { description("the pool is full and transaction is too cheap to replace any transaction"), - display("[{:?}] transaction too cheap to enter the pool", hash) + display("[{:?}] too cheap to enter the pool. Min score: {}", hash, min_score) } /// Transaction is too cheap to replace existing transaction that occupies the same slot. TooCheapToReplace(old_hash: H256, hash: H256) { description("transaction is too cheap to replace existing transaction in the pool"), - display("[{:?}] transaction too cheap to replace: {:?}", hash, old_hash) + display("[{:?}] too cheap to replace: {:?}", hash, old_hash) } } } diff --git a/transaction-pool/src/pool.rs b/transaction-pool/src/pool.rs index 7a8624c6739..fa28cdcdfe5 100644 --- a/transaction-pool/src/pool.rs +++ b/transaction-pool/src/pool.rs @@ -166,8 +166,8 @@ impl Pool where self.listener.rejected(&Arc::new(new), &error); bail!(error) }, - AddResult::TooCheapToEnter(new) => { - let error = error::ErrorKind::TooCheapToEnter(*new.hash()); + AddResult::TooCheapToEnter(new, score) => { + let error = error::ErrorKind::TooCheapToEnter(*new.hash(), format!("{:?}", score)); self.listener.rejected(&Arc::new(new), &error); bail!(error) } @@ -243,14 +243,14 @@ impl Pool where // No elements to remove? and the pool is still full? None => { warn!("The pool is full but there are no transactions to remove."); - return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash()).into()); + return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash(), "unknown".into()).into()); }, Some(old) => if self.scoring.should_replace(&old.transaction, transaction) { // New transaction is better than the worst one so we can replace it. old.clone() } else { // otherwise fail - return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash()).into()) + return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash(), format!("{:?}", old.score)).into()) }, }; diff --git a/transaction-pool/src/transactions.rs b/transaction-pool/src/transactions.rs index d7d824e8e3c..c839d9e6853 100644 --- a/transaction-pool/src/transactions.rs +++ b/transaction-pool/src/transactions.rs @@ -23,9 +23,9 @@ use ready::{Ready, Readiness}; use scoring::{self, Scoring}; #[derive(Debug)] -pub enum AddResult { +pub enum AddResult { Ok(Arc), - TooCheapToEnter(T), + TooCheapToEnter(T, S), TooCheap { old: Arc, new: T, @@ -93,10 +93,11 @@ impl> Transactions { }) } - fn push_cheapest_transaction(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult { + fn push_cheapest_transaction(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult { let index = self.transactions.len(); if index == max_count { - AddResult::TooCheapToEnter(tx) + let min_score = self.scores[index - 1].clone(); + AddResult::TooCheapToEnter(tx, min_score) } else { let shared = Arc::new(tx); self.transactions.push(shared.clone()); @@ -111,7 +112,7 @@ impl> Transactions { scoring.update_scores(&self.transactions, &mut self.scores, scoring::Change::Event(event)); } - pub fn add(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult { + pub fn add(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult { let index = match self.transactions.binary_search_by(|old| scoring.compare(old, &tx)) { Ok(index) => index, Err(index) => index, From 901e53223aded3612a0284e339e88319aadf0388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 13 Mar 2018 10:36:22 +0100 Subject: [PATCH 52/77] Fix tests. --- miner/src/pool/local_transactions.rs | 2 +- rpc/src/v1/types/transaction.rs | 6 ++++-- transaction-pool/src/error.rs | 2 +- transaction-pool/src/tests/mod.rs | 12 ++++++------ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/miner/src/pool/local_transactions.rs b/miner/src/pool/local_transactions.rs index 552a6239600..a3fabbb6bb3 100644 --- a/miner/src/pool/local_transactions.rs +++ b/miner/src/pool/local_transactions.rs @@ -235,7 +235,7 @@ mod tests { list.added(&tx1, None); list.invalid(&tx1); - list.dropped(&tx2); + list.dropped(&tx2, None); assert!(!list.contains(tx1.hash())); assert!(list.contains(tx2.hash())); assert!(!list.contains(tx3.hash())); diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 461bea007ef..0ac3e374542 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -290,7 +290,7 @@ mod tests { let status3 = LocalTransactionStatus::Mined(Transaction::default()); let status4 = LocalTransactionStatus::Dropped(Transaction::default()); let status5 = LocalTransactionStatus::Invalid(Transaction::default()); - let status6 = LocalTransactionStatus::Rejected(Transaction::default()); + let status6 = LocalTransactionStatus::Rejected(Transaction::default(), "Just because".into()); let status7 = LocalTransactionStatus::Replaced(Transaction::default(), 5.into(), 10.into()); assert_eq!( @@ -315,7 +315,9 @@ mod tests { ); assert_eq!( serde_json::to_string(&status6).unwrap(), - r#"{"status":"rejected","transaction":"#.to_owned() + &format!("{}", tx_ser) + r#"}"# + r#"{"status":"rejected","transaction":"#.to_owned() + + &format!("{}", tx_ser) + + r#","error":"Just because"}"# ); assert_eq!( serde_json::to_string(&status7).unwrap(), diff --git a/transaction-pool/src/error.rs b/transaction-pool/src/error.rs index d7591eff7f0..2e8ac739897 100644 --- a/transaction-pool/src/error.rs +++ b/transaction-pool/src/error.rs @@ -43,7 +43,7 @@ impl PartialEq for ErrorKind { match (self, other) { (&AlreadyImported(ref h1), &AlreadyImported(ref h2)) => h1 == h2, - (&TooCheapToEnter(ref h1), &TooCheapToEnter(ref h2)) => h1 == h2, + (&TooCheapToEnter(ref h1, ref s1), &TooCheapToEnter(ref h2, ref s2)) => h1 == h2 && s1 == s2, (&TooCheapToReplace(ref old1, ref new1), &TooCheapToReplace(ref old2, ref new2)) => old1 == old2 && new1 == new2, _ => false, } diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index 88fa1f1dacc..7bb44ef4516 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -125,7 +125,7 @@ fn should_reject_if_above_count() { let tx2 = b.tx().nonce(1).new(); let hash = *tx2.hash(); txq.import(tx1).unwrap(); - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash)); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); assert_eq!(txq.light_status().transaction_count, 1); txq.clear(); @@ -151,7 +151,7 @@ fn should_reject_if_above_mem_usage() { let tx2 = b.tx().nonce(2).mem_usage(2).new(); let hash = *tx2.hash(); txq.import(tx1).unwrap(); - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash)); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); assert_eq!(txq.light_status().transaction_count, 1); txq.clear(); @@ -177,7 +177,7 @@ fn should_reject_if_above_sender_count() { let tx2 = b.tx().nonce(2).new(); let hash = *tx2.hash(); txq.import(tx1).unwrap(); - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash)); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); assert_eq!(txq.light_status().transaction_count, 1); txq.clear(); @@ -188,7 +188,7 @@ fn should_reject_if_above_sender_count() { let hash = *tx2.hash(); txq.import(tx1).unwrap(); // This results in error because we also compare nonces - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash)); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); assert_eq!(txq.light_status().transaction_count, 1); } @@ -463,11 +463,11 @@ mod listener { self.0.borrow_mut().push(if old.is_some() { "replaced" } else { "added" }); } - fn rejected(&mut self, _tx: &SharedTransaction) { + fn rejected(&mut self, _tx: &SharedTransaction, _reason: &error::ErrorKind) { self.0.borrow_mut().push("rejected".into()); } - fn dropped(&mut self, _tx: &SharedTransaction) { + fn dropped(&mut self, _tx: &SharedTransaction, _new: Option<&Transaction>) { self.0.borrow_mut().push("dropped".into()); } From 7f151407652eb2b1fe3949e08ef9289fb125c16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 13 Mar 2018 12:16:32 +0100 Subject: [PATCH 53/77] Introduce nonces cache. --- ethcore/src/miner/blockchain_client.rs | 106 ++++++++++++++++++------- ethcore/src/miner/miner.rs | 14 +++- 2 files changed, 87 insertions(+), 33 deletions(-) diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index 5aba3c94e68..4da542a69b3 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -15,14 +15,17 @@ // along with Parity. If not, see . use std::fmt; +use std::collections::HashMap; use ethereum_types::{H256, U256, Address}; use ethcore_miner::pool; +use ethcore_miner::pool::client::NonceClient; use transaction::{ self, UnverifiedTransaction, SignedTransaction, }; +use parking_lot::RwLock; use account_provider::AccountProvider; use client::{TransactionId, BlockInfo, CallContract, Nonce}; @@ -31,16 +34,17 @@ use header::Header; use miner; use miner::service_transaction_checker::ServiceTransactionChecker; -// TODO [ToDr] Shit -#[derive(Clone)] -pub struct FakeContainer(Header); -unsafe impl Sync for FakeContainer {} +type NoncesCache = RwLock>; + +const MAX_NONCE_CACHE_SIZE: usize = 4096; +const EXPECTED_NONCE_CACHE_SIZE: usize = 2048; pub struct BlockChainClient<'a, C: 'a> { chain: &'a C, + cached_nonces: CachedNonceClient<'a, C>, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>, - best_block_header: FakeContainer, + best_block_header: SyncHeader, service_transaction_checker: Option, } @@ -48,6 +52,7 @@ impl<'a, C: 'a> Clone for BlockChainClient<'a, C> { fn clone(&self) -> Self { BlockChainClient { chain: self.chain, + cached_nonces: self.cached_nonces.clone(), engine: self.engine, accounts: self.accounts.clone(), best_block_header: self.best_block_header.clone(), @@ -57,20 +62,20 @@ impl<'a, C: 'a> Clone for BlockChainClient<'a, C> { } impl<'a, C: 'a> BlockChainClient<'a, C> where - C: BlockInfo + CallContract, +C: BlockInfo + CallContract, { pub fn new( chain: &'a C, + cache: &'a NoncesCache, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>, refuse_service_transactions: bool, ) -> Self { - let best_block_header = chain.best_block_header().decode(); - best_block_header.hash(); - best_block_header.bare_hash(); - let best_block_header = FakeContainer(best_block_header); + let best_block_header = SyncHeader::new(chain.best_block_header().decode()); + BlockChainClient { chain, + cached_nonces: CachedNonceClient::new(chain, cache), engine, accounts, best_block_header, @@ -100,9 +105,7 @@ impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where self.chain.transaction_block(TransactionId::Hash(*hash)).is_some() } - fn verify_transaction(&self, tx: UnverifiedTransaction) - -> Result - { + fn verify_transaction(&self, tx: UnverifiedTransaction)-> Result { self.engine.verify_transaction_basic(&tx, &self.best_block_header.0)?; let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header.0)?; @@ -113,7 +116,7 @@ impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where fn account_details(&self, address: &Address) -> pool::client::AccountDetails { pool::client::AccountDetails { - nonce: self.chain.latest_nonce(address), + nonce: self.cached_nonces.account_nonce(address), balance: self.chain.latest_balance(address), is_local: self.accounts.map_or(false, |accounts| accounts.has_account(*address).unwrap_or(false)), } @@ -138,45 +141,90 @@ impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where } } -impl<'a, C: 'a> pool::client::NonceClient for BlockChainClient<'a, C> where +impl<'a, C: 'a> NonceClient for BlockChainClient<'a, C> where C: Nonce + Sync, { fn account_nonce(&self, address: &Address) -> U256 { - self.chain.latest_nonce(address) + self.cached_nonces.account_nonce(address) } } -// TODO [ToDr] Remove! -pub struct NonceClient<'a, C: 'a> { +pub struct CachedNonceClient<'a, C: 'a> { client: &'a C, + cache: &'a NoncesCache, } -impl<'a, C: 'a> Clone for NonceClient<'a, C> { +impl<'a, C: 'a> Clone for CachedNonceClient<'a, C> { fn clone(&self) -> Self { - NonceClient { + CachedNonceClient { client: self.client, + cache: self.cache, } } } -impl<'a, C: 'a> fmt::Debug for NonceClient<'a, C> { +impl<'a, C: 'a> fmt::Debug for CachedNonceClient<'a, C> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "NonceClient") + fmt.debug_struct("CachedNonceClient") + .field("cache", &self.cache.read().len()) + .finish() } } -impl<'a, C: 'a> NonceClient<'a, C> { - pub fn new(client: &'a C) -> Self { - NonceClient { +impl<'a, C: 'a> CachedNonceClient<'a, C> { + pub fn new(client: &'a C, cache: &'a NoncesCache) -> Self { + CachedNonceClient { client, + cache, } } } -impl<'a, C: 'a> pool::client::NonceClient for NonceClient<'a, C> - where C: Nonce + Sync, +impl<'a, C: 'a> NonceClient for CachedNonceClient<'a, C> where + C: Nonce + Sync, { - fn account_nonce(&self, address: &Address) -> U256 { - self.client.latest_nonce(address) + fn account_nonce(&self, address: &Address) -> U256 { + if let Some(nonce) = self.cache.read().get(address) { + return *nonce; + } + + // We don't check again if cache has been populated. + // It's not THAT expensive to fetch the nonce from state. + let mut cache = self.cache.write(); + let nonce = self.client.latest_nonce(address); + cache.insert(*address, nonce); + + if cache.len() < MAX_NONCE_CACHE_SIZE { + return nonce + } + + // Remove excessive amount of entries from the cache + while cache.len() > EXPECTED_NONCE_CACHE_SIZE { + // Just remove random entry + if let Some(key) = cache.keys().next().cloned() { + cache.remove(&key); + } + } + nonce + } +} + +/// A `Sync` wrapper for `Header`. +#[derive(Clone)] +pub struct SyncHeader(Header); +// Since hashes are pre-computed +// we can safely implement Sync here +// +// TODO [ToDr] Remove this wrapper when `Header` +// does not use `RefCells` any more. +unsafe impl Sync for SyncHeader {} + +impl SyncHeader { + pub fn new(header: Header) -> Self { + // Make sure to compute and memoize hashes. + header.hash(); + header.bare_hash(); + + SyncHeader(header) } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 4e00803ec23..0e9ad85bf0c 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::time::{Instant, Duration}; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashSet, HashMap}; use std::sync::Arc; use ansi_term::Colour; @@ -46,7 +46,7 @@ use client::BlockId; use executive::contract_address; use header::{Header, BlockNumber}; use miner; -use miner::blockchain_client::{BlockChainClient, NonceClient}; +use miner::blockchain_client::{BlockChainClient, CachedNonceClient}; use receipt::{Receipt, RichReceipt}; use spec::Spec; use state::State; @@ -200,6 +200,7 @@ pub struct Miner { params: RwLock, listeners: RwLock>>, gas_pricer: Mutex, + nonce_cache: RwLock>, options: MinerOptions, // TODO [ToDr] Arc is only required because of price updater transaction_queue: Arc, @@ -242,6 +243,7 @@ impl Miner { params: RwLock::new(AuthoringParams::default()), listeners: RwLock::new(vec![]), gas_pricer: Mutex::new(gas_pricer), + nonce_cache: RwLock::new(HashMap::with_capacity(1024)), options, transaction_queue: Arc::new(TransactionQueue::new(limits, verifier_options, tx_queue_strategy)), accounts, @@ -305,6 +307,7 @@ impl Miner { { BlockChainClient::new( chain, + &self.nonce_cache, &*self.engine, self.accounts.as_ref().map(|x| &**x), self.options.refuse_service_transactions, @@ -789,7 +792,7 @@ impl miner::MinerService for Miner { let from_queue = || { self.transaction_queue.pending( - NonceClient::new(chain), + CachedNonceClient::new(chain, &self.nonce_cache), chain_info.best_block_number, chain_info.best_block_timestamp, // We propagate transactions over the nonce cap. @@ -825,7 +828,7 @@ impl miner::MinerService for Miner { fn next_nonce(&self, chain: &C, address: &Address) -> U256 where C: Nonce + Sync, { - self.transaction_queue.next_nonce(NonceClient::new(chain), address) + self.transaction_queue.next_nonce(CachedNonceClient::new(chain, &self.nonce_cache), address) .unwrap_or_else(|| chain.latest_nonce(address)) } @@ -983,6 +986,9 @@ impl miner::MinerService for Miner { // 2. We ignore blocks that are `invalid` because it doesn't have any meaning in terms of the transactions that // are in those blocks + // Clear nonce cache + self.nonce_cache.write().clear(); + // First update gas limit in transaction queue and minimal gas price. let gas_limit = chain.best_block_header().gas_limit(); self.update_transaction_queue_limits(gas_limit); From 1dcc47efebc5405b30a4a727f8924dbe5023b404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 13 Mar 2018 12:46:46 +0100 Subject: [PATCH 54/77] Remove uneccessary hashes allocation. --- ethcore/src/client/chain_notify.rs | 5 +++-- ethcore/src/client/client.rs | 4 +--- sync/src/api.rs | 5 +++-- sync/src/chain.rs | 5 +++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 0dbeb31bc1a..229d16fffcd 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethereum_types::H256; use bytes::Bytes; +use ethereum_types::H256; +use transaction::UnverifiedTransaction; /// Represents what has to be handled by actor listening to chain events pub trait ChainNotify : Send + Sync { @@ -49,7 +50,7 @@ pub trait ChainNotify : Send + Sync { /// fires when new transactions are received from a peer fn transactions_received(&self, - _hashes: &[H256], + _txs: &[UnverifiedTransaction], _peer_id: usize, ) { // does nothing by default diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index a4309357ff1..0ef83b481e9 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -985,10 +985,8 @@ impl Client { .filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()) .collect(); - // Notify sync that the transactions were received from given peer - let hashes: Vec<_> = txs.iter().map(|tx| tx.hash()).collect(); self.notify(|notify| { - notify.transactions_received(&hashes, peer_id); + notify.transactions_received(&txs, peer_id); }); let results = self.importer.miner.import_external_transactions(self, txs); diff --git a/sync/src/api.rs b/sync/src/api.rs index 07603c59b68..7fa7b72922d 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -37,6 +37,7 @@ use light::client::AsLightClient; use light::Provider; use light::net::{self as light_net, LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext}; use network::IpFilter; +use transaction::UnverifiedTransaction; /// Parity sync protocol pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; @@ -441,9 +442,9 @@ impl ChainNotify for EthSync { }); } - fn transactions_received(&self, hashes: &[H256], peer_id: PeerId) { + fn transactions_received(&self, txs: &[UnverifiedTransaction], peer_id: PeerId) { let mut sync = self.eth_handler.sync.write(); - sync.transactions_received(hashes, peer_id); + sync.transactions_received(txs, peer_id); } } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index d6ed6d63df6..12891eaa7e5 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -111,6 +111,7 @@ use rand::Rng; use snapshot::{Snapshot, ChunkType}; use api::{EthProtocolInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; use transactions_stats::{TransactionsStats, Stats as TransactionStats}; +use transaction::UnverifiedTransaction; known_heap_size!(0, PeerInfo); @@ -457,9 +458,9 @@ impl ChainSync { } /// Updates transactions were received by a peer - pub fn transactions_received(&mut self, hashes: &[H256], peer_id: PeerId) { + pub fn transactions_received(&mut self, txs: &[UnverifiedTransaction], peer_id: PeerId) { if let Some(peer_info) = self.peers.get_mut(&peer_id) { - peer_info.last_sent_transactions.extend(hashes); + peer_info.last_sent_transactions.extend(txs.iter().map(|tx| tx.hash())); } } From a74f18adf144e539ac03574a5d3b9e3e9e10b8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 13 Mar 2018 12:49:20 +0100 Subject: [PATCH 55/77] Lower the mem limit. --- ethcore/src/miner/miner.rs | 2 +- parity/cli/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 0e9ad85bf0c..e09d7a3e7c4 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -153,7 +153,7 @@ impl Default for MinerOptions { pool_limits: pool::Options { max_count: 8_192, max_per_sender: 81, - max_mem_usage: 8 * 1024 * 1024, + max_mem_usage: 4 * 1024 * 1024, }, pool_verification_options: pool::verifier::Options { minimal_gas_price: DEFAULT_MINIMAL_GAS_PRICE.into(), diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index d74e740ee04..cd0bf7abd87 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -651,7 +651,7 @@ usage! { "--gas-cap=[GAS]", "A cap on how large we will raise the gas limit per block due to transaction volume.", - ARG arg_tx_queue_mem_limit: (u32) = 8u32, or |c: &Config| c.mining.as_ref()?.tx_queue_mem_limit.clone(), + ARG arg_tx_queue_mem_limit: (u32) = 4u32, or |c: &Config| c.mining.as_ref()?.tx_queue_mem_limit.clone(), "--tx-queue-mem-limit=[MB]", "Maximum amount of memory that can be used by the transaction queue. Setting this parameter to 0 disables limiting.", @@ -1561,7 +1561,7 @@ mod tests { arg_extra_data: Some("Parity".into()), arg_tx_queue_size: 8192usize, arg_tx_queue_per_sender: None, - arg_tx_queue_mem_limit: 8u32, + arg_tx_queue_mem_limit: 4u32, arg_tx_queue_gas: "off".into(), arg_tx_queue_strategy: "gas_factor".into(), arg_tx_queue_ban_count: 1u16, From bdfdb3dd0559b3ad9495c1f255f5909b938181b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 13 Mar 2018 13:03:13 +0100 Subject: [PATCH 56/77] Re-enable parallel verification. --- miner/src/pool/queue.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 24190629b3c..221707bf720 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -23,6 +23,7 @@ use std::collections::BTreeMap; use ethereum_types::{H256, U256, Address}; use parking_lot::RwLock; +use rayon::prelude::*; use transaction; use txpool::{self, Verifier}; @@ -174,7 +175,7 @@ impl TransactionQueue { let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone()); let results = transactions - .into_iter() + .into_par_iter() .map(|transaction| verifier.verify_transaction(transaction)) .map(|result| match result { Ok(verified) => match self.pool.write().import(verified) { From 2d1da26767b9229ae45b12a04883f56574e7127d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 13 Mar 2018 16:24:28 +0100 Subject: [PATCH 57/77] Add miner log. Don't check the type if not below min_gas_price. --- ethcore/src/miner/miner.rs | 2 ++ miner/src/pool/verifier.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index e09d7a3e7c4..891cd3dcfd4 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -382,6 +382,8 @@ impl Miner { nonce_cap, ); + debug!(target: "miner", "Attempting to push {} transactions.", pending.len()); + for tx in pending { let start = Instant::now(); diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index 93195f6ae54..92d2eb9c86b 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -222,10 +222,11 @@ impl txpool::Verifier for Verifier { Transaction::Local(tx) => tx, }; - let transaction_type = self.client.transaction_type(&transaction); let sender = transaction.sender(); let account_details = self.client.account_details(&sender); + if transaction.gas_price < self.options.minimal_gas_price { + let transaction_type = self.client.transaction_type(&transaction); if let TransactionType::Service = transaction_type { debug!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); } else if is_own || account_details.is_local { From cca0db7b6dd10fff0399224b8104c3addf11e6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 14 Mar 2018 14:36:32 +0100 Subject: [PATCH 58/77] Add more traces, fix disabling miner. --- ethcore/src/miner/miner.rs | 31 +++++++++++++++++++++++-------- miner/src/pool/listener.rs | 10 ++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 891cd3dcfd4..ec4c6e9ce94 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -217,7 +217,9 @@ impl Miner { /// Push an URL that will get new job notifications. pub fn add_work_listener_url(&self, urls: &[String]) { - self.add_work_listener(Box::new(WorkPoster::new(&urls))); + if !urls.is_empty() { + self.add_work_listener(Box::new(WorkPoster::new(urls))); + } } /// Set a callback to be notified about imported transactions' hashes. @@ -275,7 +277,7 @@ impl Miner { /// /// Limits consist of current block gas limit and minimal gas price. pub fn update_transaction_queue_limits(&self, block_gas_limit: U256) { - debug!(target: "miner", "minimal_gas_price: recalibrating..."); + trace!(target: "miner", "minimal_gas_price: recalibrating..."); let txq = self.transaction_queue.clone(); let mut options = self.options.pool_verification_options.clone(); self.gas_pricer.lock().recalibrate(move |gas_price| { @@ -382,6 +384,12 @@ impl Miner { nonce_cap, ); + let took_ms = |elapsed: &Duration| { + elapsed.as_secs() * 1000 + elapsed.subsec_nanos() as u64 / 1_000_000 + }; + + + let block_start = Instant::now(); debug!(target: "miner", "Attempting to push {} transactions.", pending.len()); for tx in pending { @@ -399,18 +407,17 @@ impl Miner { }); let took = start.elapsed(); - let took_ms = || took.as_secs() * 1000 + took.subsec_nanos() as u64 / 1_000_000; // Check for heavy transactions match self.options.tx_queue_penalization { Penalization::Enabled { ref offend_threshold } if &took > offend_threshold => { senders_to_penalize.insert(sender); - debug!(target: "miner", "Detected heavy transaction ({} ms). Penalizing sender.", took_ms()); + debug!(target: "miner", "Detected heavy transaction ({} ms). Penalizing sender.", took_ms(&took)); }, _ => {}, } - debug!(target: "miner", "Adding tx {:?} took {} ms", hash, took_ms()); + debug!(target: "miner", "Adding tx {:?} took {} ms", hash, took_ms(&took)); match result { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); @@ -457,7 +464,8 @@ impl Miner { _ => tx_count += 1, } } - trace!(target: "miner", "Pushed {} transactions", tx_count); + let elapsed = block_start.elapsed(); + debug!(target: "miner", "Pushed {} transactions in {} ms", tx_count, took_ms(&elapsed)); let block = open_block.close(); @@ -496,12 +504,19 @@ impl Miner { // keep sealing enabled if any of the conditions is met let sealing_enabled = self.forced_sealing() || self.transaction_queue.has_local_pending_transactions() - || self.engine.seals_internally().is_some() + || self.engine.seals_internally() == Some(true) || had_requests; + let should_disable_sealing = !sealing_enabled; - trace!(target: "miner", "requires_reseal: should_disable_sealing={}; best_block={}, last_request={}", should_disable_sealing, best_block, sealing.last_request); + trace!(target: "miner", "requires_reseal: should_disable_sealing={}; forced={:?}, has_local={:?}, internal={:?}, had_requests={:?}", + should_disable_sealing, + self.forced_sealing(), + self.transaction_queue.has_local_pending_transactions(), + self.engine.seals_internally(), + had_requests, + ); if should_disable_sealing { trace!(target: "miner", "Miner sleeping (current {}, last {})", best_block, sealing.last_request); diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs index ecaa9f05ae6..4d5b41f6fdb 100644 --- a/miner/src/pool/listener.rs +++ b/miner/src/pool/listener.rs @@ -72,6 +72,16 @@ pub struct Logger; impl txpool::Listener for Logger { fn added(&mut self, tx: &Arc, old: Option<&Arc>) { debug!(target: "txqueue", "[{:?}] Added to the pool.", tx.hash()); + debug!( + target: "txqueue", + "[{:?}] Sender: {sender}, nonce: {nonce}, gasPrice: {gas_price}, gas: {gas}, value: {value}, dataLen: {data}))", + sender = tx.sender(), + nonce = tx.signed().nonce, + gas_price = tx.signed().gas_price, + gas = tx.signed().gas, + value = tx.signed().value, + data = tx.signed().data.len(), + ); if let Some(old) = old { debug!(target: "txqueue", "[{:?}] Dropped. Replaced by [{:?}]", old.hash(), tx.hash()); From 281e54d0b7725a2ee85d04f9558cb2dbad1ea84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 14 Mar 2018 15:46:01 +0100 Subject: [PATCH 59/77] Fix creating pending blocks twice on AuRa authorities. --- ethcore/src/client/client.rs | 4 ++-- ethcore/src/engines/authority_round/mod.rs | 5 ++++- ethcore/src/miner/miner.rs | 5 +++-- ethcore/src/miner/mod.rs | 4 +++- sync/src/chain.rs | 4 ++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1e4a752a8d5..85e83aed211 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -342,7 +342,7 @@ impl Importer { let (enacted, retracted) = self.calculate_enacted_retracted(&import_results); if is_empty { - self.miner.chain_new_blocks(client, &imported_blocks, &invalid_blocks, &enacted, &retracted); + self.miner.chain_new_blocks(client, &imported_blocks, &invalid_blocks, &enacted, &retracted, false); } client.notify(|notify| { @@ -2063,7 +2063,7 @@ impl ImportSealedBlock for Client { route }; let (enacted, retracted) = self.importer.calculate_enacted_retracted(&[route]); - self.importer.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted); + self.importer.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted, true); self.notify(|notify| { notify.new_blocks( vec![h.clone()], diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index c184c52f71a..93b61deafc9 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -824,7 +824,10 @@ impl Engine for AuthorityRound { fn generate_seal(&self, block: &ExecutedBlock, parent: &Header) -> Seal { // first check to avoid generating signature most of the time // (but there's still a race to the `compare_and_swap`) - if !self.can_propose.load(AtomicOrdering::SeqCst) { return Seal::None; } + if !self.can_propose.load(AtomicOrdering::SeqCst) { + trace!(target: "engine", "Aborting seal generation. Can't propose."); + return Seal::None; + } let header = block.header(); let parent_step: U256 = header_step(parent, self.empty_steps_transition) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index ec4c6e9ce94..656b3752dae 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -576,6 +576,7 @@ impl Miner { }, // Directly import a regular sealed block. Seal::Regular(seal) => { + trace!(target: "miner", "Received a Regular seal."); { let mut sealing = self.sealing.lock(); sealing.next_mandatory_reseal = Instant::now() + self.options.reseal_max_period; @@ -994,7 +995,7 @@ impl miner::MinerService for Miner { }) } - fn chain_new_blocks(&self, chain: &C, imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) + fn chain_new_blocks(&self, chain: &C, imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256], is_internal_import: bool) where C: miner::BlockChainClient, { trace!(target: "miner", "chain_new_blocks"); @@ -1032,7 +1033,7 @@ impl miner::MinerService for Miner { // ...and at the end remove the old ones self.transaction_queue.cull(client); - if enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle) { + if !is_internal_import && (enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle)) { // -------------------------------------------------------------------------- // | NOTE Code below requires transaction_queue and sealing locks. | // | Make sure to release the locks before calling that method. | diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 281d04dae3b..64bb56e9310 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -85,7 +85,9 @@ pub trait MinerService : Send + Sync { // Notifications /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, chain: &C, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256]) + /// `is_internal_import` indicates that the block has just been created in miner and internally sealed by the engine, + /// so we shouldn't attempt creating new block again. + fn chain_new_blocks(&self, chain: &C, imported: &[H256], invalid: &[H256], enacted: &[H256], retracted: &[H256], is_internal_import: bool) where C: BlockChainClient; diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 12891eaa7e5..3c5ab70ba37 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -2970,7 +2970,7 @@ mod tests { let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &queue, None); - io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks); + io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks, false); sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[], &[]); assert_eq!(io.chain.miner.ready_transactions(io.chain).len(), 1); } @@ -2983,7 +2983,7 @@ mod tests { let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); let mut io = TestIo::new(&client, &ss, &queue, None); - io.chain.miner.chain_new_blocks(io.chain, &[], &[], &good_blocks, &retracted_blocks); + io.chain.miner.chain_new_blocks(io.chain, &[], &[], &good_blocks, &retracted_blocks, false); sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[], &[]); } From 6a304c420e5fd3331a8df7128cf76b4cfdc8a3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 14 Mar 2018 15:55:22 +0100 Subject: [PATCH 60/77] Fix tests. --- rpc/src/v1/tests/helpers/miner_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 0bd2f80832a..6781d10b95b 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -170,7 +170,7 @@ impl MinerService for TestMinerService { } /// Called when blocks are imported to chain, updates transactions queue. - fn chain_new_blocks(&self, _chain: &C, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256]) { + fn chain_new_blocks(&self, _chain: &C, _imported: &[H256], _invalid: &[H256], _enacted: &[H256], _retracted: &[H256], _is_internal: bool) { unimplemented!(); } From 383a908af0d07761ce82882d5822a909180bac97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 15 Mar 2018 18:44:17 +0100 Subject: [PATCH 61/77] re-use pending blocks in AuRa --- ethcore/src/miner/miner.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 656b3752dae..42d361f1715 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -939,7 +939,12 @@ impl miner::MinerService for Miner { trace!(target: "miner", "update_sealing: imported internally sealed block"); } }, - Some(false) => trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now"), + Some(false) => { + trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now"); + // anyway, save the block for later use + let mut sealing = self.sealing.lock(); + sealing.queue.push(block); + }, None => { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); self.prepare_work(block, original_work_hash) From 93f93a0715c13d533cad6f3edbb02e4def94e2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 16 Mar 2018 11:20:30 +0100 Subject: [PATCH 62/77] Use reseal_min_period to prevent too frequent update_sealing. --- ethcore/src/miner/miner.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 4fb2d76274d..ee334be64a1 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -387,7 +387,6 @@ impl Miner { elapsed.as_secs() * 1000 + elapsed.subsec_nanos() as u64 / 1_000_000 }; - let block_start = Instant::now(); debug!(target: "miner", "Attempting to push {} transactions.", pending.len()); @@ -494,6 +493,11 @@ impl Miner { return false } + if !sealing.reseal_allowed() { + trace!(target: "miner", "requires_reseal: reseal too early"); + return false + } + trace!(target: "miner", "requires_reseal: sealing enabled"); // Disable sealing if there were no requests for SEALING_TIMEOUT_IN_BLOCKS @@ -1037,12 +1041,19 @@ impl miner::MinerService for Miner { // ...and at the end remove the old ones self.transaction_queue.cull(client); - if !is_internal_import && (enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle)) { - // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing locks. | - // | Make sure to release the locks before calling that method. | - // -------------------------------------------------------------------------- - self.update_sealing(chain); + if enacted.len() > 0 || (imported.len() > 0 && self.options.reseal_on_uncle) { + // Reset `next_allowed_reseal` in case a block is imported. + // Even if min_period is high, we will always attempt to create + // new pending block. + self.sealing.lock().next_allowed_reseal = Instant::now(); + + if !is_internal_import { + // -------------------------------------------------------------------------- + // | NOTE Code below requires transaction_queue and sealing locks. | + // | Make sure to release the locks before calling that method. | + // -------------------------------------------------------------------------- + self.update_sealing(chain); + } } } From 7f290e735b2c142b3b2a767e93c3fbcc5f0f44be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 16 Mar 2018 12:12:06 +0100 Subject: [PATCH 63/77] Fix log to contain hash not sender. --- miner/src/pool/listener.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs index 4d5b41f6fdb..3f42372e840 100644 --- a/miner/src/pool/listener.rs +++ b/miner/src/pool/listener.rs @@ -74,7 +74,8 @@ impl txpool::Listener for Logger { debug!(target: "txqueue", "[{:?}] Added to the pool.", tx.hash()); debug!( target: "txqueue", - "[{:?}] Sender: {sender}, nonce: {nonce}, gasPrice: {gas_price}, gas: {gas}, value: {value}, dataLen: {data}))", + "[{hash:?}] Sender: {sender}, nonce: {nonce}, gasPrice: {gas_price}, gas: {gas}, value: {value}, dataLen: {data}))", + hash = tx.hash(), sender = tx.sender(), nonce = tx.signed().nonce, gas_price = tx.signed().gas_price, From bed88ee8650daf436d403ea29bb128332e8d6f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 16 Mar 2018 12:52:35 +0100 Subject: [PATCH 64/77] Optimize local transactions. --- miner/src/pool/local_transactions.rs | 37 ++++++++++++++++------------ miner/src/pool/tests/mod.rs | 6 +++-- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/miner/src/pool/local_transactions.rs b/miner/src/pool/local_transactions.rs index a3fabbb6bb3..12ffa84c19b 100644 --- a/miner/src/pool/local_transactions.rs +++ b/miner/src/pool/local_transactions.rs @@ -64,6 +64,7 @@ impl Status { pub struct LocalTransactionsList { max_old: usize, transactions: LinkedHashMap, + pending: usize, } impl Default for LocalTransactionsList { @@ -78,6 +79,7 @@ impl LocalTransactionsList { LocalTransactionsList { max_old, transactions: Default::default(), + pending: 0, } } @@ -93,17 +95,11 @@ impl LocalTransactionsList { /// Returns true if there are pending local transactions. pub fn has_pending(&self) -> bool { - self.transactions - .values() - .any(|status| status.is_pending()) + self.pending > 0 } fn clear_old(&mut self) { - let number_of_old = self.transactions - .values() - .filter(|status| !status.is_pending()) - .count(); - + let number_of_old = self.transactions.len() - self.pending; if self.max_old >= number_of_old { return; } @@ -119,6 +115,15 @@ impl LocalTransactionsList { self.transactions.remove(&hash); } } + + fn insert(&mut self, hash: H256, status: Status) { + let result = self.transactions.insert(hash, status); + if let Some(old) = result { + if old.is_pending() { + self.pending -= 1; + } + } + } } impl txpool::Listener for LocalTransactionsList { @@ -129,11 +134,12 @@ impl txpool::Listener for LocalTransactionsList { debug!(target: "own_tx", "Imported to the pool (hash {:?})", tx.hash()); self.clear_old(); - self.transactions.insert(*tx.hash(), Status::Pending(tx.clone())); + self.insert(*tx.hash(), Status::Pending(tx.clone())); + self.pending += 1; if let Some(old) = old { if self.transactions.contains_key(old.hash()) { - self.transactions.insert(*old.hash(), Status::Replaced { + self.insert(*old.hash(), Status::Replaced { old: old.clone(), new: tx.clone(), }); @@ -147,7 +153,7 @@ impl txpool::Listener for LocalTransactionsList { } debug!(target: "own_tx", "Transaction rejected (hash {:?}). {}", tx.hash(), reason); - self.transactions.insert(*tx.hash(), Status::Rejected(tx.clone(), format!("{}", reason))); + self.insert(*tx.hash(), Status::Rejected(tx.clone(), format!("{}", reason))); self.clear_old(); } @@ -160,7 +166,7 @@ impl txpool::Listener for LocalTransactionsList { Some(new) => warn!(target: "own_tx", "Transaction pushed out because of limit (hash {:?}, replacement: {:?})", tx.hash(), new.hash()), None => warn!(target: "own_tx", "Transaction dropped because of limit (hash: {:?})", tx.hash()), } - self.transactions.insert(*tx.hash(), Status::Dropped(tx.clone())); + self.insert(*tx.hash(), Status::Dropped(tx.clone())); self.clear_old(); } @@ -170,7 +176,7 @@ impl txpool::Listener for LocalTransactionsList { } warn!(target: "own_tx", "Transaction marked invalid (hash {:?})", tx.hash()); - self.transactions.insert(*tx.hash(), Status::Invalid(tx.clone())); + self.insert(*tx.hash(), Status::Invalid(tx.clone())); self.clear_old(); } @@ -180,7 +186,7 @@ impl txpool::Listener for LocalTransactionsList { } warn!(target: "own_tx", "Transaction canceled (hash {:?})", tx.hash()); - self.transactions.insert(*tx.hash(), Status::Canceled(tx.clone())); + self.insert(*tx.hash(), Status::Canceled(tx.clone())); self.clear_old(); } @@ -192,8 +198,7 @@ impl txpool::Listener for LocalTransactionsList { } info!(target: "own_tx", "Transaction mined (hash {:?})", tx.hash()); - self.transactions.insert(*tx.hash(), Status::Mined(tx.clone())); - self.clear_old(); + self.insert(*tx.hash(), Status::Mined(tx.clone())); } } diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index 32db76a5c71..340cb751586 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -75,10 +75,12 @@ fn should_return_correct_nonces_when_dropped_because_of_limit() { let tx2 = Tx::gas_price(2).signed(); let tx3 = Tx::gas_price(1).signed(); let tx4 = Tx::gas_price(3).signed(); - let res = txq.import(TestClient::new(), vec![tx1, tx2, tx3, tx4].local()); + let res = txq.import(TestClient::new(), vec![tx1, tx2].local()); + let res2 = txq.import(TestClient::new(), vec![tx3, tx4].local()); // then - assert_eq!(res, vec![Ok(()), Ok(()), Err(transaction::Error::LimitReached), Ok(())]); + assert_eq!(res, vec![Ok(()), Ok(())]); + assert_eq!(res2, vec![Err(transaction::Error::LimitReached), Ok(())]); assert_eq!(txq.status().status.transaction_count, 3); // First inserted transacton got dropped because of limit assert_eq!(txq.next_nonce(TestClient::new(), &sender), None); From 4a7940ea485daa091041d6ae3534021c1d4bf4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 16 Mar 2018 22:59:48 +0100 Subject: [PATCH 65/77] Fix aura tests. --- ethcore/src/miner/miner.rs | 1 + miner/src/pool/queue.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index ee334be64a1..05b144764da 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -263,6 +263,7 @@ impl Miner { block_gas_limit: U256::max_value(), tx_gas_limit: U256::max_value(), }, + reseal_min_period: Duration::from_secs(0), ..Default::default() }, GasPricer::new_fixed(minimal_gas_price), spec, accounts) } diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 221707bf720..977ac99050a 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -241,7 +241,7 @@ impl TransactionQueue { /// /// NOTE This is re-computing the pending set and it might be expensive to do so. /// Prefer using cached pending set using `#pending` method. - pub fn collect_pending( + fn collect_pending( &self, client: C, block_number: u64, From c36d39d40cd3da0df38c70e833ad42b3ba3cbdb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 20 Mar 2018 17:25:59 +0100 Subject: [PATCH 66/77] Update locks comments. --- ethcore/src/miner/miner.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 05b144764da..d9b443c640d 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -198,8 +198,8 @@ pub struct Miner { sealing: Mutex, params: RwLock, listeners: RwLock>>, - gas_pricer: Mutex, nonce_cache: RwLock>, + gas_pricer: Mutex, options: MinerOptions, // TODO [ToDr] Arc is only required because of price updater transaction_queue: Arc, @@ -210,8 +210,8 @@ pub struct Miner { impl Miner { /// Push listener that will handle new jobs pub fn add_work_listener(&self, notifier: Box) { - self.sealing.lock().enabled = true; self.listeners.write().push(notifier); + self.sealing.lock().enabled = true; } /// Push an URL that will get new job notifications. @@ -227,7 +227,7 @@ impl Miner { } /// Creates new instance of miner Arc. - pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Self { let limits = options.pool_limits.clone(); let verifier_options = options.pool_verification_options.clone(); let tx_queue_strategy = options.tx_queue_strategy; @@ -669,7 +669,7 @@ impl Miner { if prepare_new { // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing locks. | + // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- let (block, original_work_hash) = self.prepare_block(client); @@ -782,7 +782,7 @@ impl miner::MinerService for Miner { ).pop().expect("one result returned per added transaction; one added => one result; qed"); // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing locks. | + // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- if imported.is_ok() && self.options.reseal_on_own_tx && self.sealing.lock().reseal_allowed() { @@ -922,7 +922,7 @@ impl miner::MinerService for Miner { } // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing locks. | + // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- trace!(target: "miner", "update_sealing: preparing a block"); @@ -946,8 +946,7 @@ impl miner::MinerService for Miner { Some(false) => { trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now"); // anyway, save the block for later use - let mut sealing = self.sealing.lock(); - sealing.queue.push(block); + self.sealing.lock().queue.push(block); }, None => { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); @@ -1050,7 +1049,7 @@ impl miner::MinerService for Miner { if !is_internal_import { // -------------------------------------------------------------------------- - // | NOTE Code below requires transaction_queue and sealing locks. | + // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- self.update_sealing(chain); From c92812b0ee3e5f270ebf0ce57480e6dab2d136c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 20 Mar 2018 23:19:04 +0100 Subject: [PATCH 67/77] Get rid of unsafe Sync impl. --- ethcore/src/blockchain/best_block.rs | 4 ++- ethcore/src/blockchain/blockchain.rs | 16 +++++++-- ethcore/src/header.rs | 49 +++++++++++++------------- ethcore/src/miner/blockchain_client.rs | 39 ++++++-------------- 4 files changed, 51 insertions(+), 57 deletions(-) diff --git a/ethcore/src/blockchain/best_block.rs b/ethcore/src/blockchain/best_block.rs index 1436ced275d..637cd1b0d69 100644 --- a/ethcore/src/blockchain/best_block.rs +++ b/ethcore/src/blockchain/best_block.rs @@ -16,7 +16,7 @@ use ethereum_types::{H256, U256}; use bytes::Bytes; -use header::BlockNumber; +use header::{BlockNumber}; /// Contains information on a best block that is specific to the consensus engine. /// @@ -36,6 +36,8 @@ pub struct BestBlock { pub total_difficulty: U256, /// Best block uncompressed bytes pub block: Bytes, + /// Cached header. + pub header: Option<::encoded::Header>, } /// Best ancient block info. If the blockchain has a gap this keeps track of where it starts. diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 09be1143f2e..d206c1b121d 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -581,6 +581,7 @@ impl BlockChain { hash: best_block_hash, timestamp: best_block_timestamp, block: best_block_rlp, + header: None, }; if let (Some(hash), Some(number)) = (best_ancient, best_ancient_number) { @@ -1034,6 +1035,7 @@ impl BlockChain { total_difficulty: update.info.total_difficulty, timestamp: update.timestamp, block: update.block.to_vec(), + header: None, }); } @@ -1327,9 +1329,17 @@ impl BlockChain { /// Get best block header pub fn best_block_header(&self) -> encoded::Header { - let block = self.best_block.read(); - let raw = BlockView::new(&block.block).header_view().rlp().as_raw().to_vec(); - encoded::Header::new(raw) + if let Some(ref header) = self.best_block.read().header { + return header.clone(); + } + + let header = { + let block = self.best_block.read(); + let raw = BlockView::new(&block.block).header_view().rlp().as_raw().to_vec(); + encoded::Header::new(raw) + }; + self.best_block.write().header = Some(header.clone()); + header } /// Get current cache size. diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index f6f5090ef84..507f17b0d63 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -17,7 +17,7 @@ //! Block header. use std::cmp; -use std::cell::RefCell; +use std::cell::Cell; use std::time::{SystemTime, UNIX_EPOCH}; use hash::{KECCAK_NULL_RLP, KECCAK_EMPTY_LIST_RLP, keccak}; use heapsize::HeapSizeOf; @@ -76,9 +76,9 @@ pub struct Header { seal: Vec, /// The memoized hash of the RLP representation *including* the seal fields. - hash: RefCell>, + hash: Cell>, /// The memoized hash of the RLP representation *without* the seal fields. - bare_hash: RefCell>, + bare_hash: Cell>, } impl PartialEq for Header { @@ -120,8 +120,8 @@ impl Default for Header { difficulty: U256::default(), seal: vec![], - hash: RefCell::new(None), - bare_hash: RefCell::new(None), + hash: Cell::new(None), + bare_hash: Cell::new(None), } } } @@ -210,34 +210,32 @@ impl Header { /// Get the hash of this header (keccak of the RLP). pub fn hash(&self) -> H256 { - let mut hash = self.hash.borrow_mut(); - match &mut *hash { - &mut Some(ref h) => h.clone(), - hash @ &mut None => { + match self.hash.get() { + Some(h) => h, + None => { let h = self.rlp_keccak(Seal::With); - *hash = Some(h.clone()); - h - } + self.hash.set(Some(h)); + h + }, } } /// Get the hash of the header excluding the seal pub fn bare_hash(&self) -> H256 { - let mut hash = self.bare_hash.borrow_mut(); - match &mut *hash { - &mut Some(ref h) => h.clone(), - hash @ &mut None => { + match self.bare_hash.get() { + Some(h) => h, + None => { let h = self.rlp_keccak(Seal::Without); - *hash = Some(h.clone()); + self.bare_hash.set(Some(h)); h - } + }, } } /// Note that some fields have changed. Resets the memoised hash. pub fn note_dirty(&self) { - *self.hash.borrow_mut() = None; - *self.bare_hash.borrow_mut() = None; + self.hash.set(None); + self.bare_hash.set(None); } // TODO: make these functions traity @@ -297,8 +295,8 @@ impl Decodable for Header { timestamp: cmp::min(r.val_at::(11)?, u64::max_value().into()).as_u64(), extra_data: r.val_at(12)?, seal: vec![], - hash: RefCell::new(Some(keccak(r.as_raw()))), - bare_hash: RefCell::new(None), + hash: Cell::new(Some(keccak(r.as_raw()))), + bare_hash: Cell::new(None), }; for i in 13..r.item_count()? { @@ -322,9 +320,12 @@ impl HeapSizeOf for Header { } impl ::parity_machine::Header for Header { - fn bare_hash(&self) -> H256 { Header::bare_hash(self) } + // fn bare_hash(&self) -> H256 { Header::bare_hash(self) } + + // fn hash(&self) -> H256 { Header::hash(self) } - fn hash(&self) -> H256 { Header::hash(self) } + fn bare_hash(&self) -> H256 { unimplemented!() } + fn hash(&self) -> H256 { unimplemented!() } fn seal(&self) -> &[Vec] { Header::seal(self) } diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index 4da542a69b3..107f9863983 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -44,7 +44,6 @@ pub struct BlockChainClient<'a, C: 'a> { cached_nonces: CachedNonceClient<'a, C>, engine: &'a EthEngine, accounts: Option<&'a AccountProvider>, - best_block_header: SyncHeader, service_transaction_checker: Option, } @@ -55,7 +54,6 @@ impl<'a, C: 'a> Clone for BlockChainClient<'a, C> { cached_nonces: self.cached_nonces.clone(), engine: self.engine, accounts: self.accounts.clone(), - best_block_header: self.best_block_header.clone(), service_transaction_checker: self.service_transaction_checker.clone(), } } @@ -71,14 +69,11 @@ C: BlockInfo + CallContract, accounts: Option<&'a AccountProvider>, refuse_service_transactions: bool, ) -> Self { - let best_block_header = SyncHeader::new(chain.best_block_header().decode()); - BlockChainClient { chain, cached_nonces: CachedNonceClient::new(chain, cache), engine, accounts, - best_block_header, service_transaction_checker: if refuse_service_transactions { None } else { @@ -88,7 +83,12 @@ C: BlockInfo + CallContract, } pub fn verify_signed(&self, tx: &SignedTransaction) -> Result<(), transaction::Error> { - self.engine.machine().verify_transaction(&tx, &self.best_block_header.0, self.chain) + let best_block_header = self.chain.best_block_header().decode(); + self.verify_signed_transaction(tx, &best_block_header) + } + + pub fn verify_signed_transaction(&self, tx: &SignedTransaction, header: &Header) -> Result<(), transaction::Error> { + self.engine.machine().verify_transaction(&tx, header, self.chain) } } @@ -106,10 +106,11 @@ impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where } fn verify_transaction(&self, tx: UnverifiedTransaction)-> Result { - self.engine.verify_transaction_basic(&tx, &self.best_block_header.0)?; - let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header.0)?; + let best_block_header = self.chain.best_block_header().decode(); + self.engine.verify_transaction_basic(&tx, &best_block_header)?; + let tx = self.engine.verify_transaction_unordered(tx, &best_block_header)?; - self.verify_signed(&tx)?; + self.verify_signed_transaction(&tx, &best_block_header)?; Ok(tx) } @@ -208,23 +209,3 @@ impl<'a, C: 'a> NonceClient for CachedNonceClient<'a, C> where nonce } } - -/// A `Sync` wrapper for `Header`. -#[derive(Clone)] -pub struct SyncHeader(Header); -// Since hashes are pre-computed -// we can safely implement Sync here -// -// TODO [ToDr] Remove this wrapper when `Header` -// does not use `RefCells` any more. -unsafe impl Sync for SyncHeader {} - -impl SyncHeader { - pub fn new(header: Header) -> Self { - // Make sure to compute and memoize hashes. - header.hash(); - header.bare_hash(); - - SyncHeader(header) - } -} From be75720dc90ec9d4ae8481dfa0576709ef726867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 21 Mar 2018 10:20:55 +0100 Subject: [PATCH 68/77] Review fixes. --- ethcore/src/blockchain/best_block.rs | 2 -- ethcore/src/blockchain/blockchain.rs | 16 +++------------- ethcore/src/header.rs | 7 ++----- miner/src/pool/mod.rs | 13 +++++++------ miner/src/pool/queue.rs | 3 +++ 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/ethcore/src/blockchain/best_block.rs b/ethcore/src/blockchain/best_block.rs index 637cd1b0d69..723ecfe7f4d 100644 --- a/ethcore/src/blockchain/best_block.rs +++ b/ethcore/src/blockchain/best_block.rs @@ -36,8 +36,6 @@ pub struct BestBlock { pub total_difficulty: U256, /// Best block uncompressed bytes pub block: Bytes, - /// Cached header. - pub header: Option<::encoded::Header>, } /// Best ancient block info. If the blockchain has a gap this keeps track of where it starts. diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index d206c1b121d..09be1143f2e 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -581,7 +581,6 @@ impl BlockChain { hash: best_block_hash, timestamp: best_block_timestamp, block: best_block_rlp, - header: None, }; if let (Some(hash), Some(number)) = (best_ancient, best_ancient_number) { @@ -1035,7 +1034,6 @@ impl BlockChain { total_difficulty: update.info.total_difficulty, timestamp: update.timestamp, block: update.block.to_vec(), - header: None, }); } @@ -1329,17 +1327,9 @@ impl BlockChain { /// Get best block header pub fn best_block_header(&self) -> encoded::Header { - if let Some(ref header) = self.best_block.read().header { - return header.clone(); - } - - let header = { - let block = self.best_block.read(); - let raw = BlockView::new(&block.block).header_view().rlp().as_raw().to_vec(); - encoded::Header::new(raw) - }; - self.best_block.write().header = Some(header.clone()); - header + let block = self.best_block.read(); + let raw = BlockView::new(&block.block).header_view().rlp().as_raw().to_vec(); + encoded::Header::new(raw) } /// Get current cache size. diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 507f17b0d63..df9ad0531b3 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -320,12 +320,9 @@ impl HeapSizeOf for Header { } impl ::parity_machine::Header for Header { - // fn bare_hash(&self) -> H256 { Header::bare_hash(self) } + fn bare_hash(&self) -> H256 { Header::bare_hash(self) } - // fn hash(&self) -> H256 { Header::hash(self) } - - fn bare_hash(&self) -> H256 { unimplemented!() } - fn hash(&self) -> H256 { unimplemented!() } + fn hash(&self) -> H256 { Header::hash(self) } fn seal(&self) -> &[Vec] { Header::seal(self) } diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 045e14e8326..7950510c6d0 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -16,21 +16,22 @@ //! Transaction Pool -use ethereum_types::{H256, H160 as Address}; +use ethereum_types::{H256, Address}; use heapsize::HeapSizeOf; use transaction; use txpool; +mod listener; +mod queue; +mod ready; +mod scoring; + pub mod client; -pub mod listener; pub mod local_transactions; -pub mod queue; -pub mod ready; -pub mod scoring; pub mod verifier; #[cfg(test)] -pub mod tests; +mod tests; pub use self::queue::{TransactionQueue, Status as QueueStatus}; pub use self::txpool::{VerifiedTransaction as PoolVerifiedTransaction, Options}; diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 977ac99050a..e9edd0e1a4b 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -186,6 +186,9 @@ impl TransactionQueue { }) .collect::>(); + // Notify about imported transactions. + (self.pool.write().listener_mut().1).0.notify(); + if results.iter().any(|r| r.is_ok()) { self.cached_pending.write().clear(); } From 357fcf3c38b55ed4778ff865cc5066332c30f62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 21 Mar 2018 10:36:01 +0100 Subject: [PATCH 69/77] Remove excessive matches. --- miner/src/pool/queue.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index e9edd0e1a4b..40696ff6f40 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -177,13 +177,11 @@ impl TransactionQueue { let results = transactions .into_par_iter() .map(|transaction| verifier.verify_transaction(transaction)) - .map(|result| match result { - Ok(verified) => match self.pool.write().import(verified) { - Ok(_imported) => Ok(()), - Err(err) => Err(convert_error(err)), - }, - Err(err) => Err(err), - }) + .map(|result| result.and_then(|verified| { + self.pool.write().import(verified) + .map(|_imported| ()) + .map_err(convert_error) + })) .collect::>(); // Notify about imported transactions. From da0f845649b8f049c0504f27f929d162ef974946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 9 Apr 2018 14:41:06 +0200 Subject: [PATCH 70/77] Fix compilation errors. --- Cargo.lock | 24 +++++++++++++------ ethcore/src/engines/authority_round/mod.rs | 6 ++--- ethcore/src/engines/basic_authority.rs | 6 ++--- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 6 ++--- ethcore/src/header.rs | 28 ---------------------- ethcore/src/miner/blockchain_client.rs | 2 +- ethcore/src/miner/miner.rs | 2 +- parity/run.rs | 2 +- 9 files changed, 30 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 726298f8d1d..9ee8058fc6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,8 +433,6 @@ version = "0.5.7" source = "git+https://github.com/paritytech/rust-secp256k1#db81cfea59014b4d176f10f86ed52e1a130b6822" dependencies = [ "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -651,6 +649,15 @@ dependencies = [ name = "ethcore-miner" version = "1.11.0" dependencies = [ + "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethash 1.11.0", + "ethcore-transaction 0.1.0", + "ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethkey 0.3.0", + "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.0-a.0 (git+https://github.com/paritytech/hyper)", "keccak-hash 0.1.0", @@ -658,7 +665,7 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "price-info 1.11.0", - "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "trace-time 0.1.0", "transaction-pool 1.11.0", @@ -1148,6 +1155,7 @@ name = "hidapi" version = "0.3.1" source = "git+https://github.com/paritytech/hidapi-rs#e77ea09c98f29ea8855dd9cd9461125a28ca9125" dependencies = [ + "cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1423,6 +1431,8 @@ dependencies = [ name = "keccak-hash" version = "0.1.0" dependencies = [ + "cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1509,6 +1519,7 @@ name = "libusb-sys" version = "0.2.4" source = "git+https://github.com/paritytech/libusb-sys#14bdb698003731b6344a79e1d814704e44363e7c" dependencies = [ + "cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1658,6 +1669,7 @@ name = "miniz-sys" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2623,7 +2635,7 @@ dependencies = [ [[package]] name = "rayon" -version = "1.0.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2748,6 +2760,7 @@ name = "rocksdb-sys" version = "0.3.0" source = "git+https://github.com/paritytech/rust-rocksdb#ecf06adf3148ab10f6f7686b724498382ff4f36e" dependencies = [ + "cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "local-encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "snappy-sys 0.1.0 (git+https://github.com/paritytech/rust-snappy)", @@ -3759,8 +3772,6 @@ dependencies = [ "checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" "checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" -"checksum crossbeam-epoch 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59796cc6cbbdc6bb319161349db0c3250ec73ec7fcb763a51065ec4e2e158552" -"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" "checksum crossbeam-deque 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c1bdc73742c36f7f35ebcda81dbb33a7e0d33757d03a06d9ddca762712ec5ea2" "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" "checksum crossbeam-epoch 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b4e2817eb773f770dcb294127c011e22771899c21d18fce7dd739c0b9832e81" @@ -3905,7 +3916,6 @@ dependencies = [ "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" "checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8" -"checksum rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "485541959c8ecc49865526fe6c4de9653dd6e60d829d6edf0be228167b60372d" "checksum rayon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed02d09394c94ffbdfdc755ad62a132e94c3224a8354e78a1200ced34df12edf" "checksum rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80e811e76f1dbf68abf87a759083d34600017fc4e10b6bd5ad84a700f9dba4b1" "checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8" diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 93b61deafc9..c7ad855b256 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -23,7 +23,7 @@ use std::time::{UNIX_EPOCH, Duration}; use std::collections::{BTreeMap, HashSet}; use std::iter::FromIterator; -use account_provider::{AccountProvider, SignError}; +use account_provider::AccountProvider; use block::*; use client::EngineClient; use engines::{Engine, Seal, EngineError, ConstructedVerifier}; @@ -1307,8 +1307,8 @@ impl Engine for AuthorityRound { self.signer.write().set(ap, address, password); } - fn sign(&self, hash: H256) -> Result { - self.signer.read().sign(hash) + fn sign(&self, hash: H256) -> Result { + Ok(self.signer.read().sign(hash)?) } fn snapshot_components(&self) -> Option> { diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index b222de395bf..bbf9598520e 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -20,7 +20,7 @@ use std::sync::{Weak, Arc}; use ethereum_types::{H256, H520, Address}; use parking_lot::RwLock; use ethkey::{self, Signature}; -use account_provider::{AccountProvider, SignError}; +use account_provider::AccountProvider; use block::*; use engines::{Engine, Seal, ConstructedVerifier, EngineError}; use error::{BlockError, Error}; @@ -184,8 +184,8 @@ impl Engine for BasicAuthority { self.signer.write().set(ap, address, password); } - fn sign(&self, hash: H256) -> Result { - self.signer.read().sign(hash) + fn sign(&self, hash: H256) -> Result { + Ok(self.signer.read().sign(hash)?) } fn snapshot_components(&self) -> Option> { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 6df0c8bd4b1..87c38627a83 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -41,7 +41,7 @@ use std::fmt; use self::epoch::PendingTransition; -use account_provider::{AccountProvider, SignError}; +use account_provider::AccountProvider; use builtin::Builtin; use vm::{EnvInfo, Schedule, CreateContractAddress}; use error::Error; diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 78d5a5888f1..436dc71eb31 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -38,7 +38,7 @@ use error::{Error, BlockError}; use header::{Header, BlockNumber}; use rlp::UntrustedRlp; use ethkey::{self, Message, Signature}; -use account_provider::{AccountProvider, SignError}; +use account_provider::AccountProvider; use block::*; use engines::{Engine, Seal, EngineError, ConstructedVerifier}; use io::IoService; @@ -690,8 +690,8 @@ impl Engine for Tendermint { self.to_step(Step::Propose); } - fn sign(&self, hash: H256) -> Result { - self.signer.read().sign(hash) + fn sign(&self, hash: H256) -> Result { + Ok(self.signer.read().sign(hash)?) } fn snapshot_components(&self) -> Option> { diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 0e33ebd2ad7..50529c72011 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -267,38 +267,11 @@ impl Header { /// Get the hash of this header (keccak of the RLP with seal). pub fn hash(&self) -> H256 { -<<<<<<< HEAD - match self.hash.get() { - Some(h) => h, - None => { - let h = self.rlp_keccak(Seal::With); - self.hash.set(Some(h)); - h - }, - } -======= self.hash.unwrap_or_else(|| keccak(self.rlp(Seal::With))) ->>>>>>> master } /// Get the hash of the header excluding the seal pub fn bare_hash(&self) -> H256 { -<<<<<<< HEAD - match self.bare_hash.get() { - Some(h) => h, - None => { - let h = self.rlp_keccak(Seal::Without); - self.bare_hash.set(Some(h)); - h - }, - } - } - - /// Note that some fields have changed. Resets the memoised hash. - pub fn note_dirty(&self) { - self.hash.set(None); - self.bare_hash.set(None); -======= keccak(self.rlp(Seal::Without)) } @@ -312,7 +285,6 @@ impl Header { let mut s = RlpStream::new(); self.stream_rlp(&mut s, with_seal); s.out() ->>>>>>> master } /// Place this header into an RLP stream `s`, optionally `with_seal`. diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/blockchain_client.rs index 96209576625..e2aaf74771e 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/blockchain_client.rs @@ -71,7 +71,7 @@ C: BlockInfo + CallContract, accounts: Option<&'a AccountProvider>, refuse_service_transactions: bool, ) -> Self { - let best_block_header = chain.best_block_header().decode(); + let best_block_header = chain.best_block_header(); BlockChainClient { chain, cached_nonces: CachedNonceClient::new(chain, cache), diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index d9b443c640d..70e82097413 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1016,7 +1016,7 @@ impl miner::MinerService for Miner { self.nonce_cache.write().clear(); // First update gas limit in transaction queue and minimal gas price. - let gas_limit = chain.best_block_header().gas_limit(); + let gas_limit = *chain.best_block_header().gas_limit(); self.update_transaction_queue_limits(gas_limit); // Then import all transactions... diff --git a/parity/run.rs b/parity/run.rs index 88d194e7c20..d4a6b23ed08 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -628,7 +628,7 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: // take handle to client let client = service.client(); // Update miners block gas limit - miner.update_transaction_queue_limits(client.best_block_header().gas_limit()); + miner.update_transaction_queue_limits(*client.best_block_header().gas_limit()); let connection_filter = connection_filter_address.map(|a| Arc::new(NodeFilter::new(Arc::downgrade(&client) as Weak, a))); let snapshot_service = service.snapshot_service(); From 285f4fd77b18fba45aaad7e272d00334f26e0455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 10 Apr 2018 16:06:27 +0200 Subject: [PATCH 71/77] Use new pool in private transactions. --- ethcore/private-tx/src/lib.rs | 97 ++++++++-------- .../private-tx/src/private_transactions.rs | 105 ++++++++++++------ ethcore/service/src/service.rs | 11 +- ethcore/src/miner/miner.rs | 14 +-- ethcore/src/miner/mod.rs | 2 +- .../{blockchain_client.rs => pool_client.rs} | 35 +++--- miner/src/pool/queue.rs | 10 +- parity/blockchain.rs | 6 +- 8 files changed, 167 insertions(+), 113 deletions(-) rename ethcore/src/miner/{blockchain_client.rs => pool_client.rs} (86%) diff --git a/ethcore/private-tx/src/lib.rs b/ethcore/private-tx/src/lib.rs index 4fa1f8789c1..09eef750414 100644 --- a/ethcore/private-tx/src/lib.rs +++ b/ethcore/private-tx/src/lib.rs @@ -77,12 +77,10 @@ use ethcore::executed::{Executed}; use transaction::{SignedTransaction, Transaction, Action, UnverifiedTransaction}; use ethcore::{contract_address as ethcore_contract_address}; use ethcore::client::{ - Client, ChainNotify, ChainMessageType, ClientIoMessage, BlockId, - MiningBlockChainClient, ChainInfo, Nonce, CallContract + Client, ChainNotify, ChainMessageType, ClientIoMessage, BlockId, CallContract }; use ethcore::account_provider::AccountProvider; -use ethcore_miner::transaction_queue::{TransactionDetailsProvider as TransactionQueueDetailsProvider, AccountDetails}; -use ethcore::miner::MinerService; +use ethcore::miner::{self, Miner, MinerService}; use ethcore::trace::{Tracer, VMTracer}; use rustc_hex::FromHex; @@ -94,35 +92,6 @@ use_contract!(private, "PrivateContract", "res/private.json"); /// Initialization vector length. const INIT_VEC_LEN: usize = 16; -struct TransactionDetailsProvider<'a> { - client: &'a MiningBlockChainClient, -} - -impl<'a> TransactionDetailsProvider<'a> { - pub fn new(client: &'a MiningBlockChainClient) -> Self { - TransactionDetailsProvider { - client: client, - } - } -} - -impl<'a> TransactionQueueDetailsProvider for TransactionDetailsProvider<'a> { - fn fetch_account(&self, address: &Address) -> AccountDetails { - AccountDetails { - nonce: self.client.latest_nonce(address), - balance: self.client.latest_balance(address), - } - } - - fn estimate_gas_required(&self, tx: &SignedTransaction) -> U256 { - tx.gas_required(&self.client.latest_schedule()).into() - } - - fn is_service_transaction_acceptable(&self, _tx: &SignedTransaction) -> Result { - Ok(false) - } -} - /// Configurtion for private transaction provider #[derive(Default, PartialEq, Debug, Clone)] pub struct ProviderConfig { @@ -153,8 +122,10 @@ pub struct Provider { passwords: Vec, notify: RwLock>>, transactions_for_signing: Mutex, + // TODO [ToDr] Move the Mutex/RwLock inside `VerificationStore` after refactored to `drain`. transactions_for_verification: Mutex, client: Arc, + miner: Arc, accounts: Arc, channel: IoChannel, } @@ -171,6 +142,7 @@ impl Provider where { /// Create a new provider. pub fn new( client: Arc, + miner: Arc, accounts: Arc, encryptor: Box, config: ProviderConfig, @@ -185,6 +157,7 @@ impl Provider where { transactions_for_signing: Mutex::default(), transactions_for_verification: Mutex::default(), client, + miner, accounts, channel, }) @@ -281,6 +254,9 @@ impl Provider where { match validation_account { None => { + // TODO [ToDr] This still seems a bit invalid, imho we should still import the transaction to the pool. + // Importing to pool verifies correctness and nonce; here we are just blindly forwarding. + // // Not for verification, broadcast further to peers self.broadcast_private_transaction(rlp.into()); return Ok(()); @@ -290,29 +266,59 @@ impl Provider where { trace!("Private transaction taken for verification"); let original_tx = self.extract_original_transaction(private_tx, &contract)?; trace!("Validating transaction: {:?}", original_tx); - let details_provider = TransactionDetailsProvider::new(&*self.client as &MiningBlockChainClient); - let insertion_time = self.client.chain_info().best_block_number; // Verify with the first account available trace!("The following account will be used for verification: {:?}", validation_account); - self.transactions_for_verification.lock() - .add_transaction(original_tx, contract, validation_account, hash, &details_provider, insertion_time)?; + let nonce_cache = Default::default(); + self.transactions_for_verification.lock().add_transaction( + original_tx, + contract, + validation_account, + hash, + self.pool_client(&nonce_cache), + )?; + // NOTE This will just fire `on_private_transaction_queued` but from a client thread. + // It seems that a lot of heavy work (verification) is done in this thread anyway + // it might actually make sense to decouple it from clientService and just use dedicated thread + // for both verification and execution. self.channel.send(ClientIoMessage::NewPrivateTransaction).map_err(|_| ErrorKind::ClientIsMalformed.into()) } } } + fn pool_client<'a>(&'a self, nonce_cache: &'a RwLock>) -> miner::pool_client::PoolClient<'a, Client> { + let engine = self.client.engine(); + let refuse_service_transactions = true; + miner::pool_client::PoolClient::new( + &*self.client, + nonce_cache, + engine, + Some(&*self.accounts), + refuse_service_transactions, + ) + } + /// Private transaction for validation added into queue pub fn on_private_transaction_queued(&self) -> Result<(), Error> { self.process_queue() } /// Retrieve and verify the first available private transaction for every sender + /// + /// TODO [ToDr] It seems that: + /// 1. This method will fail on any error without removing invalid transaction. + /// 2. It means that the transaction will be stuck there forever and we will never be able to make any progress. + /// + /// It might be more sensible to `drain()` transactions from the queue instead and process all of them, + /// possibly printing some errors in case of failures. + /// The 3 methods `ready_transaction,get_descriptor,remove` are always used in conjuction so most likely + /// can be replaced with a single `drain()` method instead. + /// Thanks to this we also don't really need to lock the entire verification for the time of execution. fn process_queue(&self) -> Result<(), Error> { + let nonce_cache = Default::default(); let mut verification_queue = self.transactions_for_verification.lock(); - let ready_transactions = verification_queue.ready_transactions(); - let fetch_nonce = |a: &Address| self.client.latest_nonce(a); + let ready_transactions = verification_queue.ready_transactions(self.pool_client(&nonce_cache)); for transaction in ready_transactions { - let transaction_hash = transaction.hash(); + let transaction_hash = transaction.signed().hash(); match verification_queue.private_transaction_descriptor(&transaction_hash) { Ok(desc) => { if !self.validator_accounts.contains(&desc.validator_account) { @@ -320,9 +326,10 @@ impl Provider where { bail!(ErrorKind::ValidatorAccountNotSet); } let account = desc.validator_account; - if let Action::Call(contract) = transaction.action { + if let Action::Call(contract) = transaction.signed().action { + // TODO [ToDr] Usage of BlockId::Latest let contract_nonce = self.get_contract_nonce(&contract, BlockId::Latest)?; - let private_state = self.execute_private_transaction(BlockId::Latest, &transaction)?; + let private_state = self.execute_private_transaction(BlockId::Latest, transaction.signed())?; let private_state_hash = self.calculate_state_hash(&private_state, contract_nonce); trace!("Hashed effective private state for validator: {:?}", private_state_hash); let password = find_account_password(&self.passwords, &*self.accounts, &account); @@ -340,7 +347,7 @@ impl Provider where { bail!(e); } } - verification_queue.remove_private_transaction(&transaction_hash, &fetch_nonce); + verification_queue.remove_private_transaction(&transaction_hash); } Ok(()) } @@ -353,6 +360,8 @@ impl Provider where { let private_hash = tx.private_transaction_hash(); let desc = match self.transactions_for_signing.lock().get(&private_hash) { None => { + // TODO [ToDr] Verification (we can't just blindly forward every transaction) + // Not our transaction, broadcast further to peers self.broadcast_signed_private_transaction(rlp.into()); return Ok(()); @@ -382,7 +391,7 @@ impl Provider where { let password = find_account_password(&self.passwords, &*self.accounts, &signer_account); let signature = self.accounts.sign(signer_account, password, hash)?; let signed = SignedTransaction::new(public_tx.with_signature(signature, chain_id))?; - match self.client.miner().import_own_transaction(&*self.client, signed.into()) { + match self.miner.import_own_transaction(&*self.client, signed.into()) { Ok(_) => trace!("Public transaction added to queue"), Err(err) => { trace!("Failed to add transaction to queue, error: {:?}", err); diff --git a/ethcore/private-tx/src/private_transactions.rs b/ethcore/private-tx/src/private_transactions.rs index 502694294ec..1a018d927ad 100644 --- a/ethcore/private-tx/src/private_transactions.rs +++ b/ethcore/private-tx/src/private_transactions.rs @@ -14,15 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethkey::Signature; +use std::sync::Arc; +use std::collections::{HashMap, HashSet}; + use bytes::Bytes; -use std::collections::HashMap; +use ethcore_miner::pool; use ethereum_types::{H256, U256, Address}; +use ethkey::Signature; use transaction::{UnverifiedTransaction, SignedTransaction}; -use ethcore_miner::transaction_queue::{TransactionQueue, RemovalReason, - TransactionDetailsProvider as TransactionQueueDetailsProvider, TransactionOrigin}; + use error::{Error, ErrorKind}; -use ethcore::header::BlockNumber; /// Maximum length for private transactions queues. const MAX_QUEUE_LEN: usize = 8312; @@ -39,56 +40,92 @@ pub struct PrivateTransactionDesc { } /// Storage for private transactions for verification -#[derive(Default)] pub struct VerificationStore { /// Descriptors for private transactions in queue for verification with key - hash of the original transaction descriptors: HashMap, /// Queue with transactions for verification - transactions: TransactionQueue, + /// + /// TODO [ToDr] Might actually be better to use `txpool` directly and: + /// 1. Store descriptors inside `VerifiedTransaction` + /// 2. Use custom `ready` implementation to only fetch one transaction per sender. + /// 3. Get rid of passing dummy `block_number` and `timestamp` + transactions: pool::TransactionQueue, +} + +impl Default for VerificationStore { + fn default() -> Self { + VerificationStore { + descriptors: Default::default(), + transactions: pool::TransactionQueue::new( + pool::Options { + max_count: MAX_QUEUE_LEN, + max_per_sender: MAX_QUEUE_LEN / 10, + max_mem_usage: 8 * 1024 * 1024, + }, + pool::verifier::Options { + // TODO [ToDr] This should probably be based on some real values? + minimal_gas_price: 0.into(), + block_gas_limit: 8_000_000.into(), + tx_gas_limit: U256::max_value(), + }, + pool::PrioritizationStrategy::GasPriceOnly, + ) + } + } } impl VerificationStore { /// Adds private transaction for verification into the store - pub fn add_transaction( + pub fn add_transaction( &mut self, transaction: UnverifiedTransaction, contract: Address, validator_account: Address, private_hash: H256, - details_provider: &TransactionQueueDetailsProvider, - insertion_time: BlockNumber, + client: C, ) -> Result<(), Error> { if self.descriptors.len() > MAX_QUEUE_LEN { bail!(ErrorKind::QueueIsFull); } - if self.descriptors.get(&transaction.hash()).is_some() { + let transaction_hash = transaction.hash(); + if self.descriptors.get(&transaction_hash).is_some() { bail!(ErrorKind::PrivateTransactionAlreadyImported); } - let transaction_hash = transaction.hash(); - let signed_transaction = SignedTransaction::new(transaction)?; - self.transactions - .add(signed_transaction, TransactionOrigin::External, insertion_time, None, details_provider) - .and_then(|_| { - self.descriptors.insert(transaction_hash, PrivateTransactionDesc{ - private_hash, - contract, - validator_account, - }); - Ok(()) - }) - .map_err(Into::into) + + let results = self.transactions.import( + client, + vec![pool::verifier::Transaction::Unverified(transaction)], + ); + + // Verify that transaction was imported + results.into_iter() + .next() + .expect("One transaction inserted; one result returned; qed")?; + + self.descriptors.insert(transaction_hash, PrivateTransactionDesc { + private_hash, + contract, + validator_account, + }); + + Ok(()) } /// Returns transactions ready for verification /// Returns only one transaction per sender because several cannot be verified in a row without verification from other peers - pub fn ready_transactions(&self) -> Vec { - // TODO [ToDr] Performance killer, re-work with new transaction queue. - let mut transactions = self.transactions.top_transactions(); - // TODO [ToDr] Potential issue (create low address to have your transactions processed first) - transactions.sort_by(|a, b| a.sender().cmp(&b.sender())); - transactions.dedup_by(|a, b| a.sender().eq(&b.sender())); - transactions + pub fn ready_transactions(&self, client: C) -> Vec> { + // We never store PendingTransactions and we don't use internal cache, + // so we don't need to provide real block number of timestamp here + let block_number = 0; + let timestamp = 0; + let nonce_cap = None; + + self.transactions.collect_pending(client, block_number, timestamp, nonce_cap, |transactions| { + // take only one transaction per sender + let mut senders = HashSet::with_capacity(self.descriptors.len()); + transactions.filter(move |tx| senders.insert(tx.signed().sender())).collect() + }) } /// Returns descriptor of the corresponding private transaction @@ -97,11 +134,9 @@ impl VerificationStore { } /// Remove transaction from the queue for verification - pub fn remove_private_transaction(&mut self, transaction_hash: &H256, fetch_nonce: &F) - where F: Fn(&Address) -> U256 { - + pub fn remove_private_transaction(&mut self, transaction_hash: &H256) { self.descriptors.remove(transaction_hash); - self.transactions.remove(transaction_hash, fetch_nonce, RemovalReason::Invalid); + self.transactions.remove(&[*transaction_hash], true); } } diff --git a/ethcore/service/src/service.rs b/ethcore/service/src/service.rs index 8cfcff20ee2..7156b133144 100644 --- a/ethcore/service/src/service.rs +++ b/ethcore/service/src/service.rs @@ -92,7 +92,7 @@ impl ClientService { info!("Configured for {} using {} engine", Colour::White.bold().paint(spec.name.clone()), Colour::Yellow.bold().paint(spec.engine.name())); let pruning = config.pruning; - let client = Client::new(config, &spec, client_db.clone(), miner, io_service.channel())?; + let client = Client::new(config, &spec, client_db.clone(), miner.clone(), io_service.channel())?; let snapshot_params = SnapServiceParams { engine: spec.engine.clone(), @@ -105,7 +105,14 @@ impl ClientService { }; let snapshot = Arc::new(SnapshotService::new(snapshot_params)?); - let provider = Arc::new(ethcore_private_tx::Provider::new(client.clone(), account_provider, encryptor, private_tx_conf, io_service.channel())?); + let provider = Arc::new(ethcore_private_tx::Provider::new( + client.clone(), + miner, + account_provider, + encryptor, + private_tx_conf, + io_service.channel())?, + ); let private_tx = Arc::new(PrivateTxService::new(provider)); let client_io = Arc::new(ClientIoHandler { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b7271b994af..a839d52ab8f 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -46,7 +46,7 @@ use client::BlockId; use executive::contract_address; use header::{Header, BlockNumber}; use miner; -use miner::blockchain_client::{BlockChainClient, CachedNonceClient}; +use miner::pool_client::{PoolClient, CachedNonceClient}; use receipt::{Receipt, RichReceipt}; use spec::Spec; use state::State; @@ -304,10 +304,10 @@ impl Miner { }) } - fn client<'a, C: 'a>(&'a self, chain: &'a C) -> BlockChainClient<'a, C> where + fn pool_client<'a, C: 'a>(&'a self, chain: &'a C) -> PoolClient<'a, C> where C: BlockChain + CallContract, { - BlockChainClient::new( + PoolClient::new( chain, &self.nonce_cache, &*self.engine, @@ -368,7 +368,7 @@ impl Miner { let mut tx_count = 0usize; let mut skipped_transactions = 0usize; - let client = self.client(chain); + let client = self.pool_client(chain); let engine_params = self.engine.params(); let min_tx_gas = self.engine.schedule(chain_info.best_block_number).tx_gas.into(); let nonce_cap: Option = if chain_info.best_block_number + 1 >= engine_params.dust_protection_transition { @@ -750,7 +750,7 @@ impl miner::MinerService for Miner { transactions: Vec ) -> Vec> { trace!(target: "external_tx", "Importing external transactions"); - let client = self.client(chain); + let client = self.pool_client(chain); let results = self.transaction_queue.import( client, transactions.into_iter().map(pool::verifier::Transaction::Unverified).collect(), @@ -775,7 +775,7 @@ impl miner::MinerService for Miner { trace!(target: "own_tx", "Importing transaction: {:?}", pending); - let client = self.client(chain); + let client = self.pool_client(chain); let imported = self.transaction_queue.import( client, vec![pool::verifier::Transaction::Local(pending)] @@ -1020,7 +1020,7 @@ impl miner::MinerService for Miner { self.update_transaction_queue_limits(gas_limit); // Then import all transactions... - let client = self.client(chain); + let client = self.pool_client(chain); { retracted .par_iter() diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 64bb56e9310..fbf4f11b7ad 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -19,10 +19,10 @@ //! Miner module //! Keeps track of transactions and currently sealed pending block. -mod blockchain_client; mod miner; mod service_transaction_checker; +pub mod pool_client; pub mod stratum; pub use self::miner::{Miner, MinerOptions, Penalization, PendingSet, AuthoringParams}; diff --git a/ethcore/src/miner/blockchain_client.rs b/ethcore/src/miner/pool_client.rs similarity index 86% rename from ethcore/src/miner/blockchain_client.rs rename to ethcore/src/miner/pool_client.rs index e2aaf74771e..61153be0256 100644 --- a/ethcore/src/miner/blockchain_client.rs +++ b/ethcore/src/miner/pool_client.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Blockchain access for transaction pool. + use std::fmt; use std::collections::HashMap; @@ -39,7 +41,8 @@ type NoncesCache = RwLock>; const MAX_NONCE_CACHE_SIZE: usize = 4096; const EXPECTED_NONCE_CACHE_SIZE: usize = 2048; -pub struct BlockChainClient<'a, C: 'a> { +/// Blockchain accesss for transaction pool. +pub struct PoolClient<'a, C: 'a> { chain: &'a C, cached_nonces: CachedNonceClient<'a, C>, engine: &'a EthEngine, @@ -48,9 +51,9 @@ pub struct BlockChainClient<'a, C: 'a> { service_transaction_checker: Option, } -impl<'a, C: 'a> Clone for BlockChainClient<'a, C> { +impl<'a, C: 'a> Clone for PoolClient<'a, C> { fn clone(&self) -> Self { - BlockChainClient { + PoolClient { chain: self.chain, cached_nonces: self.cached_nonces.clone(), engine: self.engine, @@ -61,9 +64,10 @@ impl<'a, C: 'a> Clone for BlockChainClient<'a, C> { } } -impl<'a, C: 'a> BlockChainClient<'a, C> where +impl<'a, C: 'a> PoolClient<'a, C> where C: BlockInfo + CallContract, { + /// Creates new client given chain, nonce cache, accounts and service transaction verifier. pub fn new( chain: &'a C, cache: &'a NoncesCache, @@ -72,7 +76,7 @@ C: BlockInfo + CallContract, refuse_service_transactions: bool, ) -> Self { let best_block_header = chain.best_block_header(); - BlockChainClient { + PoolClient { chain, cached_nonces: CachedNonceClient::new(chain, cache), engine, @@ -86,22 +90,21 @@ C: BlockInfo + CallContract, } } + /// Verifies if signed transaction is executable. + /// + /// This should perform any verifications that rely on chain status. pub fn verify_signed(&self, tx: &SignedTransaction) -> Result<(), transaction::Error> { - self.verify_signed_transaction(tx, &self.best_block_header) - } - - pub fn verify_signed_transaction(&self, tx: &SignedTransaction, header: &Header) -> Result<(), transaction::Error> { - self.engine.machine().verify_transaction(&tx, header, self.chain) + self.engine.machine().verify_transaction(&tx, &self.best_block_header, self.chain) } } -impl<'a, C: 'a> fmt::Debug for BlockChainClient<'a, C> { +impl<'a, C: 'a> fmt::Debug for PoolClient<'a, C> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "BlockChainClient") + write!(fmt, "PoolClient") } } -impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where +impl<'a, C: 'a> pool::client::Client for PoolClient<'a, C> where C: miner::TransactionVerifierClient + Sync, { fn transaction_already_included(&self, hash: &H256) -> bool { @@ -112,7 +115,7 @@ impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where self.engine.verify_transaction_basic(&tx, &self.best_block_header)?; let tx = self.engine.verify_transaction_unordered(tx, &self.best_block_header)?; - self.verify_signed_transaction(&tx, &self.best_block_header)?; + self.verify_signed(&tx)?; Ok(tx) } @@ -144,7 +147,7 @@ impl<'a, C: 'a> pool::client::Client for BlockChainClient<'a, C> where } } -impl<'a, C: 'a> NonceClient for BlockChainClient<'a, C> where +impl<'a, C: 'a> NonceClient for PoolClient<'a, C> where C: Nonce + Sync, { fn account_nonce(&self, address: &Address) -> U256 { @@ -152,7 +155,7 @@ impl<'a, C: 'a> NonceClient for BlockChainClient<'a, C> where } } -pub struct CachedNonceClient<'a, C: 'a> { +pub(crate) struct CachedNonceClient<'a, C: 'a> { client: &'a C, cache: &'a NoncesCache, } diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 40696ff6f40..edc092a119f 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -144,7 +144,11 @@ pub struct TransactionQueue { impl TransactionQueue { /// Create new queue with given pool limits and initial verification options. - pub fn new(limits: txpool::Options, verification_options: verifier::Options, strategy: PrioritizationStrategy) -> Self { + pub fn new( + limits: txpool::Options, + verification_options: verifier::Options, + strategy: PrioritizationStrategy, + ) -> Self { TransactionQueue { insertion_id: Default::default(), pool: RwLock::new(txpool::Pool::new(Default::default(), scoring::NonceAndGasPrice(strategy), limits)), @@ -203,7 +207,7 @@ impl TransactionQueue { /// Returns current pneding transactions. /// /// NOTE: This may return a cached version of pending transaction set. - /// Re-computing the pending set is possible with `#collet_pending` method, + /// Re-computing the pending set is possible with `#collect_pending` method, /// but be aware that it's a pretty expensive operation. pub fn pending( &self, @@ -242,7 +246,7 @@ impl TransactionQueue { /// /// NOTE This is re-computing the pending set and it might be expensive to do so. /// Prefer using cached pending set using `#pending` method. - fn collect_pending( + pub fn collect_pending( &self, client: C, block_number: u64, diff --git a/parity/blockchain.rs b/parity/blockchain.rs index c71c18188c0..4a4441b89fe 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -582,16 +582,12 @@ fn start_client( &snapshot_path, restoration_db_handler, &dirs.ipc_path(), -<<<<<<< HEAD // It's fine to use test version here, // since we don't care about miner parameters at all Arc::new(Miner::new_for_tests(&spec, None)), -======= - Arc::new(Miner::with_spec(&spec)), Arc::new(AccountProvider::transient_provider()), Box::new(ethcore_private_tx::NoopEncryptor), - Default::default() ->>>>>>> master + Default::default(), ).map_err(|e| format!("Client service error: {:?}", e))?; drop(spec); From af81bee765b0049db5bad85a43944082597c7b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 11 Apr 2018 10:22:41 +0200 Subject: [PATCH 72/77] Fix private-tx test. --- ethcore/private-tx/tests/private_contract.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ethcore/private-tx/tests/private_contract.rs b/ethcore/private-tx/tests/private_contract.rs index ba918c96361..e53ad5e5f68 100644 --- a/ethcore/private-tx/tests/private_contract.rs +++ b/ethcore/private-tx/tests/private_contract.rs @@ -36,6 +36,7 @@ use ethcore::account_provider::AccountProvider; use ethcore::client::BlockChainClient; use ethcore::client::BlockId; use ethcore::executive::{contract_address}; +use ethcore::miner::Miner; use ethcore::test_helpers::{generate_dummy_client, push_block_with_transactions}; use ethcore_transaction::{Transaction, Action}; use ethkey::{Secret, KeyPair, Signature}; @@ -65,7 +66,15 @@ fn private_contract() { }; let io = ethcore_io::IoChannel::disconnected(); - let pm = Arc::new(Provider::new(client.clone(), ap.clone(), Box::new(NoopEncryptor::default()), config, io).unwrap()); + let miner = Arc::new(Miner::new_for_tests(&::ethcore::spec::Spec::new_test(), None)); + let pm = Arc::new(Provider::new( + client.clone(), + miner, + ap.clone(), + Box::new(NoopEncryptor::default()), + config, + io, + ).unwrap()); let (address, _) = contract_address(CreateContractAddress::FromSenderAndNonce, &key1.address(), &0.into(), &[]); From 0e2bf522b1ee01c7ac8a2685357854bc46492cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 11 Apr 2018 14:15:45 +0200 Subject: [PATCH 73/77] Fix secret store tests. --- secret_store/src/trusted_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret_store/src/trusted_client.rs b/secret_store/src/trusted_client.rs index 5a9efe4b680..10d20f8d0e8 100644 --- a/secret_store/src/trusted_client.rs +++ b/secret_store/src/trusted_client.rs @@ -74,7 +74,7 @@ impl TrustedClient { let transaction = Transaction { nonce: client.latest_nonce(&self.self_key_pair.address()), action: Action::Call(contract), - gas: miner.gas_floor_target(), + gas: miner.sensible_gas_limit(), gas_price: miner.sensible_gas_price(), value: Default::default(), data: tx_data, From 7f15c302327f061eac1a71b53a7710055263c68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 11 Apr 2018 14:20:44 +0200 Subject: [PATCH 74/77] Actually use gas_floor_target --- secret_store/src/trusted_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret_store/src/trusted_client.rs b/secret_store/src/trusted_client.rs index 10d20f8d0e8..94b1c0174de 100644 --- a/secret_store/src/trusted_client.rs +++ b/secret_store/src/trusted_client.rs @@ -74,7 +74,7 @@ impl TrustedClient { let transaction = Transaction { nonce: client.latest_nonce(&self.self_key_pair.address()), action: Action::Call(contract), - gas: miner.sensible_gas_limit(), + gas: miner.authoring_params().gas_range_target.0, gas_price: miner.sensible_gas_price(), value: Default::default(), data: tx_data, From 483b70c479a80826f27a6c72038674aa7e095198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 11 Apr 2018 15:49:23 +0200 Subject: [PATCH 75/77] Fix config tests. --- parity/configuration.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/parity/configuration.rs b/parity/configuration.rs index 075bf5b030a..3736c198cdd 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -1542,7 +1542,6 @@ mod tests { no_hardcoded_sync: false, no_persistent_txqueue: false, whisper: Default::default(), - work_notify: Vec::new(), }; expected.secretstore_conf.enabled = cfg!(feature = "secretstore"); expected.secretstore_conf.http_enabled = cfg!(feature = "secretstore"); From b7789e211298edc3be265a317fab5d0ffecb6052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 11 Apr 2018 17:12:42 +0200 Subject: [PATCH 76/77] Fix pool tests. --- transaction-pool/src/tests/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/transaction-pool/src/tests/mod.rs b/transaction-pool/src/tests/mod.rs index 7bb44ef4516..5113a4663c3 100644 --- a/transaction-pool/src/tests/mod.rs +++ b/transaction-pool/src/tests/mod.rs @@ -125,7 +125,7 @@ fn should_reject_if_above_count() { let tx2 = b.tx().nonce(1).new(); let hash = *tx2.hash(); txq.import(tx1).unwrap(); - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into())); assert_eq!(txq.light_status().transaction_count, 1); txq.clear(); @@ -151,7 +151,7 @@ fn should_reject_if_above_mem_usage() { let tx2 = b.tx().nonce(2).mem_usage(2).new(); let hash = *tx2.hash(); txq.import(tx1).unwrap(); - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into())); assert_eq!(txq.light_status().transaction_count, 1); txq.clear(); @@ -177,7 +177,7 @@ fn should_reject_if_above_sender_count() { let tx2 = b.tx().nonce(2).new(); let hash = *tx2.hash(); txq.import(tx1).unwrap(); - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into())); assert_eq!(txq.light_status().transaction_count, 1); txq.clear(); @@ -188,7 +188,7 @@ fn should_reject_if_above_sender_count() { let hash = *tx2.hash(); txq.import(tx1).unwrap(); // This results in error because we also compare nonces - assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0".into())); + assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into())); assert_eq!(txq.light_status().transaction_count, 1); } From d7c8b94cd1dc385110c82ccf5ed30138fe64b9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 13 Apr 2018 12:05:05 +0200 Subject: [PATCH 77/77] Address grumbles. --- ethcore/src/engines/mod.rs | 2 +- ethcore/src/miner/service_transaction_checker.rs | 9 +-------- miner/src/pool/ready.rs | 4 ++-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 87c38627a83..4d22bd1f76a 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -392,7 +392,7 @@ pub trait EthEngine: Engine<::machine::EthereumMachine> { /// i.e. it should only verify stuff that doesn't assume any previous transactions /// has already been verified and executed. /// - /// NOTE This function consumuses an `UnverifiedTransaction` and produces `SignedTransaction` + /// NOTE This function consumes an `UnverifiedTransaction` and produces `SignedTransaction` /// which implies that a heavy check of the signature is performed here. fn verify_transaction_unordered(&self, t: UnverifiedTransaction, header: &Header) -> Result { self.machine().verify_transaction_unordered(t, header) diff --git a/ethcore/src/miner/service_transaction_checker.rs b/ethcore/src/miner/service_transaction_checker.rs index f8d5ba219e5..f085564d222 100644 --- a/ethcore/src/miner/service_transaction_checker.rs +++ b/ethcore/src/miner/service_transaction_checker.rs @@ -24,18 +24,11 @@ use_contract!(service_transaction, "ServiceTransaction", "res/contracts/service_ const SERVICE_TRANSACTION_CONTRACT_REGISTRY_NAME: &'static str = "service_transaction_checker"; /// Service transactions checker. -#[derive(Default)] +#[derive(Default, Clone)] pub struct ServiceTransactionChecker { contract: service_transaction::ServiceTransaction, } -// TODO [ToDr] https://github.com/paritytech/ethabi/pull/84 -impl Clone for ServiceTransactionChecker { - fn clone(&self) -> Self { - Default::default() - } -} - impl ServiceTransactionChecker { /// Checks if given address is whitelisted to send service transactions. pub fn check(&self, client: &C, tx: &SignedTransaction) -> Result { diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs index 367c9e48eee..54b5aec3a9f 100644 --- a/miner/src/pool/ready.rs +++ b/miner/src/pool/ready.rs @@ -30,9 +30,9 @@ //! the same sender. //! //! There are three possible outcomes: -//! - The transaction is old (stalled; state nonce < transaction nonce) +//! - The transaction is old (stalled; state nonce > transaction nonce) //! - The transaction is ready (current; state nonce == transaction nonce) -//! - The transaction is not ready yet (future; state nonce > transaction nonce) +//! - The transaction is not ready yet (future; state nonce < transaction nonce) //! //! NOTE The transactions are always checked for readines in order they are stored within the queue. //! First `Readiness::Future` response also causes all subsequent transactions from the same sender