diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 0d02f863196..7348db5666f 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -194,7 +194,7 @@ impl Miner { true => None, false => Some(WorkPoster::new(&options.new_work_notify)) }; - let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, options.tx_gas_limit))); + let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, !U256::zero(), options.tx_gas_limit))); Miner { transaction_queue: txq, next_allowed_reseal: Mutex::new(Instant::now()), @@ -443,6 +443,8 @@ impl Miner { let gas_limit = HeaderView::new(&chain.best_block_header()).gas_limit(); let mut queue = self.transaction_queue.lock(); queue.set_gas_limit(gas_limit); + // Set total qx queue gas limit to be 2x the block gas limit. + queue.set_total_gas_limit(gas_limit << 1); } /// Returns true if we had to prepare new pending block. diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 02e569cf2eb..38923438c28 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -130,6 +130,8 @@ struct TransactionOrder { /// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5) /// High nonce_height = Low priority (processed later) nonce_height: U256, + /// Gas specified in the transaction. + gas: U256, /// Gas Price of the transaction. /// Low gas price = Low priority (processed later) gas_price: U256, @@ -146,6 +148,7 @@ impl TransactionOrder { fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256) -> Self { TransactionOrder { nonce_height: tx.nonce() - base_nonce, + gas: tx.transaction.gas.clone(), gas_price: tx.transaction.gas_price, hash: tx.hash(), origin: tx.origin, @@ -287,6 +290,7 @@ struct TransactionSet { by_address: Table, by_gas_price: GasPriceQueue, limit: usize, + gas_limit: U256, } impl TransactionSet { @@ -317,15 +321,18 @@ impl TransactionSet { /// 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) -> Option> { - let len = self.by_priority.len(); - if len <= self.limit { - return None; - } - + let mut count = 0; + let mut gas: U256 = 0.into(); let to_drop : Vec<(Address, U256)> = { self.by_priority .iter() - .skip(self.limit) + .skip_while(|order| { + count = count + 1; + let r = gas.overflowing_add(order.gas); + if r.1 { return false } + gas = r.0; + count <= self.limit && gas <= self.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())) @@ -432,16 +439,17 @@ impl Default for TransactionQueue { impl TransactionQueue { /// Creates new instance of this Queue pub fn new() -> Self { - Self::with_limits(1024, !U256::zero()) + Self::with_limits(1024, !U256::zero(), !U256::zero()) } /// Create new instance of this Queue with specified limits - pub fn with_limits(limit: usize, tx_gas_limit: U256) -> Self { + pub fn with_limits(limit: usize, 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: limit, + gas_limit: gas_limit, }; let future = TransactionSet { @@ -449,6 +457,7 @@ impl TransactionQueue { by_address: Table::new(), by_gas_price: Default::default(), limit: limit, + gas_limit: gas_limit, }; TransactionQueue { @@ -504,6 +513,13 @@ impl TransactionQueue { }; } + /// Sets new total gas limit. + pub fn set_total_gas_limit(&mut self, gas_limit: U256) { + self.future.gas_limit = gas_limit; + self.current.gas_limit = gas_limit; + self.future.enforce_limit(&mut self.by_hash); + } + /// 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) { @@ -827,6 +843,16 @@ impl TransactionQueue { let nonce = tx.nonce(); let hash = tx.hash(); + { + // Rough size sanity check + let gas = &tx.transaction.gas; + if U256::from(tx.transaction.data.len()) > *gas { + // Droping transaction + trace!(target: "txqueue", "Dropping oversized transaction: {:?} (gas: {} < size {})", hash, gas, tx.transaction.data.len()); + return Err(TransactionError::LimitReached); + } + } + // The transaction might be old, let's check that. // This has to be the first test, otherwise calculating // nonce height would result in overflow. @@ -979,6 +1005,7 @@ mod test { } 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_price: U256) -> Transaction { @@ -986,7 +1013,7 @@ mod test { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), + gas: default_gas_val(), gas_price: gas_price, nonce: nonce } @@ -1051,7 +1078,7 @@ mod test { #[test] fn should_return_correct_nonces_when_dropped_because_of_limit() { // given - let mut txq = TransactionQueue::with_limits(2, !U256::zero()); + let mut txq = TransactionQueue::with_limits(2, !U256::zero(), !U256::zero()); let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); let sender = tx1.sender().unwrap(); let nonce = tx1.nonce; @@ -1089,7 +1116,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External).unwrap(); @@ -1129,7 +1157,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; // Create two transactions with same nonce // (same hash) @@ -1177,7 +1206,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 2 + limit: 2, + gas_limit: !U256::zero(), }; let tx = new_tx_default(); let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap(); @@ -1194,7 +1224,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; assert_eq!(set.gas_price_entry_limit(), 0.into()); @@ -1690,7 +1721,7 @@ mod test { #[test] fn should_drop_old_transactions_when_hitting_the_limit() { // given - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let sender = tx.sender().unwrap(); let nonce = tx.nonce; @@ -1711,7 +1742,7 @@ mod test { #[test] fn should_limit_future_transactions() { - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !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()); @@ -1728,6 +1759,16 @@ mod test { assert_eq!(txq.status().future, 1); } + #[test] + fn should_limit_by_gas() { + let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero()); + let (tx1, _) = new_tx_pair_default(U256::from(4), U256::from(1)); + let (tx3, _) = new_tx_pair_default(U256::from(4), U256::from(2)); + txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + assert_eq!(txq.status().pending, 2); + } + #[test] fn should_drop_transactions_with_old_nonces() { let mut txq = TransactionQueue::new(); @@ -1971,7 +2012,7 @@ mod test { #[test] fn should_keep_right_order_in_future() { // given - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let prev_nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance: default_account_details(a).balance }; diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 565c5382791..d72ad400cec 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -123,6 +123,7 @@ const MAX_ROUND_PARENTS: usize = 32; const MAX_NEW_HASHES: usize = 64; const MAX_TX_TO_IMPORT: usize = 512; const MAX_NEW_BLOCK_AGE: BlockNumber = 20; +const MAX_TRANSACTION_SIZE: usize = 300*1024; const STATUS_PACKET: u8 = 0x00; const NEW_BLOCK_HASHES_PACKET: u8 = 0x01; @@ -1261,7 +1262,12 @@ impl ChainSync { item_count = min(item_count, MAX_TX_TO_IMPORT); let mut transactions = Vec::with_capacity(item_count); for i in 0 .. item_count { - let tx = try!(r.at(i)).as_raw().to_vec(); + let rlp = try!(r.at(i)); + if rlp.as_raw().len() > MAX_TRANSACTION_SIZE { + debug!("Skipped oversized transaction of {} bytes", rlp.as_raw().len()); + continue; + } + let tx = rlp.as_raw().to_vec(); transactions.push(tx); } io.chain().queue_transactions(transactions);