Skip to content

Commit

Permalink
feat(txpool): add best_with_base_fee (paradigmxyz#3737)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse authored Jul 15, 2023
1 parent 9a00f04 commit 99a8e0f
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 7 deletions.
7 changes: 7 additions & 0 deletions crates/transaction-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,13 @@ where
Box::new(self.pool.best_transactions())
}

fn best_transactions_with_base_fee(
&self,
base_fee: u128,
) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>> {
self.pool.best_transactions_with_base_fee(base_fee)
}

fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
self.pool.pending_transactions()
}
Expand Down
7 changes: 7 additions & 0 deletions crates/transaction-pool/src/noop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ impl TransactionPool for NoopTransactionPool {
Box::new(std::iter::empty())
}

fn best_transactions_with_base_fee(
&self,
_: u128,
) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>> {
Box::new(std::iter::empty())
}

fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
vec![]
}
Expand Down
6 changes: 6 additions & 0 deletions crates/transaction-pool/src/ordering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,9 @@ impl<T> Default for GasCostOrdering<T> {
Self(Default::default())
}
}

impl<T> Clone for GasCostOrdering<T> {
fn clone(&self) -> Self {
Self::default()
}
}
44 changes: 43 additions & 1 deletion crates/transaction-pool/src/pool/best.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
identifier::TransactionId,
pool::pending::{PendingTransaction, PendingTransactionRef},
TransactionOrdering, ValidPoolTransaction,
PoolTransaction, TransactionOrdering, ValidPoolTransaction,
};
use reth_primitives::H256 as TxHash;
use std::{
Expand All @@ -10,6 +10,40 @@ use std::{
};
use tracing::debug;

/// An iterator that returns transactions that can be executed on the current state (*best*
/// transactions).
///
/// This is a wrapper around [`BestTransactions`] that also enforces a specific basefee.
///
/// This iterator guarantees that all transaction it returns satisfy the base fee.
pub(crate) struct BestTransactionsWithBasefee<T: TransactionOrdering> {
pub(crate) best: BestTransactions<T>,
pub(crate) base_fee: u128,
}

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

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

fn next(&mut self) -> Option<Self::Item> {
// find the next transaction that satisfies the base fee
loop {
let best = self.best.next()?;
if best.transaction.max_fee_per_gas() < self.base_fee {
// tx violates base fee, mark it as invalid and continue
crate::traits::BestTransactions::mark_invalid(self, &best);
} else {
return Some(best)
}
}
}
}

/// An iterator that returns transactions that can be executed on the current state (*best*
/// transactions).
///
Expand All @@ -35,6 +69,14 @@ impl<T: TransactionOrdering> BestTransactions<T> {
pub(crate) fn mark_invalid(&mut self, tx: &Arc<ValidPoolTransaction<T::Transaction>>) {
self.invalid.insert(*tx.hash());
}

/// Returns the ancestor the given transaction, the transaction with `nonce - 1`.
///
/// Note: for a transaction with nonce higher than the current on chain nonce this will always
/// return an ancestor since all transaction in this pool are gapless.
pub(crate) fn ancestor(&self, id: &TransactionId) -> Option<&Arc<PendingTransaction<T>>> {
self.all.get(&id.unchecked_ancestor()?)
}
}

impl<T: TransactionOrdering> crate::traits::BestTransactions for BestTransactions<T> {
Expand Down
10 changes: 10 additions & 0 deletions crates/transaction-pool/src/pool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,16 @@ where
self.pool.read().best_transactions()
}

/// Returns an iterator that yields transactions that are ready to be included in the block with
/// the given base fee.
pub(crate) fn best_transactions_with_base_fee(
&self,
base_fee: u128,
) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
{
self.pool.read().best_transactions_with_base_fee(base_fee)
}

/// Returns all transactions from the pending sub-pool
pub(crate) fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
self.pool.read().pending_transactions()
Expand Down
32 changes: 26 additions & 6 deletions crates/transaction-pool/src/pool/parked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,24 @@ impl<T: ParkedOrd> ParkedPool<T> {
}

impl<T: PoolTransaction> ParkedPool<BasefeeOrd<T>> {
/// Removes all transactions and their dependent transaction from the subpool that no longer
/// satisfy the given basefee.
/// Returns all transactions that satisfy the given basefee.
///
/// Note: the transactions are not returned in a particular order.
pub(crate) fn enforce_basefee(&mut self, basefee: u128) -> Vec<Arc<ValidPoolTransaction<T>>> {
let mut to_remove = Vec::new();
/// Note: this does _not_ remove the transactions
pub(crate) fn satisfy_base_fee_transactions(
&self,
basefee: u128,
) -> Vec<Arc<ValidPoolTransaction<T>>> {
let ids = self.satisfy_base_fee_ids(basefee);
let mut txs = Vec::with_capacity(ids.len());
for id in ids {
txs.push(self.by_id.get(&id).expect("transaction exists").transaction.clone().into());
}
txs
}

/// Returns all transactions that satisfy the given basefee.
fn satisfy_base_fee_ids(&self, basefee: u128) -> Vec<TransactionId> {
let mut transactions = Vec::new();
{
let mut iter = self.by_id.iter().peekable();

Expand All @@ -130,10 +141,19 @@ impl<T: PoolTransaction> ParkedPool<BasefeeOrd<T>> {
iter.next();
}
} else {
to_remove.push(*id);
transactions.push(*id);
}
}
}
transactions
}

