diff --git a/crates/transaction-pool/src/pool/best.rs b/crates/transaction-pool/src/pool/best.rs index fa40d3d34a..52c0e26f97 100644 --- a/crates/transaction-pool/src/pool/best.rs +++ b/crates/transaction-pool/src/pool/best.rs @@ -10,10 +10,23 @@ use std::{ }; use tracing::debug; -/// An iterator that returns transactions that can be executed on the current state. +/// An iterator that returns transactions that can be executed on the current state (*best* +/// transactions). +/// +/// The [`PendingPool`] contains transactions that *could* all be executed on the current state, but +/// only yields transactions that are ready to be executed now. +/// While it contains all gapless transactions of a sender, it _always_ only returns the transaction +/// with the current on chain nonce. pub struct BestTransactions { + /// Contains a copy of _all_ transactions of the pending pool at the point in time this + /// iterator was created. pub(crate) all: BTreeMap>>, + /// Transactions that can be executed right away: these have the expected nonce. + /// + /// Once an `independent` transaction with the nonce `N` is returned, it unlocks `N+1`, which + /// then can be moved from the `all` set to the `independent` set. pub(crate) independent: BTreeSet>, + /// There might be the case where a yielded transactions is invalid, this will track it. pub(crate) invalid: HashSet, } @@ -35,6 +48,7 @@ impl Iterator for BestTransactions { fn next(&mut self) -> Option { loop { + // Remove the next independent tx with the highest priority let best = self.independent.iter().next_back()?.clone(); let best = self.independent.take(&best)?; let hash = best.transaction.hash(); @@ -58,3 +72,62 @@ impl Iterator for BestTransactions { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + pool::pending::PendingPool, + test_util::{MockOrdering, MockTransaction, MockTransactionFactory}, + }; + + #[test] + fn test_best_iter() { + let mut pool = PendingPool::new(Arc::new(MockOrdering::default())); + let mut f = MockTransactionFactory::default(); + + let num_tx = 10; + // insert 10 gapless tx + let tx = MockTransaction::eip1559(); + for nonce in 0..num_tx { + let tx = tx.clone().rng_hash().with_nonce(nonce); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx)); + } + + let mut best = pool.best(); + assert_eq!(best.all.len(), num_tx as usize); + assert_eq!(best.independent.len(), 1); + + // check tx are returned in order + for nonce in 0..num_tx { + assert_eq!(best.independent.len(), 1); + let tx = best.next().unwrap(); + assert_eq!(tx.nonce(), nonce); + } + } + + #[test] + fn test_best_iter_invalid() { + let mut pool = PendingPool::new(Arc::new(MockOrdering::default())); + let mut f = MockTransactionFactory::default(); + + let num_tx = 10; + // insert 10 gapless tx + let tx = MockTransaction::eip1559(); + for nonce in 0..num_tx { + let tx = tx.clone().rng_hash().with_nonce(nonce); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx)); + } + + let mut best = pool.best(); + + // mark the first tx as invalid + let invalid = best.independent.iter().next().unwrap(); + best.mark_invalid(&invalid.transaction.clone()); + + // iterator is empty + assert!(best.next().is_none()); + } +} diff --git a/crates/transaction-pool/src/pool/pending.rs b/crates/transaction-pool/src/pool/pending.rs index e1bff4c5e9..5b99a12faa 100644 --- a/crates/transaction-pool/src/pool/pending.rs +++ b/crates/transaction-pool/src/pool/pending.rs @@ -9,8 +9,16 @@ use std::{ sync::Arc, }; -/// A pool of validated and gapless transactions that are ready on the current state and are waiting -/// to be included in a block. +/// A pool of validated and gapless transactions that are ready to be executed on the current state +/// and are waiting to be included in a block. +/// +/// This pool distinguishes between `independent` transactions and pending transactions. A +/// transaction is `independent`, if it is in the pending pool, and it has the current on chain +/// nonce of the sender. Meaning `independent` transactions can be executed right away, other +/// pending transactions depend on at least one `independent` transaction. +/// +/// Once an `independent` transaction was executed it *unlocks* the next nonce, if this transaction +/// is also pending, then this will be moved to the `independent` queue. pub(crate) struct PendingPool { /// How to order transactions. ordering: Arc,