Skip to main content

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