mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: add promotion and demotion for blob fee updates (#5053)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@ -9,10 +9,12 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A set of __all__ validated blob transactions in the pool.
|
use super::txpool::PendingFees;
|
||||||
|
|
||||||
|
/// A set of validated blob transactions in the pool that are __not pending__.
|
||||||
///
|
///
|
||||||
/// The purpose of this pool is keep track of blob transactions that are either pending or queued
|
/// The purpose of this pool is keep track of blob transactions that are queued and to evict the
|
||||||
/// and to evict the worst blob transactions once the sub-pool is full.
|
/// worst blob transactions once the sub-pool is full.
|
||||||
///
|
///
|
||||||
/// This expects that certain constraints are met:
|
/// This expects that certain constraints are met:
|
||||||
/// - blob transactions are always gap less
|
/// - blob transactions are always gap less
|
||||||
@ -22,7 +24,7 @@ pub(crate) struct BlobTransactions<T: PoolTransaction> {
|
|||||||
/// This way we can determine when transactions were submitted to the pool.
|
/// This way we can determine when transactions were submitted to the pool.
|
||||||
submission_id: u64,
|
submission_id: u64,
|
||||||
/// _All_ Transactions that are currently inside the pool grouped by their identifier.
|
/// _All_ Transactions that are currently inside the pool grouped by their identifier.
|
||||||
by_id: BTreeMap<TransactionId, Arc<ValidPoolTransaction<T>>>,
|
by_id: BTreeMap<TransactionId, BlobTransaction<T>>,
|
||||||
/// _All_ transactions sorted by blob priority.
|
/// _All_ transactions sorted by blob priority.
|
||||||
all: BTreeSet<BlobTransaction<T>>,
|
all: BTreeSet<BlobTransaction<T>>,
|
||||||
/// Keeps track of the size of this pool.
|
/// Keeps track of the size of this pool.
|
||||||
@ -53,10 +55,10 @@ impl<T: PoolTransaction> BlobTransactions<T> {
|
|||||||
// keep track of size
|
// keep track of size
|
||||||
self.size_of += tx.size();
|
self.size_of += tx.size();
|
||||||
|
|
||||||
self.by_id.insert(id, tx.clone());
|
|
||||||
|
|
||||||
let ord = BlobOrd { submission_id };
|
let ord = BlobOrd { submission_id };
|
||||||
let transaction = BlobTransaction { ord, transaction: tx };
|
let transaction = BlobTransaction { ord, transaction: tx };
|
||||||
|
|
||||||
|
self.by_id.insert(id, transaction.clone());
|
||||||
self.all.insert(transaction);
|
self.all.insert(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,13 +70,12 @@ impl<T: PoolTransaction> BlobTransactions<T> {
|
|||||||
// remove from queues
|
// remove from queues
|
||||||
let tx = self.by_id.remove(id)?;
|
let tx = self.by_id.remove(id)?;
|
||||||
|
|
||||||
// TODO: remove from ordered set
|
self.all.remove(&tx);
|
||||||
// self.best.remove(&tx);
|
|
||||||
|
|
||||||
// keep track of size
|
// keep track of size
|
||||||
self.size_of -= tx.transaction.size();
|
self.size_of -= tx.transaction.size();
|
||||||
|
|
||||||
Some(tx)
|
Some(tx.transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all transactions that satisfy the given basefee and blob_fee.
|
/// Returns all transactions that satisfy the given basefee and blob_fee.
|
||||||
@ -101,6 +102,59 @@ impl<T: PoolTransaction> BlobTransactions<T> {
|
|||||||
self.by_id.len()
|
self.by_id.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the pool is empty
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.by_id.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all transactions which:
|
||||||
|
/// * have a `max_fee_per_blob_gas` greater than or equal to the given `blob_fee`, _and_
|
||||||
|
/// * have a `max_fee_per_gas` greater than or equal to the given `base_fee`
|
||||||
|
fn satisfy_pending_fee_ids(&self, pending_fees: &PendingFees) -> Vec<TransactionId> {
|
||||||
|
let mut transactions = Vec::new();
|
||||||
|
{
|
||||||
|
let mut iter = self.by_id.iter().peekable();
|
||||||
|
|
||||||
|
while let Some((id, tx)) = iter.next() {
|
||||||
|
if tx.transaction.max_fee_per_blob_gas() < Some(pending_fees.blob_fee) ||
|
||||||
|
tx.transaction.max_fee_per_gas() < pending_fees.base_fee as u128
|
||||||
|
{
|
||||||
|
// 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(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all transactions (and their descendants) which:
|
||||||
|
/// * have a `max_fee_per_blob_gas` greater than or equal to the given `blob_fee`, _and_
|
||||||
|
/// * have a `max_fee_per_gas` greater than or equal to the given `base_fee`
|
||||||
|
///
|
||||||
|
/// Note: the transactions are not returned in a particular order.
|
||||||
|
pub(crate) fn enforce_pending_fees(
|
||||||
|
&mut self,
|
||||||
|
pending_fees: &PendingFees,
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<T>>> {
|
||||||
|
let to_remove = self.satisfy_pending_fee_ids(pending_fees);
|
||||||
|
|
||||||
|
let mut removed = Vec::with_capacity(to_remove.len());
|
||||||
|
for id in to_remove {
|
||||||
|
removed.push(self.remove_transaction(&id).expect("transaction exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
removed
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if the transaction with the given id is already included in this pool.
|
/// Returns `true` if the transaction with the given id is already included in this pool.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
@ -134,6 +188,12 @@ struct BlobTransaction<T: PoolTransaction> {
|
|||||||
ord: BlobOrd,
|
ord: BlobOrd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: PoolTransaction> Clone for BlobTransaction<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { transaction: self.transaction.clone(), ord: self.ord.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: PoolTransaction> Eq for BlobTransaction<T> {}
|
impl<T: PoolTransaction> Eq for BlobTransaction<T> {}
|
||||||
|
|
||||||
impl<T: PoolTransaction> PartialEq<Self> for BlobTransaction<T> {
|
impl<T: PoolTransaction> PartialEq<Self> for BlobTransaction<T> {
|
||||||
@ -154,7 +214,7 @@ impl<T: PoolTransaction> Ord for BlobTransaction<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
struct BlobOrd {
|
struct BlobOrd {
|
||||||
/// Identifier that tags when transaction was submitted in the pool.
|
/// Identifier that tags when transaction was submitted in the pool.
|
||||||
pub(crate) submission_id: u64,
|
pub(crate) submission_id: u64,
|
||||||
|
|||||||
@ -101,7 +101,7 @@ impl<T: ParkedOrd> ParkedPool<T> {
|
|||||||
self.by_id.len()
|
self.by_id.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the pool is empty
|
/// Returns whether the pool is empty
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(crate) fn is_empty(&self) -> bool {
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
|||||||
@ -150,6 +150,51 @@ impl<T: TransactionOrdering> PendingPool<T> {
|
|||||||
self.by_id.values().map(|tx| tx.transaction.clone())
|
self.by_id.values().map(|tx| tx.transaction.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the pool with the new blob fee. Removes
|
||||||
|
/// from the subpool all transactions and their dependents that no longer satisfy the given
|
||||||
|
/// base fee (`tx.max_blob_fee < blob_fee`).
|
||||||
|
///
|
||||||
|
/// Note: the transactions are not returned in a particular order.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Removed transactions that no longer satisfy the blob fee.
|
||||||
|
pub(crate) fn update_blob_fee(
|
||||||
|
&mut self,
|
||||||
|
blob_fee: u128,
|
||||||
|
) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
|
||||||
|
// Create a collection for removed transactions.
|
||||||
|
let mut removed = Vec::new();
|
||||||
|
|
||||||
|
// Drain and iterate over all transactions.
|
||||||
|
let mut transactions_iter = self.clear_transactions().into_iter().peekable();
|
||||||
|
while let Some((id, tx)) = transactions_iter.next() {
|
||||||
|
if tx.transaction.max_fee_per_blob_gas() < Some(blob_fee) {
|
||||||
|
// Add this tx to the removed collection since it no longer satisfies the blob fee
|
||||||
|
// condition. Decrease the total pool size.
|
||||||
|
removed.push(Arc::clone(&tx.transaction));
|
||||||
|
|
||||||
|
// Remove all dependent transactions.
|
||||||
|
'this: while let Some((next_id, next_tx)) = transactions_iter.peek() {
|
||||||
|
if next_id.sender != id.sender {
|
||||||
|
break 'this
|
||||||
|
}
|
||||||
|
removed.push(Arc::clone(&next_tx.transaction));
|
||||||
|
transactions_iter.next();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.size_of += tx.transaction.size();
|
||||||
|
if self.ancestor(&id).is_none() {
|
||||||
|
self.independent_transactions.insert(tx.clone());
|
||||||
|
}
|
||||||
|
self.all.insert(tx.clone());
|
||||||
|
self.by_id.insert(id, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removed
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the pool with the new base fee. Reorders transactions by new priorities. Removes
|
/// Updates the pool with the new base fee. Reorders transactions by new priorities. Removes
|
||||||
/// from the subpool all transactions and their dependents that no longer satisfy the given
|
/// from the subpool all transactions and their dependents that no longer satisfy the given
|
||||||
/// base fee (`tx.fee < base_fee`).
|
/// base fee (`tx.fee < base_fee`).
|
||||||
|
|||||||
@ -66,7 +66,7 @@ impl TxState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Identifier for the transaction Sub-pool
|
/// Identifier for the transaction Sub-pool
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum SubPool {
|
pub enum SubPool {
|
||||||
/// The queued sub-pool contains transactions that are not ready to be included in the next
|
/// The queued sub-pool contains transactions that are not ready to be included in the next
|
||||||
@ -190,5 +190,10 @@ mod tests {
|
|||||||
state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
||||||
assert!(state.is_blob());
|
assert!(state.is_blob());
|
||||||
assert!(!state.is_pending());
|
assert!(!state.is_pending());
|
||||||
|
|
||||||
|
state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
||||||
|
state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
|
||||||
|
assert!(state.is_blob());
|
||||||
|
assert!(!state.is_pending());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,12 +47,14 @@ use std::{
|
|||||||
/// B3[(Queued)]
|
/// B3[(Queued)]
|
||||||
/// B1[(Pending)]
|
/// B1[(Pending)]
|
||||||
/// B2[(Basefee)]
|
/// B2[(Basefee)]
|
||||||
|
/// B4[(Blob)]
|
||||||
/// end
|
/// end
|
||||||
/// end
|
/// end
|
||||||
/// discard([discard])
|
/// discard([discard])
|
||||||
/// production([Block Production])
|
/// production([Block Production])
|
||||||
/// new([New Block])
|
/// new([New Block])
|
||||||
/// A[Incoming Tx] --> B[Validation] -->|insert| pool
|
/// A[Incoming Tx] --> B[Validation] -->|insert| pool
|
||||||
|
/// pool --> |if ready + blobfee too low| B4
|
||||||
/// pool --> |if ready| B1
|
/// pool --> |if ready| B1
|
||||||
/// pool --> |if ready + basfee too low| B2
|
/// pool --> |if ready + basfee too low| B2
|
||||||
/// pool --> |nonce gap or lack of funds| B3
|
/// pool --> |nonce gap or lack of funds| B3
|
||||||
@ -60,8 +62,11 @@ use std::{
|
|||||||
/// B1 --> |best| production
|
/// B1 --> |best| production
|
||||||
/// B2 --> |worst| discard
|
/// B2 --> |worst| discard
|
||||||
/// B3 --> |worst| discard
|
/// B3 --> |worst| discard
|
||||||
/// B1 --> |increased fee| B2
|
/// B4 --> |worst| discard
|
||||||
/// B2 --> |decreased fee| B1
|
/// B1 --> |increased blob fee| B4
|
||||||
|
/// B4 --> |decreased blob fee| B1
|
||||||
|
/// B1 --> |increased base fee| B2
|
||||||
|
/// B2 --> |decreased base fee| B1
|
||||||
/// B3 --> |promote| B1
|
/// B3 --> |promote| B1
|
||||||
/// B3 --> |promote| B2
|
/// B3 --> |promote| B2
|
||||||
/// new --> |apply state changes| pool
|
/// new --> |apply state changes| pool
|
||||||
@ -131,6 +136,8 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
basefee_size: self.basefee_pool.size(),
|
basefee_size: self.basefee_pool.size(),
|
||||||
queued: self.queued_pool.len(),
|
queued: self.queued_pool.len(),
|
||||||
queued_size: self.queued_pool.size(),
|
queued_size: self.queued_pool.size(),
|
||||||
|
blob: self.blob_transactions.len(),
|
||||||
|
blob_size: self.blob_transactions.size(),
|
||||||
total: self.all_transactions.len(),
|
total: self.all_transactions.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,31 +147,108 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
BlockInfo {
|
BlockInfo {
|
||||||
last_seen_block_hash: self.all_transactions.last_seen_block_hash,
|
last_seen_block_hash: self.all_transactions.last_seen_block_hash,
|
||||||
last_seen_block_number: self.all_transactions.last_seen_block_number,
|
last_seen_block_number: self.all_transactions.last_seen_block_number,
|
||||||
pending_basefee: self.all_transactions.pending_basefee,
|
pending_basefee: self.all_transactions.pending_fees.base_fee,
|
||||||
pending_blob_fee: Some(self.all_transactions.pending_blob_fee),
|
pending_blob_fee: Some(self.all_transactions.pending_fees.blob_fee),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the tracked blob fee
|
/// Updates the tracked blob fee
|
||||||
fn update_blob_fee(&mut self, _pending_blob_fee: u128) {
|
fn update_blob_fee(&mut self, mut pending_blob_fee: u128, base_fee_update: Ordering) {
|
||||||
// TODO: std::mem::swap pending_blob_fee
|
std::mem::swap(&mut self.all_transactions.pending_fees.blob_fee, &mut pending_blob_fee);
|
||||||
// TODO(mattsse): update blob txs
|
match (self.all_transactions.pending_fees.blob_fee.cmp(&pending_blob_fee), base_fee_update)
|
||||||
|
{
|
||||||
|
(Ordering::Equal, Ordering::Equal) => {
|
||||||
|
// fee unchanged, nothing to update
|
||||||
|
}
|
||||||
|
(Ordering::Greater, Ordering::Equal) |
|
||||||
|
(Ordering::Equal, Ordering::Greater) |
|
||||||
|
(Ordering::Greater, Ordering::Greater) => {
|
||||||
|
// increased blob fee: recheck pending pool and remove all that are no longer valid
|
||||||
|
let removed =
|
||||||
|
self.pending_pool.update_blob_fee(self.all_transactions.pending_fees.blob_fee);
|
||||||
|
for tx in removed {
|
||||||
|
let to = {
|
||||||
|
let tx =
|
||||||
|
self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
|
||||||
|
|
||||||
|
// we unset the blob fee cap block flag, if the base fee is too high now
|
||||||
|
tx.state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
||||||
|
tx.subpool = tx.state.into();
|
||||||
|
tx.subpool
|
||||||
|
};
|
||||||
|
self.add_transaction_to_subpool(to, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Ordering::Less, Ordering::Equal) | (_, Ordering::Less) => {
|
||||||
|
// decreased blob fee or base fee: recheck blob pool and promote all that are now
|
||||||
|
// valid
|
||||||
|
let removed = self
|
||||||
|
.blob_transactions
|
||||||
|
.enforce_pending_fees(&self.all_transactions.pending_fees);
|
||||||
|
for tx in removed {
|
||||||
|
let to = {
|
||||||
|
let tx =
|
||||||
|
self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
|
||||||
|
tx.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
||||||
|
tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
|
||||||
|
tx.subpool = tx.state.into();
|
||||||
|
tx.subpool
|
||||||
|
};
|
||||||
|
self.add_transaction_to_subpool(to, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Ordering::Less, Ordering::Greater) => {
|
||||||
|
// increased blob fee: recheck pending pool and remove all that are no longer valid
|
||||||
|
let removed =
|
||||||
|
self.pending_pool.update_blob_fee(self.all_transactions.pending_fees.blob_fee);
|
||||||
|
for tx in removed {
|
||||||
|
let to = {
|
||||||
|
let tx =
|
||||||
|
self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
|
||||||
|
|
||||||
|
// we unset the blob fee cap block flag, if the base fee is too high now
|
||||||
|
tx.state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
||||||
|
tx.subpool = tx.state.into();
|
||||||
|
tx.subpool
|
||||||
|
};
|
||||||
|
self.add_transaction_to_subpool(to, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decreased blob fee or base fee: recheck blob pool and promote all that are now
|
||||||
|
// valid
|
||||||
|
let removed = self
|
||||||
|
.blob_transactions
|
||||||
|
.enforce_pending_fees(&self.all_transactions.pending_fees);
|
||||||
|
for tx in removed {
|
||||||
|
let to = {
|
||||||
|
let tx =
|
||||||
|
self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
|
||||||
|
tx.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
||||||
|
tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
|
||||||
|
tx.subpool = tx.state.into();
|
||||||
|
tx.subpool
|
||||||
|
};
|
||||||
|
self.add_transaction_to_subpool(to, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the tracked basefee
|
/// Updates the tracked basefee
|
||||||
///
|
///
|
||||||
/// Depending on the change in direction of the basefee, this will promote or demote
|
/// Depending on the change in direction of the basefee, this will promote or demote
|
||||||
/// transactions from the basefee pool.
|
/// transactions from the basefee pool.
|
||||||
fn update_basefee(&mut self, mut pending_basefee: u64) {
|
fn update_basefee(&mut self, mut pending_basefee: u64) -> Ordering {
|
||||||
std::mem::swap(&mut self.all_transactions.pending_basefee, &mut pending_basefee);
|
std::mem::swap(&mut self.all_transactions.pending_fees.base_fee, &mut pending_basefee);
|
||||||
match self.all_transactions.pending_basefee.cmp(&pending_basefee) {
|
match self.all_transactions.pending_fees.base_fee.cmp(&pending_basefee) {
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
// fee unchanged, nothing to update
|
// fee unchanged, nothing to update
|
||||||
|
Ordering::Equal
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
// increased base fee: recheck pending pool and remove all that are no longer valid
|
// increased base fee: recheck pending pool and remove all that are no longer valid
|
||||||
let removed =
|
let removed =
|
||||||
self.pending_pool.update_base_fee(self.all_transactions.pending_basefee);
|
self.pending_pool.update_base_fee(self.all_transactions.pending_fees.base_fee);
|
||||||
for tx in removed {
|
for tx in removed {
|
||||||
let to = {
|
let to = {
|
||||||
let tx =
|
let tx =
|
||||||
@ -175,11 +259,13 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
};
|
};
|
||||||
self.add_transaction_to_subpool(to, tx);
|
self.add_transaction_to_subpool(to, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ordering::Greater
|
||||||
}
|
}
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
// decreased base fee: recheck basefee pool and promote all that are now valid
|
// decreased base fee: recheck basefee pool and promote all that are now valid
|
||||||
let removed =
|
let removed =
|
||||||
self.basefee_pool.enforce_basefee(self.all_transactions.pending_basefee);
|
self.basefee_pool.enforce_basefee(self.all_transactions.pending_fees.base_fee);
|
||||||
for tx in removed {
|
for tx in removed {
|
||||||
let to = {
|
let to = {
|
||||||
let tx =
|
let tx =
|
||||||
@ -190,6 +276,8 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
};
|
};
|
||||||
self.add_transaction_to_subpool(to, tx);
|
self.add_transaction_to_subpool(to, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ordering::Less
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,10 +294,10 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
} = info;
|
} = info;
|
||||||
self.all_transactions.last_seen_block_hash = last_seen_block_hash;
|
self.all_transactions.last_seen_block_hash = last_seen_block_hash;
|
||||||
self.all_transactions.last_seen_block_number = last_seen_block_number;
|
self.all_transactions.last_seen_block_number = last_seen_block_number;
|
||||||
self.update_basefee(pending_basefee);
|
let basefee_ordering = self.update_basefee(pending_basefee);
|
||||||
|
|
||||||
if let Some(blob_fee) = pending_blob_fee {
|
if let Some(blob_fee) = pending_blob_fee {
|
||||||
self.update_blob_fee(blob_fee)
|
self.update_blob_fee(blob_fee, basefee_ordering)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +313,7 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
basefee: u64,
|
basefee: u64,
|
||||||
) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
|
) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
|
||||||
{
|
{
|
||||||
match basefee.cmp(&self.all_transactions.pending_basefee) {
|
match basefee.cmp(&self.all_transactions.pending_fees.base_fee) {
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
// fee unchanged, nothing to shift
|
// fee unchanged, nothing to shift
|
||||||
Box::new(self.best_transactions())
|
Box::new(self.best_transactions())
|
||||||
@ -240,7 +328,7 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
let unlocked = self.basefee_pool.satisfy_base_fee_transactions(basefee);
|
let unlocked = self.basefee_pool.satisfy_base_fee_transactions(basefee);
|
||||||
Box::new(
|
Box::new(
|
||||||
self.pending_pool
|
self.pending_pool
|
||||||
.best_with_unlocked(unlocked, self.all_transactions.pending_basefee),
|
.best_with_unlocked(unlocked, self.all_transactions.pending_fees.base_fee),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,7 +341,8 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
best_transactions_attributes: BestTransactionsAttributes,
|
best_transactions_attributes: BestTransactionsAttributes,
|
||||||
) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
|
) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
|
||||||
{
|
{
|
||||||
match best_transactions_attributes.basefee.cmp(&self.all_transactions.pending_basefee) {
|
match best_transactions_attributes.basefee.cmp(&self.all_transactions.pending_fees.base_fee)
|
||||||
|
{
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
// fee unchanged, nothing to shift
|
// fee unchanged, nothing to shift
|
||||||
Box::new(self.best_transactions())
|
Box::new(self.best_transactions())
|
||||||
@ -268,12 +357,10 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
let unlocked_with_blob =
|
let unlocked_with_blob =
|
||||||
self.blob_transactions.satisfy_attributes(best_transactions_attributes);
|
self.blob_transactions.satisfy_attributes(best_transactions_attributes);
|
||||||
|
|
||||||
Box::new(
|
Box::new(self.pending_pool.best_with_unlocked(
|
||||||
self.pending_pool.best_with_unlocked(
|
unlocked_with_blob,
|
||||||
unlocked_with_blob,
|
self.all_transactions.pending_fees.base_fee,
|
||||||
self.all_transactions.pending_basefee,
|
))
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -658,7 +745,7 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
self.queued_pool.add_transaction(tx);
|
self.queued_pool.add_transaction(tx);
|
||||||
}
|
}
|
||||||
SubPool::Pending => {
|
SubPool::Pending => {
|
||||||
self.pending_pool.add_transaction(tx, self.all_transactions.pending_basefee);
|
self.pending_pool.add_transaction(tx, self.all_transactions.pending_fees.base_fee);
|
||||||
}
|
}
|
||||||
SubPool::BaseFee => {
|
SubPool::BaseFee => {
|
||||||
self.basefee_pool.add_transaction(tx);
|
self.basefee_pool.add_transaction(tx);
|
||||||
@ -751,8 +838,8 @@ impl<T: TransactionOrdering> TxPool<T> {
|
|||||||
#[cfg(any(test, feature = "test-utils"))]
|
#[cfg(any(test, feature = "test-utils"))]
|
||||||
pub fn assert_invariants(&self) {
|
pub fn assert_invariants(&self) {
|
||||||
let size = self.size();
|
let size = self.size();
|
||||||
let actual = size.basefee + size.pending + size.queued;
|
let actual = size.basefee + size.pending + size.queued + size.blob;
|
||||||
assert_eq!(size.total, actual, "total size must be equal to the sum of all sub-pools, basefee:{}, pending:{}, queued:{}", size.basefee, size.pending, size.queued);
|
assert_eq!(size.total, actual, "total size must be equal to the sum of all sub-pools, basefee:{}, pending:{}, queued:{}, blob:{}", size.basefee, size.pending, size.queued, size.blob);
|
||||||
self.all_transactions.assert_invariants();
|
self.all_transactions.assert_invariants();
|
||||||
self.pending_pool.assert_invariants();
|
self.pending_pool.assert_invariants();
|
||||||
self.basefee_pool.assert_invariants();
|
self.basefee_pool.assert_invariants();
|
||||||
@ -822,10 +909,8 @@ pub(crate) struct AllTransactions<T: PoolTransaction> {
|
|||||||
last_seen_block_number: u64,
|
last_seen_block_number: u64,
|
||||||
/// The current block hash the pool keeps track of.
|
/// The current block hash the pool keeps track of.
|
||||||
last_seen_block_hash: B256,
|
last_seen_block_hash: B256,
|
||||||
/// Expected base fee for the pending block.
|
/// Expected blob and base fee for the pending block.
|
||||||
pending_basefee: u64,
|
pending_fees: PendingFees,
|
||||||
/// Expected blob fee for the pending block.
|
|
||||||
pending_blob_fee: u128,
|
|
||||||
/// Configured price bump settings for replacements
|
/// Configured price bump settings for replacements
|
||||||
price_bumps: PriceBumpConfig,
|
price_bumps: PriceBumpConfig,
|
||||||
}
|
}
|
||||||
@ -892,9 +977,9 @@ impl<T: PoolTransaction> AllTransactions<T> {
|
|||||||
} = block_info;
|
} = block_info;
|
||||||
self.last_seen_block_number = last_seen_block_number;
|
self.last_seen_block_number = last_seen_block_number;
|
||||||
self.last_seen_block_hash = last_seen_block_hash;
|
self.last_seen_block_hash = last_seen_block_hash;
|
||||||
self.pending_basefee = pending_basefee;
|
self.pending_fees.base_fee = pending_basefee;
|
||||||
if let Some(pending_blob_fee) = pending_blob_fee {
|
if let Some(pending_blob_fee) = pending_blob_fee {
|
||||||
self.pending_blob_fee = pending_blob_fee;
|
self.pending_fees.blob_fee = pending_blob_fee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -985,7 +1070,7 @@ impl<T: PoolTransaction> AllTransactions<T> {
|
|||||||
tx.state.insert(TxState::NO_PARKED_ANCESTORS);
|
tx.state.insert(TxState::NO_PARKED_ANCESTORS);
|
||||||
|
|
||||||
// Update the first transaction of this sender.
|
// Update the first transaction of this sender.
|
||||||
Self::update_tx_base_fee(self.pending_basefee, tx);
|
Self::update_tx_base_fee(self.pending_fees.base_fee, tx);
|
||||||
// Track if the transaction's sub-pool changed.
|
// Track if the transaction's sub-pool changed.
|
||||||
Self::record_subpool_update(&mut updates, tx);
|
Self::record_subpool_update(&mut updates, tx);
|
||||||
|
|
||||||
@ -1031,7 +1116,7 @@ impl<T: PoolTransaction> AllTransactions<T> {
|
|||||||
has_parked_ancestor = !tx.state.is_pending();
|
has_parked_ancestor = !tx.state.is_pending();
|
||||||
|
|
||||||
// Update and record sub-pool changes.
|
// Update and record sub-pool changes.
|
||||||
Self::update_tx_base_fee(self.pending_basefee, tx);
|
Self::update_tx_base_fee(self.pending_fees.base_fee, tx);
|
||||||
Self::record_subpool_update(&mut updates, tx);
|
Self::record_subpool_update(&mut updates, tx);
|
||||||
|
|
||||||
// Advance iterator
|
// Advance iterator
|
||||||
@ -1376,7 +1461,7 @@ impl<T: PoolTransaction> AllTransactions<T> {
|
|||||||
transaction =
|
transaction =
|
||||||
self.ensure_valid_blob_transaction(transaction, on_chain_balance, ancestor)?;
|
self.ensure_valid_blob_transaction(transaction, on_chain_balance, ancestor)?;
|
||||||
let blob_fee_cap = transaction.transaction.max_fee_per_blob_gas().unwrap_or_default();
|
let blob_fee_cap = transaction.transaction.max_fee_per_blob_gas().unwrap_or_default();
|
||||||
if blob_fee_cap >= self.pending_blob_fee {
|
if blob_fee_cap >= self.pending_fees.blob_fee {
|
||||||
state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1398,7 +1483,7 @@ impl<T: PoolTransaction> AllTransactions<T> {
|
|||||||
if fee_cap < self.minimal_protocol_basefee as u128 {
|
if fee_cap < self.minimal_protocol_basefee as u128 {
|
||||||
return Err(InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap })
|
return Err(InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap })
|
||||||
}
|
}
|
||||||
if fee_cap >= self.pending_basefee as u128 {
|
if fee_cap >= self.pending_fees.base_fee as u128 {
|
||||||
state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
|
state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1572,13 +1657,27 @@ impl<T: PoolTransaction> Default for AllTransactions<T> {
|
|||||||
tx_counter: Default::default(),
|
tx_counter: Default::default(),
|
||||||
last_seen_block_number: 0,
|
last_seen_block_number: 0,
|
||||||
last_seen_block_hash: Default::default(),
|
last_seen_block_hash: Default::default(),
|
||||||
pending_basefee: Default::default(),
|
pending_fees: Default::default(),
|
||||||
pending_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE,
|
|
||||||
price_bumps: Default::default(),
|
price_bumps: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents updated fees for the pending block.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct PendingFees {
|
||||||
|
/// The pending base fee
|
||||||
|
pub(crate) base_fee: u64,
|
||||||
|
/// The pending blob fee
|
||||||
|
pub(crate) blob_fee: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PendingFees {
|
||||||
|
fn default() -> Self {
|
||||||
|
PendingFees { base_fee: Default::default(), blob_fee: BLOB_TX_MIN_BLOB_GASPRICE }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Result type for inserting a transaction
|
/// Result type for inserting a transaction
|
||||||
pub(crate) type InsertResult<T> = Result<InsertOk<T>, InsertErr<T>>;
|
pub(crate) type InsertResult<T> = Result<InsertOk<T>, InsertErr<T>>;
|
||||||
|
|
||||||
@ -1752,9 +1851,12 @@ mod tests {
|
|||||||
let on_chain_balance = U256::MAX;
|
let on_chain_balance = U256::MAX;
|
||||||
let on_chain_nonce = 0;
|
let on_chain_nonce = 0;
|
||||||
let mut f = MockTransactionFactory::default();
|
let mut f = MockTransactionFactory::default();
|
||||||
let mut pool = AllTransactions { pending_blob_fee: 10_000_000, ..Default::default() };
|
let mut pool = AllTransactions {
|
||||||
pool.pending_blob_fee = 10_000_000;
|
pending_fees: PendingFees { blob_fee: 10_000_000, ..Default::default() },
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let tx = MockTransaction::eip4844().inc_price().inc_limit();
|
let tx = MockTransaction::eip4844().inc_price().inc_limit();
|
||||||
|
pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap() + 1;
|
||||||
let valid_tx = f.validated(tx);
|
let valid_tx = f.validated(tx);
|
||||||
let InsertOk { state, .. } =
|
let InsertOk { state, .. } =
|
||||||
pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
|
pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
|
||||||
@ -1764,6 +1866,321 @@ mod tests {
|
|||||||
let _ = pool.txs.get(&valid_tx.transaction_id).unwrap();
|
let _ = pool.txs.get(&valid_tx.transaction_id).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_tx_with_decreasing_blob_fee() {
|
||||||
|
let on_chain_balance = U256::MAX;
|
||||||
|
let on_chain_nonce = 0;
|
||||||
|
let mut f = MockTransactionFactory::default();
|
||||||
|
let mut pool = AllTransactions {
|
||||||
|
pending_fees: PendingFees { blob_fee: 10_000_000, ..Default::default() },
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let tx = MockTransaction::eip4844().inc_price().inc_limit();
|
||||||
|
|
||||||
|
pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap() + 1;
|
||||||
|
let valid_tx = f.validated(tx.clone());
|
||||||
|
let InsertOk { state, .. } =
|
||||||
|
pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
|
||||||
|
assert!(state.contains(TxState::NO_NONCE_GAPS));
|
||||||
|
assert!(!state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
|
||||||
|
|
||||||
|
let _ = pool.txs.get(&valid_tx.transaction_id).unwrap();
|
||||||
|
pool.remove_transaction(&valid_tx.transaction_id);
|
||||||
|
|
||||||
|
pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap();
|
||||||
|
let InsertOk { state, .. } =
|
||||||
|
pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
|
||||||
|
assert!(state.contains(TxState::NO_NONCE_GAPS));
|
||||||
|
assert!(state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_demote_valid_tx_with_increasing_blob_fee() {
|
||||||
|
let on_chain_balance = U256::MAX;
|
||||||
|
let on_chain_nonce = 0;
|
||||||
|
let mut f = MockTransactionFactory::default();
|
||||||
|
let mut pool = TxPool::new(MockOrdering::default(), Default::default());
|
||||||
|
let tx = MockTransaction::eip4844().inc_price().inc_limit();
|
||||||
|
|
||||||
|
// set block info so the tx is initially underpriced w.r.t. blob fee
|
||||||
|
let mut block_info = pool.block_info();
|
||||||
|
block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap());
|
||||||
|
pool.set_block_info(block_info);
|
||||||
|
|
||||||
|
let validated = f.validated(tx.clone());
|
||||||
|
let id = *validated.id();
|
||||||
|
pool.add_transaction(validated, on_chain_balance, on_chain_nonce).unwrap();
|
||||||
|
|
||||||
|
// assert pool lengths
|
||||||
|
assert!(pool.blob_transactions.is_empty());
|
||||||
|
assert_eq!(pool.pending_pool.len(), 1);
|
||||||
|
|
||||||
|
// check tx state and derived subpool
|
||||||
|
let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
|
||||||
|
assert!(internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
|
||||||
|
assert_eq!(internal_tx.subpool, SubPool::Pending);
|
||||||
|
|
||||||
|
// set block info so the pools are updated
|
||||||
|
block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap() + 1);
|
||||||
|
pool.set_block_info(block_info);
|
||||||
|
|
||||||
|
// check that the tx is promoted
|
||||||
|
let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
|
||||||
|
assert!(!internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
|
||||||
|
assert_eq!(internal_tx.subpool, SubPool::Blob);
|
||||||
|
|
||||||
|
// make sure the blob transaction was promoted into the pending pool
|
||||||
|
assert_eq!(pool.blob_transactions.len(), 1);
|
||||||
|
assert!(pool.pending_pool.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_promote_valid_tx_with_decreasing_blob_fee() {
|
||||||
|
let on_chain_balance = U256::MAX;
|
||||||
|
let on_chain_nonce = 0;
|
||||||
|
let mut f = MockTransactionFactory::default();
|
||||||
|
let mut pool = TxPool::new(MockOrdering::default(), Default::default());
|
||||||
|
let tx = MockTransaction::eip4844().inc_price().inc_limit();
|
||||||
|
|
||||||
|
// set block info so the tx is initially underpriced w.r.t. blob fee
|
||||||
|
let mut block_info = pool.block_info();
|
||||||
|
block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap() + 1);
|
||||||
|
pool.set_block_info(block_info);
|
||||||
|
|
||||||
|
let validated = f.validated(tx.clone());
|
||||||
|
let id = *validated.id();
|
||||||
|
pool.add_transaction(validated, on_chain_balance, on_chain_nonce).unwrap();
|
||||||
|
|
||||||
|
// assert pool lengths
|
||||||
|
assert!(pool.pending_pool.is_empty());
|
||||||
|
assert_eq!(pool.blob_transactions.len(), 1);
|
||||||
|
|
||||||
|
// check tx state and derived subpool
|
||||||
|
let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
|
||||||
|
assert!(!internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
|
||||||
|
assert_eq!(internal_tx.subpool, SubPool::Blob);
|
||||||
|
|
||||||
|
// set block info so the pools are updated
|
||||||
|
block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap());
|
||||||
|
pool.set_block_info(block_info);
|
||||||
|
|
||||||
|
// check that the tx is promoted
|
||||||
|
let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
|
||||||
|
assert!(internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
|
||||||
|
assert_eq!(internal_tx.subpool, SubPool::Pending);
|
||||||
|
|
||||||
|
// make sure the blob transaction was promoted into the pending pool
|
||||||
|
assert_eq!(pool.pending_pool.len(), 1);
|
||||||
|
assert!(pool.blob_transactions.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct representing a txpool promotion test instance
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
|
struct PromotionTest {
|
||||||
|
/// The basefee at the start of the test
|
||||||
|
basefee: u64,
|
||||||
|
/// The blobfee at the start of the test
|
||||||
|
blobfee: u128,
|
||||||
|
/// The subpool at the start of the test
|
||||||
|
subpool: SubPool,
|
||||||
|
/// The basefee update
|
||||||
|
basefee_update: u64,
|
||||||
|
/// The blobfee update
|
||||||
|
blobfee_update: u128,
|
||||||
|
/// The subpool after the update
|
||||||
|
new_subpool: SubPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromotionTest {
|
||||||
|
/// Returns the test case for the opposite update
|
||||||
|
fn opposite(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
basefee: self.basefee_update,
|
||||||
|
blobfee: self.blobfee_update,
|
||||||
|
subpool: self.new_subpool,
|
||||||
|
blobfee_update: self.blobfee,
|
||||||
|
basefee_update: self.basefee,
|
||||||
|
new_subpool: self.subpool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_subpool_lengths<T: TransactionOrdering>(
|
||||||
|
&self,
|
||||||
|
pool: &TxPool<T>,
|
||||||
|
failure_message: String,
|
||||||
|
check_subpool: SubPool,
|
||||||
|
) {
|
||||||
|
match check_subpool {
|
||||||
|
SubPool::Blob => {
|
||||||
|
assert_eq!(pool.blob_transactions.len(), 1, "{failure_message}");
|
||||||
|
assert!(pool.pending_pool.is_empty(), "{failure_message}");
|
||||||
|
assert!(pool.basefee_pool.is_empty(), "{failure_message}");
|
||||||
|
assert!(pool.queued_pool.is_empty(), "{failure_message}");
|
||||||
|
}
|
||||||
|
SubPool::Pending => {
|
||||||
|
assert!(pool.blob_transactions.is_empty(), "{failure_message}");
|
||||||
|
assert_eq!(pool.pending_pool.len(), 1, "{failure_message}");
|
||||||
|
assert!(pool.basefee_pool.is_empty(), "{failure_message}");
|
||||||
|
assert!(pool.queued_pool.is_empty(), "{failure_message}");
|
||||||
|
}
|
||||||
|
SubPool::BaseFee => {
|
||||||
|
assert!(pool.blob_transactions.is_empty(), "{failure_message}");
|
||||||
|
assert!(pool.pending_pool.is_empty(), "{failure_message}");
|
||||||
|
assert_eq!(pool.basefee_pool.len(), 1, "{failure_message}");
|
||||||
|
assert!(pool.queued_pool.is_empty(), "{failure_message}");
|
||||||
|
}
|
||||||
|
SubPool::Queued => {
|
||||||
|
assert!(pool.blob_transactions.is_empty(), "{failure_message}");
|
||||||
|
assert!(pool.pending_pool.is_empty(), "{failure_message}");
|
||||||
|
assert!(pool.basefee_pool.is_empty(), "{failure_message}");
|
||||||
|
assert_eq!(pool.queued_pool.len(), 1, "{failure_message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs an assertion on the provided pool, ensuring that the transaction is in the correct
|
||||||
|
/// subpool based on the starting condition of the test, assuming the pool contains only a
|
||||||
|
/// single transaction.
|
||||||
|
fn assert_single_tx_starting_subpool<T: TransactionOrdering>(&self, pool: &TxPool<T>) {
|
||||||
|
self.assert_subpool_lengths(
|
||||||
|
pool,
|
||||||
|
format!("pool length check failed at start of test: {self:?}"),
|
||||||
|
self.subpool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs an assertion on the provided pool, ensuring that the transaction is in the correct
|
||||||
|
/// subpool based on the ending condition of the test, assuming the pool contains only a
|
||||||
|
/// single transaction.
|
||||||
|
fn assert_single_tx_ending_subpool<T: TransactionOrdering>(&self, pool: &TxPool<T>) {
|
||||||
|
self.assert_subpool_lengths(
|
||||||
|
pool,
|
||||||
|
format!("pool length check failed at end of test: {self:?}"),
|
||||||
|
self.new_subpool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_promote_blob_tx_with_both_pending_fee_updates() {
|
||||||
|
// this exhaustively tests all possible promotion scenarios for a single transaction moving
|
||||||
|
// between the blob and pending pool
|
||||||
|
let on_chain_balance = U256::MAX;
|
||||||
|
let on_chain_nonce = 0;
|
||||||
|
let mut f = MockTransactionFactory::default();
|
||||||
|
let tx = MockTransaction::eip4844().inc_price().inc_limit();
|
||||||
|
|
||||||
|
let max_fee_per_blob_gas = tx.max_fee_per_blob_gas().unwrap();
|
||||||
|
let max_fee_per_gas = tx.max_fee_per_gas() as u64;
|
||||||
|
|
||||||
|
// These are all _promotion_ tests or idempotent tests.
|
||||||
|
let mut expected_promotions = vec![
|
||||||
|
PromotionTest {
|
||||||
|
blobfee: max_fee_per_blob_gas + 1,
|
||||||
|
basefee: max_fee_per_gas + 1,
|
||||||
|
subpool: SubPool::Blob,
|
||||||
|
blobfee_update: max_fee_per_blob_gas + 1,
|
||||||
|
basefee_update: max_fee_per_gas + 1,
|
||||||
|
new_subpool: SubPool::Blob,
|
||||||
|
},
|
||||||
|
PromotionTest {
|
||||||
|
blobfee: max_fee_per_blob_gas + 1,
|
||||||
|
basefee: max_fee_per_gas + 1,
|
||||||
|
subpool: SubPool::Blob,
|
||||||
|
blobfee_update: max_fee_per_blob_gas,
|
||||||
|
basefee_update: max_fee_per_gas + 1,
|
||||||
|
new_subpool: SubPool::Blob,
|
||||||
|
},
|
||||||
|
PromotionTest {
|
||||||
|
blobfee: max_fee_per_blob_gas + 1,
|
||||||
|
basefee: max_fee_per_gas + 1,
|
||||||
|
subpool: SubPool::Blob,
|
||||||
|
blobfee_update: max_fee_per_blob_gas + 1,
|
||||||
|
basefee_update: max_fee_per_gas,
|
||||||
|
new_subpool: SubPool::Blob,
|
||||||
|
},
|
||||||
|
PromotionTest {
|
||||||
|
blobfee: max_fee_per_blob_gas + 1,
|
||||||
|
basefee: max_fee_per_gas + 1,
|
||||||
|
subpool: SubPool::Blob,
|
||||||
|
blobfee_update: max_fee_per_blob_gas,
|
||||||
|
basefee_update: max_fee_per_gas,
|
||||||
|
new_subpool: SubPool::Pending,
|
||||||
|
},
|
||||||
|
PromotionTest {
|
||||||
|
blobfee: max_fee_per_blob_gas,
|
||||||
|
basefee: max_fee_per_gas + 1,
|
||||||
|
subpool: SubPool::Blob,
|
||||||
|
blobfee_update: max_fee_per_blob_gas,
|
||||||
|
basefee_update: max_fee_per_gas,
|
||||||
|
new_subpool: SubPool::Pending,
|
||||||
|
},
|
||||||
|
PromotionTest {
|
||||||
|
blobfee: max_fee_per_blob_gas + 1,
|
||||||
|
basefee: max_fee_per_gas,
|
||||||
|
subpool: SubPool::Blob,
|
||||||
|
blobfee_update: max_fee_per_blob_gas,
|
||||||
|
basefee_update: max_fee_per_gas,
|
||||||
|
new_subpool: SubPool::Pending,
|
||||||
|
},
|
||||||
|
PromotionTest {
|
||||||
|
blobfee: max_fee_per_blob_gas,
|
||||||
|
basefee: max_fee_per_gas,
|
||||||
|
subpool: SubPool::Pending,
|
||||||
|
blobfee_update: max_fee_per_blob_gas,
|
||||||
|
basefee_update: max_fee_per_gas,
|
||||||
|
new_subpool: SubPool::Pending,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// extend the test cases with reversed updates - this will add all _demotion_ tests
|
||||||
|
let reversed = expected_promotions.iter().map(|test| test.opposite()).collect::<Vec<_>>();
|
||||||
|
expected_promotions.extend(reversed);
|
||||||
|
|
||||||
|
// dedup the test cases
|
||||||
|
let expected_promotions = expected_promotions.into_iter().collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
for promotion_test in expected_promotions.iter() {
|
||||||
|
let mut pool = TxPool::new(MockOrdering::default(), Default::default());
|
||||||
|
|
||||||
|
// set block info so the tx is initially underpriced w.r.t. blob fee
|
||||||
|
let mut block_info = pool.block_info();
|
||||||
|
|
||||||
|
block_info.pending_blob_fee = Some(promotion_test.blobfee);
|
||||||
|
block_info.pending_basefee = promotion_test.basefee;
|
||||||
|
pool.set_block_info(block_info);
|
||||||
|
|
||||||
|
let validated = f.validated(tx.clone());
|
||||||
|
let id = *validated.id();
|
||||||
|
pool.add_transaction(validated, on_chain_balance, on_chain_nonce).unwrap();
|
||||||
|
|
||||||
|
// assert pool lengths
|
||||||
|
promotion_test.assert_single_tx_starting_subpool(&pool);
|
||||||
|
|
||||||
|
// check tx state and derived subpool, it should not move into the blob pool
|
||||||
|
let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
internal_tx.subpool, promotion_test.subpool,
|
||||||
|
"Subpools do not match at start of test: {promotion_test:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// set block info with new base fee
|
||||||
|
block_info.pending_basefee = promotion_test.basefee_update;
|
||||||
|
block_info.pending_blob_fee = Some(promotion_test.blobfee_update);
|
||||||
|
pool.set_block_info(block_info);
|
||||||
|
|
||||||
|
// check tx state and derived subpool, it should not move into the blob pool
|
||||||
|
let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
internal_tx.subpool, promotion_test.new_subpool,
|
||||||
|
"Subpools do not match at end of test: {promotion_test:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert new pool lengths
|
||||||
|
promotion_test.assert_single_tx_ending_subpool(&pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_pending() {
|
fn test_insert_pending() {
|
||||||
let on_chain_balance = U256::MAX;
|
let on_chain_balance = U256::MAX;
|
||||||
@ -2006,7 +2423,7 @@ mod tests {
|
|||||||
let on_chain_nonce = 0;
|
let on_chain_nonce = 0;
|
||||||
let mut f = MockTransactionFactory::default();
|
let mut f = MockTransactionFactory::default();
|
||||||
let mut pool = AllTransactions::default();
|
let mut pool = AllTransactions::default();
|
||||||
pool.pending_basefee = pool.minimal_protocol_basefee.checked_add(1).unwrap();
|
pool.pending_fees.base_fee = pool.minimal_protocol_basefee.checked_add(1).unwrap();
|
||||||
let tx = MockTransaction::eip1559().inc_nonce().inc_limit();
|
let tx = MockTransaction::eip1559().inc_nonce().inc_limit();
|
||||||
let first = f.validated(tx.clone());
|
let first = f.validated(tx.clone());
|
||||||
|
|
||||||
@ -2014,7 +2431,7 @@ mod tests {
|
|||||||
|
|
||||||
let first_in_pool = pool.get(first.id()).unwrap();
|
let first_in_pool = pool.get(first.id()).unwrap();
|
||||||
|
|
||||||
assert!(tx.get_gas_price() < pool.pending_basefee as u128);
|
assert!(tx.get_gas_price() < pool.pending_fees.base_fee as u128);
|
||||||
// has nonce gap
|
// has nonce gap
|
||||||
assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
|
assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
|
||||||
|
|
||||||
|
|||||||
@ -1031,6 +1031,10 @@ pub struct PoolSize {
|
|||||||
pub pending: usize,
|
pub pending: usize,
|
||||||
/// Reported size of transactions in the _pending_ sub-pool.
|
/// Reported size of transactions in the _pending_ sub-pool.
|
||||||
pub pending_size: usize,
|
pub pending_size: usize,
|
||||||
|
/// Number of transactions in the _blob_ pool.
|
||||||
|
pub blob: usize,
|
||||||
|
/// Reported size of transactions in the _blob_ pool.
|
||||||
|
pub blob_size: usize,
|
||||||
/// Number of transactions in the _basefee_ pool.
|
/// Number of transactions in the _basefee_ pool.
|
||||||
pub basefee: usize,
|
pub basefee: usize,
|
||||||
/// Reported size of transactions in the _basefee_ sub-pool.
|
/// Reported size of transactions in the _basefee_ sub-pool.
|
||||||
@ -1051,7 +1055,7 @@ impl PoolSize {
|
|||||||
/// Asserts that the invariants of the pool size are met.
|
/// Asserts that the invariants of the pool size are met.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn assert_invariants(&self) {
|
pub(crate) fn assert_invariants(&self) {
|
||||||
assert_eq!(self.total, self.pending + self.basefee + self.queued);
|
assert_eq!(self.total, self.pending + self.basefee + self.queued + self.blob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -275,6 +275,13 @@ impl<T: PoolTransaction> ValidPoolTransaction<T> {
|
|||||||
self.transaction.cost()
|
self.transaction.cost()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the EIP-4844 max blob fee the caller is willing to pay.
|
||||||
|
///
|
||||||
|
/// For non-EIP-4844 transactions, this returns [None].
|
||||||
|
pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
|
||||||
|
self.transaction.max_fee_per_blob_gas()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the EIP-1559 Max base fee the caller is willing to pay.
|
/// Returns the EIP-1559 Max base fee the caller is willing to pay.
|
||||||
///
|
///
|
||||||
/// For legacy transactions this is `gas_price`.
|
/// For legacy transactions this is `gas_price`.
|
||||||
|
|||||||
Reference in New Issue
Block a user