New Transaction Queue implementation (#8074)

* Implementation of Verifier, Scoring and Ready.

* Queue in progress.

* TransactionPool.

* Prepare for txpool release.

* Miner refactor [WiP]

* WiP reworking miner.

* Make it compile.

* Add some docs.

* Split blockchain access to a separate file.

* Work on miner API.

* Fix ethcore tests.

* Refactor miner interface for sealing/work packages.

* Implement next nonce.

* RPC compiles.

* Implement couple of missing methdods for RPC.

* Add transaction queue listeners.

* Compiles!

* Clean-up and parallelize.

* Get rid of RefCell in header.

* Revert "Get rid of RefCell in header."

This reverts commit 0f2424c9b7319a786e1565ea2a8a6d801a21b4fb.

* Override Sync requirement.

* Fix status display.

* Unify logging.

* Extract some cheap checks.

* Measurements and optimizations.

* Fix scoring bug, heap size of bug and add cache

* Disable tx queueing and parallel verification.

* Make ethcore and ethcore-miner compile again.

* Make RPC compile again.

* Bunch of txpool tests.

* Migrate transaction queue tests.

* Nonce Cap

* Nonce cap cache and tests.

* Remove stale future transactions from the queue.

* Optimize scoring and write some tests.

* Simple penalization.

* Clean up and support for different scoring algorithms.

* Add CLI parameters for the new queue.

* Remove banning queue.

* Disable debug build.

* Change per_sender limit to be 1% instead of 5%

* Avoid cloning when propagating transactions.

* Remove old todo.

* Post-review fixes.

* Fix miner options default.

* Implement back ready transactions for light client.

* Get rid of from_pending_block

* Pass rejection reason.

* Add more details to drop.

* Rollback heap size of.

* Avoid cloning hashes when propagating and include more details on rejection.

* Fix tests.

* Introduce nonces cache.

* Remove uneccessary hashes allocation.

* Lower the mem limit.

* Re-enable parallel verification.

* Add miner log. Don't check the type if not below min_gas_price.

* Add more traces, fix disabling miner.

* Fix creating pending blocks twice on AuRa authorities.

* Fix tests.

* re-use pending blocks in AuRa

* Use reseal_min_period to prevent too frequent update_sealing.

* Fix log to contain hash not sender.

* Optimize local transactions.

* Fix aura tests.

* Update locks comments.

* Get rid of unsafe Sync impl.

* Review fixes.

* Remove excessive matches.

* Fix compilation errors.

* Use new pool in private transactions.

* Fix private-tx test.

* Fix secret store tests.

* Actually use gas_floor_target

* Fix config tests.

* Fix pool tests.

* Address grumbles.
This commit is contained in:
Tomasz Drwięga
2018-04-13 17:34:27 +02:00
committed by Marek Kotewicz
parent 03b96a7c0a
commit 1cd93e4ceb
105 changed files with 5185 additions and 5784 deletions

View File

@@ -21,17 +21,17 @@ error_chain! {
/// Transaction is already imported
AlreadyImported(hash: H256) {
description("transaction is already in the pool"),
display("[{:?}] transaction already imported", hash)
display("[{:?}] already imported", hash)
}
/// Transaction is too cheap to enter the queue
TooCheapToEnter(hash: H256) {
TooCheapToEnter(hash: H256, min_score: String) {
description("the pool is full and transaction is too cheap to replace any transaction"),
display("[{:?}] transaction too cheap to enter the pool", hash)
display("[{:?}] too cheap to enter the pool. Min score: {}", hash, min_score)
}
/// Transaction is too cheap to replace existing transaction that occupies the same slot.
TooCheapToReplace(old_hash: H256, hash: H256) {
description("transaction is too cheap to replace existing transaction in the pool"),
display("[{:?}] transaction too cheap to replace: {:?}", hash, old_hash)
display("[{:?}] too cheap to replace: {:?}", hash, old_hash)
}
}
}
@@ -43,7 +43,7 @@ impl PartialEq for ErrorKind {
match (self, other) {
(&AlreadyImported(ref h1), &AlreadyImported(ref h2)) => h1 == h2,
(&TooCheapToEnter(ref h1), &TooCheapToEnter(ref h2)) => h1 == h2,
(&TooCheapToEnter(ref h1, ref s1), &TooCheapToEnter(ref h2, ref s2)) => h1 == h2 && s1 == s2,
(&TooCheapToReplace(ref old1, ref new1), &TooCheapToReplace(ref old2, ref new2)) => old1 == old2 && new1 == new2,
_ => false,
}

View File

@@ -76,6 +76,8 @@ extern crate error_chain;
#[macro_use]
extern crate log;
extern crate trace_time;
#[cfg(test)]
mod tests;
@@ -90,6 +92,7 @@ mod verifier;
pub mod scoring;
pub use self::error::{Error, ErrorKind};
pub use self::listener::{Listener, NoopListener};
pub use self::options::Options;
pub use self::pool::{Pool, PendingIterator};

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use error::ErrorKind;
/// Transaction pool listener.
///
@@ -28,16 +29,16 @@ pub trait Listener<T> {
/// The transaction was rejected from the pool.
/// It means that it was too cheap to replace any transaction already in the pool.
fn rejected(&mut self, _tx: T) {}
fn rejected(&mut self, _tx: &Arc<T>, _reason: &ErrorKind) {}
/// The transaction was dropped from the pool because of a limit.
fn dropped(&mut self, _tx: &Arc<T>) {}
/// The transaction was pushed out from the pool because of the limit.
fn dropped(&mut self, _tx: &Arc<T>, _by: Option<&T>) {}
/// The transaction was marked as invalid by executor.
fn invalid(&mut self, _tx: &Arc<T>) {}
/// The transaction has been cancelled.
fn cancelled(&mut self, _tx: &Arc<T>) {}
/// The transaction has been canceled.
fn canceled(&mut self, _tx: &Arc<T>) {}
/// The transaction has been mined.
fn mined(&mut self, _tx: &Arc<T>) {}
@@ -47,3 +48,38 @@ pub trait Listener<T> {
#[derive(Debug)]
pub struct NoopListener;
impl<T> Listener<T> for NoopListener {}
impl<T, A, B> Listener<T> for (A, B) where
A: Listener<T>,
B: Listener<T>,
{
fn added(&mut self, tx: &Arc<T>, old: Option<&Arc<T>>) {
self.0.added(tx, old);
self.1.added(tx, old);
}
fn rejected(&mut self, tx: &Arc<T>, reason: &ErrorKind) {
self.0.rejected(tx, reason);
self.1.rejected(tx, reason);
}
fn dropped(&mut self, tx: &Arc<T>, by: Option<&T>) {
self.0.dropped(tx, by);
self.1.dropped(tx, by);
}
fn invalid(&mut self, tx: &Arc<T>) {
self.0.invalid(tx);
self.1.invalid(tx);
}
fn canceled(&mut self, tx: &Arc<T>) {
self.0.canceled(tx);
self.1.canceled(tx);
}
fn mined(&mut self, tx: &Arc<T>) {
self.0.mined(tx);
self.1.mined(tx);
}
}

View File

@@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// Transaction Pool options.
#[derive(Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Options {
/// Maximal number of transactions in the pool.
pub max_count: usize,

View File

@@ -109,17 +109,17 @@ impl<T, S, L> Pool<T, S, L> where
ensure!(!self.by_hash.contains_key(transaction.hash()), error::ErrorKind::AlreadyImported(*transaction.hash()));
// TODO [ToDr] Most likely move this after the transsaction is inserted.
// TODO [ToDr] Most likely move this after the transaction is inserted.
// Avoid using should_replace, but rather use scoring for that.
{
let remove_worst = |s: &mut Self, transaction| {
match s.remove_worst(&transaction) {
Err(err) => {
s.listener.rejected(transaction);
s.listener.rejected(&Arc::new(transaction), err.kind());
Err(err)
},
Ok(removed) => {
s.listener.dropped(&removed);
s.listener.dropped(&removed, Some(&transaction));
s.finalize_remove(removed.hash());
Ok(transaction)
},
@@ -127,10 +127,12 @@ impl<T, S, L> Pool<T, S, L> where
};
while self.by_hash.len() + 1 > self.options.max_count {
trace!("Count limit reached: {} > {}", self.by_hash.len() + 1, self.options.max_count);
transaction = remove_worst(self, transaction)?;
}
while self.mem_usage + mem_usage > self.options.max_mem_usage {
trace!("Mem limit reached: {} > {}", self.mem_usage + mem_usage, self.options.max_mem_usage);
transaction = remove_worst(self, transaction)?;
}
}
@@ -160,14 +162,14 @@ impl<T, S, L> Pool<T, S, L> where
Ok(new)
},
AddResult::TooCheap { new, old } => {
let hash = *new.hash();
self.listener.rejected(new);
bail!(error::ErrorKind::TooCheapToReplace(*old.hash(), hash))
let error = error::ErrorKind::TooCheapToReplace(*old.hash(), *new.hash());
self.listener.rejected(&Arc::new(new), &error);
bail!(error)
},
AddResult::TooCheapToEnter(new) => {
let hash = *new.hash();
self.listener.rejected(new);
bail!(error::ErrorKind::TooCheapToEnter(hash))
AddResult::TooCheapToEnter(new, score) => {
let error = error::ErrorKind::TooCheapToEnter(*new.hash(), format!("{:?}", score));
self.listener.rejected(&Arc::new(new), &error);
bail!(error)
}
}
}
@@ -241,14 +243,14 @@ impl<T, S, L> Pool<T, S, L> where
// No elements to remove? and the pool is still full?
None => {
warn!("The pool is full but there are no transactions to remove.");
return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash()).into());
return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash(), "unknown".into()).into());
},
Some(old) => if self.scoring.should_replace(&old.transaction, transaction) {
// New transaction is better than the worst one so we can replace it.
old.clone()
} else {
// otherwise fail
return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash()).into())
return Err(error::ErrorKind::TooCheapToEnter(*transaction.hash(), format!("{:?}", old.score)).into())
},
};
@@ -256,6 +258,7 @@ impl<T, S, L> Pool<T, S, L> where
self.remove_from_set(to_remove.transaction.sender(), |set, scoring| {
set.remove(&to_remove.transaction, scoring)
});
Ok(to_remove.transaction)
}
@@ -283,7 +286,7 @@ impl<T, S, L> Pool<T, S, L> where
self.worst_transactions.clear();
for (_hash, tx) in self.by_hash.drain() {
self.listener.dropped(&tx)
self.listener.dropped(&tx, None)
}
}
@@ -298,7 +301,7 @@ impl<T, S, L> Pool<T, S, L> where
if is_invalid {
self.listener.invalid(&tx);
} else {
self.listener.cancelled(&tx);
self.listener.canceled(&tx);
}
Some(tx)
} else {
@@ -345,6 +348,16 @@ impl<T, S, L> Pool<T, S, L> where
removed
}
/// Returns a transaction if it's part of the pool or `None` otherwise.
pub fn find(&self, hash: &H256) -> Option<Arc<T>> {
self.by_hash.get(hash).cloned()
}
/// Returns worst transaction in the queue (if any).
pub fn worst_transaction(&self) -> Option<Arc<T>> {
self.worst_transactions.iter().next().map(|x| x.transaction.clone())
}
/// Returns an iterator of pending (ready) transactions.
pub fn pending<R: Ready<T>>(&self, ready: R) -> PendingIterator<T, R, S, L> {
PendingIterator {
@@ -354,6 +367,41 @@ impl<T, S, L> Pool<T, S, L> where
}
}
/// Returns pending (ready) transactions from given sender.
pub fn pending_from_sender<R: Ready<T>>(&self, ready: R, sender: &Sender) -> PendingIterator<T, R, S, L> {
let best_transactions = self.transactions.get(sender)
.and_then(|transactions| transactions.worst_and_best())
.map(|(_, best)| ScoreWithRef::new(best.0, best.1))
.map(|s| {
let mut set = BTreeSet::new();
set.insert(s);
set
})
.unwrap_or_default();
PendingIterator {
ready,
best_transactions,
pool: self
}
}
/// Update score of transactions of a particular sender.
pub fn update_scores(&mut self, sender: &Sender, event: S::Event) {
let res = if let Some(set) = self.transactions.get_mut(sender) {
let prev = set.worst_and_best();
set.update_scores(&self.scoring, event);
let current = set.worst_and_best();
Some((prev, current))
} else {
None
};
if let Some((prev, current)) = res {
self.update_senders_worst_and_best(prev, current);
}
}
/// Computes the full status of the pool (including readiness).
pub fn status<R: Ready<T>>(&self, mut ready: R) -> Status {
let mut status = Status::default();
@@ -383,6 +431,21 @@ impl<T, S, L> Pool<T, S, L> where
senders: self.transactions.len(),
}
}
/// Returns current pool options.
pub fn options(&self) -> Options {
self.options.clone()
}
/// Borrows listener instance.
pub fn listener(&self) -> &L {
&self.listener
}
/// Borrows listener mutably.
pub fn listener_mut(&mut self) -> &mut L {
&mut self.listener
}
}
/// An iterator over all pending (ready) transactions.
@@ -424,7 +487,7 @@ impl<'a, T, R, S, L> Iterator for PendingIterator<'a, T, R, S, L> where
return Some(best.transaction)
},
state => warn!("[{:?}] Ignoring {:?} transaction.", best.transaction.hash(), state),
state => trace!("[{:?}] Ignoring {:?} transaction.", best.transaction.hash(), state),
}
}

