Transaction Queue banning (#2524)
* Blacklisting transaction queue * Using blacklisting queue in miner * Restoring todo [ci:skip] * Blacklisting recipients and code * Renaming blacklisting->banning * CLI option for banning. * Fixing submodule commit [ci:skip] * Fixing RPC tests * Additional logging when dropping transactions * whitespace [ci:skip] * Configurable ban duration * Reverting fix for pruning history from config file
This commit is contained in:
parent
ce37b6dcb9
commit
152a551e8b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -307,6 +307,7 @@ dependencies = [
|
|||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -24,6 +24,10 @@ rayon = "0.4.2"
|
|||||||
semver = "0.2"
|
semver = "0.2"
|
||||||
bit-set = "0.4"
|
bit-set = "0.4"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
|
rand = "0.3"
|
||||||
|
byteorder = "0.5"
|
||||||
|
transient-hashmap = "0.1"
|
||||||
|
lru-cache = { git = "https://github.com/contain-rs/lru-cache" }
|
||||||
evmjit = { path = "../evmjit", optional = true }
|
evmjit = { path = "../evmjit", optional = true }
|
||||||
clippy = { version = "0.0.96", optional = true}
|
clippy = { version = "0.0.96", optional = true}
|
||||||
ethash = { path = "../ethash" }
|
ethash = { path = "../ethash" }
|
||||||
@ -36,10 +40,7 @@ ethstore = { path = "../ethstore" }
|
|||||||
ethkey = { path = "../ethkey" }
|
ethkey = { path = "../ethkey" }
|
||||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||||
rlp = { path = "../util/rlp" }
|
rlp = { path = "../util/rlp" }
|
||||||
rand = "0.3"
|
|
||||||
lru-cache = { git = "https://github.com/contain-rs/lru-cache" }
|
|
||||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||||
byteorder = "0.5"
|
|
||||||
|
|
||||||
[dependencies.hyper]
|
[dependencies.hyper]
|
||||||
git = "https://github.com/ethcore/hyper"
|
git = "https://github.com/ethcore/hyper"
|
||||||
|
@ -63,6 +63,12 @@ pub enum TransactionError {
|
|||||||
},
|
},
|
||||||
/// Transaction's gas limit (aka gas) is invalid.
|
/// Transaction's gas limit (aka gas) is invalid.
|
||||||
InvalidGasLimit(OutOfBounds<U256>),
|
InvalidGasLimit(OutOfBounds<U256>),
|
||||||
|
/// Transaction sender is banned.
|
||||||
|
SenderBanned,
|
||||||
|
/// Transaction receipient is banned.
|
||||||
|
RecipientBanned,
|
||||||
|
/// Contract creation code is banned.
|
||||||
|
CodeBanned,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TransactionError {
|
impl fmt::Display for TransactionError {
|
||||||
@ -81,6 +87,9 @@ impl fmt::Display for TransactionError {
|
|||||||
GasLimitExceeded { limit, got } =>
|
GasLimitExceeded { limit, got } =>
|
||||||
format!("Gas limit exceeded. Limit={}, Given={}", limit, got),
|
format!("Gas limit exceeded. Limit={}, Given={}", limit, got),
|
||||||
InvalidGasLimit(ref err) => format!("Invalid gas limit. {}", err),
|
InvalidGasLimit(ref err) => format!("Invalid gas limit. {}", err),
|
||||||
|
SenderBanned => "Sender is temporarily banned.".into(),
|
||||||
|
RecipientBanned => "Recipient is temporarily banned.".into(),
|
||||||
|
CodeBanned => "Contract code is temporarily banned.".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
f.write_fmt(format_args!("Transaction error ({})", msg))
|
f.write_fmt(format_args!("Transaction error ({})", msg))
|
||||||
|
@ -101,6 +101,7 @@ extern crate bit_set;
|
|||||||
extern crate rlp;
|
extern crate rlp;
|
||||||
extern crate ethcore_bloom_journal as bloom_journal;
|
extern crate ethcore_bloom_journal as bloom_journal;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
extern crate transient_hashmap;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
331
ethcore/src/miner/banning_queue.rs
Normal file
331
ethcore/src/miner/banning_queue.rs
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Banning Queue
|
||||||
|
//! Transacton Queue wrapper maintaining additional list of banned senders and contract hashes.
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::cell::Cell;
|
||||||
|
use transaction::{SignedTransaction, Action};
|
||||||
|
use transient_hashmap::TransientHashMap;
|
||||||
|
use miner::{TransactionQueue, TransactionImportResult, TransactionOrigin, AccountDetails};
|
||||||
|
use error::{Error, TransactionError};
|
||||||
|
use util::{Uint, U256, H256, Address, Hashable};
|
||||||
|
|
||||||
|
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<Address, Cell<Count>>,
|
||||||
|
recipients_bans: TransientHashMap<Address, Cell<Count>>,
|
||||||
|
codes_bans: TransientHashMap<H256, Cell<Count>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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<F>(
|
||||||
|
&mut self,
|
||||||
|
transaction: SignedTransaction,
|
||||||
|
account_details: &F,
|
||||||
|
) -> Result<TransactionImportResult, Error> where F: Fn(&Address) -> AccountDetails {
|
||||||
|
if let Threshold::BanAfter(threshold) = self.ban_threshold {
|
||||||
|
// NOTE In all checks use direct query to avoid increasing ban timeout.
|
||||||
|
|
||||||
|
// Check sender
|
||||||
|
if let Ok(sender) = transaction.sender() {
|
||||||
|
let count = self.senders_bans.direct().get(&sender).map(|v| v.get()).unwrap_or(0);
|
||||||
|
if count > threshold {
|
||||||
|
debug!(target: "txqueue", "Ignoring transaction {:?} because sender is banned.", transaction.hash());
|
||||||
|
return Err(Error::Transaction(TransactionError::SenderBanned));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check recipient
|
||||||
|
if let Action::Call(recipient) = transaction.action {
|
||||||
|
let count = self.recipients_bans.direct().get(&recipient).map(|v| v.get()).unwrap_or(0);
|
||||||
|
if count > threshold {
|
||||||
|
debug!(target: "txqueue", "Ignoring transaction {:?} because recipient is banned.", transaction.hash());
|
||||||
|
return Err(Error::Transaction(TransactionError::RecipientBanned));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check code
|
||||||
|
if let Action::Create = transaction.action {
|
||||||
|
let code_hash = transaction.data.sha3();
|
||||||
|
let count = self.codes_bans.direct().get(&code_hash).map(|v| v.get()).unwrap_or(0);
|
||||||
|
if count > threshold {
|
||||||
|
debug!(target: "txqueue", "Ignoring transaction {:?} because code is banned.", transaction.hash());
|
||||||
|
return Err(Error::Transaction(TransactionError::CodeBanned));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.queue.add(transaction, account_details, TransactionOrigin::External)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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().expect("Transaction is in queue, so the sender is already validated; qed");
|
||||||
|
// Ban sender
|
||||||
|
let sender_banned = self.ban_sender(sender);
|
||||||
|
// Ban recipient and codehash
|
||||||
|
let is_banned = sender_banned || match transaction.action {
|
||||||
|
Action::Call(recipient) => {
|
||||||
|
self.ban_recipient(recipient)
|
||||||
|
},
|
||||||
|
Action::Create => {
|
||||||
|
self.ban_codehash(transaction.data.sha3())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
is_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 mut count = self.senders_bans.entry(address).or_insert_with(|| Cell::new(0));
|
||||||
|
*count.get_mut() = count.get().saturating_add(1);
|
||||||
|
count.get()
|
||||||
|
};
|
||||||
|
match self.ban_threshold {
|
||||||
|
Threshold::BanAfter(threshold) if count > threshold => {
|
||||||
|
// Banlist the sender.
|
||||||
|
// Remove all transactions from the queue.
|
||||||
|
self.remove_all(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 mut count = self.recipients_bans.entry(address).or_insert_with(|| Cell::new(0));
|
||||||
|
*count.get_mut() = count.get().saturating_add(1);
|
||||||
|
count.get()
|
||||||
|
};
|
||||||
|
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 mut count = self.codes_bans.entry(code_hash).or_insert_with(|| Cell::new(0));
|
||||||
|
*count.get_mut() = count.get().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.get() > 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 std::time::Duration;
|
||||||
|
use super::{BanningTransactionQueue, Threshold};
|
||||||
|
use ethkey::{Random, Generator};
|
||||||
|
use transaction::{Transaction, SignedTransaction, Action};
|
||||||
|
use error::{Error, TransactionError};
|
||||||
|
use client::TransactionImportResult;
|
||||||
|
use miner::{TransactionQueue, TransactionOrigin, AccountDetails};
|
||||||
|
use util::{Uint, U256, Address, FromHex, Hashable};
|
||||||
|
|
||||||
|
fn queue() -> BanningTransactionQueue {
|
||||||
|
BanningTransactionQueue::new(TransactionQueue::default(), Threshold::BanAfter(1), Duration::from_secs(180))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_account_details(_address: &Address) -> AccountDetails {
|
||||||
|
AccountDetails {
|
||||||
|
nonce: U256::zero(),
|
||||||
|
balance: !U256::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transaction(action: Action) -> SignedTransaction {
|
||||||
|
let keypair = Random.generate().unwrap();
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_err(res: Result<TransactionImportResult, Error>) -> TransactionError {
|
||||||
|
match res {
|
||||||
|
Err(Error::Transaction(e)) => e,
|
||||||
|
Ok(x) => panic!("Expected error, got: Ok({:?})", x),
|
||||||
|
Err(e) => panic!("Unexpected error type returned by queue: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_allow_to_borrow_the_queue() {
|
||||||
|
// given
|
||||||
|
let tx = transaction(Action::Create);
|
||||||
|
let mut txq = queue();
|
||||||
|
|
||||||
|
// when
|
||||||
|
txq.queue().add(tx, &default_account_details, TransactionOrigin::External).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().unwrap());
|
||||||
|
assert!(!banlist1, "Threshold not reached yet.");
|
||||||
|
// Insert once
|
||||||
|
let import1 = txq.add_with_banlist(tx.clone(), &default_account_details).unwrap();
|
||||||
|
assert_eq!(import1, TransactionImportResult::Current);
|
||||||
|
|
||||||
|
// when
|
||||||
|
let banlist2 = txq.ban_sender(tx.sender().unwrap());
|
||||||
|
let import2 = txq.add_with_banlist(tx.clone(), &default_account_details);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(banlist2, "Threshold should be reached - banned.");
|
||||||
|
assert_eq!(unwrap_err(import2), TransactionError::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(), &default_account_details).unwrap();
|
||||||
|
assert_eq!(import1, TransactionImportResult::Current);
|
||||||
|
|
||||||
|
// when
|
||||||
|
let banlist2 = txq.ban_recipient(recipient);
|
||||||
|
let import2 = txq.add_with_banlist(tx.clone(), &default_account_details);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(banlist2, "Threshold should be reached - banned.");
|
||||||
|
assert_eq!(unwrap_err(import2), TransactionError::RecipientBanned);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_accept_transactions_with_banned_code() {
|
||||||
|
// given
|
||||||
|
let tx = transaction(Action::Create);
|
||||||
|
let codehash = tx.data.sha3();
|
||||||
|
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(), &default_account_details).unwrap();
|
||||||
|
assert_eq!(import1, TransactionImportResult::Current);
|
||||||
|
|
||||||
|
// when
|
||||||
|
let banlist2 = txq.ban_codehash(codehash);
|
||||||
|
let import2 = txq.add_with_banlist(tx.clone(), &default_account_details);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(banlist2, "Threshold should be reached - banned.");
|
||||||
|
assert_eq!(unwrap_err(import2), TransactionError::CodeBanned);
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ use receipt::{Receipt, RichReceipt};
|
|||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use engines::Engine;
|
use engines::Engine;
|
||||||
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||||
|
use miner::banning_queue::{BanningTransactionQueue, Threshold};
|
||||||
use miner::work_notify::WorkPoster;
|
use miner::work_notify::WorkPoster;
|
||||||
use client::TransactionImportResult;
|
use client::TransactionImportResult;
|
||||||
use miner::price_info::PriceInfo;
|
use miner::price_info::PriceInfo;
|
||||||
@ -59,6 +60,22 @@ pub enum GasLimit {
|
|||||||
Fixed(U256),
|
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.
|
/// Configures the behaviour of the miner.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct MinerOptions {
|
pub struct MinerOptions {
|
||||||
@ -86,6 +103,8 @@ pub struct MinerOptions {
|
|||||||
pub enable_resubmission: bool,
|
pub enable_resubmission: bool,
|
||||||
/// Global gas limit for all transaction in the queue except for local and retracted.
|
/// Global gas limit for all transaction in the queue except for local and retracted.
|
||||||
pub tx_queue_gas_limit: GasLimit,
|
pub tx_queue_gas_limit: GasLimit,
|
||||||
|
/// Banning settings
|
||||||
|
pub tx_queue_banning: Banning,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MinerOptions {
|
impl Default for MinerOptions {
|
||||||
@ -103,6 +122,7 @@ impl Default for MinerOptions {
|
|||||||
reseal_min_period: Duration::from_secs(2),
|
reseal_min_period: Duration::from_secs(2),
|
||||||
work_queue_size: 20,
|
work_queue_size: 20,
|
||||||
enable_resubmission: true,
|
enable_resubmission: true,
|
||||||
|
tx_queue_banning: Banning::Disabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,7 +206,7 @@ struct SealingWork {
|
|||||||
/// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work.
|
/// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work.
|
||||||
pub struct Miner {
|
pub struct Miner {
|
||||||
// NOTE [ToDr] When locking always lock in this order!
|
// NOTE [ToDr] When locking always lock in this order!
|
||||||
transaction_queue: Arc<Mutex<TransactionQueue>>,
|
transaction_queue: Arc<Mutex<BanningTransactionQueue>>,
|
||||||
sealing_work: Mutex<SealingWork>,
|
sealing_work: Mutex<SealingWork>,
|
||||||
next_allowed_reseal: Mutex<Instant>,
|
next_allowed_reseal: Mutex<Instant>,
|
||||||
sealing_block_last_request: Mutex<u64>,
|
sealing_block_last_request: Mutex<u64>,
|
||||||
@ -215,11 +235,18 @@ impl Miner {
|
|||||||
GasLimit::Fixed(ref limit) => *limit,
|
GasLimit::Fixed(ref limit) => *limit,
|
||||||
_ => !U256::zero(),
|
_ => !U256::zero(),
|
||||||
};
|
};
|
||||||
let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(
|
|
||||||
options.tx_queue_strategy, options.tx_queue_size, gas_limit, options.tx_gas_limit
|
let txq = TransactionQueue::with_limits(options.tx_queue_strategy, options.tx_queue_size, 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,
|
||||||
|
),
|
||||||
|
};
|
||||||
Miner {
|
Miner {
|
||||||
transaction_queue: txq,
|
transaction_queue: Arc::new(Mutex::new(txq)),
|
||||||
next_allowed_reseal: Mutex::new(Instant::now()),
|
next_allowed_reseal: Mutex::new(Instant::now()),
|
||||||
sealing_block_last_request: Mutex::new(0),
|
sealing_block_last_request: Mutex::new(0),
|
||||||
sealing_work: Mutex::new(SealingWork{
|
sealing_work: Mutex::new(SealingWork{
|
||||||
@ -318,10 +345,31 @@ impl Miner {
|
|||||||
let mut invalid_transactions = HashSet::new();
|
let mut invalid_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 block_number = open_block.block().fields().header.number();
|
||||||
// TODO: push new uncles, too.
|
|
||||||
|
// TODO Push new uncles too.
|
||||||
for tx in transactions {
|
for tx in transactions {
|
||||||
let hash = tx.hash();
|
let hash = tx.hash();
|
||||||
match open_block.push_transaction(tx, None) {
|
let start = Instant::now();
|
||||||
|
let result = 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.lock().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 result {
|
||||||
Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => {
|
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);
|
debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas);
|
||||||
|
|
||||||
@ -506,7 +554,7 @@ impl Miner {
|
|||||||
prepare_new
|
prepare_new
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec<SignedTransaction>, origin: TransactionOrigin, transaction_queue: &mut TransactionQueue) ->
|
fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec<SignedTransaction>, origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) ->
|
||||||
Vec<Result<TransactionImportResult, Error>> {
|
Vec<Result<TransactionImportResult, Error>> {
|
||||||
|
|
||||||
let fetch_account = |a: &Address| AccountDetails {
|
let fetch_account = |a: &Address| AccountDetails {
|
||||||
@ -515,7 +563,14 @@ impl Miner {
|
|||||||
};
|
};
|
||||||
|
|
||||||
transactions.into_iter()
|
transactions.into_iter()
|
||||||
.map(|tx| transaction_queue.add(tx, &fetch_account, origin))
|
.map(|tx| match origin {
|
||||||
|
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
|
||||||
|
transaction_queue.add(tx, &fetch_account, origin)
|
||||||
|
},
|
||||||
|
TransactionOrigin::External => {
|
||||||
|
transaction_queue.add_with_banlist(tx, &fetch_account)
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1099,6 +1154,7 @@ mod tests {
|
|||||||
pending_set: PendingSet::AlwaysSealing,
|
pending_set: PendingSet::AlwaysSealing,
|
||||||
work_queue_size: 5,
|
work_queue_size: 5,
|
||||||
enable_resubmission: true,
|
enable_resubmission: true,
|
||||||
|
tx_queue_banning: Banning::Disabled,
|
||||||
},
|
},
|
||||||
GasPricer::new_fixed(0u64.into()),
|
GasPricer::new_fixed(0u64.into()),
|
||||||
&Spec::new_test(),
|
&Spec::new_test(),
|
||||||
|
@ -44,11 +44,12 @@
|
|||||||
mod miner;
|
mod miner;
|
||||||
mod external;
|
mod external;
|
||||||
mod transaction_queue;
|
mod transaction_queue;
|
||||||
|
mod banning_queue;
|
||||||
mod work_notify;
|
mod work_notify;
|
||||||
mod price_info;
|
mod price_info;
|
||||||
|
|
||||||
pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||||
pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit};
|
pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit};
|
||||||
pub use self::external::{ExternalMiner, ExternalMinerService};
|
pub use self::external::{ExternalMiner, ExternalMinerService};
|
||||||
pub use client::TransactionImportResult;
|
pub use client::TransactionImportResult;
|
||||||
|
|
||||||
|
@ -74,7 +74,10 @@ gas_cap = "6283184"
|
|||||||
tx_queue_size = 1024
|
tx_queue_size = 1024
|
||||||
tx_queue_gas = "auto"
|
tx_queue_gas = "auto"
|
||||||
tx_queue_strategy = "gas_factor"
|
tx_queue_strategy = "gas_factor"
|
||||||
|
tx_queue_ban_count = 1
|
||||||
|
tx_queue_ban_time = 180 #s
|
||||||
tx_gas_limit = "6283184"
|
tx_gas_limit = "6283184"
|
||||||
|
tx_time_limit = 100 #ms
|
||||||
extra_data = "Parity"
|
extra_data = "Parity"
|
||||||
remove_solved = false
|
remove_solved = false
|
||||||
notify_work = ["http://localhost:3001"]
|
notify_work = ["http://localhost:3001"]
|
||||||
|
@ -187,6 +187,8 @@ usage! {
|
|||||||
or |c: &Config| otry!(c.mining).work_queue_size.clone(),
|
or |c: &Config| otry!(c.mining).work_queue_size.clone(),
|
||||||
flag_tx_gas_limit: Option<String> = None,
|
flag_tx_gas_limit: Option<String> = None,
|
||||||
or |c: &Config| otry!(c.mining).tx_gas_limit.clone().map(Some),
|
or |c: &Config| otry!(c.mining).tx_gas_limit.clone().map(Some),
|
||||||
|
flag_tx_time_limit: Option<u64> = None,
|
||||||
|
or |c: &Config| otry!(c.mining).tx_time_limit.clone().map(Some),
|
||||||
flag_relay_set: String = "cheap",
|
flag_relay_set: String = "cheap",
|
||||||
or |c: &Config| otry!(c.mining).relay_set.clone(),
|
or |c: &Config| otry!(c.mining).relay_set.clone(),
|
||||||
flag_usd_per_tx: String = "0",
|
flag_usd_per_tx: String = "0",
|
||||||
@ -207,6 +209,10 @@ usage! {
|
|||||||
or |c: &Config| otry!(c.mining).tx_queue_gas.clone(),
|
or |c: &Config| otry!(c.mining).tx_queue_gas.clone(),
|
||||||
flag_tx_queue_strategy: String = "gas_price",
|
flag_tx_queue_strategy: String = "gas_price",
|
||||||
or |c: &Config| otry!(c.mining).tx_queue_strategy.clone(),
|
or |c: &Config| otry!(c.mining).tx_queue_strategy.clone(),
|
||||||
|
flag_tx_queue_ban_count: u16 = 1u16,
|
||||||
|
or |c: &Config| otry!(c.mining).tx_queue_ban_count.clone(),
|
||||||
|
flag_tx_queue_ban_time: u16 = 180u16,
|
||||||
|
or |c: &Config| otry!(c.mining).tx_queue_ban_time.clone(),
|
||||||
flag_remove_solved: bool = false,
|
flag_remove_solved: bool = false,
|
||||||
or |c: &Config| otry!(c.mining).remove_solved.clone(),
|
or |c: &Config| otry!(c.mining).remove_solved.clone(),
|
||||||
flag_notify_work: Option<String> = None,
|
flag_notify_work: Option<String> = None,
|
||||||
@ -361,6 +367,7 @@ struct Mining {
|
|||||||
reseal_min_period: Option<u64>,
|
reseal_min_period: Option<u64>,
|
||||||
work_queue_size: Option<usize>,
|
work_queue_size: Option<usize>,
|
||||||
tx_gas_limit: Option<String>,
|
tx_gas_limit: Option<String>,
|
||||||
|
tx_time_limit: Option<u64>,
|
||||||
relay_set: Option<String>,
|
relay_set: Option<String>,
|
||||||
usd_per_tx: Option<String>,
|
usd_per_tx: Option<String>,
|
||||||
usd_per_eth: Option<String>,
|
usd_per_eth: Option<String>,
|
||||||
@ -371,6 +378,8 @@ struct Mining {
|
|||||||
tx_queue_size: Option<usize>,
|
tx_queue_size: Option<usize>,
|
||||||
tx_queue_gas: Option<String>,
|
tx_queue_gas: Option<String>,
|
||||||
tx_queue_strategy: Option<String>,
|
tx_queue_strategy: Option<String>,
|
||||||
|
tx_queue_ban_count: Option<u16>,
|
||||||
|
tx_queue_ban_time: Option<u16>,
|
||||||
remove_solved: Option<bool>,
|
remove_solved: Option<bool>,
|
||||||
notify_work: Option<Vec<String>>,
|
notify_work: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
@ -558,6 +567,7 @@ mod tests {
|
|||||||
flag_reseal_min_period: 4000u64,
|
flag_reseal_min_period: 4000u64,
|
||||||
flag_work_queue_size: 20usize,
|
flag_work_queue_size: 20usize,
|
||||||
flag_tx_gas_limit: Some("6283184".into()),
|
flag_tx_gas_limit: Some("6283184".into()),
|
||||||
|
flag_tx_time_limit: Some(100u64),
|
||||||
flag_relay_set: "cheap".into(),
|
flag_relay_set: "cheap".into(),
|
||||||
flag_usd_per_tx: "0".into(),
|
flag_usd_per_tx: "0".into(),
|
||||||
flag_usd_per_eth: "auto".into(),
|
flag_usd_per_eth: "auto".into(),
|
||||||
@ -568,6 +578,8 @@ mod tests {
|
|||||||
flag_tx_queue_size: 1024usize,
|
flag_tx_queue_size: 1024usize,
|
||||||
flag_tx_queue_gas: "auto".into(),
|
flag_tx_queue_gas: "auto".into(),
|
||||||
flag_tx_queue_strategy: "gas_factor".into(),
|
flag_tx_queue_strategy: "gas_factor".into(),
|
||||||
|
flag_tx_queue_ban_count: 1u16,
|
||||||
|
flag_tx_queue_ban_time: 180u16,
|
||||||
flag_remove_solved: false,
|
flag_remove_solved: false,
|
||||||
flag_notify_work: Some("http://localhost:3001".into()),
|
flag_notify_work: Some("http://localhost:3001".into()),
|
||||||
|
|
||||||
@ -727,7 +739,10 @@ mod tests {
|
|||||||
tx_queue_size: Some(1024),
|
tx_queue_size: Some(1024),
|
||||||
tx_queue_gas: Some("auto".into()),
|
tx_queue_gas: Some("auto".into()),
|
||||||
tx_queue_strategy: None,
|
tx_queue_strategy: None,
|
||||||
|
tx_queue_ban_count: None,
|
||||||
|
tx_queue_ban_time: None,
|
||||||
tx_gas_limit: None,
|
tx_gas_limit: None,
|
||||||
|
tx_time_limit: None,
|
||||||
extra_data: None,
|
extra_data: None,
|
||||||
remove_solved: None,
|
remove_solved: None,
|
||||||
notify_work: None,
|
notify_work: None,
|
||||||
|
@ -166,6 +166,11 @@ Sealing/Mining Options:
|
|||||||
--tx-gas-limit GAS Apply a limit of GAS as the maximum amount of gas
|
--tx-gas-limit GAS Apply a limit of GAS as the maximum amount of gas
|
||||||
a single transaction may have for it to be mined.
|
a single transaction may have for it to be mined.
|
||||||
(default: {flag_tx_gas_limit:?})
|
(default: {flag_tx_gas_limit:?})
|
||||||
|
--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.
|
||||||
|
(default: {flag_tx_time_limit:?})
|
||||||
--relay-set SET Set of transactions to relay. SET may be:
|
--relay-set SET Set of transactions to relay. SET may be:
|
||||||
cheap - Relay any transaction in the queue (this
|
cheap - Relay any transaction in the queue (this
|
||||||
may include invalid transactions);
|
may include invalid transactions);
|
||||||
@ -203,6 +208,13 @@ Sealing/Mining Options:
|
|||||||
gas_price - Prioritize txs with high gas price;
|
gas_price - Prioritize txs with high gas price;
|
||||||
gas_factor - Prioritize txs using gas price
|
gas_factor - Prioritize txs using gas price
|
||||||
and gas limit ratio (default: {flag_tx_queue_strategy}).
|
and gas limit ratio (default: {flag_tx_queue_strategy}).
|
||||||
|
--tx-queue-ban-count C Number of times maximal time for execution (--tx-time-limit)
|
||||||
|
can be exceeded before banning sender/recipient/code.
|
||||||
|
(default: {flag_tx_queue_ban_count})
|
||||||
|
--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.
|
||||||
|
(default: {flag_tx_queue_ban_time} seconds)
|
||||||
--remove-solved Move solved blocks from the work package queue
|
--remove-solved Move solved blocks from the work package queue
|
||||||
instead of cloning them. This gives a slightly
|
instead of cloning them. This gives a slightly
|
||||||
faster import speed, but means that extra solutions
|
faster import speed, but means that extra solutions
|
||||||
|
@ -24,7 +24,7 @@ use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address};
|
|||||||
use util::log::Colour;
|
use util::log::Colour;
|
||||||
use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP};
|
use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP};
|
||||||
use ethcore::client::{VMType, Mode};
|
use ethcore::client::{VMType, Mode};
|
||||||
use ethcore::miner::MinerOptions;
|
use ethcore::miner::{MinerOptions, Banning};
|
||||||
|
|
||||||
use rpc::{IpcConfiguration, HttpConfiguration};
|
use rpc::{IpcConfiguration, HttpConfiguration};
|
||||||
use ethcore_rpc::NetworkSettings;
|
use ethcore_rpc::NetworkSettings;
|
||||||
@ -387,6 +387,14 @@ impl Configuration {
|
|||||||
reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period),
|
reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period),
|
||||||
work_queue_size: self.args.flag_work_queue_size,
|
work_queue_size: self.args.flag_work_queue_size,
|
||||||
enable_resubmission: !self.args.flag_remove_solved,
|
enable_resubmission: !self.args.flag_remove_solved,
|
||||||
|
tx_queue_banning: match self.args.flag_tx_time_limit {
|
||||||
|
Some(limit) => Banning::Enabled {
|
||||||
|
min_offends: self.args.flag_tx_queue_ban_count,
|
||||||
|
offend_threshold: Duration::from_millis(limit),
|
||||||
|
ban_duration: Duration::from_secs(self.args.flag_tx_queue_ban_time as u64),
|
||||||
|
},
|
||||||
|
None => Banning::Disabled,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(options)
|
Ok(options)
|
||||||
|
@ -231,6 +231,9 @@ pub fn from_transaction_error(error: EthcoreError) -> Error {
|
|||||||
format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got)
|
format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got)
|
||||||
},
|
},
|
||||||
InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(),
|
InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(),
|
||||||
|
SenderBanned => "Sender is banned in local queue.".into(),
|
||||||
|
RecipientBanned => "Recipient is banned in local queue.".into(),
|
||||||
|
CodeBanned => "Code is banned in local queue.".into(),
|
||||||
};
|
};
|
||||||
Error {
|
Error {
|
||||||
code: ErrorCode::ServerError(codes::TRANSACTION_ERROR),
|
code: ErrorCode::ServerError(codes::TRANSACTION_ERROR),
|
||||||
|
@ -24,7 +24,7 @@ use ethcore::spec::{Genesis, Spec};
|
|||||||
use ethcore::block::Block;
|
use ethcore::block::Block;
|
||||||
use ethcore::views::BlockView;
|
use ethcore::views::BlockView;
|
||||||
use ethcore::ethereum;
|
use ethcore::ethereum;
|
||||||
use ethcore::miner::{MinerOptions, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, PrioritizationStrategy, GasLimit};
|
use ethcore::miner::{MinerOptions, Banning, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, PrioritizationStrategy, GasLimit};
|
||||||
use ethcore::account_provider::AccountProvider;
|
use ethcore::account_provider::AccountProvider;
|
||||||
use devtools::RandomTempPath;
|
use devtools::RandomTempPath;
|
||||||
use util::Hashable;
|
use util::Hashable;
|
||||||
@ -61,6 +61,7 @@ fn miner_service(spec: &Spec, accounts: Arc<AccountProvider>) -> Arc<Miner> {
|
|||||||
tx_gas_limit: !U256::zero(),
|
tx_gas_limit: !U256::zero(),
|
||||||
tx_queue_strategy: PrioritizationStrategy::GasPriceOnly,
|
tx_queue_strategy: PrioritizationStrategy::GasPriceOnly,
|
||||||
tx_queue_gas_limit: GasLimit::None,
|
tx_queue_gas_limit: GasLimit::None,
|
||||||
|
tx_queue_banning: Banning::Disabled,
|
||||||
pending_set: PendingSet::SealingOrElseQueue,
|
pending_set: PendingSet::SealingOrElseQueue,
|
||||||
reseal_min_period: Duration::from_secs(0),
|
reseal_min_period: Duration::from_secs(0),
|
||||||
work_queue_size: 50,
|
work_queue_size: 50,
|
||||||
|
Loading…
Reference in New Issue
Block a user