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