View File

@@ -42,7 +42,7 @@ pub enum Choice {
/// The `Scoring` implementations can use this information
/// to update the `Score` table more efficiently.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Change {
pub enum Change<T = ()> {
/// New transaction has been inserted at given index.
/// The Score at that index is initialized with default value
/// and needs to be filled in.
@@ -56,8 +56,12 @@ pub enum Change {
/// The score at that index needs to be update (it contains value from previous transaction).
ReplacedAt(usize),
/// Given number of stalled transactions has been culled from the beginning.
/// Usually the score will have to be re-computed from scratch.
/// The scores has been removed from the beginning as well.
/// For simple scoring algorithms no action is required here.
Culled(usize),
/// Custom event to update the score triggered outside of the pool.
/// Handling this event is up to scoring implementation.
Event(T),
}
/// A transaction ordering.
@@ -69,7 +73,7 @@ pub enum Change {
/// Implementation notes:
/// - Returned `Score`s should match ordering of `compare` method.
/// - `compare` will be called only within a context of transactions from the same sender.
/// - `choose` will be called only if `compare` returns `Ordering::Equal`
/// - `choose` may be called even if `compare` returns `Ordering::Equal`
/// - `should_replace` is used to decide if new transaction should push out an old transaction already in the queue.
/// - `Score`s and `compare` should align with `Ready` implementation.
///
@@ -79,9 +83,11 @@ pub enum Change {
/// - `update_scores`: score defined as `gasPrice` if `n==0` and `max(scores[n-1], gasPrice)` if `n>0`
/// - `should_replace`: compares `gasPrice` (decides if transaction from a different sender is more valuable)
///
pub trait Scoring<T> {
pub trait Scoring<T>: fmt::Debug {
/// A score of a transaction.
type Score: cmp::Ord + Clone + Default + fmt::Debug;
/// Custom scoring update event type.
type Event: fmt::Debug;
/// Decides on ordering of `T`s from a particular sender.
fn compare(&self, old: &T, other: &T) -> cmp::Ordering;
@@ -92,7 +98,7 @@ pub trait Scoring<T> {
/// Updates the transaction scores given a list of transactions and a change to previous scoring.
/// NOTE: you can safely assume that both slices have the same length.
/// (i.e. score at index `i` represents transaction at the same index)
fn update_scores(&self, txs: &[Arc<T>], scores: &mut [Self::Score], change: Change);
fn update_scores(&self, txs: &[Arc<T>], scores: &mut [Self::Score], change: Change<Self::Event>);
/// Decides if `new` should push out `old` transaction from the pool.
fn should_replace(&self, old: &T, new: &T) -> bool;

View File

@@ -16,7 +16,7 @@
/// Light pool status.
/// This status is cheap to compute and can be called frequently.
#[derive(Default, Debug, PartialEq, Eq)]
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct LightStatus {
/// Memory usage in bytes.
pub mem_usage: usize,
@@ -29,7 +29,7 @@ pub struct LightStatus {
/// A full queue status.
/// To compute this status it is required to provide `Ready`.
/// NOTE: To compute the status we need to visit each transaction in the pool.
#[derive(Default, Debug, PartialEq, Eq)]
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Status {
/// Number of stalled transactions.
pub stalled: usize,

View File

@@ -21,11 +21,12 @@ use ethereum_types::U256;
use {scoring, Scoring, Ready, Readiness, Address as Sender};
use super::{Transaction, SharedTransaction};
#[derive(Default)]
#[derive(Debug, Default)]
pub struct DummyScoring;
impl Scoring<Transaction> for DummyScoring {
type Score = U256;
type Event = ();
fn compare(&self, old: &Transaction, new: &Transaction) -> cmp::Ordering {
old.nonce.cmp(&new.nonce)
@@ -43,9 +44,17 @@ impl Scoring<Transaction> for DummyScoring {
}
}
fn update_scores(&self, txs: &[SharedTransaction], scores: &mut [Self::Score], _change: scoring::Change) {
for i in 0..txs.len() {
scores[i] = txs[i].gas_price;
fn update_scores(&self, txs: &[SharedTransaction], scores: &mut [Self::Score], change: scoring::Change) {
if let scoring::Change::Event(_) = change {
// In case of event reset all scores to 0
for i in 0..txs.len() {
scores[i] = 0.into();
}
} else {
// Set to a gas price otherwise
for i in 0..txs.len() {
scores[i] = txs[i].gas_price;
}
}
}

View File

@@ -125,7 +125,7 @@ fn should_reject_if_above_count() {
let tx2 = b.tx().nonce(1).new();
let hash = *tx2.hash();
txq.import(tx1).unwrap();
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash));
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into()));
assert_eq!(txq.light_status().transaction_count, 1);
txq.clear();
@@ -151,7 +151,7 @@ fn should_reject_if_above_mem_usage() {
let tx2 = b.tx().nonce(2).mem_usage(2).new();
let hash = *tx2.hash();
txq.import(tx1).unwrap();
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash));
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into()));
assert_eq!(txq.light_status().transaction_count, 1);
txq.clear();
@@ -177,7 +177,7 @@ fn should_reject_if_above_sender_count() {
let tx2 = b.tx().nonce(2).new();
let hash = *tx2.hash();
txq.import(tx1).unwrap();
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash));
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into()));
assert_eq!(txq.light_status().transaction_count, 1);
txq.clear();
@@ -188,7 +188,7 @@ fn should_reject_if_above_sender_count() {
let hash = *tx2.hash();
txq.import(tx1).unwrap();
// This results in error because we also compare nonces
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash));
assert_eq!(txq.import(tx2).unwrap_err().kind(), &error::ErrorKind::TooCheapToEnter(hash, "0x0".into()));
assert_eq!(txq.light_status().transaction_count, 1);
}
@@ -249,6 +249,66 @@ fn should_construct_pending() {
assert_eq!(pending.next(), None);
}
#[test]
fn should_update_scoring_correctly() {
// given
let b = TransactionBuilder::default();
let mut txq = TestPool::default();
let tx0 = txq.import(b.tx().nonce(0).gas_price(5).new()).unwrap();
let tx1 = txq.import(b.tx().nonce(1).gas_price(5).new()).unwrap();
let tx2 = txq.import(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.
txq.import(b.tx().nonce(3).gas_price(4).new()).unwrap();
//gap
txq.import(b.tx().nonce(5).new()).unwrap();
let tx5 = txq.import(b.tx().sender(1).nonce(0).new()).unwrap();
let tx6 = txq.import(b.tx().sender(1).nonce(1).new()).unwrap();
let tx7 = txq.import(b.tx().sender(1).nonce(2).new()).unwrap();
let tx8 = txq.import(b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap();
// gap
txq.import(b.tx().sender(1).nonce(5).new()).unwrap();
let tx9 = txq.import(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,
});
txq.update_scores(&0.into(), ());
// when
let mut current_gas = U256::zero();
let limit = (21_000 * 8).into();
let mut pending = txq.pending(NonceReady::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));
assert_eq!(pending.next(), Some(tx1));
assert_eq!(pending.next(), Some(tx2));
assert_eq!(pending.next(), None);
}
#[test]
fn should_remove_transaction() {
// given
@@ -375,6 +435,20 @@ fn should_re_insert_after_cull() {
});
}
#[test]
fn should_return_worst_transaction() {
// given
let b = TransactionBuilder::default();
let mut txq = TestPool::default();
assert!(txq.worst_transaction().is_none());
// when
txq.import(b.tx().nonce(0).gas_price(5).new()).unwrap();
// then
assert!(txq.worst_transaction().is_some());
}
mod listener {
use std::cell::RefCell;
use std::rc::Rc;
@@ -389,11 +463,11 @@ mod listener {
self.0.borrow_mut().push(if old.is_some() { "replaced" } else { "added" });
}
fn rejected(&mut self, _tx: Transaction) {
fn rejected(&mut self, _tx: &SharedTransaction, _reason: &error::ErrorKind) {
self.0.borrow_mut().push("rejected".into());
}
fn dropped(&mut self, _tx: &SharedTransaction) {
fn dropped(&mut self, _tx: &SharedTransaction, _new: Option<&Transaction>) {
self.0.borrow_mut().push("dropped".into());
}
@@ -401,8 +475,8 @@ mod listener {
self.0.borrow_mut().push("invalid".into());
}
fn cancelled(&mut self, _tx: &SharedTransaction) {
self.0.borrow_mut().push("cancelled".into());
fn canceled(&mut self, _tx: &SharedTransaction) {
self.0.borrow_mut().push("canceled".into());
}
fn mined(&mut self, _tx: &SharedTransaction) {
@@ -461,9 +535,9 @@ mod listener {
// then
txq.remove(&tx1.hash(), false);
assert_eq!(*results.borrow(), &["added", "added", "cancelled"]);
assert_eq!(*results.borrow(), &["added", "added", "canceled"]);
txq.remove(&tx2.hash(), true);
assert_eq!(*results.borrow(), &["added", "added", "cancelled", "invalid"]);
assert_eq!(*results.borrow(), &["added", "added", "canceled", "invalid"]);
assert_eq!(txq.light_status().transaction_count, 0);
}

View File

@@ -23,9 +23,9 @@ use ready::{Ready, Readiness};
use scoring::{self, Scoring};
#[derive(Debug)]
pub enum AddResult<T> {
pub enum AddResult<T, S> {
Ok(Arc<T>),
TooCheapToEnter(T),
TooCheapToEnter(T, S),
TooCheap {
old: Arc<T>,
new: T,
@@ -93,10 +93,11 @@ impl<T: fmt::Debug, S: Scoring<T>> Transactions<T, S> {
})
}
fn push_cheapest_transaction(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult<T> {
fn push_cheapest_transaction(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult<T, S::Score> {
let index = self.transactions.len();
if index == max_count {
AddResult::TooCheapToEnter(tx)
let min_score = self.scores[index - 1].clone();
AddResult::TooCheapToEnter(tx, min_score)
} else {
let shared = Arc::new(tx);
self.transactions.push(shared.clone());
@@ -107,7 +108,11 @@ impl<T: fmt::Debug, S: Scoring<T>> Transactions<T, S> {
}
}
pub fn add(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult<T> {
pub fn update_scores(&mut self, scoring: &S, event: S::Event) {
scoring.update_scores(&self.transactions, &mut self.scores, scoring::Change::Event(event));
}
pub fn add(&mut self, tx: T, scoring: &S, max_count: usize) -> AddResult<T, S::Score> {
let index = match self.transactions.binary_search_by(|old| scoring.compare(old, &tx)) {
Ok(index) => index,
Err(index) => index,
@@ -192,6 +197,10 @@ impl<T: fmt::Debug, S: Scoring<T>> Transactions<T, S> {
}
}
if first_non_stalled == 0 {
return result;
}
// reverse the vectors to easily remove first elements.
self.transactions.reverse();
self.scores.reverse();