/// Removes all transactions and their dependent transaction from the subpool that no longer
/// satisfy the given basefee.
///
/// Note: the transactions are not returned in a particular order.
pub(crate) fn enforce_basefee(&mut self, basefee: u128) -> Vec<Arc<ValidPoolTransaction<T>>> {
let to_remove = self.satisfy_base_fee_ids(basefee);

let mut removed = Vec::with_capacity(to_remove.len());
for id in to_remove {
Expand Down
38 changes: 38 additions & 0 deletions crates/transaction-pool/src/pool/pending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
TransactionOrdering, ValidPoolTransaction,
};

use crate::pool::best::BestTransactionsWithBasefee;
use std::{
cmp::Ordering,
collections::{BTreeMap, BTreeSet},
Expand Down Expand Up @@ -84,6 +85,43 @@ impl<T: TransactionOrdering> PendingPool<T> {
}
}

/// Same as `best` but only returns transactions that satisfy the given basefee.
pub(crate) fn best_with_basefee(&self, base_fee: u128) -> BestTransactionsWithBasefee<T> {
BestTransactionsWithBasefee { best: self.best(), base_fee }
}

/// Same as `best` but also includes the given unlocked transactions.
///
/// This mimics the [Self::add_transaction] method, but does not insert the transactions into
/// pool but only into the returned iterator.
///
/// Note: this does not insert the unlocked transactions into the pool.
///
/// # Panics
///
/// if the transaction is already included
pub(crate) fn best_with_unlocked(
&self,
unlocked: Vec<Arc<ValidPoolTransaction<T::Transaction>>>,
) -> BestTransactions<T> {
let mut best = self.best();
let mut submission_id = self.submission_id;
for tx in unlocked {
submission_id += 1;
debug_assert!(!best.all.contains_key(tx.id()), "transaction already included");
let priority = self.ordering.priority(&tx.transaction);
let tx_id = *tx.id();
let transaction = PendingTransactionRef { submission_id, transaction: tx, priority };
if best.ancestor(&tx_id).is_none() {
best.independent.insert(transaction.clone());
}
let transaction = Arc::new(PendingTransaction { transaction });
best.all.insert(tx_id, transaction);
}

best
}

/// Returns an iterator over all transactions in the pool
pub(crate) fn all(
&self,
Expand Down
25 changes: 25 additions & 0 deletions crates/transaction-pool/src/pool/txpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,31 @@ impl<T: TransactionOrdering> TxPool<T> {
self.pending_pool.best()
}

/// Returns an iterator that yields transactions that are ready to be included in the block with
/// the given base fee.
pub(crate) fn best_transactions_with_base_fee(
&self,
basefee: u128,
) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
{
match basefee.cmp(&self.all_transactions.pending_basefee) {
Ordering::Equal => {
// fee unchanged, nothing to shift
Box::new(self.best_transactions())
}
Ordering::Greater => {
// base fee increased, we only need to enforces this on the pending pool
Box::new(self.pending_pool.best_with_basefee(basefee))
}
Ordering::Less => {
// base fee decreased, we need to move transactions from the basefee pool to the
// pending pool
let unlocked = self.basefee_pool.satisfy_base_fee_transactions(basefee);
Box::new(self.pending_pool.best_with_unlocked(unlocked))
}
}
}

/// Returns all transactions from the pending sub-pool
pub(crate) fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
self.pending_pool.all().collect()
Expand Down
9 changes: 9 additions & 0 deletions crates/transaction-pool/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ pub trait TransactionPool: Send + Sync + Clone {
&self,
) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>>;

/// Returns an iterator that yields transactions that are ready for block production with the
/// given base fee.
///
/// Consumer: Block production
fn best_transactions_with_base_fee(
&self,
base_fee: u128,
) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>>;

/// Returns all transactions that can be included in the next block.
///
/// This is primarily used for the `txpool_` RPC namespace: <https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool> which distinguishes between `pending` and `queued` transactions, where `pending` are transactions ready for inclusion in the next block and `queued` are transactions that are ready for inclusion in future blocks.
Expand Down

0 comments on commit 99a8e0f

Please sign in to comment.