Skip to content

Commit

Permalink
feat(txpool): new txpool design (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse authored Oct 11, 2022
1 parent 8eb2ea4 commit 3fed7cf
Show file tree
Hide file tree
Showing 15 changed files with 1,846 additions and 1,058 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/transaction-pool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ tracing = "0.1"
serde = { version = "1.0", features = ["derive"] }
linked-hash-map = "0.5"
fnv = "1.0.7"
bitflags = "1.3"

[dev-dependencies]
paste = "1.0"
68 changes: 63 additions & 5 deletions crates/transaction-pool/src/identifier.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::U256;
use fnv::FnvHashMap;
use reth_primitives::Address;
use std::collections::HashMap;
use std::{collections::HashMap, ops::Bound};

/// An internal mapping of addresses.
///
/// This assigns a _unique_ `SenderId` for a new `Address`.
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct SenderIdentifiers {
/// The identifier to use next.
id: u64,
Expand Down Expand Up @@ -53,6 +52,21 @@ impl SenderIdentifiers {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SenderId(u64);

// === impl SenderId ===

impl SenderId {
/// Returns a `Bound` for `TransactionId` starting with nonce `0`
pub(crate) fn start_bound(self) -> Bound<TransactionId> {
Bound::Included(TransactionId::new(self, 0))
}
}

impl From<u64> for SenderId {
fn from(value: u64) -> Self {
SenderId(value)
}
}

/// A unique identifier of a transaction of a Sender.
///
/// This serves as an identifier for dependencies of a transaction:
Expand All @@ -73,11 +87,11 @@ impl TransactionId {
Self { sender, nonce }
}

/// Returns the id a transactions depends on
/// Returns the `TransactionId` this transaction depends on.
///
/// This returns `transaction_nonce - 1` if `transaction_nonce` is higher than the
/// `on_chain_none`
pub fn dependency(
pub fn ancestor(
transaction_nonce: u64,
on_chain_nonce: u64,
sender: SenderId,
Expand All @@ -92,4 +106,48 @@ impl TransactionId {
None
}
}

/// Returns the `TransactionId` that would come before this transaction.
pub(crate) fn unchecked_ancestor(&self) -> Option<TransactionId> {
if self.nonce == 0 {
None
} else {
Some(TransactionId::new(self.sender, self.nonce - 1))
}
}

/// Returns the `TransactionId` that directly follows this transaction: `self.nonce + 1`
pub fn descendant(&self) -> TransactionId {
TransactionId::new(self.sender, self.nonce + 1)
}

/// Returns the nonce the follows directly after this.
#[inline]
pub(crate) fn next_nonce(&self) -> u64 {
self.nonce + 1
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;

#[test]
fn test_transaction_id_ord_eq_sender() {
let tx1 = TransactionId::new(100u64.into(), 0u64);
let tx2 = TransactionId::new(100u64.into(), 1u64);
assert!(tx2 > tx1);
let set = BTreeSet::from([tx1, tx2]);
assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![tx1, tx2]);
}

#[test]
fn test_transaction_id_ord() {
let tx1 = TransactionId::new(99u64.into(), 0u64);
let tx2 = TransactionId::new(100u64.into(), 1u64);
assert!(tx2 > tx1);
let set = BTreeSet::from([tx1, tx2]);
assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![tx1, tx2]);
}
}
3 changes: 3 additions & 0 deletions crates/transaction-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub mod pool;
mod traits;
mod validate;

#[cfg(test)]
mod test_util;

pub use crate::{
client::PoolClient,
config::PoolConfig,
Expand Down
9 changes: 4 additions & 5 deletions crates/transaction-pool/src/ordering.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::traits::PoolTransaction;
use std::fmt;

/// Transaction ordering.
/// Transaction ordering trait to determine the order of transactions.
///
/// Decides how transactions should be ordered within the pool.
///
/// The returned priority must reflect natural `Ordering`.
// TODO: for custom, more advanced scoring it would be ideal to determine the priority in the
// context of the entire pool instead of standalone by alone looking at a single transaction
/// The returned priority must reflect natural `Ordering`
// TODO(mattsse) this should be extended so it provides a way to rank transaction in relation to
// each other.
pub trait TransactionOrdering: Send + Sync + 'static {
/// Priority of a transaction.
type Priority: Ord + Clone + Default + fmt::Debug + Send + Sync;
Expand Down
60 changes: 60 additions & 0 deletions crates/transaction-pool/src/pool/best.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::{
identifier::TransactionId,
pool::pending::{PendingTransaction, PendingTransactionRef},
TransactionOrdering, ValidPoolTransaction,
};
use reth_primitives::H256 as TxHash;
use std::{
collections::{BTreeMap, BTreeSet, HashSet},
sync::Arc,
};
use tracing::debug;

/// An iterator that returns transactions that can be executed on the current state.
pub struct BestTransactions<T: TransactionOrdering> {
pub(crate) all: BTreeMap<TransactionId, Arc<PendingTransaction<T>>>,
pub(crate) independent: BTreeSet<PendingTransactionRef<T>>,
pub(crate) invalid: HashSet<TxHash>,
}

impl<T: TransactionOrdering> BestTransactions<T> {
/// Mark the transaction and it's descendants as invalid.
pub(crate) fn mark_invalid(&mut self, tx: &Arc<ValidPoolTransaction<T::Transaction>>) {
self.invalid.insert(*tx.hash());
}
}

impl<T: TransactionOrdering> crate::traits::BestTransactions for BestTransactions<T> {
fn mark_invalid(&mut self, tx: &Self::Item) {
BestTransactions::mark_invalid(self, tx)
}
}

impl<T: TransactionOrdering> Iterator for BestTransactions<T> {
type Item = Arc<ValidPoolTransaction<T::Transaction>>;

fn next(&mut self) -> Option<Self::Item> {
loop {
let best = self.independent.iter().next_back()?.clone();
let best = self.independent.take(&best)?;
let hash = best.transaction.hash();

// skip transactions that were marked as invalid
if self.invalid.contains(hash) {
debug!(
target: "txpool",
"[{:?}] skipping invalid transaction",
hash
);
continue
}

// Insert transactions that just got unlocked.
if let Some(unlocked) = self.all.get(&best.unlocks()) {
self.independent.insert(unlocked.transaction.clone());
}

return Some(best.transaction)
}
}
}
Loading

0 comments on commit 3fed7cf

Please sign in to comment.