Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Transaction queue limited by gas (#2528)
Browse files Browse the repository at this point in the history
  • Loading branch information
arkpar committed Oct 8, 2016
1 parent 72ec936 commit c5bdc8b
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 19 deletions.
4 changes: 3 additions & 1 deletion ethcore/src/miner/miner.rs
Expand Up @@ -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()),
Expand Down Expand Up @@ -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.
Expand Down
75 changes: 58 additions & 17 deletions ethcore/src/miner/transaction_queue.rs
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -287,6 +290,7 @@ struct TransactionSet {
by_address: Table<Address, U256, TransactionOrder>,
by_gas_price: GasPriceQueue,
limit: usize,
gas_limit: U256,
}

impl TransactionSet {
Expand Down Expand Up @@ -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<H256, VerifiedTransaction>) -> Option<HashMap<Address, U256>> {
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()))
Expand Down Expand Up @@ -432,23 +439,25 @@ 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 {
by_priority: BTreeSet::new(),
by_address: Table::new(),
by_gas_price: Default::default(),
limit: limit,
gas_limit: gas_limit,
};

TransactionQueue {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -979,14 +1005,15 @@ 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 {
Transaction {
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
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand All @@ -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());
Expand Down Expand Up @@ -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;
Expand All @@ -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());
Expand All @@ -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();
Expand Down Expand Up @@ -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 };
Expand Down
8 changes: 7 additions & 1 deletion sync/src/chain.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit c5bdc8b

Please sign in to comment.