Skip to content

Commit

Permalink
feat: add blob subpool variants and state (#4824)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse authored Sep 27, 2023
1 parent 5b2c03a commit ac6570f
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 11 deletions.
55 changes: 46 additions & 9 deletions crates/transaction-pool/src/pool/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,37 @@ bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub(crate) struct TxState: u8 {
/// Set to `1` if all ancestor transactions are pending.
const NO_PARKED_ANCESTORS = 0b100000;
const NO_PARKED_ANCESTORS = 0b10000000;
/// Set to `1` of the transaction is either the next transaction of the sender (on chain nonce == tx.nonce) or all prior transactions are also present in the pool.
const NO_NONCE_GAPS = 0b010000;
const NO_NONCE_GAPS = 0b01000000;
/// Bit derived from the sender's balance.
///
/// Set to `1` if the sender's balance can cover the maximum cost for this transaction (`feeCap * gasLimit + value`).
/// This includes cumulative costs of prior transactions, which ensures that the sender has enough funds for all max cost of prior transactions.
const ENOUGH_BALANCE = 0b001000;
const ENOUGH_BALANCE = 0b00100000;
/// Bit set to true if the transaction has a lower gas limit than the block's gas limit
const NOT_TOO_MUCH_GAS = 0b000100;
const NOT_TOO_MUCH_GAS = 0b00010000;
/// Covers the Dynamic fee requirement.
///
/// Set to 1 if `maxBlobFeePerGas` of the transaction meets the requirement of the pending block.
const ENOUGH_FEE_CAP_BLOCK = 0b000010;
const ENOUGH_FEE_CAP_BLOCK = 0b00001000;
/// Covers the dynamic blob fee requirement, only relevant for EIP-4844 blob transactions
///
/// Set to 1 if `maxBlobFeePer` of the transaction meets the requirement of the pending block.
const ENOUGH_BLOB_FEE_CAP_BLOCK = 0b000001;
const ENOUGH_BLOB_FEE_CAP_BLOCK = 0b00000100;

const PENDING_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits()| Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits() | Self::ENOUGH_FEE_CAP_BLOCK.bits() | Self::ENOUGH_BLOB_FEE_CAP_BLOCK.bits();
/// Marks whether the transaction is a blob transaction
///
/// We track this as part of the state for simplicity, since blob transactions are handled differently and are mutually exclusive with normal transactions.
const BLOB_TRANSACTION = 0b00000010;

const PENDING_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits() | Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits() | Self::ENOUGH_FEE_CAP_BLOCK.bits() | Self::ENOUGH_BLOB_FEE_CAP_BLOCK.bits();

const BASE_FEE_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits() | Self::NO_NONCE_GAPS.bits() | Self::ENOUGH_BALANCE.bits() | Self::NOT_TOO_MUCH_GAS.bits();

const QUEUED_POOL_BITS = Self::NO_PARKED_ANCESTORS.bits();

const BLOB_POOL_BITS = Self::BLOB_TRANSACTION.bits();
}
}

