Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add BestTransactionsWithAttributes blob fee checks #6674

Merged
merged 11 commits into from Feb 22, 2024
16 changes: 12 additions & 4 deletions crates/transaction-pool/src/pool/best.rs
Expand Up @@ -17,13 +17,14 @@ use tracing::debug;
///
/// 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> {
/// This iterator guarantees that all transaction it returns satisfy both the base fee and blob fee!
pub(crate) struct BestTransactionsWithFees<T: TransactionOrdering> {
pub(crate) best: BestTransactions<T>,
pub(crate) base_fee: u64,
pub(crate) base_fee_per_blob_gas: u64,
}

impl<T: TransactionOrdering> crate::traits::BestTransactions for BestTransactionsWithBasefee<T> {
impl<T: TransactionOrdering> crate::traits::BestTransactions for BestTransactionsWithFees<T> {
fn mark_invalid(&mut self, tx: &Self::Item) {
BestTransactions::mark_invalid(&mut self.best, tx)
}
Expand All @@ -41,7 +42,7 @@ impl<T: TransactionOrdering> crate::traits::BestTransactions for BestTransaction
}
}

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

fn next(&mut self) -> Option<Self::Item> {
Expand All @@ -52,6 +53,13 @@ impl<T: TransactionOrdering> Iterator for BestTransactionsWithBasefee<T> {
// tx violates base fee, mark it as invalid and continue
crate::traits::BestTransactions::mark_invalid(self, &best);
} else {
// tx is EIP4844 and violates blob fee, mark it as invalid and continue
if best.transaction.max_fee_per_blob_gas().is_some_and(|max_fee_per_blob_gas| {
max_fee_per_blob_gas < self.base_fee_per_blob_gas as u128
}) {
crate::traits::BestTransactions::mark_invalid(self, &best);
continue;
};
return Some(best)
}
}
Expand Down
38 changes: 34 additions & 4 deletions crates/transaction-pool/src/pool/blob.rs
Expand Up @@ -80,12 +80,42 @@ impl<T: PoolTransaction> BlobTransactions<T> {
Some(tx.transaction)
}

/// Returns all transactions that satisfy the given basefee and blob_fee.
pub(crate) const fn satisfy_attributes(
/// Returns all transactions that satisfy the given basefee and blobfee.
///
/// Note: This does not remove any the transactions from the pool.
pub(crate) fn satisfy_attributes(
&self,
_best_transactions_attributes: BestTransactionsAttributes,
best_transactions_attributes: BestTransactionsAttributes,
) -> Vec<Arc<ValidPoolTransaction<T>>> {
Vec::new()
let mut transactions = Vec::new();
{
// short path if blob_fee is None in provided best transactions attributes
if let Some(blob_fee_to_satisfy) =
best_transactions_attributes.blob_fee.map(|fee| fee as u128)
{
let mut iter = self.by_id.iter().peekable();

while let Some((id, tx)) = iter.next() {
if tx.transaction.max_fee_per_blob_gas().unwrap_or_default() <
blob_fee_to_satisfy ||
tx.transaction.max_fee_per_gas() <
best_transactions_attributes.basefee as u128
{
// does not satisfy the blob fee or base fee
// still parked in blob pool -> skip descendant transactions
'this: while let Some((peek, _)) = iter.peek() {
if peek.sender != id.sender {
break 'this
}
iter.next();
}
} else {
transactions.push(tx.transaction.clone());
}
}
}
}
transactions
}

/// The reported size of all transactions in this pool.
Expand Down
12 changes: 8 additions & 4 deletions crates/transaction-pool/src/pool/pending.rs
@@ -1,7 +1,7 @@
use crate::{
identifier::{SenderId, TransactionId},
pool::{
best::{BestTransactions, BestTransactionsWithBasefee},
best::{BestTransactions, BestTransactionsWithFees},
size::SizeTracker,
},
Priority, SubPoolLimit, TransactionOrdering, ValidPoolTransaction,
Expand Down Expand Up @@ -115,9 +115,13 @@ 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: u64) -> BestTransactionsWithBasefee<T> {
BestTransactionsWithBasefee { best: self.best(), base_fee }
/// Same as `best` but only returns transactions that satisfy the given basefee and blobfee.
pub(crate) fn best_with_basefee_and_blobfee(
&self,
base_fee: u64,
base_fee_per_blob_gas: u64,
) -> BestTransactionsWithFees<T> {
BestTransactionsWithFees { best: self.best(), base_fee, base_fee_per_blob_gas }
}

/// Same as `best` but also includes the given unlocked transactions.
Expand Down
55 changes: 42 additions & 13 deletions crates/transaction-pool/src/pool/txpool.rs
Expand Up @@ -287,38 +287,67 @@ impl<T: TransactionOrdering> TxPool<T> {
}
}

/// Returns an iterator that yields transactions that are ready to be included in the block.
/// Returns an iterator that yields transactions that are ready to be included in the block with
/// the tracked fees.
pub(crate) fn best_transactions(&self) -> BestTransactions<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 and optional blob fee.
///
/// If the provided attributes differ from the currently tracked fees, this will also include
/// transactions that are unlocked by the new fees, or exclude transactions that are no longer
/// valid with the new fees.
pub(crate) fn best_transactions_with_attributes(
&self,
best_transactions_attributes: BestTransactionsAttributes,
) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
{
// First we need to check if the given base fee is different than what's currently being
// tracked
match best_transactions_attributes.basefee.cmp(&self.all_transactions.pending_fees.base_fee)
{
Ordering::Equal => {
// fee unchanged, nothing to shift
Box::new(self.best_transactions())
// for EIP-4844 transactions we also need to check if the blob fee is now lower than
// what's currently being tracked, if so we need to include transactions from the
// blob pool that are valid with the lower blob fee
if best_transactions_attributes
.blob_fee
.map_or(false, |fee| fee < self.all_transactions.pending_fees.blob_fee as u64)
{
let unlocked_by_blob_fee =
self.blob_pool.satisfy_attributes(best_transactions_attributes);

Box::new(self.pending_pool.best_with_unlocked(
unlocked_by_blob_fee,
self.all_transactions.pending_fees.base_fee,
))
} else {
Box::new(self.pending_pool.best())
}
}
Ordering::Greater => {
// base fee increased, we only need to enforce this on the pending pool
Box::new(self.pending_pool.best_with_basefee(best_transactions_attributes.basefee))
Box::new(self.pending_pool.best_with_basefee_and_blobfee(
best_transactions_attributes.basefee,
best_transactions_attributes.blob_fee.unwrap_or_default(),
))
allnil marked this conversation as resolved.
Show resolved Hide resolved
}
Ordering::Less => {
// base fee decreased, we need to move transactions from the basefee pool to the
// pending pool and satisfy blob fee transactions as well
let unlocked_with_blob =
self.blob_pool.satisfy_attributes(best_transactions_attributes);

Box::new(self.pending_pool.best_with_unlocked(
unlocked_with_blob,
self.all_transactions.pending_fees.base_fee,
))
// base fee decreased, we need to move transactions from the basefee + blob pool to
// the pending pool that might be unlocked by the lower base fee
let mut unlocked = self
.basefee_pool
.satisfy_base_fee_transactions(best_transactions_attributes.basefee);

// also include blob pool transactions that are now unlocked
unlocked.extend(self.blob_pool.satisfy_attributes(best_transactions_attributes));

Box::new(
self.pending_pool
.best_with_unlocked(unlocked, self.all_transactions.pending_fees.base_fee),
)
}
}
}
Expand Down