reth_transaction_pool/pool/
txpool.rs

1//! The internal transaction pool implementation.
2
3use crate::{
4    config::{LocalTransactionConfig, TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER},
5    error::{
6        Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
7        PoolError, PoolErrorKind,
8    },
9    identifier::{SenderId, TransactionId},
10    metrics::{AllTransactionsMetrics, TxPoolMetrics},
11    pool::{
12        best::BestTransactions,
13        blob::BlobTransactions,
14        parked::{BasefeeOrd, ParkedPool, QueuedOrd},
15        pending::PendingPool,
16        state::{SubPool, TxState},
17        update::{Destination, PoolUpdate, UpdateOutcome},
18        AddedPendingTransaction, AddedTransaction, OnNewCanonicalStateOutcome,
19    },
20    traits::{BestTransactionsAttributes, BlockInfo, PoolSize},
21    PoolConfig, PoolResult, PoolTransaction, PoolUpdateKind, PriceBumpConfig, TransactionOrdering,
22    ValidPoolTransaction, U256,
23};
24use alloy_consensus::constants::{
25    EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, KECCAK_EMPTY,
26    LEGACY_TX_TYPE_ID,
27};
28use alloy_eips::{
29    eip1559::{ETHEREUM_BLOCK_GAS_LIMIT_30M, MIN_PROTOCOL_BASE_FEE},
30    eip4844::BLOB_TX_MIN_BLOB_GASPRICE,
31    Typed2718,
32};
33use alloy_primitives::{Address, TxHash, B256};
34use rustc_hash::FxHashMap;
35use smallvec::SmallVec;
36use std::{
37    cmp::Ordering,
38    collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet},
39    fmt,
40    ops::Bound::{Excluded, Unbounded},
41    sync::Arc,
42};
43use tracing::{trace, warn};
44
45#[cfg_attr(doc, aquamarine::aquamarine)]
46// TODO: Inlined diagram due to a bug in aquamarine library, should become an include when it's
47// fixed. See https://github.com/mersinvald/aquamarine/issues/50
48// include_mmd!("docs/mermaid/txpool.mmd")
49/// A pool that manages transactions.
50///
51/// This pool maintains the state of all transactions and stores them accordingly.
52///
53/// ```mermaid
54/// graph TB
55///   subgraph TxPool
56///     direction TB
57///     pool[(All Transactions)]
58///     subgraph Subpools
59///         direction TB
60///         B3[(Queued)]
61///         B1[(Pending)]
62///         B2[(Basefee)]
63///         B4[(Blob)]
64///     end
65///   end
66///   discard([discard])
67///   production([Block Production])
68///   new([New Block])
69///   A[Incoming Tx] --> B[Validation] -->|ins
70///   pool --> |if ready + blobfee too low| B4
71///   pool --> |if ready| B1
72///   pool --> |if ready + basfee too low| B2
73///   pool --> |nonce gap or lack of funds| B3
74///   pool --> |update| pool
75///   B1 --> |best| production
76///   B2 --> |worst| discard
77///   B3 --> |worst| discard
78///   B4 --> |worst| discard
79///   B1 --> |increased blob fee| B4
80///   B4 --> |decreased blob fee| B1
81///   B1 --> |increased base fee| B2
82///   B2 --> |decreased base fee| B1
83///   B3 --> |promote| B1
84///   B3 --> |promote| B2
85///   new --> |apply state changes| pool
86/// ```
87pub struct TxPool<T: TransactionOrdering> {
88    /// Contains the currently known information about the senders.
89    sender_info: FxHashMap<SenderId, SenderInfo>,
90    /// pending subpool
91    ///
92    /// Holds transactions that are ready to be executed on the current state.
93    pending_pool: PendingPool<T>,
94    /// Pool settings to enforce limits etc.
95    config: PoolConfig,
96    /// queued subpool
97    ///
98    /// Holds all parked transactions that depend on external changes from the sender:
99    ///
100    ///    - blocked by missing ancestor transaction (has nonce gaps)
101    ///    - sender lacks funds to pay for this transaction.
102    queued_pool: ParkedPool<QueuedOrd<T::Transaction>>,
103    /// base fee subpool
104    ///
105    /// Holds all parked transactions that currently violate the dynamic fee requirement but could
106    /// be moved to pending if the base fee changes in their favor (decreases) in future blocks.
107    basefee_pool: ParkedPool<BasefeeOrd<T::Transaction>>,
108    /// Blob transactions in the pool that are __not pending__.
109    ///
110    /// This means they either do not satisfy the dynamic fee requirement or the blob fee
111    /// requirement. These transactions can be moved to pending if the base fee or blob fee changes
112    /// in their favor (decreases) in future blocks. The transaction may need both the base fee and
113    /// blob fee to decrease to become executable.
114    blob_pool: BlobTransactions<T::Transaction>,
115    /// All transactions in the pool.
116    all_transactions: AllTransactions<T::Transaction>,
117    /// Transaction pool metrics
118    metrics: TxPoolMetrics,
119}
120
121// === impl TxPool ===
122
123impl<T: TransactionOrdering> TxPool<T> {
124    /// Create a new graph pool instance.
125    pub fn new(ordering: T, config: PoolConfig) -> Self {
126        Self {
127            sender_info: Default::default(),
128            pending_pool: PendingPool::with_buffer(
129                ordering,
130                config.max_new_pending_txs_notifications,
131            ),
132            queued_pool: Default::default(),
133            basefee_pool: Default::default(),
134            blob_pool: Default::default(),
135            all_transactions: AllTransactions::new(&config),
136            config,
137            metrics: Default::default(),
138        }
139    }
140
141    /// Retrieves the highest nonce for a specific sender from the transaction pool.
142    pub fn get_highest_nonce_by_sender(&self, sender: SenderId) -> Option<u64> {
143        self.all().txs_iter(sender).last().map(|(_, tx)| tx.transaction.nonce())
144    }
145
146    /// Retrieves the highest transaction (wrapped in an `Arc`) for a specific sender from the
147    /// transaction pool.
148    pub fn get_highest_transaction_by_sender(
149        &self,
150        sender: SenderId,
151    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
152        self.all().txs_iter(sender).last().map(|(_, tx)| Arc::clone(&tx.transaction))
153    }
154
155    /// Returns the transaction with the highest nonce that is executable given the on chain nonce.
156    ///
157    /// If the pool already tracks a higher nonce for the given sender, then this nonce is used
158    /// instead.
159    ///
160    /// Note: The next pending pooled transaction must have the on chain nonce.
161    pub(crate) fn get_highest_consecutive_transaction_by_sender(
162        &self,
163        mut on_chain: TransactionId,
164    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
165        let mut last_consecutive_tx = None;
166
167        // ensure this operates on the most recent
168        if let Some(current) = self.sender_info.get(&on_chain.sender) {
169            on_chain.nonce = on_chain.nonce.max(current.state_nonce);
170        }
171
172        let mut next_expected_nonce = on_chain.nonce;
173        for (id, tx) in self.all().descendant_txs_inclusive(&on_chain) {
174            if next_expected_nonce != id.nonce {
175                break
176            }
177            next_expected_nonce = id.next_nonce();
178            last_consecutive_tx = Some(tx);
179        }
180
181        last_consecutive_tx.map(|tx| Arc::clone(&tx.transaction))
182    }
183
184    /// Returns access to the [`AllTransactions`] container.
185    pub(crate) const fn all(&self) -> &AllTransactions<T::Transaction> {
186        &self.all_transactions
187    }
188
189    /// Returns all senders in the pool
190    pub(crate) fn unique_senders(&self) -> HashSet<Address> {
191        self.all_transactions.txs.values().map(|tx| tx.transaction.sender()).collect()
192    }
193
194    /// Returns stats about the size of pool.
195    pub fn size(&self) -> PoolSize {
196        PoolSize {
197            pending: self.pending_pool.len(),
198            pending_size: self.pending_pool.size(),
199            basefee: self.basefee_pool.len(),
200            basefee_size: self.basefee_pool.size(),
201            queued: self.queued_pool.len(),
202            queued_size: self.queued_pool.size(),
203            blob: self.blob_pool.len(),
204            blob_size: self.blob_pool.size(),
205            total: self.all_transactions.len(),
206        }
207    }
208
209    /// Returns the currently tracked block values
210    pub const fn block_info(&self) -> BlockInfo {
211        BlockInfo {
212            block_gas_limit: self.all_transactions.block_gas_limit,
213            last_seen_block_hash: self.all_transactions.last_seen_block_hash,
214            last_seen_block_number: self.all_transactions.last_seen_block_number,
215            pending_basefee: self.all_transactions.pending_fees.base_fee,
216            pending_blob_fee: Some(self.all_transactions.pending_fees.blob_fee),
217        }
218    }
219
220    /// Updates the tracked blob fee
221    fn update_blob_fee<F>(
222        &mut self,
223        mut pending_blob_fee: u128,
224        base_fee_update: Ordering,
225        mut on_promoted: F,
226    ) where
227        F: FnMut(&Arc<ValidPoolTransaction<T::Transaction>>),
228    {
229        std::mem::swap(&mut self.all_transactions.pending_fees.blob_fee, &mut pending_blob_fee);
230        match (self.all_transactions.pending_fees.blob_fee.cmp(&pending_blob_fee), base_fee_update)
231        {
232            (Ordering::Equal, Ordering::Equal | Ordering::Greater) => {
233                // fee unchanged, nothing to update
234            }
235            (Ordering::Greater, Ordering::Equal | Ordering::Greater) => {
236                // increased blob fee: recheck pending pool and remove all that are no longer valid
237                let removed =
238                    self.pending_pool.update_blob_fee(self.all_transactions.pending_fees.blob_fee);
239                for tx in removed {
240                    let to = {
241                        let tx =
242                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
243
244                        // the blob fee is too high now, unset the blob fee cap block flag
245                        tx.state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
246                        tx.subpool = tx.state.into();
247                        tx.subpool
248                    };
249                    self.add_transaction_to_subpool(to, tx);
250                }
251            }
252            (Ordering::Less, _) | (_, Ordering::Less) => {
253                // decreased blob/base fee: recheck blob pool and promote all that are now valid
254                let removed =
255                    self.blob_pool.enforce_pending_fees(&self.all_transactions.pending_fees);
256                for tx in removed {
257                    let subpool = {
258                        let tx_meta =
259                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
260                        tx_meta.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
261                        tx_meta.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
262                        tx_meta.subpool = tx_meta.state.into();
263                        tx_meta.subpool
264                    };
265
266                    if subpool == SubPool::Pending {
267                        on_promoted(&tx);
268                    }
269
270                    self.add_transaction_to_subpool(subpool, tx);
271                }
272            }
273        }
274    }
275
276    /// Updates the tracked basefee
277    ///
278    /// Depending on the change in direction of the basefee, this will promote or demote
279    /// transactions from the basefee pool.
280    fn update_basefee<F>(&mut self, mut pending_basefee: u64, mut on_promoted: F) -> Ordering
281    where
282        F: FnMut(&Arc<ValidPoolTransaction<T::Transaction>>),
283    {
284        std::mem::swap(&mut self.all_transactions.pending_fees.base_fee, &mut pending_basefee);
285        match self.all_transactions.pending_fees.base_fee.cmp(&pending_basefee) {
286            Ordering::Equal => {
287                // fee unchanged, nothing to update
288                Ordering::Equal
289            }
290            Ordering::Greater => {
291                // increased base fee: recheck pending pool and remove all that are no longer valid
292                let removed =
293                    self.pending_pool.update_base_fee(self.all_transactions.pending_fees.base_fee);
294                for tx in removed {
295                    let to = {
296                        let tx =
297                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
298                        tx.state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
299                        tx.subpool = tx.state.into();
300                        tx.subpool
301                    };
302                    self.add_transaction_to_subpool(to, tx);
303                }
304
305                Ordering::Greater
306            }
307            Ordering::Less => {
308                // Base fee decreased: recheck BaseFee and promote.
309                // Invariants:
310                // - BaseFee contains only non-blob txs (blob txs live in Blob) and they already
311                //   have ENOUGH_BLOB_FEE_CAP_BLOCK.
312                // - PENDING_POOL_BITS = BASE_FEE_POOL_BITS | ENOUGH_FEE_CAP_BLOCK |
313                //   ENOUGH_BLOB_FEE_CAP_BLOCK.
314                // With the lower base fee they gain ENOUGH_FEE_CAP_BLOCK, so we can set the bit and
315                // insert directly into Pending (skip generic routing).
316                let current_base_fee = self.all_transactions.pending_fees.base_fee;
317                self.basefee_pool.enforce_basefee_with(current_base_fee, |tx| {
318                    // Update transaction state — guaranteed Pending by the invariants above
319                    let subpool = {
320                        let meta =
321                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
322                        meta.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
323                        meta.subpool = meta.state.into();
324                        meta.subpool
325                    };
326
327                    if subpool == SubPool::Pending {
328                        on_promoted(&tx);
329                    }
330
331                    trace!(target: "txpool", hash=%tx.transaction.hash(), pool=?subpool, "Adding transaction to a subpool");
332                    match subpool {
333                        SubPool::Queued => self.queued_pool.add_transaction(tx),
334                        SubPool::Pending => {
335                            self.pending_pool.add_transaction(tx, current_base_fee);
336                        }
337                        SubPool::Blob => {
338                            self.blob_pool.add_transaction(tx);
339                        }
340                        SubPool::BaseFee => {
341                            // This should be unreachable as transactions from BaseFee pool with decreased
342                            // basefee are guaranteed to become Pending
343                            warn!(target: "txpool", "BaseFee transactions should become Pending after basefee decrease");
344                        }
345                    }
346                });
347
348                Ordering::Less
349            }
350        }
351    }
352
353    /// Sets the current block info for the pool.
354    ///
355    /// This will also apply updates to the pool based on the new base fee and blob fee
356    pub fn set_block_info(&mut self, info: BlockInfo) {
357        // first update the subpools based on the new values
358        let basefee_ordering = self.update_basefee(info.pending_basefee, |_| {});
359        if let Some(blob_fee) = info.pending_blob_fee {
360            self.update_blob_fee(blob_fee, basefee_ordering, |_| {})
361        }
362        // then update tracked values
363        self.all_transactions.set_block_info(info);
364    }
365
366    /// Returns an iterator that yields transactions that are ready to be included in the block with
367    /// the tracked fees.
368    pub(crate) fn best_transactions(&self) -> BestTransactions<T> {
369        self.pending_pool.best()
370    }
371
372    /// Returns an iterator that yields transactions that are ready to be included in the block with
373    /// the given base fee and optional blob fee.
374    ///
375    /// If the provided attributes differ from the currently tracked fees, this will also include
376    /// transactions that are unlocked by the new fees, or exclude transactions that are no longer
377    /// valid with the new fees.
378    pub(crate) fn best_transactions_with_attributes(
379        &self,
380        best_transactions_attributes: BestTransactionsAttributes,
381    ) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
382    {
383        // First we need to check if the given base fee is different than what's currently being
384        // tracked
385        match best_transactions_attributes.basefee.cmp(&self.all_transactions.pending_fees.base_fee)
386        {
387            Ordering::Equal => {
388                // for EIP-4844 transactions we also need to check if the blob fee is now lower than
389                // what's currently being tracked, if so we need to include transactions from the
390                // blob pool that are valid with the lower blob fee
391                let new_blob_fee = best_transactions_attributes.blob_fee.unwrap_or_default();
392                match new_blob_fee.cmp(&(self.all_transactions.pending_fees.blob_fee as u64)) {
393                    Ordering::Less => {
394                        // it's possible that this swing unlocked more blob transactions
395                        let unlocked =
396                            self.blob_pool.satisfy_attributes(best_transactions_attributes);
397                        Box::new(self.pending_pool.best_with_unlocked_and_attributes(
398                            unlocked,
399                            best_transactions_attributes.basefee,
400                            new_blob_fee,
401                        ))
402                    }
403                    Ordering::Equal => Box::new(self.pending_pool.best()),
404                    Ordering::Greater => {
405                        // no additional transactions unlocked
406                        Box::new(self.pending_pool.best_with_basefee_and_blobfee(
407                            best_transactions_attributes.basefee,
408                            best_transactions_attributes.blob_fee.unwrap_or_default(),
409                        ))
410                    }
411                }
412            }
413            Ordering::Greater => {
414                // base fee increased, we need to check how the blob fee moved
415                let new_blob_fee = best_transactions_attributes.blob_fee.unwrap_or_default();
416                match new_blob_fee.cmp(&(self.all_transactions.pending_fees.blob_fee as u64)) {
417                    Ordering::Less => {
418                        // it's possible that this swing unlocked more blob transactions
419                        let unlocked =
420                            self.blob_pool.satisfy_attributes(best_transactions_attributes);
421                        Box::new(self.pending_pool.best_with_unlocked_and_attributes(
422                            unlocked,
423                            best_transactions_attributes.basefee,
424                            new_blob_fee,
425                        ))
426                    }
427                    Ordering::Equal | Ordering::Greater => {
428                        // no additional transactions unlocked
429                        Box::new(self.pending_pool.best_with_basefee_and_blobfee(
430                            best_transactions_attributes.basefee,
431                            new_blob_fee,
432                        ))
433                    }
434                }
435            }
436            Ordering::Less => {
437                // base fee decreased, we need to move transactions from the basefee + blob pool to
438                // the pending pool that might be unlocked by the lower base fee
439                let mut unlocked = self
440                    .basefee_pool
441                    .satisfy_base_fee_transactions(best_transactions_attributes.basefee);
442
443                // also include blob pool transactions that are now unlocked
444                unlocked.extend(self.blob_pool.satisfy_attributes(best_transactions_attributes));
445
446                Box::new(self.pending_pool.best_with_unlocked_and_attributes(
447                    unlocked,
448                    best_transactions_attributes.basefee,
449                    best_transactions_attributes.blob_fee.unwrap_or_default(),
450                ))
451            }
452        }
453    }
454
455    /// Returns all transactions from the pending sub-pool
456    pub(crate) fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
457        self.pending_pool.all().collect()
458    }
459    /// Returns an iterator over all transactions from the pending sub-pool
460    pub(crate) fn pending_transactions_iter(
461        &self,
462    ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
463        self.pending_pool.all()
464    }
465
466    /// Returns the number of transactions from the pending sub-pool
467    pub(crate) fn pending_transactions_count(&self) -> usize {
468        self.pending_pool.len()
469    }
470
471    /// Returns all pending transactions filtered by predicate
472    pub(crate) fn pending_transactions_with_predicate(
473        &self,
474        mut predicate: impl FnMut(&ValidPoolTransaction<T::Transaction>) -> bool,
475    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
476        self.pending_transactions_iter().filter(|tx| predicate(tx)).collect()
477    }
478
479    /// Returns all pending transactions for the specified sender
480    pub(crate) fn pending_txs_by_sender(
481        &self,
482        sender: SenderId,
483    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
484        self.pending_transactions_iter().filter(|tx| tx.sender_id() == sender).collect()
485    }
486
487    /// Returns all transactions from parked pools
488    pub(crate) fn queued_transactions(&self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
489        self.basefee_pool.all().chain(self.queued_pool.all()).collect()
490    }
491
492    /// Returns an iterator over all transactions from parked pools
493    pub(crate) fn queued_transactions_iter(
494        &self,
495    ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
496        self.basefee_pool.all().chain(self.queued_pool.all())
497    }
498
499    /// Returns the number of transactions in parked pools
500    pub(crate) fn queued_transactions_count(&self) -> usize {
501        self.basefee_pool.len() + self.queued_pool.len()
502    }
503
504    /// Returns queued and pending transactions for the specified sender
505    pub fn queued_and_pending_txs_by_sender(
506        &self,
507        sender: SenderId,
508    ) -> (SmallVec<[TransactionId; TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER]>, Vec<TransactionId>) {
509        (self.queued_pool.get_txs_by_sender(sender), self.pending_pool.get_txs_by_sender(sender))
510    }
511
512    /// Returns all queued transactions for the specified sender
513    pub(crate) fn queued_txs_by_sender(
514        &self,
515        sender: SenderId,
516    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
517        self.queued_transactions_iter().filter(|tx| tx.sender_id() == sender).collect()
518    }
519
520    /// Returns `true` if the transaction with the given hash is already included in this pool.
521    pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
522        self.all_transactions.contains(tx_hash)
523    }
524
525    /// Returns `true` if the transaction with the given id is already included in the given subpool
526    #[cfg(test)]
527    pub(crate) fn subpool_contains(&self, subpool: SubPool, id: &TransactionId) -> bool {
528        match subpool {
529            SubPool::Queued => self.queued_pool.contains(id),
530            SubPool::Pending => self.pending_pool.contains(id),
531            SubPool::BaseFee => self.basefee_pool.contains(id),
532            SubPool::Blob => self.blob_pool.contains(id),
533        }
534    }
535
536    /// Returns `true` if the pool is over its configured limits.
537    #[inline]
538    pub(crate) fn is_exceeded(&self) -> bool {
539        self.config.is_exceeded(self.size())
540    }
541
542    /// Returns the transaction for the given hash.
543    pub(crate) fn get(
544        &self,
545        tx_hash: &TxHash,
546    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
547        self.all_transactions.by_hash.get(tx_hash).cloned()
548    }
549
550    /// Returns transactions for the multiple given hashes, if they exist.
551    pub(crate) fn get_all(
552        &self,
553        txs: Vec<TxHash>,
554    ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
555        txs.into_iter().filter_map(|tx| self.get(&tx))
556    }
557
558    /// Returns all transactions sent from the given sender.
559    pub(crate) fn get_transactions_by_sender(
560        &self,
561        sender: SenderId,
562    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
563        self.all_transactions.txs_iter(sender).map(|(_, tx)| Arc::clone(&tx.transaction)).collect()
564    }
565
566    /// Updates only the pending fees without triggering subpool updates.
567    /// Returns the previous base fee and blob fee values.
568    const fn update_pending_fees_only(
569        &mut self,
570        mut new_base_fee: u64,
571        new_blob_fee: Option<u128>,
572    ) -> (u64, u128) {
573        std::mem::swap(&mut self.all_transactions.pending_fees.base_fee, &mut new_base_fee);
574
575        let prev_blob_fee = if let Some(mut blob_fee) = new_blob_fee {
576            std::mem::swap(&mut self.all_transactions.pending_fees.blob_fee, &mut blob_fee);
577            blob_fee
578        } else {
579            self.all_transactions.pending_fees.blob_fee
580        };
581
582        (new_base_fee, prev_blob_fee)
583    }
584
585    /// Applies fee-based promotion updates based on the previous fees.
586    ///
587    /// Records promoted transactions based on fee swings.
588    ///
589    /// Caution: This expects that the fees were previously already updated via
590    /// [`Self::update_pending_fees_only`].
591    fn apply_fee_updates(
592        &mut self,
593        prev_base_fee: u64,
594        prev_blob_fee: u128,
595        outcome: &mut UpdateOutcome<T::Transaction>,
596    ) {
597        let new_base_fee = self.all_transactions.pending_fees.base_fee;
598        let new_blob_fee = self.all_transactions.pending_fees.blob_fee;
599
600        if new_base_fee == prev_base_fee && new_blob_fee == prev_blob_fee {
601            // nothing to update
602            return;
603        }
604
605        // IMPORTANT:
606        // Restore previous fees so that the update fee functions correctly handle fee swings
607        self.all_transactions.pending_fees.base_fee = prev_base_fee;
608        self.all_transactions.pending_fees.blob_fee = prev_blob_fee;
609
610        let base_fee_ordering = self.update_basefee(new_base_fee, |tx| {
611            outcome.promoted.push(tx.clone());
612        });
613
614        self.update_blob_fee(new_blob_fee, base_fee_ordering, |tx| {
615            outcome.promoted.push(tx.clone());
616        });
617    }
618
619    /// Updates the transactions for the changed senders.
620    pub(crate) fn update_accounts(
621        &mut self,
622        changed_senders: FxHashMap<SenderId, SenderInfo>,
623    ) -> UpdateOutcome<T::Transaction> {
624        // Apply the state changes to the total set of transactions which triggers sub-pool updates.
625        let updates = self.all_transactions.update(&changed_senders);
626
627        // track changed accounts
628        self.sender_info.extend(changed_senders);
629
630        // Process the sub-pool updates
631        let update = self.process_updates(updates);
632        // update the metrics after the update
633        self.update_size_metrics();
634        update
635    }
636
637    /// Updates the entire pool after a new block was mined.
638    ///
639    /// This removes all mined transactions, updates according to the new base fee and blob fee and
640    /// rechecks sender allowance based on the given changed sender infos.
641    pub(crate) fn on_canonical_state_change(
642        &mut self,
643        block_info: BlockInfo,
644        mined_transactions: Vec<TxHash>,
645        changed_senders: FxHashMap<SenderId, SenderInfo>,
646        _update_kind: PoolUpdateKind,
647    ) -> OnNewCanonicalStateOutcome<T::Transaction> {
648        // update block info
649        let block_hash = block_info.last_seen_block_hash;
650
651        // Remove all transaction that were included in the block
652        let mut removed_txs_count = 0;
653        for tx_hash in &mined_transactions {
654            if self.prune_transaction_by_hash(tx_hash).is_some() {
655                removed_txs_count += 1;
656            }
657        }
658
659        // Update removed transactions metric
660        self.metrics.removed_transactions.increment(removed_txs_count);
661
662        // Update fees internally first without triggering subpool updates based on fee movements
663        // This must happen before we update the changed so that all account updates use the new fee
664        // values, this way all changed accounts remain unaffected by the fee updates that are
665        // performed in next step and we don't collect promotions twice
666        let (prev_base_fee, prev_blob_fee) =
667            self.update_pending_fees_only(block_info.pending_basefee, block_info.pending_blob_fee);
668
669        // Now update accounts with the new fees already set
670        let mut outcome = self.update_accounts(changed_senders);
671
672        // Apply subpool updates based on fee changes
673        // This will record any additional promotions based on fee movements
674        self.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
675
676        // Update the rest of block info (without triggering fee updates again)
677        self.all_transactions.set_block_info(block_info);
678
679        self.update_transaction_type_metrics();
680        self.metrics.performed_state_updates.increment(1);
681
682        OnNewCanonicalStateOutcome {
683            block_hash,
684            mined: mined_transactions,
685            promoted: outcome.promoted,
686            discarded: outcome.discarded,
687        }
688    }
689
690    /// Update sub-pools size metrics.
691    pub(crate) fn update_size_metrics(&self) {
692        let stats = self.size();
693        self.metrics.pending_pool_transactions.set(stats.pending as f64);
694        self.metrics.pending_pool_size_bytes.set(stats.pending_size as f64);
695        self.metrics.basefee_pool_transactions.set(stats.basefee as f64);
696        self.metrics.basefee_pool_size_bytes.set(stats.basefee_size as f64);
697        self.metrics.queued_pool_transactions.set(stats.queued as f64);
698        self.metrics.queued_pool_size_bytes.set(stats.queued_size as f64);
699        self.metrics.blob_pool_transactions.set(stats.blob as f64);
700        self.metrics.blob_pool_size_bytes.set(stats.blob_size as f64);
701        self.metrics.total_transactions.set(stats.total as f64);
702    }
703
704    /// Updates transaction type metrics for the entire pool.
705    pub(crate) fn update_transaction_type_metrics(&self) {
706        let mut legacy_count = 0;
707        let mut eip2930_count = 0;
708        let mut eip1559_count = 0;
709        let mut eip4844_count = 0;
710        let mut eip7702_count = 0;
711
712        for tx in self.all_transactions.transactions_iter() {
713            match tx.transaction.ty() {
714                LEGACY_TX_TYPE_ID => legacy_count += 1,
715                EIP2930_TX_TYPE_ID => eip2930_count += 1,
716                EIP1559_TX_TYPE_ID => eip1559_count += 1,
717                EIP4844_TX_TYPE_ID => eip4844_count += 1,
718                EIP7702_TX_TYPE_ID => eip7702_count += 1,
719                _ => {} // Ignore other types
720            }
721        }
722
723        self.metrics.total_legacy_transactions.set(legacy_count as f64);
724        self.metrics.total_eip2930_transactions.set(eip2930_count as f64);
725        self.metrics.total_eip1559_transactions.set(eip1559_count as f64);
726        self.metrics.total_eip4844_transactions.set(eip4844_count as f64);
727        self.metrics.total_eip7702_transactions.set(eip7702_count as f64);
728    }
729
730    pub(crate) fn add_transaction(
731        &mut self,
732        tx: ValidPoolTransaction<T::Transaction>,
733        on_chain_balance: U256,
734        on_chain_nonce: u64,
735        on_chain_code_hash: Option<B256>,
736    ) -> PoolResult<AddedTransaction<T::Transaction>> {
737        if self.contains(tx.hash()) {
738            return Err(PoolError::new(*tx.hash(), PoolErrorKind::AlreadyImported))
739        }
740
741        self.validate_auth(&tx, on_chain_nonce, on_chain_code_hash)?;
742
743        // Update sender info with balance and nonce
744        self.sender_info
745            .entry(tx.sender_id())
746            .or_default()
747            .update(on_chain_nonce, on_chain_balance);
748
749        match self.all_transactions.insert_tx(tx, on_chain_balance, on_chain_nonce) {
750            Ok(InsertOk { transaction, move_to, replaced_tx, updates, state }) => {
751                // replace the new tx and remove the replaced in the subpool(s)
752                self.add_new_transaction(transaction.clone(), replaced_tx.clone(), move_to);
753                // Update inserted transactions metric
754                self.metrics.inserted_transactions.increment(1);
755                let UpdateOutcome { promoted, discarded } = self.process_updates(updates);
756
757                let replaced = replaced_tx.map(|(tx, _)| tx);
758
759                // This transaction was moved to the pending pool.
760                let res = if move_to.is_pending() {
761                    AddedTransaction::Pending(AddedPendingTransaction {
762                        transaction,
763                        promoted,
764                        discarded,
765                        replaced,
766                    })
767                } else {
768                    // Determine the specific queued reason based on the transaction state
769                    let queued_reason = state.determine_queued_reason(move_to);
770                    AddedTransaction::Parked {
771                        transaction,
772                        subpool: move_to,
773                        replaced,
774                        queued_reason,
775                    }
776                };
777
778                // Update size metrics after adding and potentially moving transactions.
779                self.update_size_metrics();
780
781                Ok(res)
782            }
783            Err(err) => {
784                // Update invalid transactions metric
785                self.metrics.invalid_transactions.increment(1);
786                match err {
787                    InsertErr::Underpriced { existing: _, transaction } => Err(PoolError::new(
788                        *transaction.hash(),
789                        PoolErrorKind::ReplacementUnderpriced,
790                    )),
791                    InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap } => {
792                        Err(PoolError::new(
793                            *transaction.hash(),
794                            PoolErrorKind::FeeCapBelowMinimumProtocolFeeCap(fee_cap),
795                        ))
796                    }
797                    InsertErr::ExceededSenderTransactionsCapacity { transaction } => {
798                        Err(PoolError::new(
799                            *transaction.hash(),
800                            PoolErrorKind::SpammerExceededCapacity(transaction.sender()),
801                        ))
802                    }
803                    InsertErr::TxGasLimitMoreThanAvailableBlockGas {
804                        transaction,
805                        block_gas_limit,
806                        tx_gas_limit,
807                    } => Err(PoolError::new(
808                        *transaction.hash(),
809                        PoolErrorKind::InvalidTransaction(
810                            InvalidPoolTransactionError::ExceedsGasLimit(
811                                tx_gas_limit,
812                                block_gas_limit,
813                            ),
814                        ),
815                    )),
816                    InsertErr::BlobTxHasNonceGap { transaction } => Err(PoolError::new(
817                        *transaction.hash(),
818                        PoolErrorKind::InvalidTransaction(
819                            Eip4844PoolTransactionError::Eip4844NonceGap.into(),
820                        ),
821                    )),
822                    InsertErr::Overdraft { transaction } => Err(PoolError::new(
823                        *transaction.hash(),
824                        PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Overdraft {
825                            cost: *transaction.cost(),
826                            balance: on_chain_balance,
827                        }),
828                    )),
829                    InsertErr::TxTypeConflict { transaction } => Err(PoolError::new(
830                        *transaction.hash(),
831                        PoolErrorKind::ExistingConflictingTransactionType(
832                            transaction.sender(),
833                            transaction.tx_type(),
834                        ),
835                    )),
836                }
837            }
838        }
839    }
840
841    /// Determines if the tx sender is delegated or has a  pending delegation, and if so, ensures
842    /// they have at most one configured amount of in-flight **executable** transactions (default at
843    /// most one), e.g. disallow stacked and nonce-gapped transactions from the account.
844    fn check_delegation_limit(
845        &self,
846        transaction: &ValidPoolTransaction<T::Transaction>,
847        on_chain_nonce: u64,
848        on_chain_code_hash: Option<B256>,
849    ) -> Result<(), PoolError> {
850        // Short circuit if the sender has neither delegation nor pending delegation.
851        if (on_chain_code_hash.is_none() || on_chain_code_hash == Some(KECCAK_EMPTY)) &&
852            !self.all_transactions.auths.contains_key(&transaction.sender_id())
853        {
854            return Ok(())
855        }
856
857        let mut txs_by_sender =
858            self.pending_pool.iter_txs_by_sender(transaction.sender_id()).peekable();
859
860        if txs_by_sender.peek().is_none() {
861            // Transaction with gapped nonce is not supported for delegated accounts
862            // but transaction can arrive out of order if more slots are allowed
863            // by default with a slot limit of 1 this will fail if the transaction's nonce >
864            // on_chain
865            let nonce_gap_distance = transaction.nonce().saturating_sub(on_chain_nonce);
866            if nonce_gap_distance >= self.config.max_inflight_delegated_slot_limit as u64 {
867                return Err(PoolError::new(
868                    *transaction.hash(),
869                    PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702(
870                        Eip7702PoolTransactionError::OutOfOrderTxFromDelegated,
871                    )),
872                ))
873            }
874            return Ok(())
875        }
876
877        let mut count = 0;
878        for id in txs_by_sender {
879            if id == &transaction.transaction_id {
880                // Transaction replacement is supported
881                return Ok(())
882            }
883            count += 1;
884        }
885
886        if count < self.config.max_inflight_delegated_slot_limit {
887            // account still has an available slot
888            return Ok(())
889        }
890
891        Err(PoolError::new(
892            *transaction.hash(),
893            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702(
894                Eip7702PoolTransactionError::InflightTxLimitReached,
895            )),
896        ))
897    }
898
899    /// This verifies that the transaction complies with code authorization
900    /// restrictions brought by EIP-7702 transaction type:
901    /// 1. Any account with a deployed delegation or an in-flight authorization to deploy a
902    ///    delegation will only be allowed a certain amount of transaction slots (default 1) instead
903    ///    of the standard limit. This is due to the possibility of the account being sweeped by an
904    ///    unrelated account.
905    /// 2. In case the pool is tracking a pending / queued transaction from a specific account, at
906    ///    most one in-flight transaction is allowed; any additional delegated transactions from
907    ///    that account will be rejected.
908    fn validate_auth(
909        &self,
910        transaction: &ValidPoolTransaction<T::Transaction>,
911        on_chain_nonce: u64,
912        on_chain_code_hash: Option<B256>,
913    ) -> Result<(), PoolError> {
914        // Ensure in-flight limit for delegated accounts or those with a pending authorization.
915        self.check_delegation_limit(transaction, on_chain_nonce, on_chain_code_hash)?;
916
917        if let Some(authority_list) = &transaction.authority_ids {
918            for sender_id in authority_list {
919                // Ensure authority has at most 1 inflight transaction.
920                if self.all_transactions.txs_iter(*sender_id).nth(1).is_some() {
921                    return Err(PoolError::new(
922                        *transaction.hash(),
923                        PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702(
924                            Eip7702PoolTransactionError::AuthorityReserved,
925                        )),
926                    ))
927                }
928            }
929        }
930
931        Ok(())
932    }
933
934    /// Maintenance task to apply a series of updates.
935    ///
936    /// This will move/discard the given transaction according to the `PoolUpdate`
937    fn process_updates(&mut self, updates: Vec<PoolUpdate>) -> UpdateOutcome<T::Transaction> {
938        let mut outcome = UpdateOutcome::default();
939        for PoolUpdate { id, current, destination } in updates {
940            match destination {
941                Destination::Discard => {
942                    // remove the transaction from the pool and subpool
943                    if let Some(tx) = self.prune_transaction_by_id(&id) {
944                        outcome.discarded.push(tx);
945                    }
946                    self.metrics.removed_transactions.increment(1);
947                }
948                Destination::Pool(move_to) => {
949                    debug_assert_ne!(&move_to, &current, "destination must be different");
950                    let moved = self.move_transaction(current, move_to, &id);
951                    if matches!(move_to, SubPool::Pending) &&
952                        let Some(tx) = moved
953                    {
954                        trace!(target: "txpool", hash=%tx.transaction.hash(), "Promoted transaction to pending");
955                        outcome.promoted.push(tx);
956                    }
957                }
958            }
959        }
960
961        outcome
962    }
963
964    /// Moves a transaction from one sub pool to another.
965    ///
966    /// This will remove the given transaction from one sub-pool and insert it into the other
967    /// sub-pool.
968    fn move_transaction(
969        &mut self,
970        from: SubPool,
971        to: SubPool,
972        id: &TransactionId,
973    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
974        let tx = self.remove_from_subpool(from, id)?;
975        self.add_transaction_to_subpool(to, tx.clone());
976        Some(tx)
977    }
978
979    /// Removes and returns all matching transactions from the pool.
980    ///
981    /// Note: this does not advance any descendants of the removed transactions and does not apply
982    /// any additional updates.
983    pub(crate) fn remove_transactions(
984        &mut self,
985        hashes: Vec<TxHash>,
986    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
987        let txs =
988            hashes.into_iter().filter_map(|hash| self.remove_transaction_by_hash(&hash)).collect();
989        self.update_size_metrics();
990        txs
991    }
992
993    /// Removes and returns all matching transactions and their descendants from the pool.
994    pub(crate) fn remove_transactions_and_descendants(
995        &mut self,
996        hashes: Vec<TxHash>,
997    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
998        let mut removed = Vec::new();
999        for hash in hashes {
1000            if let Some(tx) = self.remove_transaction_by_hash(&hash) {
1001                removed.push(tx.clone());
1002                self.remove_descendants(tx.id(), &mut removed);
1003            }
1004        }
1005        self.update_size_metrics();
1006        removed
1007    }
1008
1009    /// Removes all transactions from the given sender.
1010    pub(crate) fn remove_transactions_by_sender(
1011        &mut self,
1012        sender_id: SenderId,
1013    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
1014        let mut removed = Vec::new();
1015        let txs = self.get_transactions_by_sender(sender_id);
1016        for tx in txs {
1017            if let Some(tx) = self.remove_transaction(tx.id()) {
1018                removed.push(tx);
1019            }
1020        }
1021        self.update_size_metrics();
1022        removed
1023    }
1024
1025    /// Remove the transaction from the __entire__ pool.
1026    ///
1027    /// This includes the total set of transaction and the subpool it currently resides in.
1028    fn remove_transaction(
1029        &mut self,
1030        id: &TransactionId,
1031    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1032        let (tx, pool) = self.all_transactions.remove_transaction(id)?;
1033        self.remove_from_subpool(pool, tx.id())
1034    }
1035
1036    /// Remove the transaction from the entire pool via its hash. This includes the total set of
1037    /// transactions and the subpool it currently resides in.
1038    ///
1039    /// This treats the descendants as if this transaction is discarded and removing the transaction
1040    /// reduces a nonce gap.
1041    fn remove_transaction_by_hash(
1042        &mut self,
1043        tx_hash: &B256,
1044    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1045        let (tx, pool) = self.all_transactions.remove_transaction_by_hash(tx_hash)?;
1046
1047        // After a tx is removed, its descendants must become parked due to the nonce gap
1048        let updates = self.all_transactions.park_descendant_transactions(tx.id());
1049        self.process_updates(updates);
1050        self.remove_from_subpool(pool, tx.id())
1051    }
1052
1053    /// This removes the transaction from the pool and advances any descendant state inside the
1054    /// subpool.
1055    ///
1056    /// This is intended to be used when a transaction is included in a block,
1057    /// [`Self::on_canonical_state_change`]. So its descendants will not change from pending to
1058    /// parked, just like what we do in `remove_transaction_by_hash`.
1059    fn prune_transaction_by_hash(
1060        &mut self,
1061        tx_hash: &B256,
1062    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1063        let (tx, pool) = self.all_transactions.remove_transaction_by_hash(tx_hash)?;
1064        self.remove_from_subpool(pool, tx.id())
1065    }
1066    /// This removes the transaction from the pool and advances any descendant state inside the
1067    /// subpool.
1068    ///
1069    /// This is intended to be used when we call [`Self::process_updates`].
1070    fn prune_transaction_by_id(
1071        &mut self,
1072        tx_id: &TransactionId,
1073    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1074        let (tx, pool) = self.all_transactions.remove_transaction_by_id(tx_id)?;
1075        self.remove_from_subpool(pool, tx.id())
1076    }
1077
1078    /// Removes the transaction from the given pool.
1079    ///
1080    /// Caution: this only removes the tx from the sub-pool and not from the pool itself
1081    fn remove_from_subpool(
1082        &mut self,
1083        pool: SubPool,
1084        tx: &TransactionId,
1085    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1086        let tx = match pool {
1087            SubPool::Queued => self.queued_pool.remove_transaction(tx),
1088            SubPool::Pending => self.pending_pool.remove_transaction(tx),
1089            SubPool::BaseFee => self.basefee_pool.remove_transaction(tx),
1090            SubPool::Blob => self.blob_pool.remove_transaction(tx),
1091        };
1092
1093        if let Some(ref tx) = tx {
1094            // We trace here instead of in subpool structs directly, because the `ParkedPool` type
1095            // is generic and it would not be possible to distinguish whether a transaction is
1096            // being removed from the `BaseFee` pool, or the `Queued` pool.
1097            trace!(target: "txpool", hash=%tx.transaction.hash(), ?pool, "Removed transaction from a subpool");
1098        }
1099
1100        tx
1101    }
1102
1103    /// Removes _only_ the descendants of the given transaction from the __entire__ pool.
1104    ///
1105    /// All removed transactions are added to the `removed` vec.
1106    fn remove_descendants(
1107        &mut self,
1108        tx: &TransactionId,
1109        removed: &mut Vec<Arc<ValidPoolTransaction<T::Transaction>>>,
1110    ) {
1111        let mut id = *tx;
1112
1113        // this will essentially pop _all_ descendant transactions one by one
1114        loop {
1115            let descendant =
1116                self.all_transactions.descendant_txs_exclusive(&id).map(|(id, _)| *id).next();
1117            if let Some(descendant) = descendant {
1118                if let Some(tx) = self.remove_transaction(&descendant) {
1119                    removed.push(tx)
1120                }
1121                id = descendant;
1122            } else {
1123                return
1124            }
1125        }
1126    }
1127
1128    /// Inserts the transaction into the given sub-pool.
1129    fn add_transaction_to_subpool(
1130        &mut self,
1131        pool: SubPool,
1132        tx: Arc<ValidPoolTransaction<T::Transaction>>,
1133    ) {
1134        // We trace here instead of in structs directly, because the `ParkedPool` type is
1135        // generic and it would not be possible to distinguish whether a transaction is being
1136        // added to the `BaseFee` pool, or the `Queued` pool.
1137        trace!(target: "txpool", hash=%tx.transaction.hash(), ?pool, "Adding transaction to a subpool");
1138        match pool {
1139            SubPool::Queued => self.queued_pool.add_transaction(tx),
1140            SubPool::Pending => {
1141                self.pending_pool.add_transaction(tx, self.all_transactions.pending_fees.base_fee);
1142            }
1143            SubPool::BaseFee => {
1144                self.basefee_pool.add_transaction(tx);
1145            }
1146            SubPool::Blob => {
1147                self.blob_pool.add_transaction(tx);
1148            }
1149        }
1150    }
1151
1152    /// Inserts the transaction into the given sub-pool.
1153    /// Optionally, removes the replacement transaction.
1154    fn add_new_transaction(
1155        &mut self,
1156        transaction: Arc<ValidPoolTransaction<T::Transaction>>,
1157        replaced: Option<(Arc<ValidPoolTransaction<T::Transaction>>, SubPool)>,
1158        pool: SubPool,
1159    ) {
1160        if let Some((replaced, replaced_pool)) = replaced {
1161            // Remove the replaced transaction
1162            self.remove_from_subpool(replaced_pool, replaced.id());
1163        }
1164
1165        self.add_transaction_to_subpool(pool, transaction)
1166    }
1167
1168    /// Ensures that the transactions in the sub-pools are within the given bounds.
1169    ///
1170    /// If the current size exceeds the given bounds, the worst transactions are evicted from the
1171    /// pool and returned.
1172    ///
1173    /// This returns all transactions that were removed from the entire pool.
1174    pub(crate) fn discard_worst(&mut self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
1175        let mut removed = Vec::new();
1176
1177        // Helper macro that discards the worst transactions for the pools
1178        macro_rules! discard_worst {
1179            ($this:ident, $removed:ident, [$($limit:ident => ($pool:ident, $metric:ident)),* $(,)*]) => {
1180                $ (
1181                while $this.$pool.exceeds(&$this.config.$limit)
1182                    {
1183                        trace!(
1184                            target: "txpool",
1185                            "discarding transactions from {}, limit: {:?}, curr size: {}, curr len: {}",
1186                            stringify!($pool),
1187                            $this.config.$limit,
1188                            $this.$pool.size(),
1189                            $this.$pool.len(),
1190                        );
1191
1192                        // 1. first remove the worst transaction from the subpool
1193                        let removed_from_subpool = $this.$pool.truncate_pool($this.config.$limit.clone());
1194
1195                        trace!(
1196                            target: "txpool",
1197                            "removed {} transactions from {}, limit: {:?}, curr size: {}, curr len: {}",
1198                            removed_from_subpool.len(),
1199                            stringify!($pool),
1200                            $this.config.$limit,
1201                            $this.$pool.size(),
1202                            $this.$pool.len()
1203                        );
1204                        $this.metrics.$metric.increment(removed_from_subpool.len() as u64);
1205
1206                        // 2. remove all transactions from the total set
1207                        for tx in removed_from_subpool {
1208                            $this.all_transactions.remove_transaction(tx.id());
1209
1210                            let id = *tx.id();
1211
1212                            // keep track of removed transaction
1213                            removed.push(tx);
1214
1215                            // 3. remove all its descendants from the entire pool
1216                            $this.remove_descendants(&id, &mut $removed);
1217                        }
1218                    }
1219
1220                )*
1221            };
1222        }
1223
1224        discard_worst!(
1225            self, removed, [
1226                pending_limit => (pending_pool, pending_transactions_evicted),
1227                basefee_limit => (basefee_pool, basefee_transactions_evicted),
1228                blob_limit    => (blob_pool, blob_transactions_evicted),
1229                queued_limit  => (queued_pool, queued_transactions_evicted),
1230            ]
1231        );
1232
1233        removed
1234    }
1235
1236    /// Number of transactions in the entire pool
1237    pub(crate) fn len(&self) -> usize {
1238        self.all_transactions.len()
1239    }
1240
1241    /// Whether the pool is empty
1242    pub(crate) fn is_empty(&self) -> bool {
1243        self.all_transactions.is_empty()
1244    }
1245
1246    /// Asserts all invariants of the  pool's:
1247    ///
1248    ///  - All maps are bijections (`by_id`, `by_hash`)
1249    ///  - Total size is equal to the sum of all sub-pools
1250    ///
1251    /// # Panics
1252    /// if any invariant is violated
1253    #[cfg(any(test, feature = "test-utils"))]
1254    pub fn assert_invariants(&self) {
1255        let size = self.size();
1256        let actual = size.basefee + size.pending + size.queued + size.blob;
1257        assert_eq!(
1258            size.total, actual,
1259            "total size must be equal to the sum of all sub-pools, basefee:{}, pending:{}, queued:{}, blob:{}",
1260            size.basefee, size.pending, size.queued, size.blob
1261        );
1262        self.all_transactions.assert_invariants();
1263        self.pending_pool.assert_invariants();
1264        self.basefee_pool.assert_invariants();
1265        self.queued_pool.assert_invariants();
1266        self.blob_pool.assert_invariants();
1267    }
1268}
1269
1270#[cfg(any(test, feature = "test-utils"))]
1271impl TxPool<crate::test_utils::MockOrdering> {
1272    /// Creates a mock instance for testing.
1273    pub fn mock() -> Self {
1274        Self::new(crate::test_utils::MockOrdering::default(), PoolConfig::default())
1275    }
1276}
1277
1278#[cfg(test)]
1279impl<T: TransactionOrdering> Drop for TxPool<T> {
1280    fn drop(&mut self) {
1281        self.assert_invariants();
1282    }
1283}
1284
1285impl<T: TransactionOrdering> TxPool<T> {
1286    /// Pending subpool
1287    pub const fn pending(&self) -> &PendingPool<T> {
1288        &self.pending_pool
1289    }
1290
1291    /// Base fee subpool
1292    pub const fn base_fee(&self) -> &ParkedPool<BasefeeOrd<T::Transaction>> {
1293        &self.basefee_pool
1294    }
1295
1296    /// Queued sub pool
1297    pub const fn queued(&self) -> &ParkedPool<QueuedOrd<T::Transaction>> {
1298        &self.queued_pool
1299    }
1300}
1301
1302impl<T: TransactionOrdering> fmt::Debug for TxPool<T> {
1303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1304        f.debug_struct("TxPool").field("config", &self.config).finish_non_exhaustive()
1305    }
1306}
1307
1308/// Container for _all_ transaction in the pool.
1309///
1310/// This is the sole entrypoint that's guarding all sub-pools, all sub-pool actions are always
1311/// derived from this set. Updates returned from this type must be applied to the sub-pools.
1312pub(crate) struct AllTransactions<T: PoolTransaction> {
1313    /// Minimum base fee required by the protocol.
1314    ///
1315    /// Transactions with a lower base fee will never be included by the chain
1316    minimal_protocol_basefee: u64,
1317    /// The max gas limit of the block
1318    block_gas_limit: u64,
1319    /// Max number of executable transaction slots guaranteed per account
1320    max_account_slots: usize,
1321    /// _All_ transactions identified by their hash.
1322    by_hash: HashMap<TxHash, Arc<ValidPoolTransaction<T>>>,
1323    /// _All_ transaction in the pool sorted by their sender and nonce pair.
1324    txs: BTreeMap<TransactionId, PoolInternalTransaction<T>>,
1325    /// Tracks the number of transactions by sender that are currently in the pool.
1326    tx_counter: FxHashMap<SenderId, usize>,
1327    /// The current block number the pool keeps track of.
1328    last_seen_block_number: u64,
1329    /// The current block hash the pool keeps track of.
1330    last_seen_block_hash: B256,
1331    /// Expected blob and base fee for the pending block.
1332    pending_fees: PendingFees,
1333    /// Configured price bump settings for replacements
1334    price_bumps: PriceBumpConfig,
1335    /// How to handle [`TransactionOrigin::Local`](crate::TransactionOrigin) transactions.
1336    local_transactions_config: LocalTransactionConfig,
1337    /// All accounts with a pooled authorization
1338    auths: FxHashMap<SenderId, HashSet<TxHash>>,
1339    /// All Transactions metrics
1340    metrics: AllTransactionsMetrics,
1341}
1342
1343impl<T: PoolTransaction> AllTransactions<T> {
1344    /// Create a new instance
1345    fn new(config: &PoolConfig) -> Self {
1346        Self {
1347            max_account_slots: config.max_account_slots,
1348            price_bumps: config.price_bumps,
1349            local_transactions_config: config.local_transactions_config.clone(),
1350            minimal_protocol_basefee: config.minimal_protocol_basefee,
1351            block_gas_limit: config.gas_limit,
1352            ..Default::default()
1353        }
1354    }
1355
1356    /// Returns an iterator over all _unique_ hashes in the pool
1357    #[expect(dead_code)]
1358    pub(crate) fn hashes_iter(&self) -> impl Iterator<Item = TxHash> + '_ {
1359        self.by_hash.keys().copied()
1360    }
1361
1362    /// Returns an iterator over all transactions in the pool
1363    pub(crate) fn transactions_iter(
1364        &self,
1365    ) -> impl Iterator<Item = &Arc<ValidPoolTransaction<T>>> + '_ {
1366        self.by_hash.values()
1367    }
1368
1369    /// Returns if the transaction for the given hash is already included in this pool
1370    pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
1371        self.by_hash.contains_key(tx_hash)
1372    }
1373
1374    /// Returns the internal transaction with additional metadata
1375    pub(crate) fn get(&self, id: &TransactionId) -> Option<&PoolInternalTransaction<T>> {
1376        self.txs.get(id)
1377    }
1378
1379    /// Increments the transaction counter for the sender
1380    pub(crate) fn tx_inc(&mut self, sender: SenderId) {
1381        let count = self.tx_counter.entry(sender).or_default();
1382        *count += 1;
1383        self.metrics.all_transactions_by_all_senders.increment(1.0);
1384    }
1385
1386    /// Decrements the transaction counter for the sender
1387    pub(crate) fn tx_decr(&mut self, sender: SenderId) {
1388        if let hash_map::Entry::Occupied(mut entry) = self.tx_counter.entry(sender) {
1389            let count = entry.get_mut();
1390            if *count == 1 {
1391                entry.remove();
1392                self.metrics.all_transactions_by_all_senders.decrement(1.0);
1393                return
1394            }
1395            *count -= 1;
1396            self.metrics.all_transactions_by_all_senders.decrement(1.0);
1397        }
1398    }
1399
1400    /// Updates the block specific info
1401    fn set_block_info(&mut self, block_info: BlockInfo) {
1402        let BlockInfo {
1403            block_gas_limit,
1404            last_seen_block_hash,
1405            last_seen_block_number,
1406            pending_basefee,
1407            pending_blob_fee,
1408        } = block_info;
1409        self.last_seen_block_number = last_seen_block_number;
1410        self.last_seen_block_hash = last_seen_block_hash;
1411
1412        self.pending_fees.base_fee = pending_basefee;
1413        self.metrics.base_fee.set(pending_basefee as f64);
1414
1415        self.block_gas_limit = block_gas_limit;
1416
1417        if let Some(pending_blob_fee) = pending_blob_fee {
1418            self.pending_fees.blob_fee = pending_blob_fee;
1419            self.metrics.blob_base_fee.set(pending_blob_fee as f64);
1420        }
1421    }
1422
1423    /// Updates the size metrics
1424    pub(crate) fn update_size_metrics(&self) {
1425        self.metrics.all_transactions_by_hash.set(self.by_hash.len() as f64);
1426        self.metrics.all_transactions_by_id.set(self.txs.len() as f64);
1427    }
1428
1429    /// Rechecks all transactions in the pool against the changes.
1430    ///
1431    /// Possible changes are:
1432    ///
1433    /// For all transactions:
1434    ///   - decreased basefee: promotes from `basefee` to `pending` sub-pool.
1435    ///   - increased basefee: demotes from `pending` to `basefee` sub-pool.
1436    ///
1437    /// Individually:
1438    ///   - decreased sender allowance: demote from (`basefee`|`pending`) to `queued`.
1439    ///   - increased sender allowance: promote from `queued` to
1440    ///       - `pending` if basefee condition is met.
1441    ///       - `basefee` if basefee condition is _not_ met.
1442    ///
1443    /// Additionally, this will also update the `cumulative_gas_used` for transactions of a sender
1444    /// that got transaction included in the block.
1445    pub(crate) fn update(
1446        &mut self,
1447        changed_accounts: &FxHashMap<SenderId, SenderInfo>,
1448    ) -> Vec<PoolUpdate> {
1449        // pre-allocate a few updates
1450        let mut updates = Vec::with_capacity(64);
1451
1452        let mut iter = self.txs.iter_mut().peekable();
1453
1454        // Loop over all individual senders and update all affected transactions.
1455        // One sender may have up to `max_account_slots` transactions here, which means, worst case
1456        // `max_accounts_slots` need to be updated, for example if the first transaction is blocked
1457        // due to too low base fee.
1458        // However, we don't have to necessarily check every transaction of a sender. If no updates
1459        // are possible (nonce gap) then we can skip to the next sender.
1460
1461        // The `unique_sender` loop will process the first transaction of all senders, update its
1462        // state and internally update all consecutive transactions
1463        'transactions: while let Some((id, tx)) = iter.next() {
1464            macro_rules! next_sender {
1465                ($iter:ident) => {
1466                    'this: while let Some((peek, _)) = iter.peek() {
1467                        if peek.sender != id.sender {
1468                            break 'this
1469                        }
1470                        iter.next();
1471                    }
1472                };
1473            }
1474
1475            // track the balance if the sender was changed in the block
1476            // check if this is a changed account
1477            let changed_balance = if let Some(info) = changed_accounts.get(&id.sender) {
1478                // discard all transactions with a nonce lower than the current state nonce
1479                if id.nonce < info.state_nonce {
1480                    updates.push(PoolUpdate {
1481                        id: *tx.transaction.id(),
1482                        current: tx.subpool,
1483                        destination: Destination::Discard,
1484                    });
1485                    continue 'transactions
1486                }
1487
1488                let ancestor = TransactionId::ancestor(id.nonce, info.state_nonce, id.sender);
1489                // If there's no ancestor then this is the next transaction.
1490                if ancestor.is_none() {
1491                    tx.state.insert(TxState::NO_NONCE_GAPS);
1492                    tx.state.insert(TxState::NO_PARKED_ANCESTORS);
1493                    tx.cumulative_cost = U256::ZERO;
1494                    if tx.transaction.cost() > &info.balance {
1495                        // sender lacks sufficient funds to pay for this transaction
1496                        tx.state.remove(TxState::ENOUGH_BALANCE);
1497                    } else {
1498                        tx.state.insert(TxState::ENOUGH_BALANCE);
1499                    }
1500                }
1501
1502                Some(&info.balance)
1503            } else {
1504                None
1505            };
1506
1507            // If there's a nonce gap, we can shortcircuit, because there's nothing to update yet.
1508            if tx.state.has_nonce_gap() {
1509                next_sender!(iter);
1510                continue 'transactions
1511            }
1512
1513            // Since this is the first transaction of the sender, it has no parked ancestors
1514            tx.state.insert(TxState::NO_PARKED_ANCESTORS);
1515
1516            // Update the first transaction of this sender.
1517            Self::update_tx_base_fee(self.pending_fees.base_fee, tx);
1518            // Track if the transaction's sub-pool changed.
1519            Self::record_subpool_update(&mut updates, tx);
1520
1521            // Track blocking transactions.
1522            let mut has_parked_ancestor = !tx.state.is_pending();
1523
1524            let mut cumulative_cost = tx.next_cumulative_cost();
1525
1526            // the next expected nonce after this transaction: nonce + 1
1527            let mut next_nonce_in_line = tx.transaction.nonce().saturating_add(1);
1528
1529            // Update all consecutive transaction of this sender
1530            while let Some((peek, tx)) = iter.peek_mut() {
1531                if peek.sender != id.sender {
1532                    // Found the next sender we need to check
1533                    continue 'transactions
1534                }
1535
1536                if tx.transaction.nonce() == next_nonce_in_line {
1537                    // no longer nonce gapped
1538                    tx.state.insert(TxState::NO_NONCE_GAPS);
1539                } else {
1540                    // can short circuit if there's still a nonce gap
1541                    next_sender!(iter);
1542                    continue 'transactions
1543                }
1544
1545                // update for next iteration of this sender's loop
1546                next_nonce_in_line = next_nonce_in_line.saturating_add(1);
1547
1548                // update cumulative cost
1549                tx.cumulative_cost = cumulative_cost;
1550                // Update for next transaction
1551                cumulative_cost = tx.next_cumulative_cost();
1552
1553                // If the account changed in the block, check the balance.
1554                if let Some(changed_balance) = changed_balance {
1555                    if &cumulative_cost > changed_balance {
1556                        // sender lacks sufficient funds to pay for this transaction
1557                        tx.state.remove(TxState::ENOUGH_BALANCE);
1558                    } else {
1559                        tx.state.insert(TxState::ENOUGH_BALANCE);
1560                    }
1561                }
1562
1563                // Update ancestor condition.
1564                if has_parked_ancestor {
1565                    tx.state.remove(TxState::NO_PARKED_ANCESTORS);
1566                } else {
1567                    tx.state.insert(TxState::NO_PARKED_ANCESTORS);
1568                }
1569                has_parked_ancestor = !tx.state.is_pending();
1570
1571                // Update and record sub-pool changes.
1572                Self::update_tx_base_fee(self.pending_fees.base_fee, tx);
1573                Self::record_subpool_update(&mut updates, tx);
1574
1575                // Advance iterator
1576                iter.next();
1577            }
1578        }
1579
1580        updates
1581    }
1582
1583    /// This will update the transaction's `subpool` based on its state.
1584    ///
1585    /// If the sub-pool derived from the state differs from the current pool, it will record a
1586    /// `PoolUpdate` for this transaction to move it to the new sub-pool.
1587    fn record_subpool_update(updates: &mut Vec<PoolUpdate>, tx: &mut PoolInternalTransaction<T>) {
1588        let current_pool = tx.subpool;
1589        tx.subpool = tx.state.into();
1590        if current_pool != tx.subpool {
1591            updates.push(PoolUpdate {
1592                id: *tx.transaction.id(),
1593                current: current_pool,
1594                destination: tx.subpool.into(),
1595            })
1596        }
1597    }
1598
1599    /// Rechecks the transaction's dynamic fee condition.
1600    fn update_tx_base_fee(pending_block_base_fee: u64, tx: &mut PoolInternalTransaction<T>) {
1601        // Recheck dynamic fee condition.
1602        match tx.transaction.max_fee_per_gas().cmp(&(pending_block_base_fee as u128)) {
1603            Ordering::Greater | Ordering::Equal => {
1604                tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
1605            }
1606            Ordering::Less => {
1607                tx.state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
1608            }
1609        }
1610    }
1611
1612    /// Returns an iterator over all transactions for the given sender, starting with the lowest
1613    /// nonce
1614    pub(crate) fn txs_iter(
1615        &self,
1616        sender: SenderId,
1617    ) -> impl Iterator<Item = (&TransactionId, &PoolInternalTransaction<T>)> + '_ {
1618        self.txs
1619            .range((sender.start_bound(), Unbounded))
1620            .take_while(move |(other, _)| sender == other.sender)
1621    }
1622
1623    /// Returns a mutable iterator over all transactions for the given sender, starting with the
1624    /// lowest nonce
1625    #[cfg(test)]
1626    #[expect(dead_code)]
1627    pub(crate) fn txs_iter_mut(
1628        &mut self,
1629        sender: SenderId,
1630    ) -> impl Iterator<Item = (&TransactionId, &mut PoolInternalTransaction<T>)> + '_ {
1631        self.txs
1632            .range_mut((sender.start_bound(), Unbounded))
1633            .take_while(move |(other, _)| sender == other.sender)
1634    }
1635
1636    /// Returns all transactions that _follow_ after the given id and have the same sender.
1637    ///
1638    /// NOTE: The range is _exclusive_
1639    pub(crate) fn descendant_txs_exclusive<'a, 'b: 'a>(
1640        &'a self,
1641        id: &'b TransactionId,
1642    ) -> impl Iterator<Item = (&'a TransactionId, &'a PoolInternalTransaction<T>)> + 'a {
1643        self.txs.range((Excluded(id), Unbounded)).take_while(|(other, _)| id.sender == other.sender)
1644    }
1645
1646    /// Returns all transactions that _follow_ after the given id but have the same sender.
1647    ///
1648    /// NOTE: The range is _inclusive_: if the transaction that belongs to `id` it will be the
1649    /// first value.
1650    pub(crate) fn descendant_txs_inclusive<'a, 'b: 'a>(
1651        &'a self,
1652        id: &'b TransactionId,
1653    ) -> impl Iterator<Item = (&'a TransactionId, &'a PoolInternalTransaction<T>)> + 'a {
1654        self.txs.range(id..).take_while(|(other, _)| id.sender == other.sender)
1655    }
1656
1657    /// Returns all mutable transactions that _follow_ after the given id but have the same sender.
1658    ///
1659    /// NOTE: The range is _inclusive_: if the transaction that belongs to `id` it field be the
1660    /// first value.
1661    pub(crate) fn descendant_txs_mut<'a, 'b: 'a>(
1662        &'a mut self,
1663        id: &'b TransactionId,
1664    ) -> impl Iterator<Item = (&'a TransactionId, &'a mut PoolInternalTransaction<T>)> + 'a {
1665        self.txs.range_mut(id..).take_while(|(other, _)| id.sender == other.sender)
1666    }
1667
1668    /// Removes a transaction from the set using its hash.
1669    pub(crate) fn remove_transaction_by_hash(
1670        &mut self,
1671        tx_hash: &B256,
1672    ) -> Option<(Arc<ValidPoolTransaction<T>>, SubPool)> {
1673        let tx = self.by_hash.remove(tx_hash)?;
1674        let internal = self.txs.remove(&tx.transaction_id)?;
1675        self.remove_auths(&internal);
1676        // decrement the counter for the sender.
1677        self.tx_decr(tx.sender_id());
1678        Some((tx, internal.subpool))
1679    }
1680
1681    /// Removes a transaction from the set using its id.
1682    ///
1683    /// This is intended for processing updates after state changes.
1684    pub(crate) fn remove_transaction_by_id(
1685        &mut self,
1686        tx_id: &TransactionId,
1687    ) -> Option<(Arc<ValidPoolTransaction<T>>, SubPool)> {
1688        let internal = self.txs.remove(tx_id)?;
1689        let tx = self.by_hash.remove(internal.transaction.hash())?;
1690        self.remove_auths(&internal);
1691        // decrement the counter for the sender.
1692        self.tx_decr(tx.sender_id());
1693        Some((tx, internal.subpool))
1694    }
1695
1696    /// If a tx is removed (_not_ mined), all descendants are set to parked due to the nonce gap
1697    pub(crate) fn park_descendant_transactions(
1698        &mut self,
1699        tx_id: &TransactionId,
1700    ) -> Vec<PoolUpdate> {
1701        let mut updates = Vec::new();
1702
1703        for (id, tx) in self.descendant_txs_mut(tx_id) {
1704            let current_pool = tx.subpool;
1705
1706            tx.state.remove(TxState::NO_NONCE_GAPS);
1707
1708            // update the pool based on the state.
1709            tx.subpool = tx.state.into();
1710
1711            // check if anything changed.
1712            if current_pool != tx.subpool {
1713                updates.push(PoolUpdate {
1714                    id: *id,
1715                    current: current_pool,
1716                    destination: tx.subpool.into(),
1717                })
1718            }
1719        }
1720
1721        updates
1722    }
1723
1724    /// Removes a transaction from the set.
1725    ///
1726    /// This will _not_ trigger additional updates, because descendants without nonce gaps are
1727    /// already in the pending pool, and this transaction will be the first transaction of the
1728    /// sender in this pool.
1729    pub(crate) fn remove_transaction(
1730        &mut self,
1731        id: &TransactionId,
1732    ) -> Option<(Arc<ValidPoolTransaction<T>>, SubPool)> {
1733        let internal = self.txs.remove(id)?;
1734
1735        // decrement the counter for the sender.
1736        self.tx_decr(internal.transaction.sender_id());
1737
1738        let result =
1739            self.by_hash.remove(internal.transaction.hash()).map(|tx| (tx, internal.subpool));
1740
1741        self.remove_auths(&internal);
1742
1743        result
1744    }
1745
1746    /// Removes any pending auths for the given transaction.
1747    ///
1748    /// This is a noop for non EIP-7702 transactions.
1749    fn remove_auths(&mut self, tx: &PoolInternalTransaction<T>) {
1750        let Some(auths) = &tx.transaction.authority_ids else { return };
1751
1752        let tx_hash = tx.transaction.hash();
1753        for auth in auths {
1754            if let Some(list) = self.auths.get_mut(auth) {
1755                list.remove(tx_hash);
1756                if list.is_empty() {
1757                    self.auths.remove(auth);
1758                }
1759            }
1760        }
1761    }
1762
1763    /// Checks if the given transaction's type conflicts with an existing transaction.
1764    ///
1765    /// See also [`ValidPoolTransaction::tx_type_conflicts_with`].
1766    ///
1767    /// Caution: This assumes that mutually exclusive invariant is always true for the same sender.
1768    #[inline]
1769    fn contains_conflicting_transaction(&self, tx: &ValidPoolTransaction<T>) -> bool {
1770        self.txs_iter(tx.transaction_id.sender)
1771            .next()
1772            .is_some_and(|(_, existing)| tx.tx_type_conflicts_with(&existing.transaction))
1773    }
1774
1775    /// Additional checks for a new transaction.
1776    ///
1777    /// This will enforce all additional rules in the context of this pool, such as:
1778    ///   - Spam protection: reject new non-local transaction from a sender that exhausted its slot
1779    ///     capacity.
1780    ///   - Gas limit: reject transactions if they exceed a block's maximum gas.
1781    ///   - Ensures transaction types are not conflicting for the sender: blob vs normal
1782    ///     transactions are mutually exclusive for the same sender.
1783    fn ensure_valid(
1784        &self,
1785        transaction: ValidPoolTransaction<T>,
1786        on_chain_nonce: u64,
1787    ) -> Result<ValidPoolTransaction<T>, InsertErr<T>> {
1788        if !self.local_transactions_config.is_local(transaction.origin, transaction.sender_ref()) {
1789            let current_txs =
1790                self.tx_counter.get(&transaction.sender_id()).copied().unwrap_or_default();
1791
1792            // Reject transactions if sender's capacity is exceeded.
1793            // If transaction's nonce matches on-chain nonce always let it through
1794            if current_txs >= self.max_account_slots && transaction.nonce() > on_chain_nonce {
1795                return Err(InsertErr::ExceededSenderTransactionsCapacity {
1796                    transaction: Arc::new(transaction),
1797                })
1798            }
1799        }
1800        if transaction.gas_limit() > self.block_gas_limit {
1801            return Err(InsertErr::TxGasLimitMoreThanAvailableBlockGas {
1802                block_gas_limit: self.block_gas_limit,
1803                tx_gas_limit: transaction.gas_limit(),
1804                transaction: Arc::new(transaction),
1805            })
1806        }
1807
1808        if self.contains_conflicting_transaction(&transaction) {
1809            // blob vs non blob transactions are mutually exclusive for the same sender
1810            return Err(InsertErr::TxTypeConflict { transaction: Arc::new(transaction) })
1811        }
1812
1813        Ok(transaction)
1814    }
1815
1816    /// Enforces additional constraints for blob transactions before attempting to insert:
1817    ///    - new blob transactions must not have any nonce gaps
1818    ///    - blob transactions cannot go into overdraft
1819    ///    - replacement blob transaction with a higher fee must not shift an already propagated
1820    ///      descending blob transaction into overdraft
1821    fn ensure_valid_blob_transaction(
1822        &self,
1823        new_blob_tx: ValidPoolTransaction<T>,
1824        on_chain_balance: U256,
1825        ancestor: Option<TransactionId>,
1826    ) -> Result<ValidPoolTransaction<T>, InsertErr<T>> {
1827        if let Some(ancestor) = ancestor {
1828            let Some(ancestor_tx) = self.txs.get(&ancestor) else {
1829                // ancestor tx is missing, so we can't insert the new blob
1830                self.metrics.blob_transactions_nonce_gaps.increment(1);
1831                return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) })
1832            };
1833            if ancestor_tx.state.has_nonce_gap() {
1834                // the ancestor transaction already has a nonce gap, so we can't insert the new
1835                // blob
1836                self.metrics.blob_transactions_nonce_gaps.increment(1);
1837                return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) })
1838            }
1839
1840            // the max cost executing this transaction requires
1841            let mut cumulative_cost = ancestor_tx.next_cumulative_cost() + new_blob_tx.cost();
1842
1843            // check if the new blob would go into overdraft
1844            if cumulative_cost > on_chain_balance {
1845                // the transaction would go into overdraft
1846                return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) })
1847            }
1848
1849            // ensure that a replacement would not shift already propagated blob transactions into
1850            // overdraft
1851            let id = new_blob_tx.transaction_id;
1852            let mut descendants = self.descendant_txs_inclusive(&id).peekable();
1853            if let Some((maybe_replacement, _)) = descendants.peek() &&
1854                **maybe_replacement == new_blob_tx.transaction_id
1855            {
1856                // replacement transaction
1857                descendants.next();
1858
1859                // check if any of descendant blob transactions should be shifted into overdraft
1860                for (_, tx) in descendants {
1861                    cumulative_cost += tx.transaction.cost();
1862                    if tx.transaction.is_eip4844() && cumulative_cost > on_chain_balance {
1863                        // the transaction would shift
1864                        return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) })
1865                    }
1866                }
1867            }
1868        } else if new_blob_tx.cost() > &on_chain_balance {
1869            // the transaction would go into overdraft
1870            return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) })
1871        }
1872
1873        Ok(new_blob_tx)
1874    }
1875
1876    /// Inserts a new _valid_ transaction into the pool.
1877    ///
1878    /// If the transaction already exists, it will be replaced if not underpriced.
1879    /// Returns info to which sub-pool the transaction should be moved.
1880    /// Also returns a set of pool updates triggered by this insert, that need to be handled by the
1881    /// caller.
1882    ///
1883    /// These can include:
1884    ///      - closing nonce gaps of descendant transactions
1885    ///      - enough balance updates
1886    ///
1887    /// Note: For EIP-4844 blob transactions additional constraints are enforced:
1888    ///      - new blob transactions must not have any nonce gaps
1889    ///      - blob transactions cannot go into overdraft
1890    ///
1891    /// ## Transaction type Exclusivity
1892    ///
1893    /// The pool enforces exclusivity of eip-4844 blob vs non-blob transactions on a per sender
1894    /// basis:
1895    ///  - If the pool already includes a blob transaction from the `transaction`'s sender, then the
1896    ///    `transaction` must also be a blob transaction
1897    ///  - If the pool already includes a non-blob transaction from the `transaction`'s sender, then
1898    ///    the `transaction` must _not_ be a blob transaction.
1899    ///
1900    /// In other words, the presence of blob transactions exclude non-blob transactions and vice
1901    /// versa.
1902    ///
1903    /// ## Replacements
1904    ///
1905    /// The replacement candidate must satisfy given price bump constraints: replacement candidate
1906    /// must not be underpriced
1907    pub(crate) fn insert_tx(
1908        &mut self,
1909        transaction: ValidPoolTransaction<T>,
1910        on_chain_balance: U256,
1911        on_chain_nonce: u64,
1912    ) -> InsertResult<T> {
1913        assert!(on_chain_nonce <= transaction.nonce(), "Invalid transaction");
1914
1915        let mut transaction = self.ensure_valid(transaction, on_chain_nonce)?;
1916
1917        let inserted_tx_id = *transaction.id();
1918        let mut state = TxState::default();
1919        let mut cumulative_cost = U256::ZERO;
1920        let mut updates = Vec::new();
1921
1922        // Current tx does not exceed block gas limit after ensure_valid check
1923        state.insert(TxState::NOT_TOO_MUCH_GAS);
1924
1925        // identifier of the ancestor transaction, will be None if the transaction is the next tx of
1926        // the sender
1927        let ancestor = TransactionId::ancestor(
1928            transaction.transaction.nonce(),
1929            on_chain_nonce,
1930            inserted_tx_id.sender,
1931        );
1932
1933        // before attempting to insert a blob transaction, we need to ensure that additional
1934        // constraints are met that only apply to blob transactions
1935        if transaction.is_eip4844() {
1936            state.insert(TxState::BLOB_TRANSACTION);
1937
1938            transaction =
1939                self.ensure_valid_blob_transaction(transaction, on_chain_balance, ancestor)?;
1940            let blob_fee_cap = transaction.transaction.max_fee_per_blob_gas().unwrap_or_default();
1941            if blob_fee_cap >= self.pending_fees.blob_fee {
1942                state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
1943            }
1944        } else {
1945            // Non-EIP4844 transaction always satisfy the blob fee cap condition
1946            state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
1947        }
1948
1949        let transaction = Arc::new(transaction);
1950
1951        // If there's no ancestor tx then this is the next transaction.
1952        if ancestor.is_none() {
1953            state.insert(TxState::NO_NONCE_GAPS);
1954            state.insert(TxState::NO_PARKED_ANCESTORS);
1955        }
1956
1957        // Check dynamic fee
1958        let fee_cap = transaction.max_fee_per_gas();
1959
1960        if fee_cap < self.minimal_protocol_basefee as u128 {
1961            return Err(InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap })
1962        }
1963        if fee_cap >= self.pending_fees.base_fee as u128 {
1964            state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
1965        }
1966
1967        // placeholder for the replaced transaction, if any
1968        let mut replaced_tx = None;
1969
1970        let pool_tx = PoolInternalTransaction {
1971            transaction: Arc::clone(&transaction),
1972            subpool: state.into(),
1973            state,
1974            cumulative_cost,
1975        };
1976
1977        // try to insert the transaction
1978        match self.txs.entry(*transaction.id()) {
1979            Entry::Vacant(entry) => {
1980                // Insert the transaction in both maps
1981                self.by_hash.insert(*pool_tx.transaction.hash(), pool_tx.transaction.clone());
1982                entry.insert(pool_tx);
1983            }
1984            Entry::Occupied(mut entry) => {
1985                // Transaction with the same nonce already exists: replacement candidate
1986                let existing_transaction = entry.get().transaction.as_ref();
1987                let maybe_replacement = transaction.as_ref();
1988
1989                // Ensure the new transaction is not underpriced
1990                if existing_transaction.is_underpriced(maybe_replacement, &self.price_bumps) {
1991                    return Err(InsertErr::Underpriced {
1992                        transaction: pool_tx.transaction,
1993                        existing: *entry.get().transaction.hash(),
1994                    })
1995                }
1996                let new_hash = *pool_tx.transaction.hash();
1997                let new_transaction = pool_tx.transaction.clone();
1998                let replaced = entry.insert(pool_tx);
1999                self.by_hash.remove(replaced.transaction.hash());
2000                self.by_hash.insert(new_hash, new_transaction);
2001
2002                self.remove_auths(&replaced);
2003
2004                // also remove the hash
2005                replaced_tx = Some((replaced.transaction, replaced.subpool));
2006            }
2007        }
2008
2009        if let Some(auths) = &transaction.authority_ids {
2010            let tx_hash = transaction.hash();
2011            for auth in auths {
2012                self.auths.entry(*auth).or_default().insert(*tx_hash);
2013            }
2014        }
2015
2016        // The next transaction of this sender
2017        let on_chain_id = TransactionId::new(transaction.sender_id(), on_chain_nonce);
2018        {
2019            // Tracks the next nonce we expect if the transactions are gapless
2020            let mut next_nonce = on_chain_id.nonce;
2021
2022            // We need to find out if the next transaction of the sender is considered pending
2023            // The direct descendant has _no_ parked ancestors because the `on_chain_nonce` is
2024            // pending, so we can set this to `false`
2025            let mut has_parked_ancestor = false;
2026
2027            // Traverse all future transactions of the sender starting with the on chain nonce, and
2028            // update existing transactions: `[on_chain_nonce,..]`
2029            for (id, tx) in self.descendant_txs_mut(&on_chain_id) {
2030                let current_pool = tx.subpool;
2031
2032                // If there's a nonce gap, we can shortcircuit
2033                if next_nonce != id.nonce {
2034                    break
2035                }
2036
2037                // close the nonce gap
2038                tx.state.insert(TxState::NO_NONCE_GAPS);
2039
2040                // set cumulative cost
2041                tx.cumulative_cost = cumulative_cost;
2042
2043                // Update for next transaction
2044                cumulative_cost = tx.next_cumulative_cost();
2045
2046                if cumulative_cost > on_chain_balance {
2047                    // sender lacks sufficient funds to pay for this transaction
2048                    tx.state.remove(TxState::ENOUGH_BALANCE);
2049                } else {
2050                    tx.state.insert(TxState::ENOUGH_BALANCE);
2051                }
2052
2053                // Update ancestor condition.
2054                if has_parked_ancestor {
2055                    tx.state.remove(TxState::NO_PARKED_ANCESTORS);
2056                } else {
2057                    tx.state.insert(TxState::NO_PARKED_ANCESTORS);
2058                }
2059                has_parked_ancestor = !tx.state.is_pending();
2060
2061                // update the pool based on the state
2062                tx.subpool = tx.state.into();
2063
2064                if inserted_tx_id.eq(id) {
2065                    // if it is the new transaction, track its updated state
2066                    state = tx.state;
2067                } else {
2068                    // check if anything changed
2069                    if current_pool != tx.subpool {
2070                        updates.push(PoolUpdate {
2071                            id: *id,
2072                            current: current_pool,
2073                            destination: tx.subpool.into(),
2074                        })
2075                    }
2076                }
2077
2078                // increment for next iteration
2079                next_nonce = id.next_nonce();
2080            }
2081        }
2082
2083        // If this wasn't a replacement transaction we need to update the counter.
2084        if replaced_tx.is_none() {
2085            self.tx_inc(inserted_tx_id.sender);
2086        }
2087
2088        self.update_size_metrics();
2089
2090        Ok(InsertOk { transaction, move_to: state.into(), state, replaced_tx, updates })
2091    }
2092
2093    /// Number of transactions in the entire pool
2094    pub(crate) fn len(&self) -> usize {
2095        self.txs.len()
2096    }
2097
2098    /// Whether the pool is empty
2099    pub(crate) fn is_empty(&self) -> bool {
2100        self.txs.is_empty()
2101    }
2102
2103    /// Asserts that the bijection between `by_hash` and `txs` is valid.
2104    #[cfg(any(test, feature = "test-utils"))]
2105    pub(crate) fn assert_invariants(&self) {
2106        assert_eq!(self.by_hash.len(), self.txs.len(), "by_hash.len() != txs.len()");
2107        assert!(self.auths.len() <= self.txs.len(), "auths.len() > txs.len()");
2108    }
2109}
2110
2111#[cfg(test)]
2112impl<T: PoolTransaction> AllTransactions<T> {
2113    /// This function retrieves the number of transactions stored in the pool for a specific sender.
2114    ///
2115    /// If there are no transactions for the given sender, it returns zero by default.
2116    pub(crate) fn tx_count(&self, sender: SenderId) -> usize {
2117        self.tx_counter.get(&sender).copied().unwrap_or_default()
2118    }
2119}
2120
2121impl<T: PoolTransaction> Default for AllTransactions<T> {
2122    fn default() -> Self {
2123        Self {
2124            max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
2125            minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE,
2126            block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
2127            by_hash: Default::default(),
2128            txs: Default::default(),
2129            tx_counter: Default::default(),
2130            last_seen_block_number: Default::default(),
2131            last_seen_block_hash: Default::default(),
2132            pending_fees: Default::default(),
2133            price_bumps: Default::default(),
2134            local_transactions_config: Default::default(),
2135            auths: Default::default(),
2136            metrics: Default::default(),
2137        }
2138    }
2139}
2140
2141/// Represents updated fees for the pending block.
2142#[derive(Debug, Clone)]
2143pub(crate) struct PendingFees {
2144    /// The pending base fee
2145    pub(crate) base_fee: u64,
2146    /// The pending blob fee
2147    pub(crate) blob_fee: u128,
2148}
2149
2150impl Default for PendingFees {
2151    fn default() -> Self {
2152        Self { base_fee: Default::default(), blob_fee: BLOB_TX_MIN_BLOB_GASPRICE }
2153    }
2154}
2155
2156/// Result type for inserting a transaction
2157pub(crate) type InsertResult<T> = Result<InsertOk<T>, InsertErr<T>>;
2158
2159/// Err variant of `InsertResult`
2160#[derive(Debug)]
2161pub(crate) enum InsertErr<T: PoolTransaction> {
2162    /// Attempted to replace existing transaction, but was underpriced
2163    Underpriced {
2164        transaction: Arc<ValidPoolTransaction<T>>,
2165        #[expect(dead_code)]
2166        existing: TxHash,
2167    },
2168    /// Attempted to insert a blob transaction with a nonce gap
2169    BlobTxHasNonceGap { transaction: Arc<ValidPoolTransaction<T>> },
2170    /// Attempted to insert a transaction that would overdraft the sender's balance at the time of
2171    /// insertion.
2172    Overdraft { transaction: Arc<ValidPoolTransaction<T>> },
2173    /// The transactions feeCap is lower than the chain's minimum fee requirement.
2174    ///
2175    /// See also [`MIN_PROTOCOL_BASE_FEE`]
2176    FeeCapBelowMinimumProtocolFeeCap { transaction: Arc<ValidPoolTransaction<T>>, fee_cap: u128 },
2177    /// Sender currently exceeds the configured limit for max account slots.
2178    ///
2179    /// The sender can be considered a spammer at this point.
2180    ExceededSenderTransactionsCapacity { transaction: Arc<ValidPoolTransaction<T>> },
2181    /// Transaction gas limit exceeds block's gas limit
2182    TxGasLimitMoreThanAvailableBlockGas {
2183        transaction: Arc<ValidPoolTransaction<T>>,
2184        block_gas_limit: u64,
2185        tx_gas_limit: u64,
2186    },
2187    /// Thrown if the mutual exclusivity constraint (blob vs normal transaction) is violated.
2188    TxTypeConflict { transaction: Arc<ValidPoolTransaction<T>> },
2189}
2190
2191/// Transaction was successfully inserted into the pool
2192#[derive(Debug)]
2193pub(crate) struct InsertOk<T: PoolTransaction> {
2194    /// Ref to the inserted transaction.
2195    transaction: Arc<ValidPoolTransaction<T>>,
2196    /// Where to move the transaction to.
2197    move_to: SubPool,
2198    /// Current state of the inserted tx.
2199    state: TxState,
2200    /// The transaction that was replaced by this.
2201    replaced_tx: Option<(Arc<ValidPoolTransaction<T>>, SubPool)>,
2202    /// Additional updates to transactions affected by this change.
2203    updates: Vec<PoolUpdate>,
2204}
2205
2206/// The internal transaction typed used by `AllTransactions` which also additional info used for
2207/// determining the current state of the transaction.
2208#[derive(Debug)]
2209pub(crate) struct PoolInternalTransaction<T: PoolTransaction> {
2210    /// The actual transaction object.
2211    pub(crate) transaction: Arc<ValidPoolTransaction<T>>,
2212    /// The `SubPool` that currently contains this transaction.
2213    pub(crate) subpool: SubPool,
2214    /// Keeps track of the current state of the transaction and therefore in which subpool it
2215    /// should reside
2216    pub(crate) state: TxState,
2217    /// The total cost all transactions before this transaction.
2218    ///
2219    /// This is the combined `cost` of all transactions from the same sender that currently
2220    /// come before this transaction.
2221    pub(crate) cumulative_cost: U256,
2222}
2223
2224// === impl PoolInternalTransaction ===
2225
2226impl<T: PoolTransaction> PoolInternalTransaction<T> {
2227    fn next_cumulative_cost(&self) -> U256 {
2228        self.cumulative_cost + self.transaction.cost()
2229    }
2230}
2231
2232/// Stores relevant context about a sender.
2233#[derive(Debug, Clone, Default)]
2234pub(crate) struct SenderInfo {
2235    /// current nonce of the sender.
2236    pub(crate) state_nonce: u64,
2237    /// Balance of the sender at the current point.
2238    pub(crate) balance: U256,
2239}
2240
2241// === impl SenderInfo ===
2242
2243impl SenderInfo {
2244    /// Updates the info with the new values.
2245    const fn update(&mut self, state_nonce: u64, balance: U256) {
2246        *self = Self { state_nonce, balance };
2247    }
2248}
2249
2250#[cfg(test)]
2251mod tests {
2252    use super::*;
2253    use crate::{
2254        test_utils::{MockOrdering, MockTransaction, MockTransactionFactory, MockTransactionSet},
2255        traits::TransactionOrigin,
2256        SubPoolLimit,
2257    };
2258    use alloy_consensus::{Transaction, TxType};
2259    use alloy_primitives::address;
2260
2261    #[test]
2262    fn test_insert_blob() {
2263        let on_chain_balance = U256::MAX;
2264        let on_chain_nonce = 0;
2265        let mut f = MockTransactionFactory::default();
2266        let mut pool = AllTransactions::default();
2267        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2268        let valid_tx = f.validated(tx);
2269        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2270            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2271        assert!(updates.is_empty());
2272        assert!(replaced_tx.is_none());
2273        assert!(state.contains(TxState::NO_NONCE_GAPS));
2274        assert!(state.contains(TxState::ENOUGH_BALANCE));
2275        assert!(state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2276        assert_eq!(move_to, SubPool::Pending);
2277
2278        let inserted = pool.txs.get(&valid_tx.transaction_id).unwrap();
2279        assert_eq!(inserted.subpool, SubPool::Pending);
2280    }
2281
2282    #[test]
2283    fn test_insert_blob_not_enough_blob_fee() {
2284        let on_chain_balance = U256::MAX;
2285        let on_chain_nonce = 0;
2286        let mut f = MockTransactionFactory::default();
2287        let mut pool = AllTransactions {
2288            pending_fees: PendingFees { blob_fee: 10_000_000, ..Default::default() },
2289            ..Default::default()
2290        };
2291        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2292        pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap() + 1;
2293        let valid_tx = f.validated(tx);
2294        let InsertOk { state, .. } =
2295            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2296        assert!(state.contains(TxState::NO_NONCE_GAPS));
2297        assert!(!state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2298
2299        let _ = pool.txs.get(&valid_tx.transaction_id).unwrap();
2300    }
2301
2302    #[test]
2303    fn test_valid_tx_with_decreasing_blob_fee() {
2304        let on_chain_balance = U256::MAX;
2305        let on_chain_nonce = 0;
2306        let mut f = MockTransactionFactory::default();
2307        let mut pool = AllTransactions {
2308            pending_fees: PendingFees { blob_fee: 10_000_000, ..Default::default() },
2309            ..Default::default()
2310        };
2311        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2312
2313        pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap() + 1;
2314        let valid_tx = f.validated(tx.clone());
2315        let InsertOk { state, .. } =
2316            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2317        assert!(state.contains(TxState::NO_NONCE_GAPS));
2318        assert!(!state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2319
2320        let _ = pool.txs.get(&valid_tx.transaction_id).unwrap();
2321        pool.remove_transaction(&valid_tx.transaction_id);
2322
2323        pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap();
2324        let InsertOk { state, .. } =
2325            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2326        assert!(state.contains(TxState::NO_NONCE_GAPS));
2327        assert!(state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2328    }
2329
2330    #[test]
2331    fn test_demote_valid_tx_with_increasing_blob_fee() {
2332        let on_chain_balance = U256::MAX;
2333        let on_chain_nonce = 0;
2334        let mut f = MockTransactionFactory::default();
2335        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2336        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2337
2338        // set block info so the tx is initially underpriced w.r.t. blob fee
2339        let mut block_info = pool.block_info();
2340        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap());
2341        pool.set_block_info(block_info);
2342
2343        let validated = f.validated(tx.clone());
2344        let id = *validated.id();
2345        pool.add_transaction(validated, on_chain_balance, on_chain_nonce, None).unwrap();
2346
2347        // assert pool lengths
2348        assert!(pool.blob_pool.is_empty());
2349        assert_eq!(pool.pending_pool.len(), 1);
2350
2351        // check tx state and derived subpool
2352        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2353        assert!(internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2354        assert_eq!(internal_tx.subpool, SubPool::Pending);
2355
2356        // set block info so the pools are updated
2357        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap() + 1);
2358        pool.set_block_info(block_info);
2359
2360        // check that the tx is promoted
2361        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2362        assert!(!internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2363        assert_eq!(internal_tx.subpool, SubPool::Blob);
2364
2365        // make sure the blob transaction was promoted into the pending pool
2366        assert_eq!(pool.blob_pool.len(), 1);
2367        assert!(pool.pending_pool.is_empty());
2368    }
2369
2370    #[test]
2371    fn test_promote_valid_tx_with_decreasing_blob_fee() {
2372        let on_chain_balance = U256::MAX;
2373        let on_chain_nonce = 0;
2374        let mut f = MockTransactionFactory::default();
2375        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2376        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2377
2378        // set block info so the tx is initially underpriced w.r.t. blob fee
2379        let mut block_info = pool.block_info();
2380        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap() + 1);
2381        pool.set_block_info(block_info);
2382
2383        let validated = f.validated(tx.clone());
2384        let id = *validated.id();
2385        pool.add_transaction(validated, on_chain_balance, on_chain_nonce, None).unwrap();
2386
2387        // assert pool lengths
2388        assert!(pool.pending_pool.is_empty());
2389        assert_eq!(pool.blob_pool.len(), 1);
2390
2391        // check tx state and derived subpool
2392        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2393        assert!(!internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2394        assert_eq!(internal_tx.subpool, SubPool::Blob);
2395
2396        // set block info so the pools are updated
2397        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap());
2398        pool.set_block_info(block_info);
2399
2400        // check that the tx is promoted
2401        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2402        assert!(internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2403        assert_eq!(internal_tx.subpool, SubPool::Pending);
2404
2405        // make sure the blob transaction was promoted into the pending pool
2406        assert_eq!(pool.pending_pool.len(), 1);
2407        assert!(pool.blob_pool.is_empty());
2408    }
2409
2410    /// A struct representing a txpool promotion test instance
2411    #[derive(Debug, PartialEq, Eq, Clone, Hash)]
2412    struct PromotionTest {
2413        /// The basefee at the start of the test
2414        basefee: u64,
2415        /// The blobfee at the start of the test
2416        blobfee: u128,
2417        /// The subpool at the start of the test
2418        subpool: SubPool,
2419        /// The basefee update
2420        basefee_update: u64,
2421        /// The blobfee update
2422        blobfee_update: u128,
2423        /// The subpool after the update
2424        new_subpool: SubPool,
2425    }
2426
2427    impl PromotionTest {
2428        /// Returns the test case for the opposite update
2429        const fn opposite(&self) -> Self {
2430            Self {
2431                basefee: self.basefee_update,
2432                blobfee: self.blobfee_update,
2433                subpool: self.new_subpool,
2434                blobfee_update: self.blobfee,
2435                basefee_update: self.basefee,
2436                new_subpool: self.subpool,
2437            }
2438        }
2439
2440        fn assert_subpool_lengths<T: TransactionOrdering>(
2441            &self,
2442            pool: &TxPool<T>,
2443            failure_message: String,
2444            check_subpool: SubPool,
2445        ) {
2446            match check_subpool {
2447                SubPool::Blob => {
2448                    assert_eq!(pool.blob_pool.len(), 1, "{failure_message}");
2449                    assert!(pool.pending_pool.is_empty(), "{failure_message}");
2450                    assert!(pool.basefee_pool.is_empty(), "{failure_message}");
2451                    assert!(pool.queued_pool.is_empty(), "{failure_message}");
2452                }
2453                SubPool::Pending => {
2454                    assert!(pool.blob_pool.is_empty(), "{failure_message}");
2455                    assert_eq!(pool.pending_pool.len(), 1, "{failure_message}");
2456                    assert!(pool.basefee_pool.is_empty(), "{failure_message}");
2457                    assert!(pool.queued_pool.is_empty(), "{failure_message}");
2458                }
2459                SubPool::BaseFee => {
2460                    assert!(pool.blob_pool.is_empty(), "{failure_message}");
2461                    assert!(pool.pending_pool.is_empty(), "{failure_message}");
2462                    assert_eq!(pool.basefee_pool.len(), 1, "{failure_message}");
2463                    assert!(pool.queued_pool.is_empty(), "{failure_message}");
2464                }
2465                SubPool::Queued => {
2466                    assert!(pool.blob_pool.is_empty(), "{failure_message}");
2467                    assert!(pool.pending_pool.is_empty(), "{failure_message}");
2468                    assert!(pool.basefee_pool.is_empty(), "{failure_message}");
2469                    assert_eq!(pool.queued_pool.len(), 1, "{failure_message}");
2470                }
2471            }
2472        }
2473
2474        /// Runs an assertion on the provided pool, ensuring that the transaction is in the correct
2475        /// subpool based on the starting condition of the test, assuming the pool contains only a
2476        /// single transaction.
2477        fn assert_single_tx_starting_subpool<T: TransactionOrdering>(&self, pool: &TxPool<T>) {
2478            self.assert_subpool_lengths(
2479                pool,
2480                format!("pool length check failed at start of test: {self:?}"),
2481                self.subpool,
2482            );
2483        }
2484
2485        /// Runs an assertion on the provided pool, ensuring that the transaction is in the correct
2486        /// subpool based on the ending condition of the test, assuming the pool contains only a
2487        /// single transaction.
2488        fn assert_single_tx_ending_subpool<T: TransactionOrdering>(&self, pool: &TxPool<T>) {
2489            self.assert_subpool_lengths(
2490                pool,
2491                format!("pool length check failed at end of test: {self:?}"),
2492                self.new_subpool,
2493            );
2494        }
2495    }
2496
2497    #[test]
2498    fn test_promote_blob_tx_with_both_pending_fee_updates() {
2499        // this exhaustively tests all possible promotion scenarios for a single transaction moving
2500        // between the blob and pending pool
2501        let on_chain_balance = U256::MAX;
2502        let on_chain_nonce = 0;
2503        let mut f = MockTransactionFactory::default();
2504        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2505
2506        let max_fee_per_blob_gas = tx.max_fee_per_blob_gas().unwrap();
2507        let max_fee_per_gas = tx.max_fee_per_gas() as u64;
2508
2509        // These are all _promotion_ tests or idempotent tests.
2510        let mut expected_promotions = vec![
2511            PromotionTest {
2512                blobfee: max_fee_per_blob_gas + 1,
2513                basefee: max_fee_per_gas + 1,
2514                subpool: SubPool::Blob,
2515                blobfee_update: max_fee_per_blob_gas + 1,
2516                basefee_update: max_fee_per_gas + 1,
2517                new_subpool: SubPool::Blob,
2518            },
2519            PromotionTest {
2520                blobfee: max_fee_per_blob_gas + 1,
2521                basefee: max_fee_per_gas + 1,
2522                subpool: SubPool::Blob,
2523                blobfee_update: max_fee_per_blob_gas,
2524                basefee_update: max_fee_per_gas + 1,
2525                new_subpool: SubPool::Blob,
2526            },
2527            PromotionTest {
2528                blobfee: max_fee_per_blob_gas + 1,
2529                basefee: max_fee_per_gas + 1,
2530                subpool: SubPool::Blob,
2531                blobfee_update: max_fee_per_blob_gas + 1,
2532                basefee_update: max_fee_per_gas,
2533                new_subpool: SubPool::Blob,
2534            },
2535            PromotionTest {
2536                blobfee: max_fee_per_blob_gas + 1,
2537                basefee: max_fee_per_gas + 1,
2538                subpool: SubPool::Blob,
2539                blobfee_update: max_fee_per_blob_gas,
2540                basefee_update: max_fee_per_gas,
2541                new_subpool: SubPool::Pending,
2542            },
2543            PromotionTest {
2544                blobfee: max_fee_per_blob_gas,
2545                basefee: max_fee_per_gas + 1,
2546                subpool: SubPool::Blob,
2547                blobfee_update: max_fee_per_blob_gas,
2548                basefee_update: max_fee_per_gas,
2549                new_subpool: SubPool::Pending,
2550            },
2551            PromotionTest {
2552                blobfee: max_fee_per_blob_gas + 1,
2553                basefee: max_fee_per_gas,
2554                subpool: SubPool::Blob,
2555                blobfee_update: max_fee_per_blob_gas,
2556                basefee_update: max_fee_per_gas,
2557                new_subpool: SubPool::Pending,
2558            },
2559            PromotionTest {
2560                blobfee: max_fee_per_blob_gas,
2561                basefee: max_fee_per_gas,
2562                subpool: SubPool::Pending,
2563                blobfee_update: max_fee_per_blob_gas,
2564                basefee_update: max_fee_per_gas,
2565                new_subpool: SubPool::Pending,
2566            },
2567        ];
2568
2569        // extend the test cases with reversed updates - this will add all _demotion_ tests
2570        let reversed = expected_promotions.iter().map(|test| test.opposite()).collect::<Vec<_>>();
2571        expected_promotions.extend(reversed);
2572
2573        // dedup the test cases
2574        let expected_promotions = expected_promotions.into_iter().collect::<HashSet<_>>();
2575
2576        for promotion_test in &expected_promotions {
2577            let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2578
2579            // set block info so the tx is initially underpriced w.r.t. blob fee
2580            let mut block_info = pool.block_info();
2581
2582            block_info.pending_blob_fee = Some(promotion_test.blobfee);
2583            block_info.pending_basefee = promotion_test.basefee;
2584            pool.set_block_info(block_info);
2585
2586            let validated = f.validated(tx.clone());
2587            let id = *validated.id();
2588            pool.add_transaction(validated, on_chain_balance, on_chain_nonce, None).unwrap();
2589
2590            // assert pool lengths
2591            promotion_test.assert_single_tx_starting_subpool(&pool);
2592
2593            // check tx state and derived subpool, it should not move into the blob pool
2594            let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2595            assert_eq!(
2596                internal_tx.subpool, promotion_test.subpool,
2597                "Subpools do not match at start of test: {promotion_test:?}"
2598            );
2599
2600            // set block info with new base fee
2601            block_info.pending_basefee = promotion_test.basefee_update;
2602            block_info.pending_blob_fee = Some(promotion_test.blobfee_update);
2603            pool.set_block_info(block_info);
2604
2605            // check tx state and derived subpool, it should not move into the blob pool
2606            let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2607            assert_eq!(
2608                internal_tx.subpool, promotion_test.new_subpool,
2609                "Subpools do not match at end of test: {promotion_test:?}"
2610            );
2611
2612            // assert new pool lengths
2613            promotion_test.assert_single_tx_ending_subpool(&pool);
2614        }
2615    }
2616
2617    #[test]
2618    fn test_insert_pending() {
2619        let on_chain_balance = U256::MAX;
2620        let on_chain_nonce = 0;
2621        let mut f = MockTransactionFactory::default();
2622        let mut pool = AllTransactions::default();
2623        let tx = MockTransaction::eip1559().inc_price().inc_limit();
2624        let valid_tx = f.validated(tx);
2625        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2626            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2627        assert!(updates.is_empty());
2628        assert!(replaced_tx.is_none());
2629        assert!(state.contains(TxState::NO_NONCE_GAPS));
2630        assert!(state.contains(TxState::ENOUGH_BALANCE));
2631        assert_eq!(move_to, SubPool::Pending);
2632
2633        let inserted = pool.txs.get(&valid_tx.transaction_id).unwrap();
2634        assert_eq!(inserted.subpool, SubPool::Pending);
2635    }
2636
2637    #[test]
2638    fn test_simple_insert() {
2639        let on_chain_balance = U256::ZERO;
2640        let on_chain_nonce = 0;
2641        let mut f = MockTransactionFactory::default();
2642        let mut pool = AllTransactions::default();
2643        let mut tx = MockTransaction::eip1559().inc_price().inc_limit();
2644        tx.set_priority_fee(100);
2645        tx.set_max_fee(100);
2646        let valid_tx = f.validated(tx.clone());
2647        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2648            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2649        assert!(updates.is_empty());
2650        assert!(replaced_tx.is_none());
2651        assert!(state.contains(TxState::NO_NONCE_GAPS));
2652        assert!(!state.contains(TxState::ENOUGH_BALANCE));
2653        assert_eq!(move_to, SubPool::Queued);
2654
2655        assert_eq!(pool.len(), 1);
2656        assert!(pool.contains(valid_tx.hash()));
2657        let expected_state = TxState::ENOUGH_FEE_CAP_BLOCK | TxState::NO_NONCE_GAPS;
2658        let inserted = pool.get(valid_tx.id()).unwrap();
2659        assert!(inserted.state.intersects(expected_state));
2660
2661        // insert the same tx again
2662        let res = pool.insert_tx(valid_tx, on_chain_balance, on_chain_nonce);
2663        res.unwrap_err();
2664        assert_eq!(pool.len(), 1);
2665
2666        let valid_tx = f.validated(tx.next());
2667        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2668            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2669
2670        assert!(updates.is_empty());
2671        assert!(replaced_tx.is_none());
2672        assert!(state.contains(TxState::NO_NONCE_GAPS));
2673        assert!(!state.contains(TxState::ENOUGH_BALANCE));
2674        assert_eq!(move_to, SubPool::Queued);
2675
2676        assert!(pool.contains(valid_tx.hash()));
2677        assert_eq!(pool.len(), 2);
2678        let inserted = pool.get(valid_tx.id()).unwrap();
2679        assert!(inserted.state.intersects(expected_state));
2680    }
2681
2682    #[test]
2683    // Test that on_canonical_state_change doesn't double-process transactions
2684    // when both fee and account updates would affect the same transaction
2685    fn test_on_canonical_state_change_no_double_processing() {
2686        let mut tx_factory = MockTransactionFactory::default();
2687        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2688
2689        // Setup: Create a sender with a transaction in basefee pool
2690        let tx = MockTransaction::eip1559().with_gas_price(50).with_gas_limit(30_000);
2691        let sender = tx.sender();
2692
2693        // Set high base fee initially
2694        let mut block_info = pool.block_info();
2695        block_info.pending_basefee = 100;
2696        pool.set_block_info(block_info);
2697
2698        let validated = tx_factory.validated(tx);
2699        pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap();
2700
2701        // Get sender_id after the transaction has been added
2702        let sender_id = tx_factory.ids.sender_id(&sender).unwrap();
2703
2704        assert_eq!(pool.basefee_pool.len(), 1);
2705        assert_eq!(pool.pending_pool.len(), 0);
2706
2707        // Now simulate a canonical state change with:
2708        // 1. Lower base fee (would promote tx)
2709        // 2. Account balance update (would also evaluate tx)
2710        block_info.pending_basefee = 40;
2711
2712        let mut changed_senders = FxHashMap::default();
2713        changed_senders.insert(
2714            sender_id,
2715            SenderInfo {
2716                state_nonce: 0,
2717                balance: U256::from(20_000_000), // Increased balance
2718            },
2719        );
2720
2721        let outcome = pool.on_canonical_state_change(
2722            block_info,
2723            vec![], // no mined transactions
2724            changed_senders,
2725            PoolUpdateKind::Commit,
2726        );
2727
2728        // Transaction should be promoted exactly once
2729        assert_eq!(pool.pending_pool.len(), 1, "Transaction should be in pending pool");
2730        assert_eq!(pool.basefee_pool.len(), 0, "Transaction should not be in basefee pool");
2731        assert_eq!(outcome.promoted.len(), 1, "Should report exactly one promotion");
2732    }
2733
2734    #[test]
2735    // Regression test: ensure we don't double-count promotions when base fee
2736    // decreases and account is updated. This test would fail before the fix.
2737    fn test_canonical_state_change_with_basefee_update_regression() {
2738        let mut tx_factory = MockTransactionFactory::default();
2739        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2740
2741        // Create transactions from different senders to test independently
2742        let sender_balance = U256::from(100_000_000);
2743
2744        // Sender 1: tx will be promoted (gas price 60 > new base fee 50)
2745        let tx1 =
2746            MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000).with_nonce(0);
2747        let sender1 = tx1.sender();
2748
2749        // Sender 2: tx will be promoted (gas price 55 > new base fee 50)
2750        let tx2 =
2751            MockTransaction::eip1559().with_gas_price(55).with_gas_limit(21_000).with_nonce(0);
2752        let sender2 = tx2.sender();
2753
2754        // Sender 3: tx will NOT be promoted (gas price 45 < new base fee 50)
2755        let tx3 =
2756            MockTransaction::eip1559().with_gas_price(45).with_gas_limit(21_000).with_nonce(0);
2757        let sender3 = tx3.sender();
2758
2759        // Set high initial base fee (all txs will go to basefee pool)
2760        let mut block_info = pool.block_info();
2761        block_info.pending_basefee = 70;
2762        pool.set_block_info(block_info);
2763
2764        // Add all transactions
2765        let validated1 = tx_factory.validated(tx1);
2766        let validated2 = tx_factory.validated(tx2);
2767        let validated3 = tx_factory.validated(tx3);
2768
2769        pool.add_transaction(validated1, sender_balance, 0, None).unwrap();
2770        pool.add_transaction(validated2, sender_balance, 0, None).unwrap();
2771        pool.add_transaction(validated3, sender_balance, 0, None).unwrap();
2772
2773        let sender1_id = tx_factory.ids.sender_id(&sender1).unwrap();
2774        let sender2_id = tx_factory.ids.sender_id(&sender2).unwrap();
2775        let sender3_id = tx_factory.ids.sender_id(&sender3).unwrap();
2776
2777        // All should be in basefee pool initially
2778        assert_eq!(pool.basefee_pool.len(), 3, "All txs should be in basefee pool");
2779        assert_eq!(pool.pending_pool.len(), 0, "No txs should be in pending pool");
2780
2781        // Now decrease base fee to 50 - this should promote tx1 and tx2 (prices 60 and 55)
2782        // but not tx3 (price 45)
2783        block_info.pending_basefee = 50;
2784
2785        // Update all senders' balances (simulating account state changes)
2786        let mut changed_senders = FxHashMap::default();
2787        changed_senders.insert(
2788            sender1_id,
2789            SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) },
2790        );
2791        changed_senders.insert(
2792            sender2_id,
2793            SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) },
2794        );
2795        changed_senders.insert(
2796            sender3_id,
2797            SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) },
2798        );
2799
2800        let outcome = pool.on_canonical_state_change(
2801            block_info,
2802            vec![],
2803            changed_senders,
2804            PoolUpdateKind::Commit,
2805        );
2806
2807        // Check final state
2808        assert_eq!(pool.pending_pool.len(), 2, "tx1 and tx2 should be promoted");
2809        assert_eq!(pool.basefee_pool.len(), 1, "tx3 should remain in basefee");
2810
2811        // CRITICAL: Should report exactly 2 promotions, not 4 (which would happen with
2812        // double-processing)
2813        assert_eq!(
2814            outcome.promoted.len(),
2815            2,
2816            "Should report exactly 2 promotions, not double-counted"
2817        );
2818
2819        // Verify the correct transactions were promoted
2820        let promoted_prices: Vec<u128> =
2821            outcome.promoted.iter().map(|tx| tx.max_fee_per_gas()).collect();
2822        assert!(promoted_prices.contains(&60));
2823        assert!(promoted_prices.contains(&55));
2824    }
2825
2826    #[test]
2827    fn test_basefee_decrease_with_empty_senders() {
2828        // Test that fee promotions still occur when basefee decreases
2829        // even with no changed_senders
2830        let mut tx_factory = MockTransactionFactory::default();
2831        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2832
2833        // Create transaction that will be promoted when fee drops
2834        let tx = MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000);
2835
2836        // Set high initial base fee
2837        let mut block_info = pool.block_info();
2838        block_info.pending_basefee = 100;
2839        pool.set_block_info(block_info);
2840
2841        // Add transaction - should go to basefee pool
2842        let validated = tx_factory.validated(tx);
2843        pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap();
2844
2845        assert_eq!(pool.basefee_pool.len(), 1);
2846        assert_eq!(pool.pending_pool.len(), 0);
2847
2848        // Decrease base fee with NO changed senders
2849        block_info.pending_basefee = 50;
2850        let outcome = pool.on_canonical_state_change(
2851            block_info,
2852            vec![],
2853            FxHashMap::default(), // Empty changed_senders!
2854            PoolUpdateKind::Commit,
2855        );
2856
2857        // Transaction should still be promoted by fee-driven logic
2858        assert_eq!(pool.pending_pool.len(), 1, "Fee decrease should promote tx");
2859        assert_eq!(pool.basefee_pool.len(), 0);
2860        assert_eq!(outcome.promoted.len(), 1, "Should report promotion from fee update");
2861    }
2862
2863    #[test]
2864    fn test_basefee_decrease_account_makes_unfundable() {
2865        // Test that when basefee decreases but account update makes tx unfundable,
2866        // we don't get transient promote-then-discard double counting
2867        let mut tx_factory = MockTransactionFactory::default();
2868        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2869
2870        let tx = MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000);
2871        let sender = tx.sender();
2872
2873        // High initial base fee
2874        let mut block_info = pool.block_info();
2875        block_info.pending_basefee = 100;
2876        pool.set_block_info(block_info);
2877
2878        let validated = tx_factory.validated(tx);
2879        pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap();
2880        let sender_id = tx_factory.ids.sender_id(&sender).unwrap();
2881
2882        assert_eq!(pool.basefee_pool.len(), 1);
2883
2884        // Decrease base fee (would normally promote) but also drain account
2885        block_info.pending_basefee = 50;
2886        let mut changed_senders = FxHashMap::default();
2887        changed_senders.insert(
2888            sender_id,
2889            SenderInfo {
2890                state_nonce: 0,
2891                balance: U256::from(100), // Too low to pay for gas!
2892            },
2893        );
2894
2895        let outcome = pool.on_canonical_state_change(
2896            block_info,
2897            vec![],
2898            changed_senders,
2899            PoolUpdateKind::Commit,
2900        );
2901
2902        // With insufficient balance, transaction goes to queued pool
2903        assert_eq!(pool.pending_pool.len(), 0, "Unfunded tx should not be in pending");
2904        assert_eq!(pool.basefee_pool.len(), 0, "Tx no longer in basefee pool");
2905        assert_eq!(pool.queued_pool.len(), 1, "Unfunded tx should be in queued pool");
2906
2907        // Transaction is not removed, just moved to queued
2908        let tx_count = pool.all_transactions.txs.len();
2909        assert_eq!(tx_count, 1, "Transaction should still be in pool (in queued)");
2910
2911        assert_eq!(outcome.promoted.len(), 0, "Should not report promotion");
2912        assert_eq!(outcome.discarded.len(), 0, "Queued tx is not reported as discarded");
2913    }
2914
2915    #[test]
2916    fn insert_already_imported() {
2917        let on_chain_balance = U256::ZERO;
2918        let on_chain_nonce = 0;
2919        let mut f = MockTransactionFactory::default();
2920        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2921        let tx = MockTransaction::eip1559().inc_price().inc_limit();
2922        let tx = f.validated(tx);
2923        pool.add_transaction(tx.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
2924        match pool.add_transaction(tx, on_chain_balance, on_chain_nonce, None).unwrap_err().kind {
2925            PoolErrorKind::AlreadyImported => {}
2926            _ => unreachable!(),
2927        }
2928    }
2929
2930    #[test]
2931    fn insert_replace() {
2932        let on_chain_balance = U256::ZERO;
2933        let on_chain_nonce = 0;
2934        let mut f = MockTransactionFactory::default();
2935        let mut pool = AllTransactions::default();
2936        let tx = MockTransaction::eip1559().inc_price().inc_limit();
2937        let first = f.validated(tx.clone());
2938        let _ = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
2939        let replacement = f.validated(tx.rng_hash().inc_price());
2940        let InsertOk { updates, replaced_tx, .. } =
2941            pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap();
2942        assert!(updates.is_empty());
2943        let replaced = replaced_tx.unwrap();
2944        assert_eq!(replaced.0.hash(), first.hash());
2945
2946        // ensure replaced tx is fully removed
2947        assert!(!pool.contains(first.hash()));
2948        assert!(pool.contains(replacement.hash()));
2949        assert_eq!(pool.len(), 1);
2950    }
2951
2952    #[test]
2953    fn insert_replace_txpool() {
2954        let on_chain_balance = U256::ZERO;
2955        let on_chain_nonce = 0;
2956        let mut f = MockTransactionFactory::default();
2957        let mut pool = TxPool::mock();
2958
2959        let tx = MockTransaction::eip1559().inc_price().inc_limit();
2960        let first = f.validated(tx.clone());
2961        let first_added =
2962            pool.add_transaction(first, on_chain_balance, on_chain_nonce, None).unwrap();
2963        let replacement = f.validated(tx.rng_hash().inc_price());
2964        let replacement_added = pool
2965            .add_transaction(replacement.clone(), on_chain_balance, on_chain_nonce, None)
2966            .unwrap();
2967
2968        // // ensure replaced tx removed
2969        assert!(!pool.contains(first_added.hash()));
2970        // but the replacement is still there
2971        assert!(pool.subpool_contains(replacement_added.subpool(), replacement_added.id()));
2972
2973        assert!(pool.contains(replacement.hash()));
2974        let size = pool.size();
2975        assert_eq!(size.total, 1);
2976        size.assert_invariants();
2977    }
2978
2979    #[test]
2980    fn insert_replace_underpriced() {
2981        let on_chain_balance = U256::ZERO;
2982        let on_chain_nonce = 0;
2983        let mut f = MockTransactionFactory::default();
2984        let mut pool = AllTransactions::default();
2985        let tx = MockTransaction::eip1559().inc_price().inc_limit();
2986        let first = f.validated(tx.clone());
2987        let _res = pool.insert_tx(first, on_chain_balance, on_chain_nonce);
2988        let mut replacement = f.validated(tx.rng_hash());
2989        replacement.transaction = replacement.transaction.decr_price();
2990        let err = pool.insert_tx(replacement, on_chain_balance, on_chain_nonce).unwrap_err();
2991        assert!(matches!(err, InsertErr::Underpriced { .. }));
2992    }
2993
2994    #[test]
2995    fn insert_replace_underpriced_not_enough_bump() {
2996        let on_chain_balance = U256::ZERO;
2997        let on_chain_nonce = 0;
2998        let mut f = MockTransactionFactory::default();
2999        let mut pool = AllTransactions::default();
3000        let mut tx = MockTransaction::eip1559().inc_price().inc_limit();
3001        tx.set_priority_fee(100);
3002        tx.set_max_fee(100);
3003        let first = f.validated(tx.clone());
3004        let _ = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
3005        let mut replacement = f.validated(tx.rng_hash().inc_price());
3006
3007        // a price bump of 9% is not enough for a default min price bump of 10%
3008        replacement.transaction.set_priority_fee(109);
3009        replacement.transaction.set_max_fee(109);
3010        let err =
3011            pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err();
3012        assert!(matches!(err, InsertErr::Underpriced { .. }));
3013        // ensure first tx is not removed
3014        assert!(pool.contains(first.hash()));
3015        assert_eq!(pool.len(), 1);
3016
3017        // should also fail if the bump in max fee is not enough
3018        replacement.transaction.set_priority_fee(110);
3019        replacement.transaction.set_max_fee(109);
3020        let err =
3021            pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err();
3022        assert!(matches!(err, InsertErr::Underpriced { .. }));
3023        assert!(pool.contains(first.hash()));
3024        assert_eq!(pool.len(), 1);
3025
3026        // should also fail if the bump in priority fee is not enough
3027        replacement.transaction.set_priority_fee(109);
3028        replacement.transaction.set_max_fee(110);
3029        let err = pool.insert_tx(replacement, on_chain_balance, on_chain_nonce).unwrap_err();
3030        assert!(matches!(err, InsertErr::Underpriced { .. }));
3031        assert!(pool.contains(first.hash()));
3032        assert_eq!(pool.len(), 1);
3033    }
3034
3035    #[test]
3036    fn insert_conflicting_type_normal_to_blob() {
3037        let on_chain_balance = U256::from(10_000);
3038        let on_chain_nonce = 0;
3039        let mut f = MockTransactionFactory::default();
3040        let mut pool = AllTransactions::default();
3041        let tx = MockTransaction::eip1559().inc_price().inc_limit();
3042        let first = f.validated(tx.clone());
3043        pool.insert_tx(first, on_chain_balance, on_chain_nonce).unwrap();
3044        let tx = MockTransaction::eip4844().set_sender(tx.sender()).inc_price_by(100).inc_limit();
3045        let blob = f.validated(tx);
3046        let err = pool.insert_tx(blob, on_chain_balance, on_chain_nonce).unwrap_err();
3047        assert!(matches!(err, InsertErr::TxTypeConflict { .. }), "{err:?}");
3048    }
3049
3050    #[test]
3051    fn insert_conflicting_type_blob_to_normal() {
3052        let on_chain_balance = U256::from(10_000);
3053        let on_chain_nonce = 0;
3054        let mut f = MockTransactionFactory::default();
3055        let mut pool = AllTransactions::default();
3056        let tx = MockTransaction::eip4844().inc_price().inc_limit();
3057        let first = f.validated(tx.clone());
3058        pool.insert_tx(first, on_chain_balance, on_chain_nonce).unwrap();
3059        let tx = MockTransaction::eip1559().set_sender(tx.sender()).inc_price_by(100).inc_limit();
3060        let tx = f.validated(tx);
3061        let err = pool.insert_tx(tx, on_chain_balance, on_chain_nonce).unwrap_err();
3062        assert!(matches!(err, InsertErr::TxTypeConflict { .. }), "{err:?}");
3063    }
3064
3065    // insert nonce then nonce - 1
3066    #[test]
3067    fn insert_previous() {
3068        let on_chain_balance = U256::ZERO;
3069        let on_chain_nonce = 0;
3070        let mut f = MockTransactionFactory::default();
3071        let mut pool = AllTransactions::default();
3072        let tx = MockTransaction::eip1559().inc_nonce().inc_price().inc_limit();
3073        let first = f.validated(tx.clone());
3074        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce);
3075
3076        let first_in_pool = pool.get(first.id()).unwrap();
3077
3078        // has nonce gap
3079        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3080
3081        let prev = f.validated(tx.prev());
3082        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3083            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3084
3085        // no updates since still in queued pool
3086        assert!(updates.is_empty());
3087        assert!(replaced_tx.is_none());
3088        assert!(state.contains(TxState::NO_NONCE_GAPS));
3089        assert_eq!(move_to, SubPool::Queued);
3090
3091        let first_in_pool = pool.get(first.id()).unwrap();
3092        // has non nonce gap
3093        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3094    }
3095
3096    // insert nonce then nonce - 1
3097    #[test]
3098    fn insert_with_updates() {
3099        let on_chain_balance = U256::from(10_000);
3100        let on_chain_nonce = 0;
3101        let mut f = MockTransactionFactory::default();
3102        let mut pool = AllTransactions::default();
3103        let tx = MockTransaction::eip1559().inc_nonce().set_gas_price(100).inc_limit();
3104        let first = f.validated(tx.clone());
3105        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
3106
3107        let first_in_pool = pool.get(first.id()).unwrap();
3108        // has nonce gap
3109        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3110        assert_eq!(SubPool::Queued, first_in_pool.subpool);
3111
3112        let prev = f.validated(tx.prev());
3113        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3114            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3115
3116        // updated previous tx
3117        assert_eq!(updates.len(), 1);
3118        assert!(replaced_tx.is_none());
3119        assert!(state.contains(TxState::NO_NONCE_GAPS));
3120        assert_eq!(move_to, SubPool::Pending);
3121
3122        let first_in_pool = pool.get(first.id()).unwrap();
3123        // has non nonce gap
3124        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3125        assert_eq!(SubPool::Pending, first_in_pool.subpool);
3126    }
3127
3128    #[test]
3129    fn insert_previous_blocking() {
3130        let on_chain_balance = U256::from(1_000);
3131        let on_chain_nonce = 0;
3132        let mut f = MockTransactionFactory::default();
3133        let mut pool = AllTransactions::default();
3134        pool.pending_fees.base_fee = pool.minimal_protocol_basefee.checked_add(1).unwrap();
3135        let tx = MockTransaction::eip1559().inc_nonce().inc_limit();
3136        let first = f.validated(tx.clone());
3137
3138        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce);
3139
3140        let first_in_pool = pool.get(first.id()).unwrap();
3141
3142        assert!(tx.get_gas_price() < pool.pending_fees.base_fee as u128);
3143        // has nonce gap
3144        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3145
3146        let prev = f.validated(tx.prev());
3147        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3148            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3149
3150        assert!(!state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3151        // no updates since still in queued pool
3152        assert!(updates.is_empty());
3153        assert!(replaced_tx.is_none());
3154        assert!(state.contains(TxState::NO_NONCE_GAPS));
3155        assert_eq!(move_to, SubPool::BaseFee);
3156
3157        let first_in_pool = pool.get(first.id()).unwrap();
3158        // has non nonce gap
3159        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3160    }
3161
3162    #[test]
3163    fn rejects_spammer() {
3164        let on_chain_balance = U256::from(1_000);
3165        let on_chain_nonce = 0;
3166        let mut f = MockTransactionFactory::default();
3167        let mut pool = AllTransactions::default();
3168
3169        let mut tx = MockTransaction::eip1559();
3170        let unblocked_tx = tx.clone();
3171        for _ in 0..pool.max_account_slots {
3172            tx = tx.next();
3173            pool.insert_tx(f.validated(tx.clone()), on_chain_balance, on_chain_nonce).unwrap();
3174        }
3175
3176        assert_eq!(
3177            pool.max_account_slots,
3178            pool.tx_count(f.ids.sender_id(tx.get_sender()).unwrap())
3179        );
3180
3181        let err =
3182            pool.insert_tx(f.validated(tx.next()), on_chain_balance, on_chain_nonce).unwrap_err();
3183        assert!(matches!(err, InsertErr::ExceededSenderTransactionsCapacity { .. }));
3184
3185        assert!(pool
3186            .insert_tx(f.validated(unblocked_tx), on_chain_balance, on_chain_nonce)
3187            .is_ok());
3188    }
3189
3190    #[test]
3191    fn allow_local_spamming() {
3192        let on_chain_balance = U256::from(1_000);
3193        let on_chain_nonce = 0;
3194        let mut f = MockTransactionFactory::default();
3195        let mut pool = AllTransactions::default();
3196
3197        let mut tx = MockTransaction::eip1559();
3198        for _ in 0..pool.max_account_slots {
3199            tx = tx.next();
3200            pool.insert_tx(
3201                f.validated_with_origin(TransactionOrigin::Local, tx.clone()),
3202                on_chain_balance,
3203                on_chain_nonce,
3204            )
3205            .unwrap();
3206        }
3207
3208        assert_eq!(
3209            pool.max_account_slots,
3210            pool.tx_count(f.ids.sender_id(tx.get_sender()).unwrap())
3211        );
3212
3213        pool.insert_tx(
3214            f.validated_with_origin(TransactionOrigin::Local, tx.next()),
3215            on_chain_balance,
3216            on_chain_nonce,
3217        )
3218        .unwrap();
3219    }
3220
3221    #[test]
3222    fn reject_tx_over_gas_limit() {
3223        let on_chain_balance = U256::from(1_000);
3224        let on_chain_nonce = 0;
3225        let mut f = MockTransactionFactory::default();
3226        let mut pool = AllTransactions::default();
3227
3228        let tx = MockTransaction::eip1559().with_gas_limit(30_000_001);
3229
3230        assert!(matches!(
3231            pool.insert_tx(f.validated(tx), on_chain_balance, on_chain_nonce),
3232            Err(InsertErr::TxGasLimitMoreThanAvailableBlockGas { .. })
3233        ));
3234    }
3235
3236    #[test]
3237    fn test_tx_equal_gas_limit() {
3238        let on_chain_balance = U256::from(1_000);
3239        let on_chain_nonce = 0;
3240        let mut f = MockTransactionFactory::default();
3241        let mut pool = AllTransactions::default();
3242
3243        let tx = MockTransaction::eip1559().with_gas_limit(30_000_000);
3244
3245        let InsertOk { state, .. } =
3246            pool.insert_tx(f.validated(tx), on_chain_balance, on_chain_nonce).unwrap();
3247        assert!(state.contains(TxState::NOT_TOO_MUCH_GAS));
3248    }
3249
3250    #[test]
3251    fn update_basefee_subpools() {
3252        let mut f = MockTransactionFactory::default();
3253        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3254
3255        let tx = MockTransaction::eip1559().inc_price_by(10);
3256        let validated = f.validated(tx.clone());
3257        let id = *validated.id();
3258        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3259
3260        assert_eq!(pool.pending_pool.len(), 1);
3261
3262        pool.update_basefee((tx.max_fee_per_gas() + 1) as u64, |_| {});
3263
3264        assert!(pool.pending_pool.is_empty());
3265        assert_eq!(pool.basefee_pool.len(), 1);
3266
3267        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3268    }
3269
3270    #[test]
3271    fn update_basefee_subpools_setting_block_info() {
3272        let mut f = MockTransactionFactory::default();
3273        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3274
3275        let tx = MockTransaction::eip1559().inc_price_by(10);
3276        let validated = f.validated(tx.clone());
3277        let id = *validated.id();
3278        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3279
3280        assert_eq!(pool.pending_pool.len(), 1);
3281
3282        // use set_block_info for the basefee update
3283        let mut block_info = pool.block_info();
3284        block_info.pending_basefee = (tx.max_fee_per_gas() + 1) as u64;
3285        pool.set_block_info(block_info);
3286
3287        assert!(pool.pending_pool.is_empty());
3288        assert_eq!(pool.basefee_pool.len(), 1);
3289
3290        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3291    }
3292
3293    #[test]
3294    fn basefee_decrease_promotes_affordable_and_keeps_unaffordable() {
3295        use alloy_primitives::address;
3296        let mut f = MockTransactionFactory::default();
3297        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3298
3299        // Create transactions that will be in basefee pool (can't afford initial high fee)
3300        // Use different senders to avoid nonce gap issues
3301        let sender_a = address!("0x000000000000000000000000000000000000000a");
3302        let sender_b = address!("0x000000000000000000000000000000000000000b");
3303        let sender_c = address!("0x000000000000000000000000000000000000000c");
3304
3305        let tx1 = MockTransaction::eip1559()
3306            .set_sender(sender_a)
3307            .set_nonce(0)
3308            .set_max_fee(500)
3309            .inc_limit();
3310        let tx2 = MockTransaction::eip1559()
3311            .set_sender(sender_b)
3312            .set_nonce(0)
3313            .set_max_fee(600)
3314            .inc_limit();
3315        let tx3 = MockTransaction::eip1559()
3316            .set_sender(sender_c)
3317            .set_nonce(0)
3318            .set_max_fee(400)
3319            .inc_limit();
3320
3321        // Set high initial basefee so transactions go to basefee pool
3322        let mut block_info = pool.block_info();
3323        block_info.pending_basefee = 700;
3324        pool.set_block_info(block_info);
3325
3326        let validated1 = f.validated(tx1);
3327        let validated2 = f.validated(tx2);
3328        let validated3 = f.validated(tx3);
3329        let id1 = *validated1.id();
3330        let id2 = *validated2.id();
3331        let id3 = *validated3.id();
3332
3333        // Add transactions - they should go to basefee pool due to high basefee
3334        // All transactions have nonce 0 from different senders, so on_chain_nonce should be 0 for
3335        // all
3336        pool.add_transaction(validated1, U256::from(10_000), 0, None).unwrap();
3337        pool.add_transaction(validated2, U256::from(10_000), 0, None).unwrap();
3338        pool.add_transaction(validated3, U256::from(10_000), 0, None).unwrap();
3339
3340        // Debug: Check where transactions ended up
3341        println!("Basefee pool len: {}", pool.basefee_pool.len());
3342        println!("Pending pool len: {}", pool.pending_pool.len());
3343        println!("tx1 subpool: {:?}", pool.all_transactions.txs.get(&id1).unwrap().subpool);
3344        println!("tx2 subpool: {:?}", pool.all_transactions.txs.get(&id2).unwrap().subpool);
3345        println!("tx3 subpool: {:?}", pool.all_transactions.txs.get(&id3).unwrap().subpool);
3346
3347        // Verify they're in basefee pool
3348        assert_eq!(pool.basefee_pool.len(), 3);
3349        assert_eq!(pool.pending_pool.len(), 0);
3350        assert_eq!(pool.all_transactions.txs.get(&id1).unwrap().subpool, SubPool::BaseFee);
3351        assert_eq!(pool.all_transactions.txs.get(&id2).unwrap().subpool, SubPool::BaseFee);
3352        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3353
3354        // Now decrease basefee to trigger the zero-allocation optimization
3355        let mut block_info = pool.block_info();
3356        block_info.pending_basefee = 450; // tx1 (500) and tx2 (600) can now afford it, tx3 (400) cannot
3357        pool.set_block_info(block_info);
3358
3359        // Verify the optimization worked correctly:
3360        // - tx1 and tx2 should be promoted to pending (mathematical certainty)
3361        // - tx3 should remain in basefee pool
3362        // - All state transitions should be correct
3363        assert_eq!(pool.basefee_pool.len(), 1);
3364        assert_eq!(pool.pending_pool.len(), 2);
3365
3366        // tx3 should still be in basefee pool (fee 400 < basefee 450)
3367        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3368
3369        // tx1 and tx2 should be in pending pool with correct state bits
3370        let tx1_meta = pool.all_transactions.txs.get(&id1).unwrap();
3371        let tx2_meta = pool.all_transactions.txs.get(&id2).unwrap();
3372        assert_eq!(tx1_meta.subpool, SubPool::Pending);
3373        assert_eq!(tx2_meta.subpool, SubPool::Pending);
3374        assert!(tx1_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3375        assert!(tx2_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3376
3377        // Verify that best_transactions returns the promoted transactions
3378        let best: Vec<_> = pool.best_transactions().take(3).collect();
3379        assert_eq!(best.len(), 2); // Only tx1 and tx2 should be returned
3380        assert!(best.iter().any(|tx| tx.id() == &id1));
3381        assert!(best.iter().any(|tx| tx.id() == &id2));
3382    }
3383
3384    #[test]
3385    fn apply_fee_updates_records_promotions_after_basefee_drop() {
3386        let mut f = MockTransactionFactory::default();
3387        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3388
3389        let tx = MockTransaction::eip1559()
3390            .with_gas_limit(21_000)
3391            .with_max_fee(500)
3392            .with_priority_fee(1);
3393        let validated = f.validated(tx);
3394        let id = *validated.id();
3395        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3396
3397        assert_eq!(pool.pending_pool.len(), 1);
3398
3399        // Raise base fee beyond the transaction's cap so it gets parked in BaseFee pool.
3400        pool.update_basefee(600, |_| {});
3401        assert!(pool.pending_pool.is_empty());
3402        assert_eq!(pool.basefee_pool.len(), 1);
3403
3404        let prev_base_fee = 600;
3405        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3406
3407        // Simulate the canonical state path updating pending fees before applying promotions.
3408        pool.all_transactions.pending_fees.base_fee = 400;
3409
3410        let mut outcome = UpdateOutcome::default();
3411        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3412
3413        assert_eq!(pool.pending_pool.len(), 1);
3414        assert!(pool.basefee_pool.is_empty());
3415        assert_eq!(outcome.promoted.len(), 1);
3416        assert_eq!(outcome.promoted[0].id(), &id);
3417        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
3418        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
3419
3420        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3421        assert_eq!(tx_meta.subpool, SubPool::Pending);
3422        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3423    }
3424
3425    #[test]
3426    fn apply_fee_updates_records_promotions_after_blob_fee_drop() {
3427        let mut f = MockTransactionFactory::default();
3428        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3429
3430        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3431
3432        let tx = MockTransaction::eip4844().with_blob_fee(initial_blob_fee + 100);
3433        let validated = f.validated(tx.clone());
3434        let id = *validated.id();
3435        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3436
3437        assert_eq!(pool.pending_pool.len(), 1);
3438
3439        // Raise blob fee beyond the transaction's cap so it gets parked in Blob pool.
3440        let increased_blob_fee = tx.max_fee_per_blob_gas().unwrap() + 200;
3441        pool.update_blob_fee(increased_blob_fee, Ordering::Equal, |_| {});
3442        assert!(pool.pending_pool.is_empty());
3443        assert_eq!(pool.blob_pool.len(), 1);
3444
3445        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
3446        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3447
3448        // Simulate the canonical state path updating pending fees before applying promotions.
3449        pool.all_transactions.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap();
3450
3451        let mut outcome = UpdateOutcome::default();
3452        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3453
3454        assert_eq!(pool.pending_pool.len(), 1);
3455        assert!(pool.blob_pool.is_empty());
3456        assert_eq!(outcome.promoted.len(), 1);
3457        assert_eq!(outcome.promoted[0].id(), &id);
3458        assert_eq!(pool.all_transactions.pending_fees.base_fee, prev_base_fee);
3459        assert_eq!(pool.all_transactions.pending_fees.blob_fee, tx.max_fee_per_blob_gas().unwrap());
3460
3461        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3462        assert_eq!(tx_meta.subpool, SubPool::Pending);
3463        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
3464        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3465    }
3466
3467    #[test]
3468    fn apply_fee_updates_promotes_blob_after_basefee_drop() {
3469        let mut f = MockTransactionFactory::default();
3470        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3471
3472        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3473
3474        let tx = MockTransaction::eip4844()
3475            .with_max_fee(500)
3476            .with_priority_fee(1)
3477            .with_blob_fee(initial_blob_fee + 100);
3478        let validated = f.validated(tx);
3479        let id = *validated.id();
3480        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3481
3482        assert_eq!(pool.pending_pool.len(), 1);
3483
3484        // Raise base fee beyond the transaction's cap so it gets parked in Blob pool.
3485        let high_base_fee = 600;
3486        pool.update_basefee(high_base_fee, |_| {});
3487        assert!(pool.pending_pool.is_empty());
3488        assert_eq!(pool.blob_pool.len(), 1);
3489
3490        let prev_base_fee = high_base_fee;
3491        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3492
3493        // Simulate applying a lower base fee while keeping blob fee unchanged.
3494        pool.all_transactions.pending_fees.base_fee = 400;
3495
3496        let mut outcome = UpdateOutcome::default();
3497        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3498
3499        assert_eq!(pool.pending_pool.len(), 1);
3500        assert!(pool.blob_pool.is_empty());
3501        assert_eq!(outcome.promoted.len(), 1);
3502        assert_eq!(outcome.promoted[0].id(), &id);
3503        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
3504        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
3505
3506        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3507        assert_eq!(tx_meta.subpool, SubPool::Pending);
3508        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
3509        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3510    }
3511
3512    #[test]
3513    fn apply_fee_updates_demotes_after_basefee_rise() {
3514        let mut f = MockTransactionFactory::default();
3515        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3516
3517        let tx = MockTransaction::eip1559()
3518            .with_gas_limit(21_000)
3519            .with_max_fee(400)
3520            .with_priority_fee(1);
3521        let validated = f.validated(tx);
3522        let id = *validated.id();
3523        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3524
3525        assert_eq!(pool.pending_pool.len(), 1);
3526
3527        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
3528        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3529
3530        // Simulate canonical path raising the base fee beyond the transaction's cap.
3531        let new_base_fee = prev_base_fee + 1_000;
3532        pool.all_transactions.pending_fees.base_fee = new_base_fee;
3533
3534        let mut outcome = UpdateOutcome::default();
3535        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3536
3537        assert!(pool.pending_pool.is_empty());
3538        assert_eq!(pool.basefee_pool.len(), 1);
3539        assert!(outcome.promoted.is_empty());
3540        assert_eq!(pool.all_transactions.pending_fees.base_fee, new_base_fee);
3541        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
3542
3543        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3544        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
3545        assert!(!tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3546    }
3547
3548    #[test]
3549    fn get_highest_transaction_by_sender_and_nonce() {
3550        // Set up a mock transaction factory and a new transaction pool.
3551        let mut f = MockTransactionFactory::default();
3552        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3553
3554        // Create a mock transaction and add it to the pool.
3555        let tx = MockTransaction::eip1559();
3556        pool.add_transaction(f.validated(tx.clone()), U256::from(1_000), 0, None).unwrap();
3557
3558        // Create another mock transaction with an incremented price.
3559        let tx1 = tx.inc_price().next();
3560
3561        // Validate the second mock transaction and add it to the pool.
3562        let tx1_validated = f.validated(tx1.clone());
3563        pool.add_transaction(tx1_validated, U256::from(1_000), 0, None).unwrap();
3564
3565        // Ensure that the calculated next nonce for the sender matches the expected value.
3566        assert_eq!(
3567            pool.get_highest_nonce_by_sender(f.ids.sender_id(&tx.sender()).unwrap()),
3568            Some(1)
3569        );
3570
3571        // Retrieve the highest transaction by sender.
3572        let highest_tx = pool
3573            .get_highest_transaction_by_sender(f.ids.sender_id(&tx.sender()).unwrap())
3574            .expect("Failed to retrieve highest transaction");
3575
3576        // Validate that the retrieved highest transaction matches the expected transaction.
3577        assert_eq!(highest_tx.as_ref().transaction, tx1);
3578    }
3579
3580    #[test]
3581    fn get_highest_consecutive_transaction_by_sender() {
3582        // Set up a mock transaction factory and a new transaction pool.
3583        let mut pool = TxPool::new(MockOrdering::default(), PoolConfig::default());
3584        let mut f = MockTransactionFactory::default();
3585
3586        // Create transactions with nonces 0, 1, 2, 4, 5.
3587        let sender = Address::random();
3588        let txs: Vec<_> = vec![0, 1, 2, 4, 5, 8, 9];
3589        for nonce in txs {
3590            let mut mock_tx = MockTransaction::eip1559();
3591            mock_tx.set_sender(sender);
3592            mock_tx.set_nonce(nonce);
3593
3594            let validated_tx = f.validated(mock_tx);
3595            pool.add_transaction(validated_tx, U256::from(1000), 0, None).unwrap();
3596        }
3597
3598        // Get last consecutive transaction
3599        let sender_id = f.ids.sender_id(&sender).unwrap();
3600        let next_tx =
3601            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(0));
3602        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(2), "Expected nonce 2 for on-chain nonce 0");
3603
3604        let next_tx =
3605            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(4));
3606        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 4");
3607
3608        let next_tx =
3609            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
3610        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 5");
3611
3612        // update the tracked nonce
3613        let mut info = SenderInfo::default();
3614        info.update(8, U256::ZERO);
3615        pool.sender_info.insert(sender_id, info);
3616        let next_tx =
3617            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
3618        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(9), "Expected nonce 9 for on-chain nonce 8");
3619    }
3620
3621    #[test]
3622    fn discard_nonce_too_low() {
3623        let mut f = MockTransactionFactory::default();
3624        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3625
3626        let tx = MockTransaction::eip1559().inc_price_by(10);
3627        let validated = f.validated(tx.clone());
3628        let id = *validated.id();
3629        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3630
3631        let next = tx.next();
3632        let validated = f.validated(next.clone());
3633        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3634
3635        assert_eq!(pool.pending_pool.len(), 2);
3636
3637        let mut changed_senders = HashMap::default();
3638        changed_senders.insert(
3639            id.sender,
3640            SenderInfo { state_nonce: next.nonce(), balance: U256::from(1_000) },
3641        );
3642        let outcome = pool.update_accounts(changed_senders);
3643        assert_eq!(outcome.discarded.len(), 1);
3644        assert_eq!(pool.pending_pool.len(), 1);
3645    }
3646
3647    #[test]
3648    fn discard_with_large_blob_txs() {
3649        // init tracing
3650        reth_tracing::init_test_tracing();
3651
3652        // this test adds large txs to the parked pool, then attempting to discard worst
3653        let mut f = MockTransactionFactory::default();
3654        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3655        let default_limits = pool.config.blob_limit;
3656
3657        // create a chain of transactions by sender A
3658        // make sure they are all one over half the limit
3659        let a_sender = address!("0x000000000000000000000000000000000000000a");
3660
3661        // set the base fee of the pool
3662        let mut block_info = pool.block_info();
3663        block_info.pending_blob_fee = Some(100);
3664        block_info.pending_basefee = 100;
3665
3666        // update
3667        pool.set_block_info(block_info);
3668
3669        // 2 txs, that should put the pool over the size limit but not max txs
3670        let a_txs = MockTransactionSet::dependent(a_sender, 0, 2, TxType::Eip4844)
3671            .into_iter()
3672            .map(|mut tx| {
3673                tx.set_size(default_limits.max_size / 2 + 1);
3674                tx.set_max_fee((block_info.pending_basefee - 1).into());
3675                tx
3676            })
3677            .collect::<Vec<_>>();
3678
3679        // add all the transactions to the parked pool
3680        for tx in a_txs {
3681            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
3682        }
3683
3684        // truncate the pool, it should remove at least one transaction
3685        let removed = pool.discard_worst();
3686        assert_eq!(removed.len(), 1);
3687    }
3688
3689    #[test]
3690    fn discard_with_parked_large_txs() {
3691        // init tracing
3692        reth_tracing::init_test_tracing();
3693
3694        // this test adds large txs to the parked pool, then attempting to discard worst
3695        let mut f = MockTransactionFactory::default();
3696        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3697        let default_limits = pool.config.queued_limit;
3698
3699        // create a chain of transactions by sender A
3700        // make sure they are all one over half the limit
3701        let a_sender = address!("0x000000000000000000000000000000000000000a");
3702
3703        // set the base fee of the pool
3704        let pool_base_fee = 100;
3705        pool.update_basefee(pool_base_fee, |_| {});
3706
3707        // 2 txs, that should put the pool over the size limit but not max txs
3708        let a_txs = MockTransactionSet::dependent(a_sender, 0, 3, TxType::Eip1559)
3709            .into_iter()
3710            .map(|mut tx| {
3711                tx.set_size(default_limits.max_size / 2 + 1);
3712                tx.set_max_fee((pool_base_fee - 1).into());
3713                tx
3714            })
3715            .collect::<Vec<_>>();
3716
3717        // add all the transactions to the parked pool
3718        for tx in a_txs {
3719            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
3720        }
3721
3722        // truncate the pool, it should remove at least one transaction
3723        let removed = pool.discard_worst();
3724        assert_eq!(removed.len(), 1);
3725    }
3726
3727    #[test]
3728    fn discard_at_capacity() {
3729        let mut f = MockTransactionFactory::default();
3730        let queued_limit = SubPoolLimit::new(1000, usize::MAX);
3731        let mut pool =
3732            TxPool::new(MockOrdering::default(), PoolConfig { queued_limit, ..Default::default() });
3733
3734        // insert a bunch of transactions into the queued pool
3735        for _ in 0..queued_limit.max_txs {
3736            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
3737            let validated = f.validated(tx.clone());
3738            let _id = *validated.id();
3739            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3740        }
3741
3742        let size = pool.size();
3743        assert_eq!(size.queued, queued_limit.max_txs);
3744
3745        for _ in 0..queued_limit.max_txs {
3746            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
3747            let validated = f.validated(tx.clone());
3748            let _id = *validated.id();
3749            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3750
3751            pool.discard_worst();
3752            pool.assert_invariants();
3753            assert!(pool.size().queued <= queued_limit.max_txs);
3754        }
3755    }
3756
3757    #[test]
3758    fn discard_blobs_at_capacity() {
3759        let mut f = MockTransactionFactory::default();
3760        let blob_limit = SubPoolLimit::new(1000, usize::MAX);
3761        let mut pool =
3762            TxPool::new(MockOrdering::default(), PoolConfig { blob_limit, ..Default::default() });
3763        pool.all_transactions.pending_fees.blob_fee = 10000;
3764        // insert a bunch of transactions into the queued pool
3765        for _ in 0..blob_limit.max_txs {
3766            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
3767            let validated = f.validated(tx.clone());
3768            let _id = *validated.id();
3769            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3770        }
3771
3772        let size = pool.size();
3773        assert_eq!(size.blob, blob_limit.max_txs);
3774
3775        for _ in 0..blob_limit.max_txs {
3776            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
3777            let validated = f.validated(tx.clone());
3778            let _id = *validated.id();
3779            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3780
3781            pool.discard_worst();
3782            pool.assert_invariants();
3783            assert!(pool.size().blob <= blob_limit.max_txs);
3784        }
3785    }
3786
3787    #[test]
3788    fn account_updates_sender_balance() {
3789        let mut on_chain_balance = U256::from(100);
3790        let on_chain_nonce = 0;
3791        let mut f = MockTransactionFactory::default();
3792        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3793
3794        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3795        let tx_1 = tx_0.next();
3796        let tx_2 = tx_1.next();
3797
3798        // Create 3 transactions
3799        let v0 = f.validated(tx_0);
3800        let v1 = f.validated(tx_1);
3801        let v2 = f.validated(tx_2);
3802
3803        let _res =
3804            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3805        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
3806        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
3807
3808        // The sender does not have enough balance to put all txs into pending.
3809        assert_eq!(1, pool.pending_transactions().len());
3810        assert_eq!(2, pool.queued_transactions().len());
3811
3812        // Simulate new block arrival - and chain balance increase.
3813        let mut updated_accounts = HashMap::default();
3814        on_chain_balance = U256::from(300);
3815        updated_accounts.insert(
3816            v0.sender_id(),
3817            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
3818        );
3819        pool.update_accounts(updated_accounts.clone());
3820
3821        assert_eq!(3, pool.pending_transactions().len());
3822        assert!(pool.queued_transactions().is_empty());
3823
3824        // Simulate new block arrival - and chain balance decrease.
3825        updated_accounts.entry(v0.sender_id()).and_modify(|v| v.balance = U256::from(1));
3826        pool.update_accounts(updated_accounts);
3827
3828        assert!(pool.pending_transactions().is_empty());
3829        assert_eq!(3, pool.queued_transactions().len());
3830    }
3831
3832    #[test]
3833    fn account_updates_nonce_gap() {
3834        let on_chain_balance = U256::from(10_000);
3835        let mut on_chain_nonce = 0;
3836        let mut f = MockTransactionFactory::default();
3837        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3838
3839        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3840        let tx_1 = tx_0.next();
3841        let tx_2 = tx_1.next();
3842
3843        // Create 3 transactions
3844        let v0 = f.validated(tx_0);
3845        let v1 = f.validated(tx_1);
3846        let v2 = f.validated(tx_2);
3847
3848        // Add first 2 to the pool
3849        let _res =
3850            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3851        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
3852
3853        assert!(pool.queued_transactions().is_empty());
3854        assert_eq!(2, pool.pending_transactions().len());
3855
3856        // Remove first (nonce 0)
3857        pool.remove_transaction_by_hash(v0.hash());
3858
3859        // Now add transaction with nonce 2
3860        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
3861
3862        // v1 and v2 should both be in the queue now.
3863        assert_eq!(2, pool.queued_transactions().len());
3864        assert!(pool.pending_transactions().is_empty());
3865
3866        // Simulate new block arrival - and chain nonce increasing.
3867        let mut updated_accounts = HashMap::default();
3868        on_chain_nonce += 1;
3869        updated_accounts.insert(
3870            v0.sender_id(),
3871            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
3872        );
3873        pool.update_accounts(updated_accounts);
3874
3875        // 'pending' now).
3876        assert!(pool.queued_transactions().is_empty());
3877        assert_eq!(2, pool.pending_transactions().len());
3878    }
3879    #[test]
3880    fn test_transaction_removal() {
3881        let on_chain_balance = U256::from(10_000);
3882        let on_chain_nonce = 0;
3883        let mut f = MockTransactionFactory::default();
3884        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3885
3886        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3887        let tx_1 = tx_0.next();
3888
3889        // Create 2 transactions
3890        let v0 = f.validated(tx_0);
3891        let v1 = f.validated(tx_1);
3892
3893        // Add them to the pool
3894        let _res =
3895            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3896        let _res =
3897            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3898
3899        assert_eq!(0, pool.queued_transactions().len());
3900        assert_eq!(2, pool.pending_transactions().len());
3901
3902        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
3903        pool.remove_transaction(v0.id());
3904        // assert the second transaction is really at the top of the queue
3905        let pool_txs = pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>();
3906        assert_eq!(vec![v1.nonce()], pool_txs);
3907    }
3908    #[test]
3909    fn test_remove_transactions() {
3910        let on_chain_balance = U256::from(10_000);
3911        let on_chain_nonce = 0;
3912        let mut f = MockTransactionFactory::default();
3913        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3914
3915        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3916        let tx_1 = tx_0.next();
3917        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3918        let tx_3 = tx_2.next();
3919
3920        // Create 4 transactions
3921        let v0 = f.validated(tx_0);
3922        let v1 = f.validated(tx_1);
3923        let v2 = f.validated(tx_2);
3924        let v3 = f.validated(tx_3);
3925
3926        // Add them to the pool
3927        let _res =
3928            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3929        let _res =
3930            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3931        let _res =
3932            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3933        let _res =
3934            pool.add_transaction(v3.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3935
3936        assert_eq!(0, pool.queued_transactions().len());
3937        assert_eq!(4, pool.pending_transactions().len());
3938
3939        pool.remove_transactions(vec![*v0.hash(), *v2.hash()]);
3940
3941        assert_eq!(2, pool.queued_transactions().len());
3942        assert!(pool.pending_transactions().is_empty());
3943        assert!(pool.contains(v1.hash()));
3944        assert!(pool.contains(v3.hash()));
3945    }
3946
3947    #[test]
3948    fn test_remove_transactions_middle_pending_hash() {
3949        let on_chain_balance = U256::from(10_000);
3950        let on_chain_nonce = 0;
3951        let mut f = MockTransactionFactory::default();
3952        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3953
3954        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3955        let tx_1 = tx_0.next();
3956        let tx_2 = tx_1.next();
3957        let tx_3 = tx_2.next();
3958
3959        // Create 4 transactions
3960        let v0 = f.validated(tx_0);
3961        let v1 = f.validated(tx_1);
3962        let v2 = f.validated(tx_2);
3963        let v3 = f.validated(tx_3);
3964
3965        // Add them to the pool
3966        let _res = pool.add_transaction(v0, on_chain_balance, on_chain_nonce, None).unwrap();
3967        let _res =
3968            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3969        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
3970        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
3971
3972        assert_eq!(0, pool.queued_transactions().len());
3973        assert_eq!(4, pool.pending_transactions().len());
3974
3975        let mut removed_txs = pool.remove_transactions(vec![*v1.hash()]);
3976        assert_eq!(1, removed_txs.len());
3977
3978        assert_eq!(2, pool.queued_transactions().len());
3979        assert_eq!(1, pool.pending_transactions().len());
3980
3981        // reinsert
3982        let removed_tx = removed_txs.pop().unwrap();
3983        let v1 = f.validated(removed_tx.transaction.clone());
3984        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
3985        assert_eq!(0, pool.queued_transactions().len());
3986        assert_eq!(4, pool.pending_transactions().len());
3987    }
3988
3989    #[test]
3990    fn test_remove_transactions_and_descendants() {
3991        let on_chain_balance = U256::from(10_000);
3992        let on_chain_nonce = 0;
3993        let mut f = MockTransactionFactory::default();
3994        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3995
3996        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3997        let tx_1 = tx_0.next();
3998        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3999        let tx_3 = tx_2.next();
4000        let tx_4 = tx_3.next();
4001
4002        // Create 5 transactions
4003        let v0 = f.validated(tx_0);
4004        let v1 = f.validated(tx_1);
4005        let v2 = f.validated(tx_2);
4006        let v3 = f.validated(tx_3);
4007        let v4 = f.validated(tx_4);
4008
4009        // Add them to the pool
4010        let _res =
4011            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4012        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4013        let _res =
4014            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4015        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4016        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
4017
4018        assert_eq!(0, pool.queued_transactions().len());
4019        assert_eq!(5, pool.pending_transactions().len());
4020
4021        pool.remove_transactions_and_descendants(vec![*v0.hash(), *v2.hash()]);
4022
4023        assert_eq!(0, pool.queued_transactions().len());
4024        assert_eq!(0, pool.pending_transactions().len());
4025    }
4026    #[test]
4027    fn test_remove_descendants() {
4028        let on_chain_balance = U256::from(10_000);
4029        let on_chain_nonce = 0;
4030        let mut f = MockTransactionFactory::default();
4031        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4032
4033        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4034        let tx_1 = tx_0.next();
4035        let tx_2 = tx_1.next();
4036        let tx_3 = tx_2.next();
4037
4038        // Create 4 transactions
4039        let v0 = f.validated(tx_0);
4040        let v1 = f.validated(tx_1);
4041        let v2 = f.validated(tx_2);
4042        let v3 = f.validated(tx_3);
4043
4044        // Add them to the  pool
4045        let _res =
4046            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4047        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4048        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4049        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4050
4051        assert_eq!(0, pool.queued_transactions().len());
4052        assert_eq!(4, pool.pending_transactions().len());
4053
4054        let mut removed = Vec::new();
4055        pool.remove_transaction(v0.id());
4056        pool.remove_descendants(v0.id(), &mut removed);
4057
4058        assert_eq!(0, pool.queued_transactions().len());
4059        assert_eq!(0, pool.pending_transactions().len());
4060        assert_eq!(3, removed.len());
4061    }
4062    #[test]
4063    fn test_remove_transactions_by_sender() {
4064        let on_chain_balance = U256::from(10_000);
4065        let on_chain_nonce = 0;
4066        let mut f = MockTransactionFactory::default();
4067        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4068
4069        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4070        let tx_1 = tx_0.next();
4071        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4072        let tx_3 = tx_2.next();
4073        let tx_4 = tx_3.next();
4074
4075        // Create 5 transactions
4076        let v0 = f.validated(tx_0);
4077        let v1 = f.validated(tx_1);
4078        let v2 = f.validated(tx_2);
4079        let v3 = f.validated(tx_3);
4080        let v4 = f.validated(tx_4);
4081
4082        // Add them to the pool
4083        let _res =
4084            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4085        let _res =
4086            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4087        let _res =
4088            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4089        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4090        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
4091
4092        assert_eq!(0, pool.queued_transactions().len());
4093        assert_eq!(5, pool.pending_transactions().len());
4094
4095        pool.remove_transactions_by_sender(v2.sender_id());
4096
4097        assert_eq!(0, pool.queued_transactions().len());
4098        assert_eq!(2, pool.pending_transactions().len());
4099        assert!(pool.contains(v0.hash()));
4100        assert!(pool.contains(v1.hash()));
4101    }
4102    #[test]
4103    fn wrong_best_order_of_transactions() {
4104        let on_chain_balance = U256::from(10_000);
4105        let mut on_chain_nonce = 0;
4106        let mut f = MockTransactionFactory::default();
4107        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4108
4109        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4110        let tx_1 = tx_0.next();
4111        let tx_2 = tx_1.next();
4112        let tx_3 = tx_2.next();
4113
4114        // Create 4 transactions
4115        let v0 = f.validated(tx_0);
4116        let v1 = f.validated(tx_1);
4117        let v2 = f.validated(tx_2);
4118        let v3 = f.validated(tx_3);
4119
4120        // Add first 2 to the pool
4121        let _res =
4122            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4123        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4124
4125        assert_eq!(0, pool.queued_transactions().len());
4126        assert_eq!(2, pool.pending_transactions().len());
4127
4128        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
4129        pool.remove_transaction(v0.id());
4130
4131        // Now add transaction with nonce 2
4132        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4133
4134        // v2 is in the queue now. v1 is still in 'pending'.
4135        assert_eq!(1, pool.queued_transactions().len());
4136        assert_eq!(1, pool.pending_transactions().len());
4137
4138        // Simulate new block arrival - and chain nonce increasing.
4139        let mut updated_accounts = HashMap::default();
4140        on_chain_nonce += 1;
4141        updated_accounts.insert(
4142            v0.sender_id(),
4143            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
4144        );
4145        pool.update_accounts(updated_accounts);
4146
4147        // Transactions are not changed (IMHO - this is a bug, as transaction v2 should be in the
4148        // 'pending' now).
4149        assert_eq!(0, pool.queued_transactions().len());
4150        assert_eq!(2, pool.pending_transactions().len());
4151
4152        // Add transaction v3 - it 'unclogs' everything.
4153        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4154        assert_eq!(0, pool.queued_transactions().len());
4155        assert_eq!(3, pool.pending_transactions().len());
4156
4157        // It should have returned transactions in order (v1, v2, v3 - as there is nothing blocking
4158        // them).
4159        assert_eq!(
4160            pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>(),
4161            vec![1, 2, 3]
4162        );
4163    }
4164
4165    #[test]
4166    fn test_best_with_attributes() {
4167        let on_chain_balance = U256::MAX;
4168        let on_chain_nonce = 0;
4169        let mut f = MockTransactionFactory::default();
4170        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4171
4172        let base_fee: u128 = 100;
4173        let blob_fee: u128 = 100;
4174
4175        // set base fee and blob fee.
4176        let mut block_info = pool.block_info();
4177        block_info.pending_basefee = base_fee as u64;
4178        block_info.pending_blob_fee = Some(blob_fee);
4179        pool.set_block_info(block_info);
4180
4181        // Insert transactions with varying max_fee_per_gas and max_fee_per_blob_gas.
4182        let tx1 = MockTransaction::eip4844()
4183            .with_sender(Address::with_last_byte(1))
4184            .with_max_fee(base_fee + 10)
4185            .with_blob_fee(blob_fee + 10);
4186        let tx2 = MockTransaction::eip4844()
4187            .with_sender(Address::with_last_byte(2))
4188            .with_max_fee(base_fee + 10)
4189            .with_blob_fee(blob_fee);
4190        let tx3 = MockTransaction::eip4844()
4191            .with_sender(Address::with_last_byte(3))
4192            .with_max_fee(base_fee)
4193            .with_blob_fee(blob_fee + 10);
4194        let tx4 = MockTransaction::eip4844()
4195            .with_sender(Address::with_last_byte(4))
4196            .with_max_fee(base_fee)
4197            .with_blob_fee(blob_fee);
4198        let tx5 = MockTransaction::eip4844()
4199            .with_sender(Address::with_last_byte(5))
4200            .with_max_fee(base_fee)
4201            .with_blob_fee(blob_fee - 10);
4202        let tx6 = MockTransaction::eip4844()
4203            .with_sender(Address::with_last_byte(6))
4204            .with_max_fee(base_fee - 10)
4205            .with_blob_fee(blob_fee);
4206        let tx7 = MockTransaction::eip4844()
4207            .with_sender(Address::with_last_byte(7))
4208            .with_max_fee(base_fee - 10)
4209            .with_blob_fee(blob_fee - 10);
4210
4211        for tx in vec![
4212            tx1.clone(),
4213            tx2.clone(),
4214            tx3.clone(),
4215            tx4.clone(),
4216            tx5.clone(),
4217            tx6.clone(),
4218            tx7.clone(),
4219        ] {
4220            pool.add_transaction(f.validated(tx.clone()), on_chain_balance, on_chain_nonce, None)
4221                .unwrap();
4222        }
4223
4224        let base_fee = base_fee as u64;
4225        let blob_fee = blob_fee as u64;
4226
4227        let cases = vec![
4228            // 1. Base fee increase, blob fee increase
4229            (BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee + 5)), vec![tx1.clone()]),
4230            // 2. Base fee increase, blob fee not change
4231            (
4232                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee)),
4233                vec![tx1.clone(), tx2.clone()],
4234            ),
4235            // 3. Base fee increase, blob fee decrease
4236            (
4237                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee - 5)),
4238                vec![tx1.clone(), tx2.clone()],
4239            ),
4240            // 4. Base fee not change, blob fee increase
4241            (
4242                BestTransactionsAttributes::new(base_fee, Some(blob_fee + 5)),
4243                vec![tx1.clone(), tx3.clone()],
4244            ),
4245            // 5. Base fee not change, blob fee not change
4246            (
4247                BestTransactionsAttributes::new(base_fee, Some(blob_fee)),
4248                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()],
4249            ),
4250            // 6. Base fee not change, blob fee decrease
4251            (
4252                BestTransactionsAttributes::new(base_fee, Some(blob_fee - 10)),
4253                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()],
4254            ),
4255            // 7. Base fee decrease, blob fee increase
4256            (
4257                BestTransactionsAttributes::new(base_fee - 5, Some(blob_fee + 5)),
4258                vec![tx1.clone(), tx3.clone()],
4259            ),
4260            // 8. Base fee decrease, blob fee not change
4261            (
4262                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee)),
4263                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx6.clone()],
4264            ),
4265            // 9. Base fee decrease, blob fee decrease
4266            (
4267                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee - 10)),
4268                vec![tx1, tx2, tx5, tx3, tx4, tx6, tx7],
4269            ),
4270        ];
4271
4272        for (idx, (attribute, expected)) in cases.into_iter().enumerate() {
4273            let mut best = pool.best_transactions_with_attributes(attribute);
4274
4275            for (tx_idx, expected_tx) in expected.into_iter().enumerate() {
4276                let tx = best.next().expect("Transaction should be returned");
4277                assert_eq!(
4278                    tx.transaction,
4279                    expected_tx,
4280                    "Failed tx {} in case {}",
4281                    tx_idx + 1,
4282                    idx + 1
4283                );
4284            }
4285
4286            // No more transactions should be returned
4287            assert!(best.next().is_none());
4288        }
4289    }
4290
4291    #[test]
4292    fn test_pending_ordering() {
4293        let mut f = MockTransactionFactory::default();
4294        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4295
4296        let tx_0 = MockTransaction::eip1559().with_nonce(1).set_gas_price(100).inc_limit();
4297        let tx_1 = tx_0.next();
4298
4299        let v0 = f.validated(tx_0);
4300        let v1 = f.validated(tx_1);
4301
4302        // nonce gap, tx should be queued
4303        pool.add_transaction(v0.clone(), U256::MAX, 0, None).unwrap();
4304        assert_eq!(1, pool.queued_transactions().len());
4305
4306        // nonce gap is closed on-chain, both transactions should be moved to pending
4307        pool.add_transaction(v1, U256::MAX, 1, None).unwrap();
4308
4309        assert_eq!(2, pool.pending_transactions().len());
4310        assert_eq!(0, pool.queued_transactions().len());
4311
4312        assert_eq!(
4313            pool.pending_pool.independent().get(&v0.sender_id()).unwrap().transaction.nonce(),
4314            v0.nonce()
4315        );
4316    }
4317
4318    // <https://github.com/paradigmxyz/reth/issues/12286>
4319    #[test]
4320    fn one_sender_one_independent_transaction() {
4321        let mut on_chain_balance = U256::from(4_999); // only enough for 4 txs
4322        let mut on_chain_nonce = 40;
4323        let mut f = MockTransactionFactory::default();
4324        let mut pool = TxPool::mock();
4325        let mut submitted_txs = Vec::new();
4326
4327        // We use a "template" because we want all txs to have the same sender.
4328        let template =
4329            MockTransaction::eip1559().inc_price().inc_limit().with_value(U256::from(1_001));
4330
4331        // Add 8 txs. Because the balance is only sufficient for 4, so the last 4 will be
4332        // Queued.
4333        for tx_nonce in 40..48 {
4334            let tx = f.validated(template.clone().with_nonce(tx_nonce).rng_hash());
4335            submitted_txs.push(*tx.id());
4336            pool.add_transaction(tx, on_chain_balance, on_chain_nonce, None).unwrap();
4337        }
4338
4339        // A block is mined with two txs (so nonce is changed from 40 to 42).
4340        // Now the balance gets so high that it's enough to execute alltxs.
4341        on_chain_balance = U256::from(999_999);
4342        on_chain_nonce = 42;
4343        pool.remove_transaction(&submitted_txs[0]);
4344        pool.remove_transaction(&submitted_txs[1]);
4345
4346        // Add 4 txs.
4347        for tx_nonce in 48..52 {
4348            pool.add_transaction(
4349                f.validated(template.clone().with_nonce(tx_nonce).rng_hash()),
4350                on_chain_balance,
4351                on_chain_nonce,
4352                None,
4353            )
4354            .unwrap();
4355        }
4356
4357        let best_txs: Vec<_> = pool.pending().best().map(|tx| *tx.id()).collect();
4358        assert_eq!(best_txs.len(), 10); // 8 - 2 + 4 = 10
4359
4360        assert_eq!(pool.pending_pool.independent().len(), 1);
4361    }
4362
4363    #[test]
4364    fn test_insertion_disorder() {
4365        let mut f = MockTransactionFactory::default();
4366        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4367
4368        let sender = address!("0x1234567890123456789012345678901234567890");
4369        let tx0 = f.validated_arc(
4370            MockTransaction::legacy().with_sender(sender).with_nonce(0).with_gas_price(10),
4371        );
4372        let tx1 = f.validated_arc(
4373            MockTransaction::eip1559()
4374                .with_sender(sender)
4375                .with_nonce(1)
4376                .with_gas_limit(1000)
4377                .with_gas_price(10),
4378        );
4379        let tx2 = f.validated_arc(
4380            MockTransaction::legacy().with_sender(sender).with_nonce(2).with_gas_price(10),
4381        );
4382        let tx3 = f.validated_arc(
4383            MockTransaction::legacy().with_sender(sender).with_nonce(3).with_gas_price(10),
4384        );
4385
4386        // tx0 should be put in the pending subpool
4387        pool.add_transaction((*tx0).clone(), U256::from(1000), 0, None).unwrap();
4388        let mut best = pool.best_transactions();
4389        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4390        assert_eq!(t0.id(), tx0.id());
4391        // tx1 should be put in the queued subpool due to insufficient sender balance
4392        pool.add_transaction((*tx1).clone(), U256::from(1000), 0, None).unwrap();
4393        let mut best = pool.best_transactions();
4394        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4395        assert_eq!(t0.id(), tx0.id());
4396        assert!(best.next().is_none());
4397
4398        // tx2 should be put in the pending subpool, and tx1 should be promoted to pending
4399        pool.add_transaction((*tx2).clone(), U256::MAX, 0, None).unwrap();
4400
4401        let mut best = pool.best_transactions();
4402
4403        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4404        let t1 = best.next().expect("tx1 should be put in the pending subpool");
4405        let t2 = best.next().expect("tx2 should be put in the pending subpool");
4406        assert_eq!(t0.id(), tx0.id());
4407        assert_eq!(t1.id(), tx1.id());
4408        assert_eq!(t2.id(), tx2.id());
4409
4410        // tx3 should be put in the pending subpool,
4411        pool.add_transaction((*tx3).clone(), U256::MAX, 0, None).unwrap();
4412        let mut best = pool.best_transactions();
4413        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4414        let t1 = best.next().expect("tx1 should be put in the pending subpool");
4415        let t2 = best.next().expect("tx2 should be put in the pending subpool");
4416        let t3 = best.next().expect("tx3 should be put in the pending subpool");
4417        assert_eq!(t0.id(), tx0.id());
4418        assert_eq!(t1.id(), tx1.id());
4419        assert_eq!(t2.id(), tx2.id());
4420        assert_eq!(t3.id(), tx3.id());
4421    }
4422
4423    #[test]
4424    fn test_non_4844_blob_fee_bit_invariant() {
4425        let mut f = MockTransactionFactory::default();
4426        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4427
4428        let non_4844_tx = MockTransaction::eip1559().set_max_fee(200).inc_limit();
4429        let validated = f.validated(non_4844_tx.clone());
4430
4431        assert!(!non_4844_tx.is_eip4844());
4432        pool.add_transaction(validated.clone(), U256::from(10_000), 0, None).unwrap();
4433
4434        // Core invariant: Non-4844 transactions must ALWAYS have ENOUGH_BLOB_FEE_CAP_BLOCK bit
4435        let tx_meta = pool.all_transactions.txs.get(validated.id()).unwrap();
4436        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4437        assert_eq!(tx_meta.subpool, SubPool::Pending);
4438    }
4439
4440    #[test]
4441    fn test_blob_fee_enforcement_only_applies_to_eip4844() {
4442        let mut f = MockTransactionFactory::default();
4443        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4444
4445        // Set blob fee higher than EIP-4844 tx can afford
4446        let mut block_info = pool.block_info();
4447        block_info.pending_blob_fee = Some(160);
4448        block_info.pending_basefee = 100;
4449        pool.set_block_info(block_info);
4450
4451        let eip4844_tx = MockTransaction::eip4844()
4452            .with_sender(address!("0x000000000000000000000000000000000000000a"))
4453            .with_max_fee(200)
4454            .with_blob_fee(150) // Less than block blob fee (160)
4455            .inc_limit();
4456
4457        let non_4844_tx = MockTransaction::eip1559()
4458            .with_sender(address!("0x000000000000000000000000000000000000000b"))
4459            .set_max_fee(200)
4460            .inc_limit();
4461
4462        let validated_4844 = f.validated(eip4844_tx);
4463        let validated_non_4844 = f.validated(non_4844_tx);
4464
4465        pool.add_transaction(validated_4844.clone(), U256::from(10_000), 0, None).unwrap();
4466        pool.add_transaction(validated_non_4844.clone(), U256::from(10_000), 0, None).unwrap();
4467
4468        let tx_4844_meta = pool.all_transactions.txs.get(validated_4844.id()).unwrap();
4469        let tx_non_4844_meta = pool.all_transactions.txs.get(validated_non_4844.id()).unwrap();
4470
4471        // EIP-4844: blob fee enforcement applies - insufficient blob fee removes bit
4472        assert!(!tx_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4473        assert_eq!(tx_4844_meta.subpool, SubPool::Blob);
4474
4475        // Non-4844: blob fee enforcement does NOT apply - bit always remains true
4476        assert!(tx_non_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4477        assert_eq!(tx_non_4844_meta.subpool, SubPool::Pending);
4478    }
4479
4480    #[test]
4481    fn test_basefee_decrease_preserves_non_4844_blob_fee_bit() {
4482        let mut f = MockTransactionFactory::default();
4483        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4484
4485        // Create non-4844 transaction with fee that initially can't afford high basefee
4486        let non_4844_tx = MockTransaction::eip1559()
4487            .with_sender(address!("0x000000000000000000000000000000000000000a"))
4488            .set_max_fee(500) // Can't afford basefee of 600
4489            .inc_limit();
4490
4491        // Set high basefee so transaction goes to BaseFee pool initially
4492        pool.update_basefee(600, |_| {});
4493
4494        let validated = f.validated(non_4844_tx);
4495        let tx_id = *validated.id();
4496        pool.add_transaction(validated, U256::from(10_000), 0, None).unwrap();
4497
4498        // Initially should be in BaseFee pool but STILL have blob fee bit (critical invariant)
4499        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
4500        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
4501        assert!(
4502            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
4503            "Non-4844 tx in BaseFee pool must retain ENOUGH_BLOB_FEE_CAP_BLOCK bit"
4504        );
4505
4506        // Decrease basefee - transaction should be promoted to Pending
4507        // This is where PR #18215 bug would manifest: blob fee bit incorrectly removed
4508        pool.update_basefee(400, |_| {});
4509
4510        // After basefee decrease: should be promoted to Pending with blob fee bit preserved
4511        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
4512        assert_eq!(
4513            tx_meta.subpool,
4514            SubPool::Pending,
4515            "Non-4844 tx should be promoted from BaseFee to Pending after basefee decrease"
4516        );
4517        assert!(
4518            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
4519            "Non-4844 tx must NEVER lose ENOUGH_BLOB_FEE_CAP_BLOCK bit during basefee promotion"
4520        );
4521        assert!(
4522            tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK),
4523            "Non-4844 tx should gain ENOUGH_FEE_CAP_BLOCK bit after basefee decrease"
4524        );
4525    }
4526}