Expand All @@ -40,11 +46,18 @@ impl TxState {
/// - _No_ parked ancestors
/// - enough balance
/// - enough fee cap
/// - enough blob fee cap
#[inline]
pub(crate) fn is_pending(&self) -> bool {
self.bits() >= TxState::PENDING_POOL_BITS.bits()
}

/// Whether this transaction is a blob transaction.
#[inline]
pub(crate) fn is_blob(&self) -> bool {
self.contains(TxState::BLOB_TRANSACTION)
}

/// Returns `true` if the transaction has a nonce gap.
#[inline]
pub(crate) fn has_nonce_gap(&self) -> bool {
Expand All @@ -62,6 +75,8 @@ pub enum SubPool {
/// The base-fee sub-pool contains transactions that are not ready to be included in the next
/// block because they don't meet the base fee requirement.
BaseFee,
/// The blob sub-pool contains all blob transactions that are __not__ pending.
Blob,
/// The pending sub-pool contains transactions that are ready to be included in the next block.
Pending,
}
Expand All @@ -87,6 +102,12 @@ impl SubPool {
matches!(self, SubPool::BaseFee)
}

/// Whether this transaction is in the blob pool.
#[inline]
pub fn is_blob(&self) -> bool {
matches!(self, SubPool::Blob)
}

/// Returns whether this is a promotion depending on the current sub-pool location.
#[inline]
pub fn is_promoted(&self, other: SubPool) -> bool {
Expand All @@ -99,6 +120,10 @@ impl From<TxState> for SubPool {
if value.is_pending() {
return SubPool::Pending
}
if value.is_blob() {
// all _non-pending_ blob transactions are in the blob sub-pool
return SubPool::Blob
}
if value.bits() < TxState::BASE_FEE_POOL_BITS.bits() {
return SubPool::Queued
}
Expand All @@ -115,6 +140,7 @@ mod tests {
assert!(SubPool::BaseFee.is_promoted(SubPool::Queued));
assert!(SubPool::Pending.is_promoted(SubPool::BaseFee));
assert!(SubPool::Pending.is_promoted(SubPool::Queued));
assert!(SubPool::Pending.is_promoted(SubPool::Blob));
assert!(!SubPool::BaseFee.is_promoted(SubPool::Pending));
assert!(!SubPool::Queued.is_promoted(SubPool::BaseFee));
}
Expand Down Expand Up @@ -144,14 +170,25 @@ mod tests {
assert_eq!(SubPool::Pending, state.into());
assert!(state.is_pending());

let bits = 0b111111;
let bits = 0b11111100;
let state = TxState::from_bits(bits).unwrap();
assert_eq!(SubPool::Pending, state.into());
assert!(state.is_pending());

let bits = 0b111111;
let bits = 0b11111110;
let state = TxState::from_bits(bits).unwrap();
assert_eq!(SubPool::Pending, state.into());
assert!(state.is_pending());
}

#[test]
fn test_blob() {
let mut state = TxState::PENDING_POOL_BITS;
state.insert(TxState::BLOB_TRANSACTION);
assert!(state.is_pending());

state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
assert!(state.is_blob());
assert!(!state.is_pending());
}
}
11 changes: 9 additions & 2 deletions crates/transaction-pool/src/pool/txpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ pub struct TxPool<T: TransactionOrdering> {
/// be moved to pending if the base fee changes in their favor (decreases) in future blocks.
basefee_pool: ParkedPool<BasefeeOrd<T::Transaction>>,
/// All blob transactions in the pool
#[allow(unused)]
blob_transactions: BlobTransactions<T::Transaction>,
/// All transactions in the pool.
all_transactions: AllTransactions<T::Transaction>,
Expand Down Expand Up @@ -269,6 +268,7 @@ impl<T: TransactionOrdering> TxPool<T> {
SubPool::Queued => self.queued_pool.contains(id),
SubPool::Pending => self.pending_pool.contains(id),
SubPool::BaseFee => self.basefee_pool.contains(id),
SubPool::Blob => self.blob_transactions.contains(id),
}
}

Expand Down Expand Up @@ -561,6 +561,7 @@ impl<T: TransactionOrdering> TxPool<T> {
SubPool::Queued => self.queued_pool.remove_transaction(tx),
SubPool::Pending => self.pending_pool.remove_transaction(tx),
SubPool::BaseFee => self.basefee_pool.remove_transaction(tx),
SubPool::Blob => self.blob_transactions.remove_transaction(tx),
}
}

Expand All @@ -572,9 +573,10 @@ impl<T: TransactionOrdering> TxPool<T> {
tx: &TransactionId,
) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
match pool {
SubPool::Queued => self.queued_pool.remove_transaction(tx),
SubPool::Pending => self.pending_pool.prune_transaction(tx),
SubPool::Queued => self.queued_pool.remove_transaction(tx),
SubPool::BaseFee => self.basefee_pool.remove_transaction(tx),
SubPool::Blob => self.blob_transactions.remove_transaction(tx),
}
}

Expand Down Expand Up @@ -619,6 +621,9 @@ impl<T: TransactionOrdering> TxPool<T> {
SubPool::BaseFee => {
self.basefee_pool.add_transaction(tx);
}
SubPool::Blob => {
self.blob_transactions.add_transaction(tx);
}
}
}

Expand Down Expand Up @@ -1324,6 +1329,8 @@ impl<T: PoolTransaction> AllTransactions<T> {
// before attempting to insert a blob transaction, we need to ensure that additional
// constraints are met that only apply to blob transactions
if transaction.is_eip4844() {
state.insert(TxState::BLOB_TRANSACTION);

transaction =
self.ensure_valid_blob_transaction(transaction, on_chain_balance, ancestor)?;
let blob_fee_cap = transaction.transaction.max_fee_per_blob_gas().unwrap_or_default();
Expand Down

0 comments on commit ac6570f

Please sign in to comment.