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)",
|
||||
"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)",
|
||||
"transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -24,6 +24,10 @@ rayon = "0.4.2"
|
||||
semver = "0.2"
|
||||
bit-set = "0.4"
|
||||
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 }
|
||||
clippy = { version = "0.0.96", optional = true}
|
||||
ethash = { path = "../ethash" }
|
||||
@ -36,10 +40,7 @@ ethstore = { path = "../ethstore" }
|
||||
ethkey = { path = "../ethkey" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rlp = { path = "../util/rlp" }
|
||||
rand = "0.3"
|
||||
lru-cache = { git = "https://github.com/contain-rs/lru-cache" }
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
byteorder = "0.5"
|
||||
|
||||
[dependencies.hyper]
|
||||
git = "https://github.com/ethcore/hyper"
|
||||
|
@ -63,6 +63,12 @@ pub enum TransactionError {
|
||||
},
|
||||
/// Transaction's gas limit (aka gas) is invalid.
|
||||
InvalidGasLimit(OutOfBounds<U256>),
|
||||
/// Transaction sender is banned.
|
||||
SenderBanned,
|
||||
/// Transaction receipient is banned.
|
||||
RecipientBanned,
|
||||
/// Contract creation code is banned.
|
||||
CodeBanned,
|
||||
}
|
||||
|
||||
impl fmt::Display for TransactionError {
|
||||
@ -81,6 +87,9 @@ impl fmt::Display for TransactionError {
|
||||
GasLimitExceeded { limit, got } =>
|
||||
format!("Gas limit exceeded. Limit={}, Given={}", limit, got),
|
||||
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))
|
||||
|
@ -101,6 +101,7 @@ extern crate bit_set;
|
||||
extern crate rlp;
|
||||
extern crate ethcore_bloom_journal as bloom_journal;
|
||||
extern crate byteorder;
|
||||
extern crate transient_hashmap;
|
||||
|
||||
#[macro_use]
|
||||
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 engines::Engine;
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||
use miner::banning_queue::{BanningTransactionQueue, Threshold};
|
||||
use miner::work_notify::WorkPoster;
|
||||
use client::TransactionImportResult;
|
||||
use miner::price_info::PriceInfo;
|
||||
@ -59,6 +60,22 @@ pub enum GasLimit {
|
||||
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)]
|
||||
pub struct MinerOptions {
|
||||
@ -86,6 +103,8 @@ pub struct MinerOptions {
|
||||
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,
|
||||
}
|
||||
|
||||
impl Default for MinerOptions {
|
||||
@ -103,6 +122,7 @@ impl Default for MinerOptions {
|
||||
reseal_min_period: Duration::from_secs(2),
|
||||
work_queue_size: 20,
|
||||
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.
|
||||
pub struct Miner {
|
||||
// NOTE [ToDr] When locking always lock in this order!
|
||||
transaction_queue: Arc<Mutex<TransactionQueue>>,
|
||||
transaction_queue: Arc<Mutex<BanningTransactionQueue>>,
|
||||
sealing_work: Mutex<SealingWork>,
|
||||
next_allowed_reseal: Mutex<Instant>,
|
||||
sealing_block_last_request: Mutex<u64>,
|
||||
@ -215,11 +235,18 @@ impl Miner {
|
||||
GasLimit::Fixed(ref limit) => *limit,
|
||||
_ => !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 {
|
||||
transaction_queue: txq,
|
||||
transaction_queue: Arc::new(Mutex::new(txq)),
|
||||
next_allowed_reseal: Mutex::new(Instant::now()),
|
||||
sealing_block_last_request: Mutex::new(0),
|
||||
sealing_work: Mutex::new(SealingWork{
|
||||
@ -318,10 +345,31 @@ impl Miner {
|
||||
let mut invalid_transactions = HashSet::new();
|
||||
let mut transactions_to_penalize = HashSet::new();
|
||||
let block_number = open_block.block().fields().header.number();
|
||||
// TODO: push new uncles, too.
|
||||
|
||||
// TODO Push new uncles too.
|
||||
for tx in transactions {
|
||||
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 })) => {
|
||||
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
|
||||
}
|
||||
|
||||
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>> {
|
||||
|
||||
let fetch_account = |a: &Address| AccountDetails {
|
||||
@ -515,7 +563,14 @@ impl Miner {
|
||||
};
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@ -1099,6 +1154,7 @@ mod tests {
|
||||
pending_set: PendingSet::AlwaysSealing,
|
||||
work_queue_size: 5,
|
||||
enable_resubmission: true,
|
||||
tx_queue_banning: Banning::Disabled,
|
||||
},
|
||||
GasPricer::new_fixed(0u64.into()),
|
||||
&Spec::new_test(),
|
||||
|
@ -44,11 +44,12 @@
|
||||
mod miner;
|
||||
mod external;
|
||||
mod transaction_queue;
|
||||
mod banning_queue;
|
||||
mod work_notify;
|
||||
mod price_info;
|
||||
|
||||
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 client::TransactionImportResult;
|
||||
|
||||
|
@ -74,7 +74,10 @@ gas_cap = "6283184"
|
||||
tx_queue_size = 1024
|
||||
tx_queue_gas = "auto"
|
||||
tx_queue_strategy = "gas_factor"
|
||||
tx_queue_ban_count = 1
|
||||
tx_queue_ban_time = 180 #s
|
||||
tx_gas_limit = "6283184"
|
||||
tx_time_limit = 100 #ms
|
||||
extra_data = "Parity"
|
||||
remove_solved = false
|
||||
notify_work = ["http://localhost:3001"]
|
||||
|
@ -187,6 +187,8 @@ usage! {
|
||||
or |c: &Config| otry!(c.mining).work_queue_size.clone(),
|
||||
flag_tx_gas_limit: Option<String> = None,
|
||||
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",
|
||||
or |c: &Config| otry!(c.mining).relay_set.clone(),
|
||||
flag_usd_per_tx: String = "0",
|
||||
@ -207,6 +209,10 @@ usage! {
|
||||
or |c: &Config| otry!(c.mining).tx_queue_gas.clone(),
|
||||
flag_tx_queue_strategy: String = "gas_price",
|
||||
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,
|
||||
or |c: &Config| otry!(c.mining).remove_solved.clone(),
|
||||
flag_notify_work: Option<String> = None,
|
||||
@ -361,6 +367,7 @@ struct Mining {
|
||||
reseal_min_period: Option<u64>,
|
||||
work_queue_size: Option<usize>,
|
||||
tx_gas_limit: Option<String>,
|
||||
tx_time_limit: Option<u64>,
|
||||
relay_set: Option<String>,
|
||||
usd_per_tx: Option<String>,
|
||||
usd_per_eth: Option<String>,
|
||||
@ -371,6 +378,8 @@ struct Mining {
|
||||
tx_queue_size: Option<usize>,
|
||||
tx_queue_gas: Option<String>,
|
||||
tx_queue_strategy: Option<String>,
|
||||
tx_queue_ban_count: Option<u16>,
|
||||
tx_queue_ban_time: Option<u16>,
|
||||
remove_solved: Option<bool>,
|
||||
notify_work: Option<Vec<String>>,
|
||||
}
|
||||
@ -558,6 +567,7 @@ mod tests {
|
||||
flag_reseal_min_period: 4000u64,
|
||||
flag_work_queue_size: 20usize,
|
||||
flag_tx_gas_limit: Some("6283184".into()),
|
||||
flag_tx_time_limit: Some(100u64),
|
||||
flag_relay_set: "cheap".into(),
|
||||
flag_usd_per_tx: "0".into(),
|
||||
flag_usd_per_eth: "auto".into(),
|
||||
@ -568,6 +578,8 @@ mod tests {
|
||||
flag_tx_queue_size: 1024usize,
|
||||
flag_tx_queue_gas: "auto".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_notify_work: Some("http://localhost:3001".into()),
|
||||
|
||||
@ -727,7 +739,10 @@ mod tests {
|
||||
tx_queue_size: Some(1024),
|
||||
tx_queue_gas: Some("auto".into()),
|
||||
tx_queue_strategy: None,
|
||||
tx_queue_ban_count: None,
|
||||
tx_queue_ban_time: None,
|
||||
tx_gas_limit: None,
|
||||
tx_time_limit: None,
|
||||
extra_data: None,
|
||||
remove_solved: 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
|
||||
a single transaction may have for it to be mined.
|
||||
(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:
|
||||
cheap - Relay any transaction in the queue (this
|
||||
may include invalid transactions);
|
||||
@ -203,6 +208,13 @@ Sealing/Mining Options:
|
||||
gas_price - Prioritize txs with high gas price;
|
||||
gas_factor - Prioritize txs using gas price
|
||||
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
|
||||
instead of cloning them. This gives a slightly
|
||||
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 ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP};
|
||||
use ethcore::client::{VMType, Mode};
|
||||
use ethcore::miner::MinerOptions;
|
||||
use ethcore::miner::{MinerOptions, Banning};
|
||||
|
||||
use rpc::{IpcConfiguration, HttpConfiguration};
|
||||
use ethcore_rpc::NetworkSettings;
|
||||
@ -387,6 +387,14 @@ impl Configuration {
|
||||
reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period),
|
||||
work_queue_size: self.args.flag_work_queue_size,
|
||||
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)
|
||||
|
@ -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)
|
||||
},
|
||||
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 {
|
||||
code: ErrorCode::ServerError(codes::TRANSACTION_ERROR),
|
||||
|
@ -24,7 +24,7 @@ use ethcore::spec::{Genesis, Spec};
|
||||
use ethcore::block::Block;
|
||||
use ethcore::views::BlockView;
|
||||
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 devtools::RandomTempPath;
|
||||
use util::Hashable;
|
||||
@ -61,6 +61,7 @@ fn miner_service(spec: &Spec, accounts: Arc<AccountProvider>) -> Arc<Miner> {
|
||||
tx_gas_limit: !U256::zero(),
|
||||
tx_queue_strategy: PrioritizationStrategy::GasPriceOnly,
|
||||
tx_queue_gas_limit: GasLimit::None,
|
||||
tx_queue_banning: Banning::Disabled,
|
||||
pending_set: PendingSet::SealingOrElseQueue,
|
||||
reseal_min_period: Duration::from_secs(0),
|
||||
work_queue_size: 50,
|
||||
|
Loading…
Reference in New Issue
Block a user