2021-06-04 12:12:24 +02:00
|
|
|
// Copyright 2015-2018 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
mod helpers;
|
|
|
|
mod tx_builder;
|
|
|
|
|
|
|
|
use self::{
|
|
|
|
helpers::{DummyScoring, NonceReady},
|
|
|
|
tx_builder::TransactionBuilder,
|
|
|
|
};
|
|
|
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
use ethereum_types::{Address, H256, U256};
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub struct Transaction {
|
|
|
|
pub hash: H256,
|
|
|
|
pub nonce: U256,
|
|
|
|
pub gas_price: U256,
|
|
|
|
pub gas: U256,
|
|
|
|
pub sender: Address,
|
|
|
|
pub mem_usage: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VerifiedTransaction for Transaction {
|
|
|
|
type Hash = H256;
|
|
|
|
type Sender = Address;
|
|
|
|
|
|
|
|
fn hash(&self) -> &H256 {
|
|
|
|
&self.hash
|
|
|
|
}
|
|
|
|
fn mem_usage(&self) -> usize {
|
|
|
|
self.mem_usage
|
|
|
|
}
|
|
|
|
fn sender(&self) -> &Address {
|
|
|
|
&self.sender
|
|
|
|
}
|
2021-11-10 14:36:17 +01:00
|
|
|
fn has_zero_gas_price(&self) -> bool {
|
2021-09-28 12:54:20 +02:00
|
|
|
false
|
|
|
|
}
|
2021-06-04 12:12:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub type SharedTransaction = Arc<Transaction>;
|
|
|
|
|
|
|
|
type TestPool = Pool<Transaction, DummyScoring>;
|
|
|
|
|
|
|
|
impl TestPool {
|
|
|
|
pub fn with_limit(max_count: usize) -> Self {
|
|
|
|
Self::with_options(Options {
|
|
|
|
max_count,
|
|
|
|
..Default::default()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn import<S: Scoring<Transaction>, L: Listener<Transaction>>(
|
|
|
|
txq: &mut Pool<Transaction, S, L>,
|
|
|
|
tx: Transaction,
|
|
|
|
) -> Result<Arc<Transaction>, Error<<Transaction as VerifiedTransaction>::Hash>> {
|
|
|
|
txq.import(tx, &mut DummyScoring::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_clear_queue() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
mem_usage: 0,
|
|
|
|
transaction_count: 0,
|
|
|
|
senders: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
let tx1 = b.tx().nonce(0).new();
|
|
|
|
let tx2 = b.tx().nonce(1).mem_usage(1).new();
|
|
|
|
|
|
|
|
// add
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
import(&mut txq, tx2).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
mem_usage: 1,
|
|
|
|
transaction_count: 2,
|
|
|
|
senders: 1,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// when
|
|
|
|
txq.clear();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
mem_usage: 0,
|
|
|
|
transaction_count: 0,
|
|
|
|
senders: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_not_allow_same_transaction_twice() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
let tx1 = b.tx().nonce(0).new();
|
|
|
|
let tx2 = b.tx().nonce(0).new();
|
|
|
|
|
|
|
|
// when
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
import(&mut txq, tx2).unwrap_err();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_replace_transaction() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
let tx1 = b.tx().nonce(0).gas_price(1).new();
|
|
|
|
let tx2 = b.tx().nonce(0).gas_price(2).new();
|
|
|
|
|
|
|
|
// when
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
import(&mut txq, tx2).unwrap();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_reject_if_above_count() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::with_options(Options {
|
|
|
|
max_count: 1,
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
|
|
|
|
// Reject second
|
|
|
|
let tx1 = b.tx().nonce(0).new();
|
|
|
|
let tx2 = b.tx().nonce(1).new();
|
|
|
|
let hash = tx2.hash.clone();
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
import(&mut txq, tx2).unwrap_err(),
|
|
|
|
error::Error::TooCheapToEnter(hash, "0x0".into())
|
|
|
|
);
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
|
|
|
|
txq.clear();
|
|
|
|
|
|
|
|
// Replace first
|
|
|
|
let tx1 = b.tx().nonce(0).new();
|
|
|
|
let tx2 = b.tx().nonce(0).sender(1).gas_price(2).new();
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
import(&mut txq, tx2).unwrap();
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_reject_if_above_mem_usage() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::with_options(Options {
|
|
|
|
max_mem_usage: 1,
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
|
|
|
|
// Reject second
|
|
|
|
let tx1 = b.tx().nonce(1).mem_usage(1).new();
|
|
|
|
let tx2 = b.tx().nonce(2).mem_usage(2).new();
|
|
|
|
let hash = tx2.hash.clone();
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
import(&mut txq, tx2).unwrap_err(),
|
|
|
|
error::Error::TooCheapToEnter(hash, "0x0".into())
|
|
|
|
);
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
|
|
|
|
txq.clear();
|
|
|
|
|
|
|
|
// Replace first
|
|
|
|
let tx1 = b.tx().nonce(1).mem_usage(1).new();
|
|
|
|
let tx2 = b.tx().nonce(1).sender(1).gas_price(2).mem_usage(1).new();
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
import(&mut txq, tx2).unwrap();
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_reject_if_above_sender_count() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::with_options(Options {
|
|
|
|
max_per_sender: 1,
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
|
|
|
|
// Reject second
|
|
|
|
let tx1 = b.tx().nonce(1).new();
|
|
|
|
let tx2 = b.tx().nonce(2).new();
|
|
|
|
let hash = tx2.hash.clone();
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
import(&mut txq, tx2).unwrap_err(),
|
|
|
|
error::Error::TooCheapToEnter(hash, "0x0".into())
|
|
|
|
);
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
|
|
|
|
txq.clear();
|
|
|
|
|
|
|
|
// Replace first
|
|
|
|
let tx1 = b.tx().nonce(1).new();
|
|
|
|
let tx2 = b.tx().nonce(2).gas_price(2).new();
|
|
|
|
let hash = tx2.hash.clone();
|
|
|
|
import(&mut txq, tx1).unwrap();
|
|
|
|
// This results in error because we also compare nonces
|
|
|
|
assert_eq!(
|
|
|
|
import(&mut txq, tx2).unwrap_err(),
|
|
|
|
error::Error::TooCheapToEnter(hash, "0x0".into())
|
|
|
|
);
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_construct_pending() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
let tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
let tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap();
|
|
|
|
|
|
|
|
let tx9 = import(&mut txq, b.tx().sender(2).nonce(0).new()).unwrap();
|
|
|
|
|
|
|
|
let tx5 = import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap();
|
|
|
|
let tx6 = import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap();
|
|
|
|
let tx7 = import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap();
|
|
|
|
let tx8 = import(&mut txq, b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap();
|
|
|
|
|
|
|
|
let tx2 = import(&mut txq, 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.
|
|
|
|
let tx3 = import(&mut txq, b.tx().nonce(3).gas_price(4).new()).unwrap();
|
|
|
|
//gap
|
|
|
|
import(&mut txq, b.tx().nonce(5).new()).unwrap();
|
|
|
|
|
|
|
|
// gap
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(5).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,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// get only includable part of the pending transactions
|
|
|
|
let mut includable = txq.pending(NonceReady::default(), U256::from(4));
|
|
|
|
|
|
|
|
assert_eq!(includable.next(), Some(tx0.clone()));
|
|
|
|
assert_eq!(includable.next(), Some(tx1.clone()));
|
|
|
|
assert_eq!(includable.next(), Some(tx8.clone()));
|
|
|
|
assert_eq!(includable.next(), Some(tx3.clone()));
|
|
|
|
assert_eq!(includable.next(), None);
|
|
|
|
|
|
|
|
// get all pending transactions
|
|
|
|
let mut current_gas = U256::zero();
|
|
|
|
let limit = (21_000 * 8).into();
|
|
|
|
let mut pending = txq
|
|
|
|
.pending(NonceReady::default(), U256::from(0))
|
|
|
|
.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(tx0));
|
|
|
|
assert_eq!(pending.next(), Some(tx1));
|
|
|
|
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));
|
|
|
|
assert_eq!(pending.next(), Some(tx2));
|
|
|
|
assert_eq!(pending.next(), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_skip_staled_pending_transactions() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
let _tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
let tx2 = import(&mut txq, b.tx().nonce(2).gas_price(5).new()).unwrap();
|
|
|
|
let _tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap();
|
|
|
|
|
|
|
|
// tx0 and tx1 are Stale, tx2 is Ready
|
|
|
|
let mut pending = txq.pending(NonceReady::new(2), Default::default());
|
|
|
|
|
|
|
|
// tx0 and tx1 should be skipped, tx2 should be the next Ready
|
|
|
|
assert_eq!(pending.next(), Some(tx2));
|
|
|
|
assert_eq!(pending.next(), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_return_unordered_iterator() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
let tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
let tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap();
|
|
|
|
let tx2 = import(&mut txq, b.tx().nonce(2).new()).unwrap();
|
|
|
|
let tx3 = import(&mut txq, b.tx().nonce(3).gas_price(4).new()).unwrap();
|
|
|
|
//gap
|
|
|
|
import(&mut txq, b.tx().nonce(5).new()).unwrap();
|
|
|
|
|
|
|
|
let tx5 = import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap();
|
|
|
|
let tx6 = import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap();
|
|
|
|
let tx7 = import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap();
|
|
|
|
let tx8 = import(&mut txq, b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap();
|
|
|
|
// gap
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(5).new()).unwrap();
|
|
|
|
|
|
|
|
let tx9 = import(&mut txq, 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,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// get all pending transaction in unordered way
|
|
|
|
let all: Vec<_> = txq
|
|
|
|
.unordered_pending(NonceReady::default(), Default::default())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let chain1 = vec![tx0, tx1, tx2, tx3];
|
|
|
|
let chain2 = vec![tx5, tx6, tx7, tx8];
|
|
|
|
let chain3 = vec![tx9];
|
|
|
|
|
|
|
|
assert_eq!(all.len(), chain1.len() + chain2.len() + chain3.len());
|
|
|
|
|
|
|
|
let mut options = vec![
|
|
|
|
vec![chain1.clone(), chain2.clone(), chain3.clone()],
|
|
|
|
vec![chain2.clone(), chain1.clone(), chain3.clone()],
|
|
|
|
vec![chain2.clone(), chain3.clone(), chain1.clone()],
|
|
|
|
vec![chain3.clone(), chain2.clone(), chain1.clone()],
|
|
|
|
vec![chain3.clone(), chain1.clone(), chain2.clone()],
|
|
|
|
vec![chain1.clone(), chain3.clone(), chain2.clone()],
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.map(|mut v| {
|
|
|
|
let mut first = v.pop().unwrap();
|
|
|
|
for mut x in v {
|
|
|
|
first.append(&mut x);
|
|
|
|
}
|
|
|
|
first
|
|
|
|
});
|
|
|
|
|
|
|
|
assert!(options.any(|opt| all == opt));
|
|
|
|
|
|
|
|
// get only includable part of the pending transactions in unordered way
|
|
|
|
let includable: Vec<_> = txq
|
|
|
|
.unordered_pending(NonceReady::default(), U256::from(3))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
assert_eq!(includable.len(), 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_update_scoring_correctly() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
let tx9 = import(&mut txq, b.tx().sender(2).nonce(0).new()).unwrap();
|
|
|
|
|
|
|
|
let tx5 = import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap();
|
|
|
|
let tx6 = import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap();
|
|
|
|
let tx7 = import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap();
|
|
|
|
let tx8 = import(&mut txq, b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap();
|
|
|
|
|
|
|
|
let tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
let tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap();
|
|
|
|
let tx2 = import(&mut txq, 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.
|
|
|
|
import(&mut txq, b.tx().nonce(3).gas_price(4).new()).unwrap();
|
|
|
|
//gap
|
|
|
|
import(&mut txq, b.tx().nonce(5).new()).unwrap();
|
|
|
|
|
|
|
|
// gap
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(5).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_scores(&Address::zero(), helpers::DummyScoringEvent::Penalize);
|
|
|
|
|
|
|
|
// when
|
|
|
|
let mut current_gas = U256::zero();
|
|
|
|
let limit = (21_000 * 8).into();
|
|
|
|
let mut pending = txq
|
|
|
|
.pending(NonceReady::default(), Default::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.clone()));
|
|
|
|
assert_eq!(pending.next(), Some(tx1.clone()));
|
|
|
|
assert_eq!(pending.next(), Some(tx2));
|
|
|
|
assert_eq!(pending.next(), None);
|
|
|
|
|
|
|
|
// update scores to initial values
|
|
|
|
txq.set_scoring(
|
|
|
|
DummyScoring::default(),
|
|
|
|
helpers::DummyScoringEvent::UpdateScores,
|
|
|
|
);
|
|
|
|
|
|
|
|
current_gas = U256::zero();
|
|
|
|
let mut includable = txq
|
|
|
|
.pending(NonceReady::default(), Default::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!(includable.next(), Some(tx0));
|
|
|
|
assert_eq!(includable.next(), Some(tx1));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_remove_transaction() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
let tx1 = import(&mut txq, b.tx().nonce(0).new()).unwrap();
|
|
|
|
let tx2 = import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().nonce(2).new()).unwrap();
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 3);
|
|
|
|
|
|
|
|
// when
|
|
|
|
assert!(txq.remove(&tx2.hash(), false).is_some());
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 2);
|
|
|
|
let mut pending = txq.pending(NonceReady::default(), Default::default());
|
|
|
|
assert_eq!(pending.next(), Some(tx1));
|
|
|
|
assert_eq!(pending.next(), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_cull_stalled_transactions() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().nonce(3).new()).unwrap();
|
|
|
|
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(5).new()).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
txq.status(NonceReady::new(1)),
|
|
|
|
Status {
|
|
|
|
stalled: 2,
|
|
|
|
pending: 2,
|
|
|
|
future: 2,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// when
|
|
|
|
assert_eq!(txq.cull(None, NonceReady::new(1)), 2);
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
txq.status(NonceReady::new(1)),
|
|
|
|
Status {
|
|
|
|
stalled: 0,
|
|
|
|
pending: 2,
|
|
|
|
future: 2,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 4,
|
|
|
|
senders: 2,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_cull_stalled_transactions_from_a_sender() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
txq.status(NonceReady::new(2)),
|
|
|
|
Status {
|
|
|
|
stalled: 4,
|
|
|
|
pending: 1,
|
|
|
|
future: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// when
|
|
|
|
let sender = Address::zero();
|
|
|
|
assert_eq!(txq.cull(Some(&[sender]), NonceReady::new(2)), 2);
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
txq.status(NonceReady::new(2)),
|
|
|
|
Status {
|
|
|
|
stalled: 2,
|
|
|
|
pending: 1,
|
|
|
|
future: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 3,
|
|
|
|
senders: 1,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_re_insert_after_cull() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
|
|
|
|
import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
txq.status(NonceReady::new(1)),
|
|
|
|
Status {
|
|
|
|
stalled: 2,
|
|
|
|
pending: 2,
|
|
|
|
future: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// when
|
|
|
|
assert_eq!(txq.cull(None, NonceReady::new(1)), 2);
|
|
|
|
assert_eq!(
|
|
|
|
txq.status(NonceReady::new(1)),
|
|
|
|
Status {
|
|
|
|
stalled: 0,
|
|
|
|
pending: 2,
|
|
|
|
future: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
txq.status(NonceReady::new(1)),
|
|
|
|
Status {
|
|
|
|
stalled: 2,
|
|
|
|
pending: 2,
|
|
|
|
future: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_return_worst_transaction() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::default();
|
|
|
|
assert!(txq.worst_transaction().is_none());
|
|
|
|
|
|
|
|
// when
|
|
|
|
import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(0).gas_price(4).new()).unwrap();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(txq.worst_transaction().unwrap().gas_price, 4.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_return_is_full() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::with_limit(2);
|
|
|
|
assert!(!txq.is_full());
|
|
|
|
|
|
|
|
// when
|
|
|
|
import(&mut txq, b.tx().nonce(0).gas_price(110).new()).unwrap();
|
|
|
|
assert!(!txq.is_full());
|
|
|
|
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(0).gas_price(100).new()).unwrap();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert!(txq.is_full());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_import_even_if_limit_is_reached_and_should_replace_returns_insert_new() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::with_scoring(
|
|
|
|
DummyScoring::always_insert(),
|
|
|
|
Options {
|
|
|
|
max_count: 1,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
txq.import(
|
|
|
|
b.tx().nonce(0).gas_price(5).new(),
|
|
|
|
&mut DummyScoring::always_insert(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 1,
|
|
|
|
senders: 1,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// when
|
|
|
|
txq.import(
|
|
|
|
b.tx().nonce(1).gas_price(5).new(),
|
|
|
|
&mut DummyScoring::always_insert(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 2,
|
|
|
|
senders: 1,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_not_import_even_if_limit_is_reached_and_should_replace_returns_false() {
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::with_scoring(
|
|
|
|
DummyScoring::default(),
|
|
|
|
Options {
|
|
|
|
max_count: 1,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 1,
|
|
|
|
senders: 1,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// when
|
|
|
|
let err = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap_err();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
err,
|
|
|
|
error::Error::TooCheapToEnter(
|
|
|
|
H256::from_str("00000000000000000000000000000000000000000000000000000000000001f5")
|
|
|
|
.unwrap(),
|
|
|
|
"0x5".into()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 1,
|
|
|
|
senders: 1,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_import_even_if_sender_limit_is_reached() {
|
|
|
|
// given
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let mut txq = TestPool::with_scoring(
|
|
|
|
DummyScoring::always_insert(),
|
|
|
|
Options {
|
|
|
|
max_count: 1,
|
|
|
|
max_per_sender: 1,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
txq.import(
|
|
|
|
b.tx().nonce(0).gas_price(5).new(),
|
|
|
|
&mut DummyScoring::always_insert(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 1,
|
|
|
|
senders: 1,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// when
|
|
|
|
txq.import(
|
|
|
|
b.tx().nonce(1).gas_price(5).new(),
|
|
|
|
&mut DummyScoring::always_insert(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
txq.light_status(),
|
|
|
|
LightStatus {
|
|
|
|
transaction_count: 2,
|
|
|
|
senders: 1,
|
|
|
|
mem_usage: 0,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
mod listener {
|
|
|
|
use std::{cell::RefCell, fmt, rc::Rc};
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct MyListener(pub Rc<RefCell<Vec<&'static str>>>);
|
|
|
|
|
|
|
|
impl Listener<Transaction> for MyListener {
|
|
|
|
fn added(&mut self, _tx: &SharedTransaction, old: Option<&SharedTransaction>) {
|
|
|
|
self.0
|
|
|
|
.borrow_mut()
|
|
|
|
.push(if old.is_some() { "replaced" } else { "added" });
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rejected<H: fmt::Debug + fmt::LowerHex>(
|
|
|
|
&mut self,
|
|
|
|
_tx: &SharedTransaction,
|
|
|
|
_reason: &error::Error<H>,
|
|
|
|
) {
|
|
|
|
self.0.borrow_mut().push("rejected".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn dropped(&mut self, _tx: &SharedTransaction, _new: Option<&Transaction>) {
|
|
|
|
self.0.borrow_mut().push("dropped".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn invalid(&mut self, _tx: &SharedTransaction) {
|
|
|
|
self.0.borrow_mut().push("invalid".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn canceled(&mut self, _tx: &SharedTransaction) {
|
|
|
|
self.0.borrow_mut().push("canceled".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn culled(&mut self, _tx: &SharedTransaction) {
|
|
|
|
self.0.borrow_mut().push("culled".into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn insert_transaction() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let listener = MyListener::default();
|
|
|
|
let results = listener.0.clone();
|
|
|
|
let mut txq = Pool::new(
|
|
|
|
listener,
|
|
|
|
DummyScoring::default(),
|
|
|
|
Options {
|
|
|
|
max_per_sender: 1,
|
|
|
|
max_count: 2,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert!(results.borrow().is_empty());
|
|
|
|
|
|
|
|
// Regular import
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
assert_eq!(*results.borrow(), &["added"]);
|
|
|
|
// Already present (no notification)
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap_err();
|
|
|
|
assert_eq!(*results.borrow(), &["added"]);
|
|
|
|
// Push out the first one
|
|
|
|
import(&mut txq, b.tx().nonce(1).gas_price(1).new()).unwrap();
|
|
|
|
assert_eq!(*results.borrow(), &["added", "replaced"]);
|
|
|
|
// Reject
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap_err();
|
|
|
|
assert_eq!(*results.borrow(), &["added", "replaced", "rejected"]);
|
|
|
|
results.borrow_mut().clear();
|
|
|
|
// Different sender (accept)
|
|
|
|
import(&mut txq, b.tx().sender(1).nonce(1).gas_price(2).new()).unwrap();
|
|
|
|
assert_eq!(*results.borrow(), &["added"]);
|
|
|
|
// Third sender push out low gas price
|
|
|
|
import(&mut txq, b.tx().sender(2).nonce(1).gas_price(4).new()).unwrap();
|
|
|
|
assert_eq!(*results.borrow(), &["added", "dropped", "added"]);
|
|
|
|
// Reject (too cheap)
|
|
|
|
import(&mut txq, b.tx().sender(2).nonce(1).gas_price(2).new()).unwrap_err();
|
|
|
|
assert_eq!(
|
|
|
|
*results.borrow(),
|
|
|
|
&["added", "dropped", "added", "rejected"]
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn remove_transaction() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let listener = MyListener::default();
|
|
|
|
let results = listener.0.clone();
|
|
|
|
let mut txq = Pool::new(listener, DummyScoring::default(), Options::default());
|
|
|
|
|
|
|
|
// insert
|
|
|
|
let tx1 = import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
let tx2 = import(&mut txq, b.tx().nonce(2).new()).unwrap();
|
|
|
|
|
|
|
|
// then
|
|
|
|
txq.remove(&tx1.hash(), false);
|
|
|
|
assert_eq!(*results.borrow(), &["added", "added", "canceled"]);
|
|
|
|
txq.remove(&tx2.hash(), true);
|
|
|
|
assert_eq!(
|
|
|
|
*results.borrow(),
|
|
|
|
&["added", "added", "canceled", "invalid"]
|
|
|
|
);
|
|
|
|
assert_eq!(txq.light_status().transaction_count, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn clear_queue() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let listener = MyListener::default();
|
|
|
|
let results = listener.0.clone();
|
|
|
|
let mut txq = Pool::new(listener, DummyScoring::default(), Options::default());
|
|
|
|
|
|
|
|
// insert
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().nonce(2).new()).unwrap();
|
|
|
|
|
|
|
|
// when
|
|
|
|
txq.clear();
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(*results.borrow(), &["added", "added", "dropped", "dropped"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cull_stalled() {
|
|
|
|
let b = TransactionBuilder::default();
|
|
|
|
let listener = MyListener::default();
|
|
|
|
let results = listener.0.clone();
|
|
|
|
let mut txq = Pool::new(listener, DummyScoring::default(), Options::default());
|
|
|
|
|
|
|
|
// insert
|
|
|
|
import(&mut txq, b.tx().nonce(1).new()).unwrap();
|
|
|
|
import(&mut txq, b.tx().nonce(2).new()).unwrap();
|
|
|
|
|
|
|
|
// when
|
|
|
|
txq.cull(None, NonceReady::new(3));
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(*results.borrow(), &["added", "added", "culled", "culled"]);
|
|
|
|
}
|
|
|
|
}
|