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 == SubPool::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 == SubPool::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 == SubPool::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 == SubPool::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 matches!(move_to, SubPool::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            if current_txs >= self.max_account_slots && transaction.nonce() > on_chain_nonce {
2001                return Err(InsertErr::ExceededSenderTransactionsCapacity {
2002                    transaction: Arc::new(transaction),
2003                })
2004            }
2005        }
2006        if transaction.gas_limit() > self.block_gas_limit {
2007            return Err(InsertErr::TxGasLimitMoreThanAvailableBlockGas {
2008                block_gas_limit: self.block_gas_limit,
2009                tx_gas_limit: transaction.gas_limit(),
2010                transaction: Arc::new(transaction),
2011            })
2012        }
2013
2014        if self.contains_conflicting_transaction(&transaction) {
2015            // blob vs non blob transactions are mutually exclusive for the same sender
2016            return Err(InsertErr::TxTypeConflict { transaction: Arc::new(transaction) })
2017        }
2018
2019        Ok(transaction)
2020    }
2021
2022    /// Enforces additional constraints for blob transactions before attempting to insert:
2023    ///    - new blob transactions must not have any nonce gaps
2024    ///    - blob transactions cannot go into overdraft
2025    ///    - replacement blob transaction with a higher fee must not shift an already propagated
2026    ///      descending blob transaction into overdraft
2027    fn ensure_valid_blob_transaction(
2028        &self,
2029        new_blob_tx: ValidPoolTransaction<T>,
2030        on_chain_balance: U256,
2031        ancestor: Option<TransactionId>,
2032    ) -> Result<ValidPoolTransaction<T>, InsertErr<T>> {
2033        if let Some(ancestor) = ancestor {
2034            let Some(ancestor_tx) = self.txs.get(&ancestor) else {
2035                // ancestor tx is missing, so we can't insert the new blob
2036                self.metrics.blob_transactions_nonce_gaps.increment(1);
2037                return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) })
2038            };
2039            if ancestor_tx.state.has_nonce_gap() {
2040                // the ancestor transaction already has a nonce gap, so we can't insert the new
2041                // blob
2042                self.metrics.blob_transactions_nonce_gaps.increment(1);
2043                return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) })
2044            }
2045
2046            // the max cost executing this transaction requires
2047            let mut cumulative_cost = ancestor_tx.next_cumulative_cost() + new_blob_tx.cost();
2048
2049            // check if the new blob would go into overdraft
2050            if cumulative_cost > on_chain_balance {
2051                // the transaction would go into overdraft
2052                return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) })
2053            }
2054
2055            // ensure that a replacement would not shift already propagated blob transactions into
2056            // overdraft
2057            let id = new_blob_tx.transaction_id;
2058            let mut descendants = self.descendant_txs_inclusive(&id).peekable();
2059            if let Some((maybe_replacement, _)) = descendants.peek() &&
2060                **maybe_replacement == new_blob_tx.transaction_id
2061            {
2062                // replacement transaction
2063                descendants.next();
2064
2065                // check if any of descendant blob transactions should be shifted into overdraft
2066                for (_, tx) in descendants {
2067                    cumulative_cost += tx.transaction.cost();
2068                    if tx.transaction.is_eip4844() && cumulative_cost > on_chain_balance {
2069                        // the transaction would shift
2070                        return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) })
2071                    }
2072                }
2073            }
2074        } else if new_blob_tx.cost() > &on_chain_balance {
2075            // the transaction would go into overdraft
2076            return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) })
2077        }
2078
2079        Ok(new_blob_tx)
2080    }
2081
2082    /// Inserts a new _valid_ transaction into the pool.
2083    ///
2084    /// If the transaction already exists, it will be replaced if not underpriced.
2085    /// Returns info to which sub-pool the transaction should be moved.
2086    /// Also returns a set of pool updates triggered by this insert, that need to be handled by the
2087    /// caller.
2088    ///
2089    /// These can include:
2090    ///      - closing nonce gaps of descendant transactions
2091    ///      - enough balance updates
2092    ///
2093    /// Note: For EIP-4844 blob transactions additional constraints are enforced:
2094    ///      - new blob transactions must not have any nonce gaps
2095    ///      - blob transactions cannot go into overdraft
2096    ///
2097    /// ## Transaction type Exclusivity
2098    ///
2099    /// The pool enforces exclusivity of eip-4844 blob vs non-blob transactions on a per sender
2100    /// basis:
2101    ///  - If the pool already includes a blob transaction from the `transaction`'s sender, then the
2102    ///    `transaction` must also be a blob transaction
2103    ///  - If the pool already includes a non-blob transaction from the `transaction`'s sender, then
2104    ///    the `transaction` must _not_ be a blob transaction.
2105    ///
2106    /// In other words, the presence of blob transactions exclude non-blob transactions and vice
2107    /// versa.
2108    ///
2109    /// ## Replacements
2110    ///
2111    /// The replacement candidate must satisfy given price bump constraints: replacement candidate
2112    /// must not be underpriced
2113    pub(crate) fn insert_tx(
2114        &mut self,
2115        transaction: ValidPoolTransaction<T>,
2116        on_chain_balance: U256,
2117        on_chain_nonce: u64,
2118    ) -> InsertResult<T> {
2119        assert!(on_chain_nonce <= transaction.nonce(), "Invalid transaction");
2120
2121        let mut transaction = self.ensure_valid(transaction, on_chain_nonce)?;
2122
2123        let inserted_tx_id = *transaction.id();
2124        let mut state = TxState::default();
2125        let mut cumulative_cost = U256::ZERO;
2126
2127        // Current tx does not exceed block gas limit after ensure_valid check
2128        state.insert(TxState::NOT_TOO_MUCH_GAS);
2129
2130        // identifier of the ancestor transaction, will be None if the transaction is the next tx of
2131        // the sender
2132        let ancestor = TransactionId::ancestor(
2133            transaction.transaction.nonce(),
2134            on_chain_nonce,
2135            inserted_tx_id.sender,
2136        );
2137
2138        // before attempting to insert a blob transaction, we need to ensure that additional
2139        // constraints are met that only apply to blob transactions
2140        if transaction.is_eip4844() {
2141            state.insert(TxState::BLOB_TRANSACTION);
2142
2143            transaction =
2144                self.ensure_valid_blob_transaction(transaction, on_chain_balance, ancestor)?;
2145            let blob_fee_cap = transaction.transaction.max_fee_per_blob_gas().unwrap_or_default();
2146            if blob_fee_cap >= self.pending_fees.blob_fee {
2147                state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
2148            }
2149        } else {
2150            // Non-EIP4844 transaction always satisfy the blob fee cap condition
2151            state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
2152        }
2153
2154        let transaction = Arc::new(transaction);
2155
2156        // If there's no ancestor tx then this is the next transaction.
2157        if ancestor.is_none() {
2158            state.insert(TxState::NO_NONCE_GAPS);
2159            state.insert(TxState::NO_PARKED_ANCESTORS);
2160        }
2161
2162        // Check dynamic fee
2163        let fee_cap = transaction.max_fee_per_gas();
2164
2165        if fee_cap < self.minimal_protocol_basefee as u128 {
2166            return Err(InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap })
2167        }
2168        if fee_cap >= self.pending_fees.base_fee as u128 {
2169            state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
2170        }
2171
2172        // placeholder for the replaced transaction, if any
2173        let mut replaced_tx = None;
2174
2175        let pool_tx = PoolInternalTransaction {
2176            transaction: Arc::clone(&transaction),
2177            subpool: state.into(),
2178            state,
2179            cumulative_cost,
2180        };
2181
2182        // try to insert the transaction
2183        match self.txs.entry(*transaction.id()) {
2184            Entry::Vacant(entry) => {
2185                // Insert the transaction in both maps
2186                self.by_hash.insert(*pool_tx.transaction.hash(), pool_tx.transaction.clone());
2187                self.tx_type_counts.inc(pool_tx.transaction.transaction.ty());
2188                entry.insert(pool_tx);
2189            }
2190            Entry::Occupied(mut entry) => {
2191                // Transaction with the same nonce already exists: replacement candidate
2192                let existing_transaction = entry.get().transaction.as_ref();
2193                let maybe_replacement = transaction.as_ref();
2194
2195                // Ensure the new transaction is not underpriced
2196                if existing_transaction
2197                    .is_replacement_underpriced(maybe_replacement, &self.price_bumps)
2198                {
2199                    return Err(InsertErr::Underpriced {
2200                        transaction: pool_tx.transaction,
2201                        existing: *entry.get().transaction.hash(),
2202                    })
2203                }
2204                let new_hash = *pool_tx.transaction.hash();
2205                let new_transaction = pool_tx.transaction.clone();
2206                self.tx_type_counts.inc(pool_tx.transaction.transaction.ty());
2207                let replaced = entry.insert(pool_tx);
2208                self.tx_type_counts.dec(replaced.transaction.transaction.ty());
2209                self.by_hash.remove(replaced.transaction.hash());
2210                self.by_hash.insert(new_hash, new_transaction);
2211
2212                self.remove_auths(&replaced);
2213
2214                // also remove the hash
2215                replaced_tx = Some((replaced.transaction, replaced.subpool));
2216            }
2217        }
2218
2219        if let Some(auths) = &transaction.authority_ids {
2220            let tx_hash = transaction.hash();
2221            for auth in auths {
2222                self.auths.entry(*auth).or_default().insert(*tx_hash);
2223            }
2224        }
2225
2226        // Take the scratch buffer only after all fallible checks so rejected inserts retain it.
2227        let mut updates = std::mem::take(&mut self.update_buffer);
2228
2229        // The next transaction of this sender
2230        let on_chain_id = TransactionId::new(transaction.sender_id(), on_chain_nonce);
2231        let pending_fees = self.pending_fees;
2232        {
2233            // Tracks the next nonce we expect if the transactions are gapless
2234            let mut next_nonce = on_chain_id.nonce;
2235
2236            // We need to find out if the next transaction of the sender is considered pending
2237            // The direct descendant has _no_ parked ancestors because the `on_chain_nonce` is
2238            // pending, so we can set this to `false`
2239            let mut has_parked_ancestor = false;
2240
2241            // Traverse all future transactions of the sender starting with the on chain nonce, and
2242            // update existing transactions: `[on_chain_nonce,..]`
2243            for (id, tx) in self.descendant_txs_mut(&on_chain_id) {
2244                let current_pool = tx.subpool;
2245
2246                // If there's a nonce gap, we can shortcircuit
2247                if next_nonce != id.nonce {
2248                    break
2249                }
2250
2251                // close the nonce gap
2252                tx.state.insert(TxState::NO_NONCE_GAPS);
2253
2254                // set cumulative cost
2255                tx.cumulative_cost = cumulative_cost;
2256
2257                // Update for next transaction
2258                cumulative_cost = tx.next_cumulative_cost();
2259
2260                if cumulative_cost > on_chain_balance {
2261                    // sender lacks sufficient funds to pay for this transaction
2262                    tx.state.remove(TxState::ENOUGH_BALANCE);
2263                } else {
2264                    tx.state.insert(TxState::ENOUGH_BALANCE);
2265                }
2266
2267                // Update ancestor condition.
2268                if has_parked_ancestor {
2269                    tx.state.remove(TxState::NO_PARKED_ANCESTORS);
2270                } else {
2271                    tx.state.insert(TxState::NO_PARKED_ANCESTORS);
2272                }
2273
2274                Self::update_tx_fees(pending_fees, tx);
2275
2276                // update the pool based on the state
2277                tx.subpool = tx.state.into();
2278                has_parked_ancestor = !tx.state.is_pending();
2279
2280                if inserted_tx_id.eq(id) {
2281                    // if it is the new transaction, track its updated state
2282                    state = tx.state;
2283                } else {
2284                    // check if anything changed
2285                    if current_pool != tx.subpool {
2286                        updates.push(PoolUpdate {
2287                            id: *id,
2288                            current: current_pool,
2289                            destination: tx.subpool.into(),
2290                        })
2291                    }
2292                }
2293
2294                // increment for next iteration
2295                next_nonce = id.next_nonce();
2296            }
2297        }
2298
2299        // If this wasn't a replacement transaction we need to update the counter.
2300        if replaced_tx.is_none() {
2301            self.tx_inc(inserted_tx_id.sender);
2302        }
2303
2304        Ok(InsertOk { transaction, move_to: state.into(), state, replaced_tx, updates })
2305    }
2306
2307    /// Number of transactions in the entire pool
2308    pub(crate) fn len(&self) -> usize {
2309        self.txs.len()
2310    }
2311
2312    /// Whether the pool is empty
2313    pub(crate) fn is_empty(&self) -> bool {
2314        self.txs.is_empty()
2315    }
2316
2317    /// Asserts that the bijection between `by_hash` and `txs` is valid.
2318    #[cfg(any(test, feature = "test-utils"))]
2319    pub(crate) fn assert_invariants(&self) {
2320        assert_eq!(self.by_hash.len(), self.txs.len(), "by_hash.len() != txs.len()");
2321        assert!(self.auths.len() <= self.txs.len(), "auths.len() > txs.len()");
2322    }
2323}
2324
2325#[cfg(test)]
2326impl<T: PoolTransaction> AllTransactions<T> {
2327    /// This function retrieves the number of transactions stored in the pool for a specific sender.
2328    ///
2329    /// If there are no transactions for the given sender, it returns zero by default.
2330    pub(crate) fn tx_count(&self, sender: SenderId) -> usize {
2331        self.tx_counter.get(&sender).copied().unwrap_or_default()
2332    }
2333}
2334
2335impl<T: PoolTransaction> Default for AllTransactions<T> {
2336    fn default() -> Self {
2337        Self {
2338            max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER,
2339            minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE,
2340            block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
2341            by_hash: Default::default(),
2342            txs: Default::default(),
2343            sender_info: Default::default(),
2344            tx_counter: Default::default(),
2345            last_seen_block_number: Default::default(),
2346            last_seen_block_hash: Default::default(),
2347            pending_fees: Default::default(),
2348            // an empty pool trivially reflects the initial fees
2349            last_full_update_fees: Default::default(),
2350            price_bumps: Default::default(),
2351            local_transactions_config: Default::default(),
2352            auths: Default::default(),
2353            tx_type_counts: Default::default(),
2354            update_buffer: Default::default(),
2355            metrics: Default::default(),
2356        }
2357    }
2358}
2359
2360/// Number of transactions in the pool grouped by transaction type.
2361///
2362/// Maintained incrementally on insert/remove so that metrics updates don't require iterating
2363/// all transactions.
2364#[derive(Debug, Clone, Copy, Default)]
2365pub(crate) struct TxTypeCounts {
2366    legacy: u64,
2367    eip2930: u64,
2368    eip1559: u64,
2369    eip4844: u64,
2370    eip7702: u64,
2371    other: u64,
2372}
2373
2374impl TxTypeCounts {
2375    /// Returns a mutable reference to the counter for the given transaction type.
2376    const fn counter_mut(&mut self, tx_type: u8) -> &mut u64 {
2377        match tx_type {
2378            LEGACY_TX_TYPE_ID => &mut self.legacy,
2379            EIP2930_TX_TYPE_ID => &mut self.eip2930,
2380            EIP1559_TX_TYPE_ID => &mut self.eip1559,
2381            EIP4844_TX_TYPE_ID => &mut self.eip4844,
2382            EIP7702_TX_TYPE_ID => &mut self.eip7702,
2383            _ => &mut self.other,
2384        }
2385    }
2386
2387    /// Increments the counter for the given transaction type.
2388    const fn inc(&mut self, tx_type: u8) {
2389        *self.counter_mut(tx_type) += 1;
2390    }
2391
2392    /// Decrements the counter for the given transaction type.
2393    const fn dec(&mut self, tx_type: u8) {
2394        *self.counter_mut(tx_type) -= 1;
2395    }
2396}
2397
2398/// Represents updated fees for the pending block.
2399#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2400pub(crate) struct PendingFees {
2401    /// The pending base fee
2402    pub(crate) base_fee: u64,
2403    /// The pending blob fee
2404    pub(crate) blob_fee: u128,
2405}
2406
2407impl Default for PendingFees {
2408    fn default() -> Self {
2409        Self { base_fee: Default::default(), blob_fee: BLOB_TX_MIN_BLOB_GASPRICE }
2410    }
2411}
2412
2413/// Result type for inserting a transaction
2414pub(crate) type InsertResult<T> = Result<InsertOk<T>, InsertErr<T>>;
2415
2416/// Err variant of `InsertResult`
2417#[derive(Debug)]
2418pub(crate) enum InsertErr<T: PoolTransaction> {
2419    /// Attempted to replace existing transaction, but was underpriced
2420    Underpriced {
2421        transaction: Arc<ValidPoolTransaction<T>>,
2422        #[expect(dead_code)]
2423        existing: TxHash,
2424    },
2425    /// Attempted to insert a blob transaction with a nonce gap
2426    BlobTxHasNonceGap { transaction: Arc<ValidPoolTransaction<T>> },
2427    /// Attempted to insert a transaction that would overdraft the sender's balance at the time of
2428    /// insertion.
2429    Overdraft { transaction: Arc<ValidPoolTransaction<T>> },
2430    /// The transactions feeCap is lower than the chain's minimum fee requirement.
2431    ///
2432    /// See also [`MIN_PROTOCOL_BASE_FEE`]
2433    FeeCapBelowMinimumProtocolFeeCap { transaction: Arc<ValidPoolTransaction<T>>, fee_cap: u128 },
2434    /// Sender currently exceeds the configured limit for max account slots.
2435    ///
2436    /// The sender can be considered a spammer at this point.
2437    ExceededSenderTransactionsCapacity { transaction: Arc<ValidPoolTransaction<T>> },
2438    /// Transaction gas limit exceeds block's gas limit
2439    TxGasLimitMoreThanAvailableBlockGas {
2440        transaction: Arc<ValidPoolTransaction<T>>,
2441        block_gas_limit: u64,
2442        tx_gas_limit: u64,
2443    },
2444    /// Thrown if the mutual exclusivity constraint (blob vs normal transaction) is violated.
2445    TxTypeConflict { transaction: Arc<ValidPoolTransaction<T>> },
2446}
2447
2448/// Transaction was successfully inserted into the pool
2449#[derive(Debug)]
2450pub(crate) struct InsertOk<T: PoolTransaction> {
2451    /// Ref to the inserted transaction.
2452    transaction: Arc<ValidPoolTransaction<T>>,
2453    /// Where to move the transaction to.
2454    move_to: SubPool,
2455    /// Current state of the inserted tx.
2456    state: TxState,
2457    /// The transaction that was replaced by this.
2458    replaced_tx: Option<(Arc<ValidPoolTransaction<T>>, SubPool)>,
2459    /// Additional updates to transactions affected by this change.
2460    updates: Vec<PoolUpdate>,
2461}
2462
2463/// The internal transaction typed used by `AllTransactions` which also additional info used for
2464/// determining the current state of the transaction.
2465#[derive(Debug)]
2466pub(crate) struct PoolInternalTransaction<T: PoolTransaction> {
2467    /// The actual transaction object.
2468    pub(crate) transaction: Arc<ValidPoolTransaction<T>>,
2469    /// The `SubPool` that currently contains this transaction.
2470    pub(crate) subpool: SubPool,
2471    /// Keeps track of the current state of the transaction and therefore in which subpool it
2472    /// should reside
2473    pub(crate) state: TxState,
2474    /// The total cost all transactions before this transaction.
2475    ///
2476    /// This is the combined `cost` of all transactions from the same sender that currently
2477    /// come before this transaction.
2478    pub(crate) cumulative_cost: U256,
2479}
2480
2481// === impl PoolInternalTransaction ===
2482
2483impl<T: PoolTransaction> PoolInternalTransaction<T> {
2484    fn next_cumulative_cost(&self) -> U256 {
2485        self.cumulative_cost + self.transaction.cost()
2486    }
2487}
2488
2489/// Stores relevant context about a sender.
2490#[derive(Debug, Clone, Default)]
2491pub(crate) struct SenderInfo {
2492    /// current nonce of the sender.
2493    pub(crate) state_nonce: u64,
2494    /// Balance of the sender at the current point.
2495    pub(crate) balance: U256,
2496}
2497
2498// === impl SenderInfo ===
2499
2500impl SenderInfo {
2501    /// Updates the info with the new values.
2502    const fn update(&mut self, state_nonce: u64, balance: U256) {
2503        *self = Self { state_nonce, balance };
2504    }
2505}
2506
2507#[cfg(test)]
2508mod tests {
2509    use super::*;
2510    use crate::{
2511        test_utils::{MockOrdering, MockTransaction, MockTransactionFactory, MockTransactionSet},
2512        traits::TransactionOrigin,
2513        SubPoolLimit,
2514    };
2515    use alloy_consensus::{Transaction, TxType};
2516    use alloy_primitives::address;
2517
2518    #[test]
2519    fn test_insert_blob() {
2520        let on_chain_balance = U256::MAX;
2521        let on_chain_nonce = 0;
2522        let mut f = MockTransactionFactory::default();
2523        let mut pool = AllTransactions::default();
2524        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2525        let valid_tx = f.validated(tx);
2526        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2527            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2528        assert!(updates.is_empty());
2529        assert!(replaced_tx.is_none());
2530        assert!(state.contains(TxState::NO_NONCE_GAPS));
2531        assert!(state.contains(TxState::ENOUGH_BALANCE));
2532        assert!(state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2533        assert_eq!(move_to, SubPool::Pending);
2534
2535        let inserted = pool.txs.get(&valid_tx.transaction_id).unwrap();
2536        assert_eq!(inserted.subpool, SubPool::Pending);
2537    }
2538
2539    #[test]
2540    fn test_insert_blob_not_enough_blob_fee() {
2541        let on_chain_balance = U256::MAX;
2542        let on_chain_nonce = 0;
2543        let mut f = MockTransactionFactory::default();
2544        let mut pool = AllTransactions {
2545            pending_fees: PendingFees { blob_fee: 10_000_000, ..Default::default() },
2546            ..Default::default()
2547        };
2548        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2549        pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap() + 1;
2550        let valid_tx = f.validated(tx);
2551        let InsertOk { state, .. } =
2552            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2553        assert!(state.contains(TxState::NO_NONCE_GAPS));
2554        assert!(!state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2555
2556        let _ = pool.txs.get(&valid_tx.transaction_id).unwrap();
2557    }
2558
2559    #[test]
2560    fn test_valid_tx_with_decreasing_blob_fee() {
2561        let on_chain_balance = U256::MAX;
2562        let on_chain_nonce = 0;
2563        let mut f = MockTransactionFactory::default();
2564        let mut pool = AllTransactions {
2565            pending_fees: PendingFees { blob_fee: 10_000_000, ..Default::default() },
2566            ..Default::default()
2567        };
2568        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2569
2570        pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap() + 1;
2571        let valid_tx = f.validated(tx.clone());
2572        let InsertOk { state, .. } =
2573            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2574        assert!(state.contains(TxState::NO_NONCE_GAPS));
2575        assert!(!state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2576
2577        let _ = pool.txs.get(&valid_tx.transaction_id).unwrap();
2578        pool.remove_transaction(&valid_tx.transaction_id);
2579
2580        pool.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap();
2581        let InsertOk { state, .. } =
2582            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2583        assert!(state.contains(TxState::NO_NONCE_GAPS));
2584        assert!(state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2585    }
2586
2587    #[test]
2588    fn test_demote_valid_tx_with_increasing_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 = TxPool::new(MockOrdering::default(), Default::default());
2593        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2594
2595        // set block info so the tx is initially underpriced w.r.t. blob fee
2596        let mut block_info = pool.block_info();
2597        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap());
2598        pool.set_block_info(block_info);
2599
2600        let validated = f.validated(tx.clone());
2601        let id = *validated.id();
2602        pool.add_transaction(validated, on_chain_balance, on_chain_nonce, None).unwrap();
2603
2604        // assert pool lengths
2605        assert!(pool.blob_pool.is_empty());
2606        assert_eq!(pool.pending_pool.len(), 1);
2607
2608        // check tx state and derived subpool
2609        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2610        assert!(internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2611        assert_eq!(internal_tx.subpool, SubPool::Pending);
2612
2613        // set block info so the pools are updated
2614        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap() + 1);
2615        pool.set_block_info(block_info);
2616
2617        // check that the tx is promoted
2618        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2619        assert!(!internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2620        assert_eq!(internal_tx.subpool, SubPool::Blob);
2621
2622        // make sure the blob transaction was promoted into the pending pool
2623        assert_eq!(pool.blob_pool.len(), 1);
2624        assert!(pool.pending_pool.is_empty());
2625    }
2626
2627    #[test]
2628    fn test_promote_valid_tx_with_decreasing_blob_fee() {
2629        let on_chain_balance = U256::MAX;
2630        let on_chain_nonce = 0;
2631        let mut f = MockTransactionFactory::default();
2632        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2633        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2634
2635        // set block info so the tx is initially underpriced w.r.t. blob fee
2636        let mut block_info = pool.block_info();
2637        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap() + 1);
2638        pool.set_block_info(block_info);
2639
2640        let validated = f.validated(tx.clone());
2641        let id = *validated.id();
2642        pool.add_transaction(validated, on_chain_balance, on_chain_nonce, None).unwrap();
2643
2644        // assert pool lengths
2645        assert!(pool.pending_pool.is_empty());
2646        assert_eq!(pool.blob_pool.len(), 1);
2647
2648        // check tx state and derived subpool
2649        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2650        assert!(!internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2651        assert_eq!(internal_tx.subpool, SubPool::Blob);
2652
2653        // set block info so the pools are updated
2654        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap());
2655        pool.set_block_info(block_info);
2656
2657        // check that the tx is promoted
2658        let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2659        assert!(internal_tx.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
2660        assert_eq!(internal_tx.subpool, SubPool::Pending);
2661
2662        // make sure the blob transaction was promoted into the pending pool
2663        assert_eq!(pool.pending_pool.len(), 1);
2664        assert!(pool.blob_pool.is_empty());
2665    }
2666
2667    #[test]
2668    fn test_queued_count_includes_blob_pool() {
2669        let on_chain_balance = U256::MAX;
2670        let on_chain_nonce = 0;
2671        let mut f = MockTransactionFactory::default();
2672        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2673        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2674
2675        // set block info so the tx is underpriced w.r.t. blob fee and lands in the blob pool
2676        let mut block_info = pool.block_info();
2677        block_info.pending_blob_fee = Some(tx.max_fee_per_blob_gas().unwrap() + 1);
2678        pool.set_block_info(block_info);
2679
2680        let validated = f.validated(tx);
2681        pool.add_transaction(validated, on_chain_balance, on_chain_nonce, None).unwrap();
2682
2683        assert_eq!(pool.blob_pool.len(), 1);
2684        assert!(pool.pending_pool.is_empty());
2685
2686        // blob pool transactions are parked and must be reported as queued
2687        assert_eq!(pool.queued_transactions_count(), 1);
2688    }
2689
2690    /// A struct representing a txpool promotion test instance
2691    #[derive(Debug, PartialEq, Eq, Clone, Hash)]
2692    struct PromotionTest {
2693        /// The basefee at the start of the test
2694        basefee: u64,
2695        /// The blobfee at the start of the test
2696        blobfee: u128,
2697        /// The subpool at the start of the test
2698        subpool: SubPool,
2699        /// The basefee update
2700        basefee_update: u64,
2701        /// The blobfee update
2702        blobfee_update: u128,
2703        /// The subpool after the update
2704        new_subpool: SubPool,
2705    }
2706
2707    impl PromotionTest {
2708        /// Returns the test case for the opposite update
2709        const fn opposite(&self) -> Self {
2710            Self {
2711                basefee: self.basefee_update,
2712                blobfee: self.blobfee_update,
2713                subpool: self.new_subpool,
2714                blobfee_update: self.blobfee,
2715                basefee_update: self.basefee,
2716                new_subpool: self.subpool,
2717            }
2718        }
2719
2720        fn assert_subpool_lengths<T: TransactionOrdering>(
2721            &self,
2722            pool: &TxPool<T>,
2723            failure_message: String,
2724            check_subpool: SubPool,
2725        ) {
2726            match check_subpool {
2727                SubPool::Blob => {
2728                    assert_eq!(pool.blob_pool.len(), 1, "{failure_message}");
2729                    assert!(pool.pending_pool.is_empty(), "{failure_message}");
2730                    assert!(pool.basefee_pool.is_empty(), "{failure_message}");
2731                    assert!(pool.queued_pool.is_empty(), "{failure_message}");
2732                }
2733                SubPool::Pending => {
2734                    assert!(pool.blob_pool.is_empty(), "{failure_message}");
2735                    assert_eq!(pool.pending_pool.len(), 1, "{failure_message}");
2736                    assert!(pool.basefee_pool.is_empty(), "{failure_message}");
2737                    assert!(pool.queued_pool.is_empty(), "{failure_message}");
2738                }
2739                SubPool::BaseFee => {
2740                    assert!(pool.blob_pool.is_empty(), "{failure_message}");
2741                    assert!(pool.pending_pool.is_empty(), "{failure_message}");
2742                    assert_eq!(pool.basefee_pool.len(), 1, "{failure_message}");
2743                    assert!(pool.queued_pool.is_empty(), "{failure_message}");
2744                }
2745                SubPool::Queued => {
2746                    assert!(pool.blob_pool.is_empty(), "{failure_message}");
2747                    assert!(pool.pending_pool.is_empty(), "{failure_message}");
2748                    assert!(pool.basefee_pool.is_empty(), "{failure_message}");
2749                    assert_eq!(pool.queued_pool.len(), 1, "{failure_message}");
2750                }
2751            }
2752        }
2753
2754        /// Runs an assertion on the provided pool, ensuring that the transaction is in the correct
2755        /// subpool based on the starting condition of the test, assuming the pool contains only a
2756        /// single transaction.
2757        fn assert_single_tx_starting_subpool<T: TransactionOrdering>(&self, pool: &TxPool<T>) {
2758            self.assert_subpool_lengths(
2759                pool,
2760                format!("pool length check failed at start of test: {self:?}"),
2761                self.subpool,
2762            );
2763        }
2764
2765        /// Runs an assertion on the provided pool, ensuring that the transaction is in the correct
2766        /// subpool based on the ending condition of the test, assuming the pool contains only a
2767        /// single transaction.
2768        fn assert_single_tx_ending_subpool<T: TransactionOrdering>(&self, pool: &TxPool<T>) {
2769            self.assert_subpool_lengths(
2770                pool,
2771                format!("pool length check failed at end of test: {self:?}"),
2772                self.new_subpool,
2773            );
2774        }
2775    }
2776
2777    #[test]
2778    fn test_promote_blob_tx_with_both_pending_fee_updates() {
2779        // this exhaustively tests all possible promotion scenarios for a single transaction moving
2780        // between the blob and pending pool
2781        let on_chain_balance = U256::MAX;
2782        let on_chain_nonce = 0;
2783        let mut f = MockTransactionFactory::default();
2784        let tx = MockTransaction::eip4844().inc_price().inc_limit();
2785
2786        let max_fee_per_blob_gas = tx.max_fee_per_blob_gas().unwrap();
2787        let max_fee_per_gas = tx.max_fee_per_gas() as u64;
2788
2789        // These are all _promotion_ tests or idempotent tests.
2790        let mut expected_promotions = vec![
2791            PromotionTest {
2792                blobfee: max_fee_per_blob_gas + 1,
2793                basefee: max_fee_per_gas + 1,
2794                subpool: SubPool::Blob,
2795                blobfee_update: max_fee_per_blob_gas + 1,
2796                basefee_update: max_fee_per_gas + 1,
2797                new_subpool: SubPool::Blob,
2798            },
2799            PromotionTest {
2800                blobfee: max_fee_per_blob_gas + 1,
2801                basefee: max_fee_per_gas + 1,
2802                subpool: SubPool::Blob,
2803                blobfee_update: max_fee_per_blob_gas,
2804                basefee_update: max_fee_per_gas + 1,
2805                new_subpool: SubPool::Blob,
2806            },
2807            PromotionTest {
2808                blobfee: max_fee_per_blob_gas + 1,
2809                basefee: max_fee_per_gas + 1,
2810                subpool: SubPool::Blob,
2811                blobfee_update: max_fee_per_blob_gas + 1,
2812                basefee_update: max_fee_per_gas,
2813                new_subpool: SubPool::Blob,
2814            },
2815            PromotionTest {
2816                blobfee: max_fee_per_blob_gas + 1,
2817                basefee: max_fee_per_gas + 1,
2818                subpool: SubPool::Blob,
2819                blobfee_update: max_fee_per_blob_gas,
2820                basefee_update: max_fee_per_gas,
2821                new_subpool: SubPool::Pending,
2822            },
2823            PromotionTest {
2824                blobfee: max_fee_per_blob_gas,
2825                basefee: max_fee_per_gas + 1,
2826                subpool: SubPool::Blob,
2827                blobfee_update: max_fee_per_blob_gas,
2828                basefee_update: max_fee_per_gas,
2829                new_subpool: SubPool::Pending,
2830            },
2831            PromotionTest {
2832                blobfee: max_fee_per_blob_gas + 1,
2833                basefee: max_fee_per_gas,
2834                subpool: SubPool::Blob,
2835                blobfee_update: max_fee_per_blob_gas,
2836                basefee_update: max_fee_per_gas,
2837                new_subpool: SubPool::Pending,
2838            },
2839            PromotionTest {
2840                blobfee: max_fee_per_blob_gas,
2841                basefee: max_fee_per_gas,
2842                subpool: SubPool::Pending,
2843                blobfee_update: max_fee_per_blob_gas,
2844                basefee_update: max_fee_per_gas,
2845                new_subpool: SubPool::Pending,
2846            },
2847        ];
2848
2849        // extend the test cases with reversed updates - this will add all _demotion_ tests
2850        let reversed = expected_promotions.iter().map(|test| test.opposite()).collect::<Vec<_>>();
2851        expected_promotions.extend(reversed);
2852
2853        // dedup the test cases
2854        let expected_promotions = expected_promotions.into_iter().collect::<HashSet<_>>();
2855
2856        for promotion_test in &expected_promotions {
2857            let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2858
2859            // set block info so the tx is initially underpriced w.r.t. blob fee
2860            let mut block_info = pool.block_info();
2861
2862            block_info.pending_blob_fee = Some(promotion_test.blobfee);
2863            block_info.pending_basefee = promotion_test.basefee;
2864            pool.set_block_info(block_info);
2865
2866            let validated = f.validated(tx.clone());
2867            let id = *validated.id();
2868            pool.add_transaction(validated, on_chain_balance, on_chain_nonce, None).unwrap();
2869
2870            // assert pool lengths
2871            promotion_test.assert_single_tx_starting_subpool(&pool);
2872
2873            // check tx state and derived subpool, it should not move into the blob pool
2874            let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2875            assert_eq!(
2876                internal_tx.subpool, promotion_test.subpool,
2877                "Subpools do not match at start of test: {promotion_test:?}"
2878            );
2879
2880            // set block info with new base fee
2881            block_info.pending_basefee = promotion_test.basefee_update;
2882            block_info.pending_blob_fee = Some(promotion_test.blobfee_update);
2883            pool.set_block_info(block_info);
2884
2885            // check tx state and derived subpool, it should not move into the blob pool
2886            let internal_tx = pool.all_transactions.txs.get(&id).unwrap();
2887            assert_eq!(
2888                internal_tx.subpool, promotion_test.new_subpool,
2889                "Subpools do not match at end of test: {promotion_test:?}"
2890            );
2891
2892            // assert new pool lengths
2893            promotion_test.assert_single_tx_ending_subpool(&pool);
2894        }
2895    }
2896
2897    #[test]
2898    fn test_insert_pending() {
2899        let on_chain_balance = U256::MAX;
2900        let on_chain_nonce = 0;
2901        let mut f = MockTransactionFactory::default();
2902        let mut pool = AllTransactions::default();
2903        let tx = MockTransaction::eip1559().inc_price().inc_limit();
2904        let valid_tx = f.validated(tx);
2905        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2906            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2907        assert!(updates.is_empty());
2908        assert!(replaced_tx.is_none());
2909        assert!(state.contains(TxState::NO_NONCE_GAPS));
2910        assert!(state.contains(TxState::ENOUGH_BALANCE));
2911        assert_eq!(move_to, SubPool::Pending);
2912
2913        let inserted = pool.txs.get(&valid_tx.transaction_id).unwrap();
2914        assert_eq!(inserted.subpool, SubPool::Pending);
2915    }
2916
2917    #[test]
2918    fn test_simple_insert() {
2919        let on_chain_balance = U256::ZERO;
2920        let on_chain_nonce = 0;
2921        let mut f = MockTransactionFactory::default();
2922        let mut pool = AllTransactions::default();
2923        let mut tx = MockTransaction::eip1559().inc_price().inc_limit();
2924        tx.set_priority_fee(100);
2925        tx.set_max_fee(100);
2926        let valid_tx = f.validated(tx.clone());
2927        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2928            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2929        assert!(updates.is_empty());
2930        assert!(replaced_tx.is_none());
2931        assert!(state.contains(TxState::NO_NONCE_GAPS));
2932        assert!(!state.contains(TxState::ENOUGH_BALANCE));
2933        assert_eq!(move_to, SubPool::Queued);
2934
2935        assert_eq!(pool.len(), 1);
2936        assert!(pool.contains(valid_tx.hash()));
2937        let expected_state = TxState::ENOUGH_FEE_CAP_BLOCK | TxState::NO_NONCE_GAPS;
2938        let inserted = pool.get(valid_tx.id()).unwrap();
2939        assert!(inserted.state.intersects(expected_state));
2940
2941        // insert the same tx again
2942        let res = pool.insert_tx(valid_tx, on_chain_balance, on_chain_nonce);
2943        res.unwrap_err();
2944        assert_eq!(pool.len(), 1);
2945
2946        let valid_tx = f.validated(tx.next());
2947        let InsertOk { updates, replaced_tx, move_to, state, .. } =
2948            pool.insert_tx(valid_tx.clone(), on_chain_balance, on_chain_nonce).unwrap();
2949
2950        assert!(updates.is_empty());
2951        assert!(replaced_tx.is_none());
2952        assert!(state.contains(TxState::NO_NONCE_GAPS));
2953        assert!(!state.contains(TxState::ENOUGH_BALANCE));
2954        assert_eq!(move_to, SubPool::Queued);
2955
2956        assert!(pool.contains(valid_tx.hash()));
2957        assert_eq!(pool.len(), 2);
2958        let inserted = pool.get(valid_tx.id()).unwrap();
2959        assert!(inserted.state.intersects(expected_state));
2960    }
2961
2962    #[test]
2963    // Test that on_canonical_state_change doesn't double-process transactions
2964    // when both fee and account updates would affect the same transaction
2965    fn test_on_canonical_state_change_no_double_processing() {
2966        let mut tx_factory = MockTransactionFactory::default();
2967        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
2968
2969        // Setup: Create a sender with a transaction in basefee pool
2970        let tx = MockTransaction::eip1559().with_gas_price(50).with_gas_limit(30_000);
2971        let sender = tx.sender();
2972
2973        // Set high base fee initially
2974        let mut block_info = pool.block_info();
2975        block_info.pending_basefee = 100;
2976        pool.set_block_info(block_info);
2977
2978        let validated = tx_factory.validated(tx);
2979        pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap();
2980
2981        // Get sender_id after the transaction has been added
2982        let sender_id = tx_factory.ids.sender_id(&sender).unwrap();
2983
2984        assert_eq!(pool.basefee_pool.len(), 1);
2985        assert_eq!(pool.pending_pool.len(), 0);
2986
2987        // Now simulate a canonical state change with:
2988        // 1. Lower base fee (would promote tx)
2989        // 2. Account balance update (would also evaluate tx)
2990        block_info.pending_basefee = 40;
2991
2992        let mut changed_senders = FxHashMap::default();
2993        changed_senders.insert(
2994            sender_id,
2995            SenderInfo {
2996                state_nonce: 0,
2997                balance: U256::from(20_000_000), // Increased balance
2998            },
2999        );
3000
3001        let outcome = pool.on_canonical_state_change(
3002            block_info,
3003            vec![], // no mined transactions
3004            changed_senders,
3005            PoolUpdateKind::Commit,
3006        );
3007
3008        // Transaction should be promoted exactly once
3009        assert_eq!(pool.pending_pool.len(), 1, "Transaction should be in pending pool");
3010        assert_eq!(pool.basefee_pool.len(), 0, "Transaction should not be in basefee pool");
3011        assert_eq!(outcome.promoted.len(), 1, "Should report exactly one promotion");
3012    }
3013
3014    #[test]
3015    // Regression test: ensure we don't double-count promotions when base fee
3016    // decreases and account is updated. This test would fail before the fix.
3017    fn test_canonical_state_change_with_basefee_update_regression() {
3018        let mut tx_factory = MockTransactionFactory::default();
3019        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3020
3021        // Create transactions from different senders to test independently
3022        let sender_balance = U256::from(100_000_000);
3023
3024        // Sender 1: tx will be promoted (gas price 60 > new base fee 50)
3025        let tx1 =
3026            MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000).with_nonce(0);
3027        let sender1 = tx1.sender();
3028
3029        // Sender 2: tx will be promoted (gas price 55 > new base fee 50)
3030        let tx2 =
3031            MockTransaction::eip1559().with_gas_price(55).with_gas_limit(21_000).with_nonce(0);
3032        let sender2 = tx2.sender();
3033
3034        // Sender 3: tx will NOT be promoted (gas price 45 < new base fee 50)
3035        let tx3 =
3036            MockTransaction::eip1559().with_gas_price(45).with_gas_limit(21_000).with_nonce(0);
3037        let sender3 = tx3.sender();
3038
3039        // Set high initial base fee (all txs will go to basefee pool)
3040        let mut block_info = pool.block_info();
3041        block_info.pending_basefee = 70;
3042        pool.set_block_info(block_info);
3043
3044        // Add all transactions
3045        let validated1 = tx_factory.validated(tx1);
3046        let validated2 = tx_factory.validated(tx2);
3047        let validated3 = tx_factory.validated(tx3);
3048
3049        pool.add_transaction(validated1, sender_balance, 0, None).unwrap();
3050        pool.add_transaction(validated2, sender_balance, 0, None).unwrap();
3051        pool.add_transaction(validated3, sender_balance, 0, None).unwrap();
3052
3053        let sender1_id = tx_factory.ids.sender_id(&sender1).unwrap();
3054        let sender2_id = tx_factory.ids.sender_id(&sender2).unwrap();
3055        let sender3_id = tx_factory.ids.sender_id(&sender3).unwrap();
3056
3057        // All should be in basefee pool initially
3058        assert_eq!(pool.basefee_pool.len(), 3, "All txs should be in basefee pool");
3059        assert_eq!(pool.pending_pool.len(), 0, "No txs should be in pending pool");
3060
3061        // Now decrease base fee to 50 - this should promote tx1 and tx2 (prices 60 and 55)
3062        // but not tx3 (price 45)
3063        block_info.pending_basefee = 50;
3064
3065        // Update all senders' balances (simulating account state changes)
3066        let mut changed_senders = FxHashMap::default();
3067        changed_senders.insert(
3068            sender1_id,
3069            SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) },
3070        );
3071        changed_senders.insert(
3072            sender2_id,
3073            SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) },
3074        );
3075        changed_senders.insert(
3076            sender3_id,
3077            SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) },
3078        );
3079
3080        let outcome = pool.on_canonical_state_change(
3081            block_info,
3082            vec![],
3083            changed_senders,
3084            PoolUpdateKind::Commit,
3085        );
3086
3087        // Check final state
3088        assert_eq!(pool.pending_pool.len(), 2, "tx1 and tx2 should be promoted");
3089        assert_eq!(pool.basefee_pool.len(), 1, "tx3 should remain in basefee");
3090
3091        // CRITICAL: Should report exactly 2 promotions, not 4 (which would happen with
3092        // double-processing)
3093        assert_eq!(
3094            outcome.promoted.len(),
3095            2,
3096            "Should report exactly 2 promotions, not double-counted"
3097        );
3098
3099        // Verify the correct transactions were promoted
3100        let promoted_prices: Vec<u128> =
3101            outcome.promoted.iter().map(|tx| tx.max_fee_per_gas()).collect();
3102        assert!(promoted_prices.contains(&60));
3103        assert!(promoted_prices.contains(&55));
3104    }
3105
3106    #[test]
3107    fn test_basefee_decrease_with_empty_senders() {
3108        // Test that fee promotions still occur when basefee decreases
3109        // even with no changed_senders
3110        let mut tx_factory = MockTransactionFactory::default();
3111        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3112
3113        // Create transaction that will be promoted when fee drops
3114        let tx = MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000);
3115
3116        // Set high initial base fee
3117        let mut block_info = pool.block_info();
3118        block_info.pending_basefee = 100;
3119        pool.set_block_info(block_info);
3120
3121        // Add transaction - should go to basefee pool
3122        let validated = tx_factory.validated(tx);
3123        pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap();
3124
3125        assert_eq!(pool.basefee_pool.len(), 1);
3126        assert_eq!(pool.pending_pool.len(), 0);
3127
3128        // Decrease base fee with NO changed senders
3129        block_info.pending_basefee = 50;
3130        let outcome = pool.on_canonical_state_change(
3131            block_info,
3132            vec![],
3133            FxHashMap::default(), // Empty changed_senders!
3134            PoolUpdateKind::Commit,
3135        );
3136
3137        // Transaction should still be promoted by fee-driven logic
3138        assert_eq!(pool.pending_pool.len(), 1, "Fee decrease should promote tx");
3139        assert_eq!(pool.basefee_pool.len(), 0);
3140        assert_eq!(outcome.promoted.len(), 1, "Should report promotion from fee update");
3141    }
3142
3143    #[test]
3144    fn test_basefee_decrease_account_makes_unfundable() {
3145        // Test that when basefee decreases but account update makes tx unfundable,
3146        // we don't get transient promote-then-discard double counting
3147        let mut tx_factory = MockTransactionFactory::default();
3148        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3149
3150        let tx = MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000);
3151        let sender = tx.sender();
3152
3153        // High initial base fee
3154        let mut block_info = pool.block_info();
3155        block_info.pending_basefee = 100;
3156        pool.set_block_info(block_info);
3157
3158        let validated = tx_factory.validated(tx);
3159        pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap();
3160        let sender_id = tx_factory.ids.sender_id(&sender).unwrap();
3161
3162        assert_eq!(pool.basefee_pool.len(), 1);
3163
3164        // Decrease base fee (would normally promote) but also drain account
3165        block_info.pending_basefee = 50;
3166        let mut changed_senders = FxHashMap::default();
3167        changed_senders.insert(
3168            sender_id,
3169            SenderInfo {
3170                state_nonce: 0,
3171                balance: U256::from(100), // Too low to pay for gas!
3172            },
3173        );
3174
3175        let outcome = pool.on_canonical_state_change(
3176            block_info,
3177            vec![],
3178            changed_senders,
3179            PoolUpdateKind::Commit,
3180        );
3181
3182        // With insufficient balance, transaction goes to queued pool
3183        assert_eq!(pool.pending_pool.len(), 0, "Unfunded tx should not be in pending");
3184        assert_eq!(pool.basefee_pool.len(), 0, "Tx no longer in basefee pool");
3185        assert_eq!(pool.queued_pool.len(), 1, "Unfunded tx should be in queued pool");
3186
3187        // Transaction is not removed, just moved to queued
3188        let tx_count = pool.all_transactions.txs.len();
3189        assert_eq!(tx_count, 1, "Transaction should still be in pool (in queued)");
3190
3191        assert_eq!(outcome.promoted.len(), 0, "Should not report promotion");
3192        assert_eq!(outcome.discarded.len(), 0, "Queued tx is not reported as discarded");
3193    }
3194
3195    #[test]
3196    fn insert_already_imported() {
3197        let on_chain_balance = U256::ZERO;
3198        let on_chain_nonce = 0;
3199        let mut f = MockTransactionFactory::default();
3200        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3201        let tx = MockTransaction::eip1559().inc_price().inc_limit();
3202        let tx = f.validated(tx);
3203        pool.add_transaction(tx.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3204        match pool.add_transaction(tx, on_chain_balance, on_chain_nonce, None).unwrap_err().kind {
3205            PoolErrorKind::AlreadyImported => {}
3206            _ => unreachable!(),
3207        }
3208    }
3209
3210    #[test]
3211    fn insert_replace() {
3212        let on_chain_balance = U256::ZERO;
3213        let on_chain_nonce = 0;
3214        let mut f = MockTransactionFactory::default();
3215        let mut pool = AllTransactions::default();
3216        let tx = MockTransaction::eip1559().inc_price().inc_limit();
3217        let first = f.validated(tx.clone());
3218        let _ = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
3219        let replacement = f.validated(tx.rng_hash().inc_price());
3220        let InsertOk { updates, replaced_tx, .. } =
3221            pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap();
3222        assert!(updates.is_empty());
3223        let replaced = replaced_tx.unwrap();
3224        assert_eq!(replaced.0.hash(), first.hash());
3225
3226        // ensure replaced tx is fully removed
3227        assert!(!pool.contains(first.hash()));
3228        assert!(pool.contains(replacement.hash()));
3229        assert_eq!(pool.len(), 1);
3230    }
3231
3232    #[test]
3233    fn insert_replace_txpool() {
3234        let on_chain_balance = U256::ZERO;
3235        let on_chain_nonce = 0;
3236        let mut f = MockTransactionFactory::default();
3237        let mut pool = TxPool::mock();
3238
3239        let tx = MockTransaction::eip1559().inc_price().inc_limit();
3240        let first = f.validated(tx.clone());
3241        let first_added =
3242            pool.add_transaction(first, on_chain_balance, on_chain_nonce, None).unwrap();
3243        let replacement = f.validated(tx.rng_hash().inc_price());
3244        let replacement_added = pool
3245            .add_transaction(replacement.clone(), on_chain_balance, on_chain_nonce, None)
3246            .unwrap();
3247
3248        // // ensure replaced tx removed
3249        assert!(!pool.contains(first_added.hash()));
3250        // but the replacement is still there
3251        assert!(pool.subpool_contains(replacement_added.subpool(), replacement_added.id()));
3252
3253        assert!(pool.contains(replacement.hash()));
3254        let size = pool.size();
3255        assert_eq!(size.total, 1);
3256        size.assert_invariants();
3257    }
3258
3259    #[test]
3260    fn insert_replace_underpriced() {
3261        let on_chain_balance = U256::ZERO;
3262        let on_chain_nonce = 0;
3263        let mut f = MockTransactionFactory::default();
3264        let mut pool = AllTransactions::default();
3265        let tx = MockTransaction::eip1559().inc_price().inc_limit();
3266        let first = f.validated(tx.clone());
3267        let _res = pool.insert_tx(first, on_chain_balance, on_chain_nonce);
3268        let mut replacement = f.validated(tx.rng_hash());
3269        replacement.transaction = replacement.transaction.decr_price();
3270        let err = pool.insert_tx(replacement, on_chain_balance, on_chain_nonce).unwrap_err();
3271        assert!(matches!(err, InsertErr::Underpriced { .. }));
3272    }
3273
3274    #[test]
3275    fn insert_replace_underpriced_not_enough_bump() {
3276        let on_chain_balance = U256::ZERO;
3277        let on_chain_nonce = 0;
3278        let mut f = MockTransactionFactory::default();
3279        let mut pool = AllTransactions::default();
3280        let mut tx = MockTransaction::eip1559().inc_price().inc_limit();
3281        tx.set_priority_fee(100);
3282        tx.set_max_fee(100);
3283        let first = f.validated(tx.clone());
3284        let _ = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
3285        let mut replacement = f.validated(tx.rng_hash().inc_price());
3286
3287        // a price bump of 9% is not enough for a default min price bump of 10%
3288        replacement.transaction.set_priority_fee(109);
3289        replacement.transaction.set_max_fee(109);
3290        let err =
3291            pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err();
3292        assert!(matches!(err, InsertErr::Underpriced { .. }));
3293        // ensure first tx is not removed
3294        assert!(pool.contains(first.hash()));
3295        assert_eq!(pool.len(), 1);
3296
3297        // should also fail if the bump in max fee is not enough
3298        replacement.transaction.set_priority_fee(110);
3299        replacement.transaction.set_max_fee(109);
3300        let err =
3301            pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err();
3302        assert!(matches!(err, InsertErr::Underpriced { .. }));
3303        assert!(pool.contains(first.hash()));
3304        assert_eq!(pool.len(), 1);
3305
3306        // should also fail if the bump in priority fee is not enough
3307        replacement.transaction.set_priority_fee(109);
3308        replacement.transaction.set_max_fee(110);
3309        let err = pool.insert_tx(replacement, on_chain_balance, on_chain_nonce).unwrap_err();
3310        assert!(matches!(err, InsertErr::Underpriced { .. }));
3311        assert!(pool.contains(first.hash()));
3312        assert_eq!(pool.len(), 1);
3313    }
3314
3315    #[test]
3316    fn insert_replace_underpriced_rounds_up_minimum_bump() {
3317        let on_chain_balance = U256::ZERO;
3318        let on_chain_nonce = 0;
3319        let mut f = MockTransactionFactory::default();
3320        let mut pool = AllTransactions { minimal_protocol_basefee: 0, ..Default::default() };
3321        let mut tx = MockTransaction::eip1559().inc_price().inc_limit();
3322        tx.set_priority_fee(1);
3323        tx.set_max_fee(1);
3324
3325        let first = f.validated(tx.clone());
3326        let _ = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
3327
3328        let mut replacement = f.validated(tx.rng_hash().inc_price());
3329        replacement.transaction.set_priority_fee(1);
3330        replacement.transaction.set_max_fee(2);
3331        let err =
3332            pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err();
3333        assert!(matches!(err, InsertErr::Underpriced { .. }));
3334        assert!(pool.contains(first.hash()));
3335        assert_eq!(pool.len(), 1);
3336
3337        replacement.transaction.set_priority_fee(2);
3338        replacement.transaction.set_max_fee(2);
3339        let replaced = pool.insert_tx(replacement, on_chain_balance, on_chain_nonce).unwrap();
3340        assert!(replaced.replaced_tx.is_some());
3341        assert_eq!(pool.len(), 1);
3342    }
3343
3344    #[test]
3345    fn insert_conflicting_type_normal_to_blob() {
3346        let on_chain_balance = U256::from(10_000);
3347        let on_chain_nonce = 0;
3348        let mut f = MockTransactionFactory::default();
3349        let mut pool = AllTransactions::default();
3350        let tx = MockTransaction::eip1559().inc_price().inc_limit();
3351        let first = f.validated(tx.clone());
3352        pool.insert_tx(first, on_chain_balance, on_chain_nonce).unwrap();
3353        let tx = MockTransaction::eip4844().set_sender(tx.sender()).inc_price_by(100).inc_limit();
3354        let blob = f.validated(tx);
3355        let err = pool.insert_tx(blob, on_chain_balance, on_chain_nonce).unwrap_err();
3356        assert!(matches!(err, InsertErr::TxTypeConflict { .. }), "{err:?}");
3357    }
3358
3359    #[test]
3360    fn insert_conflicting_type_blob_to_normal() {
3361        let on_chain_balance = U256::from(10_000);
3362        let on_chain_nonce = 0;
3363        let mut f = MockTransactionFactory::default();
3364        let mut pool = AllTransactions::default();
3365        let tx = MockTransaction::eip4844().inc_price().inc_limit();
3366        let first = f.validated(tx.clone());
3367        pool.insert_tx(first, on_chain_balance, on_chain_nonce).unwrap();
3368        let tx = MockTransaction::eip1559().set_sender(tx.sender()).inc_price_by(100).inc_limit();
3369        let tx = f.validated(tx);
3370        let err = pool.insert_tx(tx, on_chain_balance, on_chain_nonce).unwrap_err();
3371        assert!(matches!(err, InsertErr::TxTypeConflict { .. }), "{err:?}");
3372    }
3373
3374    // insert nonce then nonce - 1
3375    #[test]
3376    fn insert_previous() {
3377        let on_chain_balance = U256::ZERO;
3378        let on_chain_nonce = 0;
3379        let mut f = MockTransactionFactory::default();
3380        let mut pool = AllTransactions::default();
3381        let tx = MockTransaction::eip1559().inc_nonce().inc_price().inc_limit();
3382        let first = f.validated(tx.clone());
3383        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce);
3384
3385        let first_in_pool = pool.get(first.id()).unwrap();
3386
3387        // has nonce gap
3388        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3389
3390        let prev = f.validated(tx.prev());
3391        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3392            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3393
3394        // no updates since still in queued pool
3395        assert!(updates.is_empty());
3396        assert!(replaced_tx.is_none());
3397        assert!(state.contains(TxState::NO_NONCE_GAPS));
3398        assert_eq!(move_to, SubPool::Queued);
3399
3400        let first_in_pool = pool.get(first.id()).unwrap();
3401        // has non nonce gap
3402        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3403    }
3404
3405    // insert nonce then nonce - 1
3406    #[test]
3407    fn insert_with_updates() {
3408        let on_chain_balance = U256::from(10_000);
3409        let on_chain_nonce = 0;
3410        let mut f = MockTransactionFactory::default();
3411        let mut pool = AllTransactions::default();
3412        let tx = MockTransaction::eip1559().inc_nonce().set_gas_price(100).inc_limit();
3413        let first = f.validated(tx.clone());
3414        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
3415
3416        let first_in_pool = pool.get(first.id()).unwrap();
3417        // has nonce gap
3418        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3419        assert_eq!(SubPool::Queued, first_in_pool.subpool);
3420
3421        let prev = f.validated(tx.prev());
3422        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3423            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3424
3425        // updated previous tx
3426        assert_eq!(updates.len(), 1);
3427        assert!(replaced_tx.is_none());
3428        assert!(state.contains(TxState::NO_NONCE_GAPS));
3429        assert_eq!(move_to, SubPool::Pending);
3430
3431        let first_in_pool = pool.get(first.id()).unwrap();
3432        // has non nonce gap
3433        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3434        assert_eq!(SubPool::Pending, first_in_pool.subpool);
3435    }
3436
3437    #[test]
3438    fn insert_previous_blocking() {
3439        let on_chain_balance = U256::from(1_000);
3440        let on_chain_nonce = 0;
3441        let mut f = MockTransactionFactory::default();
3442        let mut pool = AllTransactions::default();
3443        pool.pending_fees.base_fee = pool.minimal_protocol_basefee.checked_add(1).unwrap();
3444        let tx = MockTransaction::eip1559().inc_nonce().inc_limit();
3445        let first = f.validated(tx.clone());
3446
3447        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce);
3448
3449        let first_in_pool = pool.get(first.id()).unwrap();
3450
3451        assert!(tx.get_gas_price() < pool.pending_fees.base_fee as u128);
3452        // has nonce gap
3453        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3454
3455        let prev = f.validated(tx.prev());
3456        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3457            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3458
3459        assert!(!state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3460        // no updates since still in queued pool
3461        assert!(updates.is_empty());
3462        assert!(replaced_tx.is_none());
3463        assert!(state.contains(TxState::NO_NONCE_GAPS));
3464        assert_eq!(move_to, SubPool::BaseFee);
3465
3466        let first_in_pool = pool.get(first.id()).unwrap();
3467        // has non nonce gap
3468        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3469    }
3470
3471    #[test]
3472    fn rejects_spammer() {
3473        let on_chain_balance = U256::from(1_000);
3474        let on_chain_nonce = 0;
3475        let mut f = MockTransactionFactory::default();
3476        let mut pool = AllTransactions::default();
3477
3478        let mut tx = MockTransaction::eip1559();
3479        let unblocked_tx = tx.clone();
3480        for _ in 0..pool.max_account_slots {
3481            tx = tx.next();
3482            pool.insert_tx(f.validated(tx.clone()), on_chain_balance, on_chain_nonce).unwrap();
3483        }
3484
3485        assert_eq!(
3486            pool.max_account_slots,
3487            pool.tx_count(f.ids.sender_id(tx.get_sender()).unwrap())
3488        );
3489
3490        let err =
3491            pool.insert_tx(f.validated(tx.next()), on_chain_balance, on_chain_nonce).unwrap_err();
3492        assert!(matches!(err, InsertErr::ExceededSenderTransactionsCapacity { .. }));
3493
3494        assert!(pool
3495            .insert_tx(f.validated(unblocked_tx), on_chain_balance, on_chain_nonce)
3496            .is_ok());
3497    }
3498
3499    #[test]
3500    fn allow_local_spamming() {
3501        let on_chain_balance = U256::from(1_000);
3502        let on_chain_nonce = 0;
3503        let mut f = MockTransactionFactory::default();
3504        let mut pool = AllTransactions::default();
3505
3506        let mut tx = MockTransaction::eip1559();
3507        for _ in 0..pool.max_account_slots {
3508            tx = tx.next();
3509            pool.insert_tx(
3510                f.validated_with_origin(TransactionOrigin::Local, tx.clone()),
3511                on_chain_balance,
3512                on_chain_nonce,
3513            )
3514            .unwrap();
3515        }
3516
3517        assert_eq!(
3518            pool.max_account_slots,
3519            pool.tx_count(f.ids.sender_id(tx.get_sender()).unwrap())
3520        );
3521
3522        pool.insert_tx(
3523            f.validated_with_origin(TransactionOrigin::Local, tx.next()),
3524            on_chain_balance,
3525            on_chain_nonce,
3526        )
3527        .unwrap();
3528    }
3529
3530    #[test]
3531    fn reject_tx_over_gas_limit() {
3532        let on_chain_balance = U256::from(1_000);
3533        let on_chain_nonce = 0;
3534        let mut f = MockTransactionFactory::default();
3535        let mut pool = AllTransactions::default();
3536
3537        let tx = MockTransaction::eip1559().with_gas_limit(30_000_001);
3538
3539        assert!(matches!(
3540            pool.insert_tx(f.validated(tx), on_chain_balance, on_chain_nonce),
3541            Err(InsertErr::TxGasLimitMoreThanAvailableBlockGas { .. })
3542        ));
3543    }
3544
3545    #[test]
3546    fn test_tx_equal_gas_limit() {
3547        let on_chain_balance = U256::from(1_000);
3548        let on_chain_nonce = 0;
3549        let mut f = MockTransactionFactory::default();
3550        let mut pool = AllTransactions::default();
3551
3552        let tx = MockTransaction::eip1559().with_gas_limit(30_000_000);
3553
3554        let InsertOk { state, .. } =
3555            pool.insert_tx(f.validated(tx), on_chain_balance, on_chain_nonce).unwrap();
3556        assert!(state.contains(TxState::NOT_TOO_MUCH_GAS));
3557    }
3558
3559    #[test]
3560    fn full_update_sender_heuristic() {
3561        let mut pool = AllTransactions::<MockTransaction>::default();
3562        for sender in 0..(FULL_UPDATE_MIN_SENDERS - 1) as u64 {
3563            pool.tx_counter.insert(sender.into(), 1);
3564        }
3565
3566        // Below the pool-size floor, only covering every live sender triggers a full update.
3567        assert!(!pool.should_update_all_senders(250));
3568        assert!(pool.should_update_all_senders(FULL_UPDATE_MIN_SENDERS - 1));
3569
3570        pool.tx_counter.insert((FULL_UPDATE_MIN_SENDERS as u64 - 1).into(), 1);
3571
3572        // At the floor, one quarter of the live senders is the crossover.
3573        assert!(
3574            !pool.should_update_all_senders(FULL_UPDATE_MIN_SENDERS / FULL_UPDATE_SENDER_RATIO - 1)
3575        );
3576        assert!(pool.should_update_all_senders(FULL_UPDATE_MIN_SENDERS / FULL_UPDATE_SENDER_RATIO));
3577        assert!(pool.should_update_all_senders(FULL_UPDATE_MIN_SENDERS + 1));
3578    }
3579
3580    #[test]
3581    fn update_only_visits_changed_senders_when_fees_are_unchanged() {
3582        let mut f = MockTransactionFactory::default();
3583        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3584
3585        // two senders, each with one transaction
3586        let a = f.validated(MockTransaction::eip1559().inc_price_by(10));
3587        let b = f.validated(MockTransaction::eip1559().inc_price_by(10));
3588        let (a_id, b_id) = (*a.id(), *b.id());
3589        assert_ne!(a_id.sender, b_id.sender);
3590        pool.add_transaction(a, U256::from(1_000_000), 0, None).unwrap();
3591        pool.add_transaction(b, U256::from(1_000_000), 0, None).unwrap();
3592
3593        // a full update applies the current fees to every transaction and records them
3594        pool.all_transactions.update(&Default::default());
3595        assert_eq!(pool.all_transactions.last_full_update_fees, pool.all_transactions.pending_fees);
3596
3597        // sender `a` moved past its transaction on chain, the fees did not move. Only `a` should
3598        // be evaluated, and `b` must be left exactly as it was.
3599        let b_state_before = pool.all_transactions.txs.get(&b_id).unwrap().state;
3600        let mut changed = FxHashMap::default();
3601        changed.insert(a_id.sender, SenderInfo { state_nonce: 1, balance: U256::from(1_000_000) });
3602
3603        let produced = pool.all_transactions.update(&changed);
3604
3605        assert_eq!(produced.len(), 1, "expected exactly the changed sender's update");
3606        assert_eq!(produced[0].id, a_id);
3607        assert!(matches!(produced[0].destination, Destination::Discard));
3608        assert_eq!(
3609            pool.all_transactions.txs.get(&b_id).unwrap().state,
3610            b_state_before,
3611            "unchanged sender was modified"
3612        );
3613    }
3614
3615    #[test]
3616    fn changed_sender_update_matches_full_update() {
3617        let senders = [
3618            address!("0x000000000000000000000000000000000000000a"),
3619            address!("0x000000000000000000000000000000000000000b"),
3620            address!("0x000000000000000000000000000000000000000c"),
3621        ];
3622        let starting_nonces = [5, 11, 17];
3623
3624        let build_pool = || {
3625            let mut f = MockTransactionFactory::default();
3626            let mut pool = AllTransactions::default();
3627            pool.pending_fees.base_fee = 1;
3628
3629            for (sender, starting_nonce) in senders.into_iter().zip(starting_nonces) {
3630                for nonce in starting_nonce..starting_nonce + 3 {
3631                    let tx = MockTransaction::eip1559()
3632                        .with_sender(sender)
3633                        .with_nonce(nonce)
3634                        .inc_price_by(10)
3635                        .rng_hash();
3636                    pool.insert_tx(f.validated(tx), U256::from(1_000_000), starting_nonce).unwrap();
3637                }
3638            }
3639
3640            // The fee differs from the initial marker, so this takes the all-transactions path.
3641            pool.update(&Default::default());
3642            let sender_ids = senders.map(|sender| f.ids.sender_id(&sender).unwrap());
3643            (pool, sender_ids)
3644        };
3645
3646        let (mut changed_senders_only, sender_ids) = build_pool();
3647        let (mut full_update, full_update_sender_ids) = build_pool();
3648        assert_eq!(sender_ids, full_update_sender_ids);
3649
3650        let mut changed = FxHashMap::default();
3651        changed.insert(
3652            sender_ids[0],
3653            SenderInfo { state_nonce: starting_nonces[0] + 1, balance: U256::from(1_000_000) },
3654        );
3655        changed.insert(
3656            sender_ids[1],
3657            SenderInfo { state_nonce: starting_nonces[1], balance: U256::ZERO },
3658        );
3659
3660        let mut changed_sender_updates = changed_senders_only.update(&changed);
3661        // Force the reference pool through the all-transactions path with the same pending fees.
3662        full_update.last_full_update_fees.base_fee =
3663            full_update.last_full_update_fees.base_fee.saturating_add(1);
3664        let mut full_updates = full_update.update(&changed);
3665
3666        let update_key = |update: &PoolUpdate| {
3667            let destination = match &update.destination {
3668                Destination::Discard => None,
3669                Destination::Pool(pool) => Some(*pool),
3670            };
3671            (update.id, update.current, destination)
3672        };
3673        changed_sender_updates.sort_unstable_by_key(|update| update.id);
3674        full_updates.sort_unstable_by_key(|update| update.id);
3675        assert_eq!(
3676            changed_sender_updates.iter().map(update_key).collect::<Vec<_>>(),
3677            full_updates.iter().map(update_key).collect::<Vec<_>>()
3678        );
3679
3680        let metadata = |pool: &AllTransactions<MockTransaction>| {
3681            pool.txs
3682                .iter()
3683                .map(|(id, tx)| (*id, tx.state, tx.subpool, tx.cumulative_cost))
3684                .collect::<Vec<_>>()
3685        };
3686        assert_eq!(metadata(&changed_senders_only), metadata(&full_update));
3687    }
3688
3689    #[test]
3690    fn update_visits_every_sender_when_the_base_fee_moved() {
3691        let mut f = MockTransactionFactory::default();
3692        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3693
3694        let tx = MockTransaction::eip1559().inc_price_by(10);
3695        let validated = f.validated(tx.clone());
3696        let id = *validated.id();
3697        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3698
3699        pool.all_transactions.update(&Default::default());
3700        assert!(pool
3701            .all_transactions
3702            .txs
3703            .get(&id)
3704            .unwrap()
3705            .state
3706            .contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3707
3708        // raising the base fee past the tx must still reach it even though no account changed,
3709        // otherwise the fast path would leave a stale fee bit behind
3710        pool.all_transactions.pending_fees.base_fee = (tx.max_fee_per_gas() + 1) as u64;
3711        pool.all_transactions.update(&Default::default());
3712
3713        assert!(
3714            !pool
3715                .all_transactions
3716                .txs
3717                .get(&id)
3718                .unwrap()
3719                .state
3720                .contains(TxState::ENOUGH_FEE_CAP_BLOCK),
3721            "fee change was not applied to an unchanged sender"
3722        );
3723    }
3724
3725    #[test]
3726    fn blob_fee_change_records_full_update() {
3727        let mut pool = AllTransactions::<MockTransaction>::default();
3728        pool.pending_fees.blob_fee += 1;
3729
3730        pool.update(&Default::default());
3731
3732        assert_eq!(pool.last_full_update_fees, pool.pending_fees);
3733    }
3734
3735    #[test]
3736    fn gap_fill_rechecks_descendant_fee_eligibility() {
3737        let mut f = MockTransactionFactory::default();
3738        let mut pool = AllTransactions::default();
3739        let sender = address!("0x000000000000000000000000000000000000000d");
3740        let balance = U256::MAX;
3741
3742        pool.pending_fees.base_fee = 100;
3743        let descendant = MockTransaction::eip1559()
3744            .with_sender(sender)
3745            .with_nonce(1)
3746            .with_gas_limit(21_000)
3747            .with_max_fee(150)
3748            .with_priority_fee(1)
3749            .rng_hash();
3750        let descendant = f.validated(descendant);
3751        let descendant_id = *descendant.id();
3752        pool.insert_tx(descendant, balance, 0).unwrap();
3753        pool.update(&Default::default());
3754
3755        // The fee increase cannot affect a nonce-gapped transaction yet, but closing its gap must
3756        // evaluate it against the current fee.
3757        pool.pending_fees.base_fee = 200;
3758        pool.update(&Default::default());
3759        assert!(pool.get(&descendant_id).unwrap().state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3760
3761        let predecessor = MockTransaction::eip1559()
3762            .with_sender(sender)
3763            .with_nonce(0)
3764            .with_gas_limit(21_000)
3765            .with_max_fee(250)
3766            .with_priority_fee(1)
3767            .rng_hash();
3768        let InsertOk { move_to, .. } =
3769            pool.insert_tx(f.validated(predecessor), balance, 0).unwrap();
3770
3771        assert_eq!(move_to, SubPool::Pending);
3772        let descendant = pool.get(&descendant_id).unwrap();
3773        assert_eq!(descendant.subpool, SubPool::BaseFee);
3774        assert!(!descendant.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3775        assert!(descendant.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
3776    }
3777
3778    #[test]
3779    fn base_fee_update_unparks_all_descendants() {
3780        let mut f = MockTransactionFactory::default();
3781        let mut pool = AllTransactions::default();
3782        let sender = address!("0x000000000000000000000000000000000000000e");
3783        let mut ids = Vec::new();
3784
3785        pool.pending_fees.base_fee = 200;
3786        for nonce in 0..3 {
3787            let tx = MockTransaction::eip1559()
3788                .with_sender(sender)
3789                .with_nonce(nonce)
3790                .with_gas_limit(21_000)
3791                .with_max_fee(150)
3792                .with_priority_fee(1)
3793                .rng_hash();
3794            let tx = f.validated(tx);
3795            ids.push(*tx.id());
3796            pool.insert_tx(tx, U256::MAX, 0).unwrap();
3797        }
3798
3799        pool.pending_fees.base_fee = 100;
3800        pool.update(&Default::default());
3801
3802        for id in ids {
3803            assert_eq!(pool.get(&id).unwrap().subpool, SubPool::Pending);
3804        }
3805    }
3806
3807    #[test]
3808    fn fee_update_keeps_descendants_of_underpriced_transaction_parked() {
3809        let mut f = MockTransactionFactory::default();
3810        let mut pool = AllTransactions::default();
3811        let sender = address!("0x0000000000000000000000000000000000000010");
3812        let fee_caps = [150, 50, 150];
3813        let mut ids = Vec::new();
3814
3815        pool.pending_fees.base_fee = 200;
3816        for (nonce, fee_cap) in fee_caps.into_iter().enumerate() {
3817            let tx = MockTransaction::eip1559()
3818                .with_sender(sender)
3819                .with_nonce(nonce as u64)
3820                .with_gas_limit(21_000)
3821                .with_max_fee(fee_cap)
3822                .with_priority_fee(1)
3823                .rng_hash();
3824            let tx = f.validated(tx);
3825            ids.push(*tx.id());
3826            pool.insert_tx(tx, U256::MAX, 0).unwrap();
3827        }
3828
3829        pool.pending_fees.base_fee = 100;
3830        pool.update(&Default::default());
3831
3832        assert_eq!(pool.get(&ids[0]).unwrap().subpool, SubPool::Pending);
3833        assert_eq!(pool.get(&ids[1]).unwrap().subpool, SubPool::BaseFee);
3834        assert_eq!(pool.get(&ids[2]).unwrap().subpool, SubPool::Queued);
3835    }
3836
3837    #[test]
3838    fn blob_fee_update_unparks_all_descendants() {
3839        let mut f = MockTransactionFactory::default();
3840        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3841        let sender = address!("0x000000000000000000000000000000000000000f");
3842        let mut block_info = pool.block_info();
3843        block_info.pending_blob_fee = Some(200);
3844        pool.set_block_info(block_info);
3845
3846        for nonce in 0..3 {
3847            let tx = MockTransaction::eip4844()
3848                .with_sender(sender)
3849                .with_nonce(nonce)
3850                .with_gas_limit(21_000)
3851                .with_max_fee(1_000)
3852                .with_priority_fee(1)
3853                .with_blob_fee(150)
3854                .rng_hash();
3855            pool.add_transaction(f.validated(tx), U256::MAX, 0, None).unwrap();
3856        }
3857        assert_eq!(pool.blob_pool.len(), 3);
3858
3859        block_info.pending_blob_fee = Some(100);
3860        pool.on_canonical_state_change(
3861            block_info,
3862            Vec::new(),
3863            FxHashMap::default(),
3864            PoolUpdateKind::Commit,
3865        );
3866
3867        let nonces = pool.best_transactions().map(|tx| tx.nonce()).collect::<Vec<_>>();
3868        assert_eq!(nonces, vec![0, 1, 2]);
3869    }
3870
3871    #[test]
3872    fn update_basefee_subpools() {
3873        let mut f = MockTransactionFactory::default();
3874        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3875
3876        let tx = MockTransaction::eip1559().inc_price_by(10);
3877        let validated = f.validated(tx.clone());
3878        let id = *validated.id();
3879        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3880
3881        assert_eq!(pool.pending_pool.len(), 1);
3882
3883        pool.update_basefee((tx.max_fee_per_gas() + 1) as u64, |_| {});
3884
3885        assert!(pool.pending_pool.is_empty());
3886        assert_eq!(pool.basefee_pool.len(), 1);
3887
3888        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3889    }
3890
3891    #[test]
3892    fn update_basefee_subpools_setting_block_info() {
3893        let mut f = MockTransactionFactory::default();
3894        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3895
3896        let tx = MockTransaction::eip1559().inc_price_by(10);
3897        let validated = f.validated(tx.clone());
3898        let id = *validated.id();
3899        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3900
3901        assert_eq!(pool.pending_pool.len(), 1);
3902
3903        // use set_block_info for the basefee update
3904        let mut block_info = pool.block_info();
3905        block_info.pending_basefee = (tx.max_fee_per_gas() + 1) as u64;
3906        pool.set_block_info(block_info);
3907
3908        assert!(pool.pending_pool.is_empty());
3909        assert_eq!(pool.basefee_pool.len(), 1);
3910
3911        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3912    }
3913
3914    #[test]
3915    fn basefee_decrease_promotes_affordable_and_keeps_unaffordable() {
3916        use alloy_primitives::address;
3917        let mut f = MockTransactionFactory::default();
3918        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3919
3920        // Create transactions that will be in basefee pool (can't afford initial high fee)
3921        // Use different senders to avoid nonce gap issues
3922        let sender_a = address!("0x000000000000000000000000000000000000000a");
3923        let sender_b = address!("0x000000000000000000000000000000000000000b");
3924        let sender_c = address!("0x000000000000000000000000000000000000000c");
3925
3926        let tx1 = MockTransaction::eip1559()
3927            .set_sender(sender_a)
3928            .set_nonce(0)
3929            .set_max_fee(500)
3930            .inc_limit();
3931        let tx2 = MockTransaction::eip1559()
3932            .set_sender(sender_b)
3933            .set_nonce(0)
3934            .set_max_fee(600)
3935            .inc_limit();
3936        let tx3 = MockTransaction::eip1559()
3937            .set_sender(sender_c)
3938            .set_nonce(0)
3939            .set_max_fee(400)
3940            .inc_limit();
3941
3942        // Set high initial basefee so transactions go to basefee pool
3943        let mut block_info = pool.block_info();
3944        block_info.pending_basefee = 700;
3945        pool.set_block_info(block_info);
3946
3947        let validated1 = f.validated(tx1);
3948        let validated2 = f.validated(tx2);
3949        let validated3 = f.validated(tx3);
3950        let id1 = *validated1.id();
3951        let id2 = *validated2.id();
3952        let id3 = *validated3.id();
3953
3954        // Add transactions - they should go to basefee pool due to high basefee
3955        // All transactions have nonce 0 from different senders, so on_chain_nonce should be 0 for
3956        // all
3957        pool.add_transaction(validated1, U256::from(10_000), 0, None).unwrap();
3958        pool.add_transaction(validated2, U256::from(10_000), 0, None).unwrap();
3959        pool.add_transaction(validated3, U256::from(10_000), 0, None).unwrap();
3960
3961        // Debug: Check where transactions ended up
3962        println!("Basefee pool len: {}", pool.basefee_pool.len());
3963        println!("Pending pool len: {}", pool.pending_pool.len());
3964        println!("tx1 subpool: {:?}", pool.all_transactions.txs.get(&id1).unwrap().subpool);
3965        println!("tx2 subpool: {:?}", pool.all_transactions.txs.get(&id2).unwrap().subpool);
3966        println!("tx3 subpool: {:?}", pool.all_transactions.txs.get(&id3).unwrap().subpool);
3967
3968        // Verify they're in basefee pool
3969        assert_eq!(pool.basefee_pool.len(), 3);
3970        assert_eq!(pool.pending_pool.len(), 0);
3971        assert_eq!(pool.all_transactions.txs.get(&id1).unwrap().subpool, SubPool::BaseFee);
3972        assert_eq!(pool.all_transactions.txs.get(&id2).unwrap().subpool, SubPool::BaseFee);
3973        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3974
3975        // Now decrease basefee to trigger the zero-allocation optimization
3976        let mut block_info = pool.block_info();
3977        block_info.pending_basefee = 450; // tx1 (500) and tx2 (600) can now afford it, tx3 (400)
3978                                          // cannot
3979        pool.set_block_info(block_info);
3980
3981        // Verify the optimization worked correctly:
3982        // - tx1 and tx2 should be promoted to pending (mathematical certainty)
3983        // - tx3 should remain in basefee pool
3984        // - All state transitions should be correct
3985        assert_eq!(pool.basefee_pool.len(), 1);
3986        assert_eq!(pool.pending_pool.len(), 2);
3987
3988        // tx3 should still be in basefee pool (fee 400 < basefee 450)
3989        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3990
3991        // tx1 and tx2 should be in pending pool with correct state bits
3992        let tx1_meta = pool.all_transactions.txs.get(&id1).unwrap();
3993        let tx2_meta = pool.all_transactions.txs.get(&id2).unwrap();
3994        assert_eq!(tx1_meta.subpool, SubPool::Pending);
3995        assert_eq!(tx2_meta.subpool, SubPool::Pending);
3996        assert!(tx1_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3997        assert!(tx2_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3998
3999        // Verify that best_transactions returns the promoted transactions
4000        let best: Vec<_> = pool.best_transactions().take(3).collect();
4001        assert_eq!(best.len(), 2); // Only tx1 and tx2 should be returned
4002        assert!(best.iter().any(|tx| tx.id() == &id1));
4003        assert!(best.iter().any(|tx| tx.id() == &id2));
4004    }
4005
4006    #[test]
4007    fn apply_fee_updates_records_promotions_after_basefee_drop() {
4008        let mut f = MockTransactionFactory::default();
4009        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4010
4011        let tx = MockTransaction::eip1559()
4012            .with_gas_limit(21_000)
4013            .with_max_fee(500)
4014            .with_priority_fee(1);
4015        let validated = f.validated(tx);
4016        let id = *validated.id();
4017        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4018
4019        assert_eq!(pool.pending_pool.len(), 1);
4020
4021        // Raise base fee beyond the transaction's cap so it gets parked in BaseFee pool.
4022        pool.update_basefee(600, |_| {});
4023        assert!(pool.pending_pool.is_empty());
4024        assert_eq!(pool.basefee_pool.len(), 1);
4025
4026        let prev_base_fee = 600;
4027        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4028
4029        // Simulate the canonical state path updating pending fees before applying promotions.
4030        pool.all_transactions.pending_fees.base_fee = 400;
4031
4032        let mut outcome = UpdateOutcome::default();
4033        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
4034
4035        assert_eq!(pool.pending_pool.len(), 1);
4036        assert!(pool.basefee_pool.is_empty());
4037        assert_eq!(outcome.promoted.len(), 1);
4038        assert_eq!(outcome.promoted[0].id(), &id);
4039        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
4040        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
4041
4042        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4043        assert_eq!(tx_meta.subpool, SubPool::Pending);
4044        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4045    }
4046
4047    #[test]
4048    fn update_blob_fee_parks_pending_when_base_fee_falls_in_the_same_block() {
4049        let mut f = MockTransactionFactory::default();
4050        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4051
4052        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4053        let initial_base_fee = 100u64;
4054        pool.all_transactions.pending_fees.base_fee = initial_base_fee;
4055
4056        // comfortably satisfies both fees, so it starts out pending
4057        let tx = MockTransaction::eip4844()
4058            .with_max_fee(500)
4059            .with_priority_fee(1)
4060            .with_blob_fee(initial_blob_fee + 100);
4061        let validated = f.validated(tx.clone());
4062        let id = *validated.id();
4063        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4064        assert_eq!(pool.pending_pool.len(), 1);
4065
4066        // the blob fee rises past its cap while the base fee falls, which is what a blob heavy
4067        // but gas light block produces. The rise still has to park it.
4068        let raised_blob_fee = tx.max_fee_per_blob_gas().unwrap() + 1;
4069        pool.all_transactions.pending_fees.base_fee = initial_base_fee - 1;
4070        pool.update_blob_fee(raised_blob_fee, Ordering::Less, |_| {});
4071
4072        assert!(pool.pending_pool.is_empty(), "transaction was left in the pending pool");
4073        assert_eq!(pool.blob_pool.len(), 1);
4074
4075        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4076        assert!(
4077            !tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
4078            "blob fee cap flag was not cleared"
4079        );
4080        assert_eq!(tx_meta.subpool, SubPool::Blob);
4081    }
4082
4083    #[test]
4084    fn update_blob_fee_demotes_and_promotes_when_base_fee_falls() {
4085        let mut f = MockTransactionFactory::default();
4086        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4087
4088        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4089        let initial_base_fee = 600;
4090        pool.all_transactions.pending_fees.base_fee = initial_base_fee;
4091
4092        let tx_to_demote = MockTransaction::eip4844()
4093            .with_max_fee(700)
4094            .with_priority_fee(1)
4095            .with_blob_fee(initial_blob_fee + 100);
4096        let validated = f.validated(tx_to_demote);
4097        let demoted_id = *validated.id();
4098        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4099
4100        let tx_to_promote = MockTransaction::eip4844()
4101            .with_max_fee(500)
4102            .with_priority_fee(1)
4103            .with_blob_fee(initial_blob_fee + 300);
4104        let validated = f.validated(tx_to_promote);
4105        let promoted_id = *validated.id();
4106        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4107
4108        assert_eq!(pool.pending_pool.len(), 1);
4109        assert_eq!(pool.blob_pool.len(), 1);
4110
4111        // The lower base fee makes one transaction affordable while the higher blob fee makes the
4112        // other unaffordable, so the same update must both promote and demote.
4113        let raised_blob_fee = initial_blob_fee + 200;
4114        pool.all_transactions.pending_fees.base_fee = 400;
4115        let mut promoted = Vec::new();
4116        pool.update_blob_fee(raised_blob_fee, Ordering::Less, |tx| promoted.push(*tx.id()));
4117
4118        assert_eq!(pool.pending_pool.len(), 1);
4119        assert_eq!(pool.blob_pool.len(), 1);
4120        assert_eq!(promoted, vec![promoted_id]);
4121
4122        assert_eq!(pool.all_transactions.txs.get(&demoted_id).unwrap().subpool, SubPool::Blob);
4123        assert_eq!(pool.all_transactions.txs.get(&promoted_id).unwrap().subpool, SubPool::Pending);
4124    }
4125
4126    #[test]
4127    fn apply_fee_updates_records_promotions_after_blob_fee_drop() {
4128        let mut f = MockTransactionFactory::default();
4129        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4130
4131        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4132
4133        let tx = MockTransaction::eip4844().with_blob_fee(initial_blob_fee + 100);
4134        let validated = f.validated(tx.clone());
4135        let id = *validated.id();
4136        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4137
4138        assert_eq!(pool.pending_pool.len(), 1);
4139
4140        // Raise blob fee beyond the transaction's cap so it gets parked in Blob pool.
4141        let increased_blob_fee = tx.max_fee_per_blob_gas().unwrap() + 200;
4142        pool.update_blob_fee(increased_blob_fee, Ordering::Equal, |_| {});
4143        assert!(pool.pending_pool.is_empty());
4144        assert_eq!(pool.blob_pool.len(), 1);
4145
4146        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
4147        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4148
4149        // Simulate the canonical state path updating pending fees before applying promotions.
4150        pool.all_transactions.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap();
4151
4152        let mut outcome = UpdateOutcome::default();
4153        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
4154
4155        assert_eq!(pool.pending_pool.len(), 1);
4156        assert!(pool.blob_pool.is_empty());
4157        assert_eq!(outcome.promoted.len(), 1);
4158        assert_eq!(outcome.promoted[0].id(), &id);
4159        assert_eq!(pool.all_transactions.pending_fees.base_fee, prev_base_fee);
4160        assert_eq!(pool.all_transactions.pending_fees.blob_fee, tx.max_fee_per_blob_gas().unwrap());
4161
4162        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4163        assert_eq!(tx_meta.subpool, SubPool::Pending);
4164        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4165        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4166    }
4167
4168    #[test]
4169    fn apply_fee_updates_promotes_blob_after_basefee_drop() {
4170        let mut f = MockTransactionFactory::default();
4171        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4172
4173        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4174
4175        let tx = MockTransaction::eip4844()
4176            .with_max_fee(500)
4177            .with_priority_fee(1)
4178            .with_blob_fee(initial_blob_fee + 100);
4179        let validated = f.validated(tx);
4180        let id = *validated.id();
4181        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4182
4183        assert_eq!(pool.pending_pool.len(), 1);
4184
4185        // Raise base fee beyond the transaction's cap so it gets parked in Blob pool.
4186        let high_base_fee = 600;
4187        pool.update_basefee(high_base_fee, |_| {});
4188        assert!(pool.pending_pool.is_empty());
4189        assert_eq!(pool.blob_pool.len(), 1);
4190
4191        let prev_base_fee = high_base_fee;
4192        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4193
4194        // Simulate applying a lower base fee while keeping blob fee unchanged.
4195        pool.all_transactions.pending_fees.base_fee = 400;
4196
4197        let mut outcome = UpdateOutcome::default();
4198        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
4199
4200        assert_eq!(pool.pending_pool.len(), 1);
4201        assert!(pool.blob_pool.is_empty());
4202        assert_eq!(outcome.promoted.len(), 1);
4203        assert_eq!(outcome.promoted[0].id(), &id);
4204        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
4205        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
4206
4207        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4208        assert_eq!(tx_meta.subpool, SubPool::Pending);
4209        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4210        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4211    }
4212
4213    #[test]
4214    fn queued_transactions_include_blob_pool() {
4215        let mut f = MockTransactionFactory::default();
4216        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4217
4218        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4219        let tx = MockTransaction::eip4844()
4220            .with_max_fee(500)
4221            .with_priority_fee(1)
4222            .with_blob_fee(initial_blob_fee + 100);
4223        let validated = f.validated(tx);
4224        let id = *validated.id();
4225        let sender = validated.sender_id();
4226        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4227
4228        // Raise the base fee beyond the transaction's cap so it gets parked in the blob pool.
4229        pool.update_basefee(600, |_| {});
4230        assert_eq!(pool.blob_pool.len(), 1);
4231
4232        let queued = pool.queued_transactions();
4233        assert_eq!(queued.len(), 1);
4234        assert_eq!(queued[0].id(), &id);
4235
4236        let by_sender = pool.queued_txs_by_sender(sender);
4237        assert_eq!(by_sender.len(), 1);
4238        assert_eq!(by_sender[0].id(), &id);
4239    }
4240
4241    #[test]
4242    fn apply_fee_updates_demotes_after_basefee_rise() {
4243        let mut f = MockTransactionFactory::default();
4244        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4245
4246        let tx = MockTransaction::eip1559()
4247            .with_gas_limit(21_000)
4248            .with_max_fee(400)
4249            .with_priority_fee(1);
4250        let validated = f.validated(tx);
4251        let id = *validated.id();
4252        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4253
4254        assert_eq!(pool.pending_pool.len(), 1);
4255
4256        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
4257        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4258
4259        // Simulate canonical path raising the base fee beyond the transaction's cap.
4260        let new_base_fee = prev_base_fee + 1_000;
4261        pool.all_transactions.pending_fees.base_fee = new_base_fee;
4262
4263        let mut outcome = UpdateOutcome::default();
4264        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
4265
4266        assert!(pool.pending_pool.is_empty());
4267        assert_eq!(pool.basefee_pool.len(), 1);
4268        assert!(outcome.promoted.is_empty());
4269        assert_eq!(pool.all_transactions.pending_fees.base_fee, new_base_fee);
4270        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
4271
4272        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4273        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
4274        assert!(!tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4275    }
4276
4277    #[test]
4278    fn get_highest_transaction_by_sender_and_nonce() {
4279        // Set up a mock transaction factory and a new transaction pool.
4280        let mut f = MockTransactionFactory::default();
4281        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4282
4283        // Create a mock transaction and add it to the pool.
4284        let tx = MockTransaction::eip1559();
4285        pool.add_transaction(f.validated(tx.clone()), U256::from(1_000), 0, None).unwrap();
4286
4287        // Create another mock transaction with an incremented price.
4288        let tx1 = tx.inc_price().next();
4289
4290        // Validate the second mock transaction and add it to the pool.
4291        let tx1_validated = f.validated(tx1.clone());
4292        pool.add_transaction(tx1_validated, U256::from(1_000), 0, None).unwrap();
4293
4294        // Ensure that the calculated next nonce for the sender matches the expected value.
4295        assert_eq!(
4296            pool.get_highest_nonce_by_sender(f.ids.sender_id(&tx.sender()).unwrap()),
4297            Some(1)
4298        );
4299
4300        // Retrieve the highest transaction by sender.
4301        let highest_tx = pool
4302            .get_highest_transaction_by_sender(f.ids.sender_id(&tx.sender()).unwrap())
4303            .expect("Failed to retrieve highest transaction");
4304
4305        // Validate that the retrieved highest transaction matches the expected transaction.
4306        assert_eq!(highest_tx.as_ref().transaction, tx1);
4307    }
4308
4309    #[test]
4310    fn get_highest_consecutive_transaction_by_sender() {
4311        // Set up a mock transaction factory and a new transaction pool.
4312        let mut pool = TxPool::new(MockOrdering::default(), PoolConfig::default());
4313        let mut f = MockTransactionFactory::default();
4314
4315        // Create transactions with nonces 0, 1, 2, 4, 5.
4316        let sender = Address::random();
4317        let txs: Vec<_> = vec![0, 1, 2, 4, 5, 8, 9];
4318        for nonce in txs {
4319            let mut mock_tx = MockTransaction::eip1559();
4320            mock_tx.set_sender(sender);
4321            mock_tx.set_nonce(nonce);
4322
4323            let validated_tx = f.validated(mock_tx);
4324            pool.add_transaction(validated_tx, U256::from(1000), 0, None).unwrap();
4325        }
4326
4327        // Get last consecutive transaction
4328        let sender_id = f.ids.sender_id(&sender).unwrap();
4329        let next_tx =
4330            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(0));
4331        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(2), "Expected nonce 2 for on-chain nonce 0");
4332
4333        let next_tx =
4334            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(4));
4335        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 4");
4336
4337        let next_tx =
4338            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
4339        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 5");
4340
4341        // update the tracked nonce
4342        let mut info = SenderInfo::default();
4343        info.update(8, U256::ZERO);
4344        pool.all_transactions.sender_info.insert(sender_id, info);
4345        let next_tx =
4346            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
4347        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(9), "Expected nonce 9 for on-chain nonce 8");
4348    }
4349
4350    #[test]
4351    fn discard_nonce_too_low() {
4352        let mut f = MockTransactionFactory::default();
4353        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4354
4355        let tx = MockTransaction::eip1559().inc_price_by(10);
4356        let validated = f.validated(tx.clone());
4357        let id = *validated.id();
4358        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4359
4360        let next = tx.next();
4361        let validated = f.validated(next.clone());
4362        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4363
4364        assert_eq!(pool.pending_pool.len(), 2);
4365
4366        let mut changed_senders = HashMap::default();
4367        changed_senders.insert(
4368            id.sender,
4369            SenderInfo { state_nonce: next.nonce(), balance: U256::from(1_000) },
4370        );
4371        let outcome = pool.update_accounts(changed_senders);
4372        assert_eq!(outcome.discarded.len(), 1);
4373        assert_eq!(pool.pending_pool.len(), 1);
4374    }
4375
4376    /// Pool with `enforce_tracked_nonce` enabled.
4377    fn stale_validation_pool() -> TxPool<MockOrdering> {
4378        TxPool::new(
4379            MockOrdering::default(),
4380            PoolConfig { enforce_tracked_nonce: true, ..Default::default() },
4381        )
4382    }
4383
4384    #[test]
4385    fn stale_validation_accepted_by_default() {
4386        let mut f = MockTransactionFactory::default();
4387        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4388
4389        let template = MockTransaction::eip1559();
4390        let first = f.validated(template.clone().with_nonce(0).rng_hash());
4391        let sender = first.sender_id();
4392        pool.add_transaction(first, U256::from(1_000), 0, None).unwrap();
4393
4394        let changed = FxHashMap::from_iter([(
4395            sender,
4396            SenderInfo { state_nonce: 1, balance: U256::from(1_000) },
4397        )]);
4398        let outcome = pool.update_accounts(changed);
4399        assert_eq!(outcome.discarded.len(), 1);
4400        // discarding the sender's last transaction drops its info
4401        assert!(!pool.all_transactions.sender_info.contains_key(&sender));
4402
4403        // the validation snapshot is trusted as is
4404        let stale = f.validated(template.with_nonce(0).rng_hash());
4405        pool.add_transaction(stale, U256::from(1_000), 0, None).unwrap();
4406        assert_eq!(pool.pending_pool.len(), 1);
4407        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 0);
4408    }
4409
4410    #[test]
4411    fn stale_validation_does_not_regress_sender_state() {
4412        let mut f = MockTransactionFactory::default();
4413        let mut pool = stale_validation_pool();
4414
4415        let template = MockTransaction::eip1559();
4416        let first = f.validated(template.clone().with_nonce(0).rng_hash());
4417        let sender = first.sender_id();
4418        pool.add_transaction(first, U256::from(1_000), 0, None).unwrap();
4419
4420        let mut changed_senders = HashMap::default();
4421        changed_senders.insert(sender, SenderInfo { state_nonce: 1, balance: U256::from(1_000) });
4422        let outcome = pool.update_accounts(changed_senders);
4423        assert_eq!(outcome.discarded.len(), 1);
4424        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4425
4426        // Stale validation for the next nonce remains pending.
4427        let current = f.validated(template.clone().with_nonce(1).rng_hash());
4428        pool.add_transaction(current, U256::from(1_000), 0, None).unwrap();
4429        assert_eq!(pool.pending_pool.len(), 1);
4430        assert_eq!(pool.queued_pool.len(), 0);
4431        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4432
4433        let stale = f.validated(template.with_nonce(0).rng_hash());
4434        let stale_hash = *stale.hash();
4435        let err = pool.add_transaction(stale, U256::from(1_000), 0, None).unwrap_err();
4436        assert!(matches!(
4437            err.kind,
4438            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
4439                InvalidTransactionError::NonceNotConsistent { tx: 0, state: 1 }
4440            ))
4441        ));
4442        assert!(!pool.contains(&stale_hash));
4443        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4444    }
4445
4446    #[test]
4447    fn stale_validation_resubmission_after_commit() {
4448        let mut f = MockTransactionFactory::default();
4449        let mut pool = stale_validation_pool();
4450        let block_info = pool.block_info();
4451
4452        let template = MockTransaction::eip1559();
4453        let tx0 = template.clone().with_nonce(0).rng_hash();
4454        let tx1 = template.clone().with_nonce(1).rng_hash();
4455        let first = f.validated(tx0.clone());
4456        let sender = first.sender_id();
4457        pool.add_transaction(first, U256::from(1_000), 0, None).unwrap();
4458        pool.add_transaction(f.validated(tx1.clone()), U256::from(1_000), 0, None).unwrap();
4459        assert_eq!(pool.pending_pool.len(), 2);
4460
4461        // Nonce 0 is mined while resubmissions of both nonces are still being validated.
4462        let changed = FxHashMap::from_iter([(
4463            sender,
4464            SenderInfo { state_nonce: 1, balance: U256::from(1_000) },
4465        )]);
4466        pool.on_canonical_state_change(
4467            block_info,
4468            vec![*tx0.get_hash()],
4469            changed,
4470            PoolUpdateKind::Commit,
4471        );
4472        assert_eq!(pool.pending_pool.len(), 1);
4473
4474        // The resubmitted mined nonce is rejected instead of shadowing nonce 1.
4475        let retry = f.validated(template.with_nonce(0).rng_hash());
4476        let err = pool.add_transaction(retry, U256::from(1_000), 0, None).unwrap_err();
4477        assert!(matches!(
4478            err.kind,
4479            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
4480                InvalidTransactionError::NonceNotConsistent { tx: 0, state: 1 }
4481            ))
4482        ));
4483
4484        // The replacement of the executable nonce is accepted using the tracked nonce.
4485        let replacement = f.validated(tx1.inc_price_by(10).rng_hash());
4486        let added = pool.add_transaction(replacement, U256::from(1_000), 0, None).unwrap();
4487        assert!(added.as_pending().is_some());
4488        assert!(added.replaced().is_some());
4489        assert_eq!(pool.pending_pool.len(), 1);
4490        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4491        let next =
4492            pool.get_highest_consecutive_transaction_by_sender(sender.into_transaction_id(1));
4493        assert_eq!(next.map(|tx| tx.nonce()), Some(1));
4494    }
4495
4496    #[test]
4497    fn stale_validation_uses_tracked_balance() {
4498        let mut f = MockTransactionFactory::default();
4499        let mut pool = stale_validation_pool();
4500
4501        // each transaction costs 1_500_000
4502        let template = MockTransaction::eip1559().with_gas_price(50).with_gas_limit(30_000);
4503        let first = f.validated(template.clone().with_nonce(0).rng_hash());
4504        let sender = first.sender_id();
4505        pool.add_transaction(first, U256::from(10_000_000), 0, None).unwrap();
4506        assert_eq!(pool.pending_pool.len(), 1);
4507
4508        // The commit mines nonce 0 and drains the balance below the cost of the next transaction.
4509        let changed = FxHashMap::from_iter([(
4510            sender,
4511            SenderInfo { state_nonce: 1, balance: U256::from(1_000) },
4512        )]);
4513        pool.update_accounts(changed);
4514
4515        // The stale snapshot still reports the old balance, the tracked balance applies.
4516        let next = f.validated(template.with_nonce(1).rng_hash());
4517        let id = *next.id();
4518        pool.add_transaction(next, U256::from(10_000_000), 0, None).unwrap();
4519        assert_eq!(pool.pending_pool.len(), 0);
4520        assert_eq!(pool.queued_pool.len(), 1);
4521        let state = pool.all_transactions.get(&id).unwrap().state;
4522        assert!(state.contains(TxState::NO_NONCE_GAPS));
4523        assert!(!state.contains(TxState::ENOUGH_BALANCE));
4524        assert_eq!(pool.all_transactions.sender_info[&sender].balance, U256::from(1_000));
4525    }
4526
4527    #[test]
4528    fn discard_with_large_blob_txs() {
4529        // init tracing
4530        reth_tracing::init_test_tracing();
4531
4532        // this test adds large txs to the parked pool, then attempting to discard worst
4533        let mut f = MockTransactionFactory::default();
4534        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4535        let default_limits = pool.config.blob_limit;
4536
4537        // create a chain of transactions by sender A
4538        // make sure they are all one over half the limit
4539        let a_sender = address!("0x000000000000000000000000000000000000000a");
4540
4541        // set the base fee of the pool
4542        let mut block_info = pool.block_info();
4543        block_info.pending_blob_fee = Some(100);
4544        block_info.pending_basefee = 100;
4545
4546        // update
4547        pool.set_block_info(block_info);
4548
4549        // 2 txs, that should put the pool over the size limit but not max txs
4550        let a_txs = MockTransactionSet::dependent(a_sender, 0, 2, TxType::Eip4844)
4551            .into_iter()
4552            .map(|mut tx| {
4553                tx.set_size(default_limits.max_size / 2 + 1);
4554                tx.set_max_fee((block_info.pending_basefee - 1).into());
4555                tx
4556            })
4557            .collect::<Vec<_>>();
4558
4559        // add all the transactions to the parked pool
4560        for tx in a_txs {
4561            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
4562        }
4563
4564        // truncate the pool, it should remove at least one transaction
4565        let removed = pool.discard_worst();
4566        assert_eq!(removed.len(), 1);
4567    }
4568
4569    #[test]
4570    fn discard_with_parked_large_txs() {
4571        // init tracing
4572        reth_tracing::init_test_tracing();
4573
4574        // this test adds large txs to the parked pool, then attempting to discard worst
4575        let mut f = MockTransactionFactory::default();
4576        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4577        let default_limits = pool.config.queued_limit;
4578
4579        // create a chain of transactions by sender A
4580        // make sure they are all one over half the limit
4581        let a_sender = address!("0x000000000000000000000000000000000000000a");
4582
4583        // set the base fee of the pool
4584        let pool_base_fee = 100;
4585        pool.update_basefee(pool_base_fee, |_| {});
4586
4587        // 2 txs, that should put the pool over the size limit but not max txs
4588        let a_txs = MockTransactionSet::dependent(a_sender, 0, 3, TxType::Eip1559)
4589            .into_iter()
4590            .map(|mut tx| {
4591                tx.set_size(default_limits.max_size / 2 + 1);
4592                tx.set_max_fee((pool_base_fee - 1).into());
4593                tx
4594            })
4595            .collect::<Vec<_>>();
4596
4597        // add all the transactions to the parked pool
4598        for tx in a_txs {
4599            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
4600        }
4601
4602        // truncate the pool, it should remove at least one transaction
4603        let removed = pool.discard_worst();
4604        assert_eq!(removed.len(), 1);
4605    }
4606
4607    #[test]
4608    fn discard_at_capacity() {
4609        let mut f = MockTransactionFactory::default();
4610        let queued_limit = SubPoolLimit::new(1000, usize::MAX);
4611        let mut pool =
4612            TxPool::new(MockOrdering::default(), PoolConfig { queued_limit, ..Default::default() });
4613
4614        // insert a bunch of transactions into the queued pool
4615        for _ in 0..queued_limit.max_txs {
4616            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
4617            let validated = f.validated(tx);
4618            let _id = *validated.id();
4619            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4620        }
4621
4622        let size = pool.size();
4623        assert_eq!(size.queued, queued_limit.max_txs);
4624
4625        for _ in 0..queued_limit.max_txs {
4626            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
4627            let validated = f.validated(tx);
4628            let _id = *validated.id();
4629            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4630
4631            pool.discard_worst();
4632            pool.assert_invariants();
4633            assert!(pool.size().queued <= queued_limit.max_txs);
4634        }
4635    }
4636
4637    #[test]
4638    fn discard_blobs_at_capacity() {
4639        let mut f = MockTransactionFactory::default();
4640        let blob_limit = SubPoolLimit::new(1000, usize::MAX);
4641        let mut pool =
4642            TxPool::new(MockOrdering::default(), PoolConfig { blob_limit, ..Default::default() });
4643        pool.all_transactions.pending_fees.blob_fee = 10000;
4644        // insert a bunch of transactions into the queued pool
4645        for _ in 0..blob_limit.max_txs {
4646            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
4647            let validated = f.validated(tx);
4648            let _id = *validated.id();
4649            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4650        }
4651
4652        let size = pool.size();
4653        assert_eq!(size.blob, blob_limit.max_txs);
4654
4655        for _ in 0..blob_limit.max_txs {
4656            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
4657            let validated = f.validated(tx);
4658            let _id = *validated.id();
4659            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4660
4661            pool.discard_worst();
4662            pool.assert_invariants();
4663            assert!(pool.size().blob <= blob_limit.max_txs);
4664        }
4665    }
4666
4667    #[test]
4668    fn reuse_update_buffer_across_pool_operations() {
4669        let mut f = MockTransactionFactory::default();
4670        let mut pool = TxPool::new(
4671            MockOrdering::default(),
4672            PoolConfig { max_account_slots: 128, ..Default::default() },
4673        );
4674        let mut tx = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4675        let first = f.validated(tx.clone());
4676        for _ in 0..80 {
4677            pool.add_transaction(f.validated(tx.clone()), U256::MAX, 0, None).unwrap();
4678            tx = tx.next();
4679        }
4680        assert_eq!(pool.all_transactions.update_buffer.capacity(), 0);
4681
4682        let changed = |balance, state_nonce| {
4683            FxHashMap::from_iter([(first.sender_id(), SenderInfo { balance, state_nonce })])
4684        };
4685        let outcome = pool.update_accounts(changed(U256::ZERO, 0));
4686        assert!(outcome.promoted.is_empty());
4687        assert!(outcome.discarded.is_empty());
4688        assert_eq!(pool.queued_pool.len(), 80);
4689        let capacity = pool.all_transactions.update_buffer.capacity();
4690        assert!(capacity >= 80);
4691        let buffer = pool.all_transactions.update_buffer.as_ptr();
4692
4693        for _ in 0..2 {
4694            let outcome = pool.update_accounts(changed(U256::MAX, 0));
4695            assert_eq!(outcome.promoted.len(), 80);
4696            assert!(outcome.discarded.is_empty());
4697            assert_eq!(pool.pending_pool.len(), 80);
4698
4699            let outcome = pool.update_accounts(FxHashMap::default());
4700            assert!(outcome.promoted.is_empty());
4701            assert!(outcome.discarded.is_empty());
4702
4703            pool.remove_transaction_by_hash(first.hash()).unwrap();
4704            assert_eq!(pool.queued_pool.len(), 79);
4705
4706            // A rejected replacement must leave the reusable allocation available.
4707            let replacement = f.validated(first.transaction.next());
4708            assert!(pool.add_transaction(replacement, U256::MAX, 0, None).is_err());
4709            assert_eq!(pool.all_transactions.update_buffer.as_ptr(), buffer);
4710
4711            let added = pool.add_transaction(first.clone(), U256::MAX, 0, None).unwrap();
4712            let AddedTransaction::Pending(added) = added else { panic!("expected pending") };
4713            assert_eq!(added.promoted.len(), 79);
4714            assert!(added.discarded.is_empty());
4715            assert_eq!(pool.pending_pool.len(), 80);
4716
4717            pool.update_accounts(changed(U256::ZERO, 0));
4718            assert_eq!(pool.queued_pool.len(), 80);
4719            assert!(pool.all_transactions.update_buffer.is_empty());
4720            assert_eq!(pool.all_transactions.update_buffer.capacity(), capacity);
4721            assert_eq!(pool.all_transactions.update_buffer.as_ptr(), buffer);
4722            pool.assert_invariants();
4723        }
4724
4725        let outcome = pool.update_accounts(changed(U256::MAX, 80));
4726        assert_eq!(outcome.discarded.len(), 80);
4727        assert!(outcome.promoted.is_empty());
4728        assert!(pool.is_empty());
4729        assert!(pool.all_transactions.update_buffer.is_empty());
4730        assert_eq!(pool.all_transactions.update_buffer.as_ptr(), buffer);
4731    }
4732
4733    #[test]
4734    fn account_updates_sender_balance() {
4735        let mut on_chain_balance = U256::from(100);
4736        let on_chain_nonce = 0;
4737        let mut f = MockTransactionFactory::default();
4738        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4739
4740        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4741        let tx_1 = tx_0.next();
4742        let tx_2 = tx_1.next();
4743
4744        // Create 3 transactions
4745        let v0 = f.validated(tx_0);
4746        let v1 = f.validated(tx_1);
4747        let v2 = f.validated(tx_2);
4748
4749        let _res =
4750            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4751        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4752        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4753
4754        // The sender does not have enough balance to put all txs into pending.
4755        assert_eq!(1, pool.pending_transactions().len());
4756        assert_eq!(2, pool.queued_transactions().len());
4757
4758        // Simulate new block arrival - and chain balance increase.
4759        let mut updated_accounts = HashMap::default();
4760        on_chain_balance = U256::from(300);
4761        updated_accounts.insert(
4762            v0.sender_id(),
4763            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
4764        );
4765        pool.update_accounts(updated_accounts.clone());
4766
4767        assert_eq!(3, pool.pending_transactions().len());
4768        assert!(pool.queued_transactions().is_empty());
4769
4770        // Simulate new block arrival - and chain balance decrease.
4771        updated_accounts.entry(v0.sender_id()).and_modify(|v| v.balance = U256::from(1));
4772        pool.update_accounts(updated_accounts);
4773
4774        assert!(pool.pending_transactions().is_empty());
4775        assert_eq!(3, pool.queued_transactions().len());
4776    }
4777
4778    #[test]
4779    fn account_updates_nonce_gap() {
4780        let on_chain_balance = U256::from(10_000);
4781        let mut on_chain_nonce = 0;
4782        let mut f = MockTransactionFactory::default();
4783        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4784
4785        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4786        let tx_1 = tx_0.next();
4787        let tx_2 = tx_1.next();
4788
4789        // Create 3 transactions
4790        let v0 = f.validated(tx_0);
4791        let v1 = f.validated(tx_1);
4792        let v2 = f.validated(tx_2);
4793
4794        // Add first 2 to the pool
4795        let _res =
4796            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4797        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4798
4799        assert!(pool.queued_transactions().is_empty());
4800        assert_eq!(2, pool.pending_transactions().len());
4801
4802        // Remove first (nonce 0)
4803        pool.remove_transaction_by_hash(v0.hash());
4804
4805        // Now add transaction with nonce 2
4806        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4807
4808        // v1 and v2 should both be in the queue now.
4809        assert_eq!(2, pool.queued_transactions().len());
4810        assert!(pool.pending_transactions().is_empty());
4811
4812        // Simulate new block arrival - and chain nonce increasing.
4813        let mut updated_accounts = HashMap::default();
4814        on_chain_nonce += 1;
4815        updated_accounts.insert(
4816            v0.sender_id(),
4817            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
4818        );
4819        pool.update_accounts(updated_accounts);
4820
4821        // 'pending' now).
4822        assert!(pool.queued_transactions().is_empty());
4823        assert_eq!(2, pool.pending_transactions().len());
4824    }
4825    #[test]
4826    fn test_transaction_removal() {
4827        let on_chain_balance = U256::from(10_000);
4828        let on_chain_nonce = 0;
4829        let mut f = MockTransactionFactory::default();
4830        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4831
4832        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4833        let tx_1 = tx_0.next();
4834
4835        // Create 2 transactions
4836        let v0 = f.validated(tx_0);
4837        let v1 = f.validated(tx_1);
4838
4839        // Add them to the pool
4840        let _res =
4841            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4842        let _res =
4843            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4844
4845        assert_eq!(0, pool.queued_transactions().len());
4846        assert_eq!(2, pool.pending_transactions().len());
4847
4848        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
4849        pool.remove_transaction(v0.id());
4850        // assert the second transaction is really at the top of the queue
4851        let pool_txs = pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>();
4852        assert_eq!(vec![v1.nonce()], pool_txs);
4853    }
4854    #[test]
4855    fn test_remove_transactions() {
4856        let on_chain_balance = U256::from(10_000);
4857        let on_chain_nonce = 0;
4858        let mut f = MockTransactionFactory::default();
4859        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4860
4861        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4862        let tx_1 = tx_0.next();
4863        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4864        let tx_3 = tx_2.next();
4865
4866        // Create 4 transactions
4867        let v0 = f.validated(tx_0);
4868        let v1 = f.validated(tx_1);
4869        let v2 = f.validated(tx_2);
4870        let v3 = f.validated(tx_3);
4871
4872        // Add them to the pool
4873        let _res =
4874            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4875        let _res =
4876            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4877        let _res =
4878            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4879        let _res =
4880            pool.add_transaction(v3.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4881
4882        assert_eq!(0, pool.queued_transactions().len());
4883        assert_eq!(4, pool.pending_transactions().len());
4884
4885        pool.remove_transactions(vec![*v0.hash(), *v2.hash()]);
4886
4887        assert_eq!(2, pool.queued_transactions().len());
4888        assert!(pool.pending_transactions().is_empty());
4889        assert!(pool.contains(v1.hash()));
4890        assert!(pool.contains(v3.hash()));
4891    }
4892
4893    #[test]
4894    fn test_remove_transactions_middle_pending_hash() {
4895        let on_chain_balance = U256::from(10_000);
4896        let on_chain_nonce = 0;
4897        let mut f = MockTransactionFactory::default();
4898        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4899
4900        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4901        let tx_1 = tx_0.next();
4902        let tx_2 = tx_1.next();
4903        let tx_3 = tx_2.next();
4904
4905        // Create 4 transactions
4906        let v0 = f.validated(tx_0);
4907        let v1 = f.validated(tx_1);
4908        let v2 = f.validated(tx_2);
4909        let v3 = f.validated(tx_3);
4910
4911        // Add them to the pool
4912        let _res = pool.add_transaction(v0, on_chain_balance, on_chain_nonce, None).unwrap();
4913        let _res =
4914            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4915        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4916        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4917
4918        assert_eq!(0, pool.queued_transactions().len());
4919        assert_eq!(4, pool.pending_transactions().len());
4920
4921        let mut removed_txs = pool.remove_transactions(vec![*v1.hash()]);
4922        assert_eq!(1, removed_txs.len());
4923
4924        assert_eq!(2, pool.queued_transactions().len());
4925        assert_eq!(1, pool.pending_transactions().len());
4926
4927        // reinsert
4928        let removed_tx = removed_txs.pop().unwrap();
4929        let v1 = f.validated(removed_tx.transaction.clone());
4930        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4931        assert_eq!(0, pool.queued_transactions().len());
4932        assert_eq!(4, pool.pending_transactions().len());
4933    }
4934
4935    #[test]
4936    fn test_remove_transactions_and_descendants() {
4937        let on_chain_balance = U256::from(10_000);
4938        let on_chain_nonce = 0;
4939        let mut f = MockTransactionFactory::default();
4940        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4941
4942        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4943        let tx_1 = tx_0.next();
4944        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4945        let tx_3 = tx_2.next();
4946        let tx_4 = tx_3.next();
4947
4948        // Create 5 transactions
4949        let v0 = f.validated(tx_0);
4950        let v1 = f.validated(tx_1);
4951        let v2 = f.validated(tx_2);
4952        let v3 = f.validated(tx_3);
4953        let v4 = f.validated(tx_4);
4954
4955        // Add them to the pool
4956        let _res =
4957            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4958        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4959        let _res =
4960            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4961        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4962        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
4963
4964        assert_eq!(0, pool.queued_transactions().len());
4965        assert_eq!(5, pool.pending_transactions().len());
4966
4967        pool.remove_transactions_and_descendants(vec![*v0.hash(), *v2.hash()]);
4968
4969        assert_eq!(0, pool.queued_transactions().len());
4970        assert_eq!(0, pool.pending_transactions().len());
4971    }
4972    #[test]
4973    fn test_remove_descendants() {
4974        let on_chain_balance = U256::from(10_000);
4975        let on_chain_nonce = 0;
4976        let mut f = MockTransactionFactory::default();
4977        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4978
4979        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4980        let tx_1 = tx_0.next();
4981        let tx_2 = tx_1.next();
4982        let tx_3 = tx_2.next();
4983
4984        // Create 4 transactions
4985        let v0 = f.validated(tx_0);
4986        let v1 = f.validated(tx_1);
4987        let v2 = f.validated(tx_2);
4988        let v3 = f.validated(tx_3);
4989
4990        // Add them to the  pool
4991        let _res =
4992            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4993        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4994        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4995        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4996
4997        assert_eq!(0, pool.queued_transactions().len());
4998        assert_eq!(4, pool.pending_transactions().len());
4999
5000        let mut removed = Vec::new();
5001        pool.remove_transaction(v0.id());
5002        pool.remove_descendants(v0.id(), &mut removed);
5003
5004        assert_eq!(0, pool.queued_transactions().len());
5005        assert_eq!(0, pool.pending_transactions().len());
5006        assert_eq!(3, removed.len());
5007    }
5008    #[test]
5009    fn test_remove_transactions_by_sender() {
5010        let on_chain_balance = U256::from(10_000);
5011        let on_chain_nonce = 0;
5012        let mut f = MockTransactionFactory::default();
5013        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5014
5015        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
5016        let tx_1 = tx_0.next();
5017        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
5018        let tx_3 = tx_2.next();
5019        let tx_4 = tx_3.next();
5020
5021        // Create 5 transactions
5022        let v0 = f.validated(tx_0);
5023        let v1 = f.validated(tx_1);
5024        let v2 = f.validated(tx_2);
5025        let v3 = f.validated(tx_3);
5026        let v4 = f.validated(tx_4);
5027
5028        // Add them to the pool
5029        let _res =
5030            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
5031        let _res =
5032            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
5033        let _res =
5034            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
5035        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
5036        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
5037
5038        assert_eq!(0, pool.queued_transactions().len());
5039        assert_eq!(5, pool.pending_transactions().len());
5040
5041        pool.remove_transactions_by_sender(v2.sender_id());
5042
5043        assert_eq!(0, pool.queued_transactions().len());
5044        assert_eq!(2, pool.pending_transactions().len());
5045        assert!(pool.contains(v0.hash()));
5046        assert!(pool.contains(v1.hash()));
5047    }
5048    #[test]
5049    fn wrong_best_order_of_transactions() {
5050        let on_chain_balance = U256::from(10_000);
5051        let mut on_chain_nonce = 0;
5052        let mut f = MockTransactionFactory::default();
5053        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5054
5055        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
5056        let tx_1 = tx_0.next();
5057        let tx_2 = tx_1.next();
5058        let tx_3 = tx_2.next();
5059
5060        // Create 4 transactions
5061        let v0 = f.validated(tx_0);
5062        let v1 = f.validated(tx_1);
5063        let v2 = f.validated(tx_2);
5064        let v3 = f.validated(tx_3);
5065
5066        // Add first 2 to the pool
5067        let _res =
5068            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
5069        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
5070
5071        assert_eq!(0, pool.queued_transactions().len());
5072        assert_eq!(2, pool.pending_transactions().len());
5073
5074        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
5075        pool.remove_transaction(v0.id());
5076
5077        // Now add transaction with nonce 2
5078        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
5079
5080        // v2 is in the queue now. v1 is still in 'pending'.
5081        assert_eq!(1, pool.queued_transactions().len());
5082        assert_eq!(1, pool.pending_transactions().len());
5083
5084        // Simulate new block arrival - and chain nonce increasing.
5085        let mut updated_accounts = HashMap::default();
5086        on_chain_nonce += 1;
5087        updated_accounts.insert(
5088            v0.sender_id(),
5089            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
5090        );
5091        pool.update_accounts(updated_accounts);
5092
5093        // Transactions are not changed (IMHO - this is a bug, as transaction v2 should be in the
5094        // 'pending' now).
5095        assert_eq!(0, pool.queued_transactions().len());
5096        assert_eq!(2, pool.pending_transactions().len());
5097
5098        // Add transaction v3 - it 'unclogs' everything.
5099        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
5100        assert_eq!(0, pool.queued_transactions().len());
5101        assert_eq!(3, pool.pending_transactions().len());
5102
5103        // It should have returned transactions in order (v1, v2, v3 - as there is nothing blocking
5104        // them).
5105        assert_eq!(
5106            pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>(),
5107            vec![1, 2, 3]
5108        );
5109    }
5110
5111    #[test]
5112    fn test_best_with_attributes() {
5113        let on_chain_balance = U256::MAX;
5114        let on_chain_nonce = 0;
5115        let mut f = MockTransactionFactory::default();
5116        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5117
5118        let base_fee: u128 = 100;
5119        let blob_fee: u128 = 100;
5120
5121        // set base fee and blob fee.
5122        let mut block_info = pool.block_info();
5123        block_info.pending_basefee = base_fee as u64;
5124        block_info.pending_blob_fee = Some(blob_fee);
5125        pool.set_block_info(block_info);
5126
5127        // Insert transactions with varying max_fee_per_gas and max_fee_per_blob_gas.
5128        let tx1 = MockTransaction::eip4844()
5129            .with_sender(Address::with_last_byte(1))
5130            .with_max_fee(base_fee + 10)
5131            .with_blob_fee(blob_fee + 10);
5132        let tx2 = MockTransaction::eip4844()
5133            .with_sender(Address::with_last_byte(2))
5134            .with_max_fee(base_fee + 10)
5135            .with_blob_fee(blob_fee);
5136        let tx3 = MockTransaction::eip4844()
5137            .with_sender(Address::with_last_byte(3))
5138            .with_max_fee(base_fee)
5139            .with_blob_fee(blob_fee + 10);
5140        let tx4 = MockTransaction::eip4844()
5141            .with_sender(Address::with_last_byte(4))
5142            .with_max_fee(base_fee)
5143            .with_blob_fee(blob_fee);
5144        let tx5 = MockTransaction::eip4844()
5145            .with_sender(Address::with_last_byte(5))
5146            .with_max_fee(base_fee)
5147            .with_blob_fee(blob_fee - 10);
5148        let tx6 = MockTransaction::eip4844()
5149            .with_sender(Address::with_last_byte(6))
5150            .with_max_fee(base_fee - 10)
5151            .with_blob_fee(blob_fee);
5152        let tx7 = MockTransaction::eip4844()
5153            .with_sender(Address::with_last_byte(7))
5154            .with_max_fee(base_fee - 10)
5155            .with_blob_fee(blob_fee - 10);
5156
5157        for tx in vec![
5158            tx1.clone(),
5159            tx2.clone(),
5160            tx3.clone(),
5161            tx4.clone(),
5162            tx5.clone(),
5163            tx6.clone(),
5164            tx7.clone(),
5165        ] {
5166            pool.add_transaction(f.validated(tx), on_chain_balance, on_chain_nonce, None).unwrap();
5167        }
5168
5169        let base_fee = base_fee as u64;
5170        let blob_fee = blob_fee as u64;
5171
5172        let cases = vec![
5173            // 1. Base fee increase, blob fee increase
5174            (BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee + 5)), vec![tx1.clone()]),
5175            // 2. Base fee increase, blob fee not change
5176            (
5177                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee)),
5178                vec![tx1.clone(), tx2.clone()],
5179            ),
5180            // 3. Base fee increase, blob fee decrease
5181            (
5182                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee - 5)),
5183                vec![tx1.clone(), tx2.clone()],
5184            ),
5185            // 4. Base fee not change, blob fee increase
5186            (
5187                BestTransactionsAttributes::new(base_fee, Some(blob_fee + 5)),
5188                vec![tx1.clone(), tx3.clone()],
5189            ),
5190            // 5. Base fee not change, blob fee not change
5191            (
5192                BestTransactionsAttributes::new(base_fee, Some(blob_fee)),
5193                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()],
5194            ),
5195            // 6. Base fee not change, blob fee decrease
5196            (
5197                BestTransactionsAttributes::new(base_fee, Some(blob_fee - 10)),
5198                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()],
5199            ),
5200            // 7. Base fee decrease, blob fee increase
5201            (
5202                BestTransactionsAttributes::new(base_fee - 5, Some(blob_fee + 5)),
5203                vec![tx1.clone(), tx3.clone()],
5204            ),
5205            // 8. Base fee decrease, blob fee not change
5206            (
5207                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee)),
5208                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx6.clone()],
5209            ),
5210            // 9. Base fee decrease, blob fee decrease
5211            (
5212                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee - 10)),
5213                vec![tx1, tx2, tx5, tx3, tx4, tx6, tx7],
5214            ),
5215        ];
5216
5217        for (idx, (attribute, expected)) in cases.into_iter().enumerate() {
5218            let mut best = pool.best_transactions_with_attributes(attribute);
5219
5220            for (tx_idx, expected_tx) in expected.into_iter().enumerate() {
5221                let tx = best.next().expect("Transaction should be returned");
5222                assert_eq!(
5223                    tx.transaction,
5224                    expected_tx,
5225                    "Failed tx {} in case {}",
5226                    tx_idx + 1,
5227                    idx + 1
5228                );
5229            }
5230
5231            // No more transactions should be returned
5232            assert!(best.next().is_none());
5233        }
5234    }
5235
5236    #[test]
5237    fn test_pending_ordering() {
5238        let mut f = MockTransactionFactory::default();
5239        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5240
5241        let tx_0 = MockTransaction::eip1559().with_nonce(1).set_gas_price(100).inc_limit();
5242        let tx_1 = tx_0.next();
5243
5244        let v0 = f.validated(tx_0);
5245        let v1 = f.validated(tx_1);
5246
5247        // nonce gap, tx should be queued
5248        pool.add_transaction(v0.clone(), U256::MAX, 0, None).unwrap();
5249        assert_eq!(1, pool.queued_transactions().len());
5250
5251        // nonce gap is closed on-chain, both transactions should be moved to pending
5252        pool.add_transaction(v1, U256::MAX, 1, None).unwrap();
5253
5254        assert_eq!(2, pool.pending_transactions().len());
5255        assert_eq!(0, pool.queued_transactions().len());
5256
5257        assert_eq!(
5258            pool.pending_pool.independent().get(&v0.sender_id()).unwrap().transaction.nonce(),
5259            v0.nonce()
5260        );
5261    }
5262
5263    // <https://github.com/paradigmxyz/reth/issues/12286>
5264    #[test]
5265    fn one_sender_one_independent_transaction() {
5266        let mut on_chain_balance = U256::from(4_999); // only enough for 4 txs
5267        let mut on_chain_nonce = 40;
5268        let mut f = MockTransactionFactory::default();
5269        let mut pool = TxPool::mock();
5270        let mut submitted_txs = Vec::new();
5271
5272        // We use a "template" because we want all txs to have the same sender.
5273        let template =
5274            MockTransaction::eip1559().inc_price().inc_limit().with_value(U256::from(1_001));
5275
5276        // Add 8 txs. Because the balance is only sufficient for 4, so the last 4 will be
5277        // Queued.
5278        for tx_nonce in 40..48 {
5279            let tx = f.validated(template.clone().with_nonce(tx_nonce).rng_hash());
5280            submitted_txs.push(*tx.id());
5281            pool.add_transaction(tx, on_chain_balance, on_chain_nonce, None).unwrap();
5282        }
5283
5284        // A block is mined with two txs (so nonce is changed from 40 to 42).
5285        // Now the balance gets so high that it's enough to execute alltxs.
5286        on_chain_balance = U256::from(999_999);
5287        on_chain_nonce = 42;
5288        pool.remove_transaction(&submitted_txs[0]);
5289        pool.remove_transaction(&submitted_txs[1]);
5290
5291        // Add 4 txs.
5292        for tx_nonce in 48..52 {
5293            pool.add_transaction(
5294                f.validated(template.clone().with_nonce(tx_nonce).rng_hash()),
5295                on_chain_balance,
5296                on_chain_nonce,
5297                None,
5298            )
5299            .unwrap();
5300        }
5301
5302        let best_txs: Vec<_> = pool.pending().best().map(|tx| *tx.id()).collect();
5303        assert_eq!(best_txs.len(), 10); // 8 - 2 + 4 = 10
5304
5305        assert_eq!(pool.pending_pool.independent().len(), 1);
5306    }
5307
5308    #[test]
5309    fn test_insertion_disorder() {
5310        let mut f = MockTransactionFactory::default();
5311        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5312
5313        let sender = address!("0x1234567890123456789012345678901234567890");
5314        let tx0 = f.validated_arc(
5315            MockTransaction::legacy().with_sender(sender).with_nonce(0).with_gas_price(10),
5316        );
5317        let tx1 = f.validated_arc(
5318            MockTransaction::eip1559()
5319                .with_sender(sender)
5320                .with_nonce(1)
5321                .with_gas_limit(1000)
5322                .with_gas_price(10),
5323        );
5324        let tx2 = f.validated_arc(
5325            MockTransaction::legacy().with_sender(sender).with_nonce(2).with_gas_price(10),
5326        );
5327        let tx3 = f.validated_arc(
5328            MockTransaction::legacy().with_sender(sender).with_nonce(3).with_gas_price(10),
5329        );
5330
5331        // tx0 should be put in the pending subpool
5332        pool.add_transaction((*tx0).clone(), U256::from(1000), 0, None).unwrap();
5333        let mut best = pool.best_transactions();
5334        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5335        assert_eq!(t0.id(), tx0.id());
5336        // tx1 should be put in the queued subpool due to insufficient sender balance
5337        pool.add_transaction((*tx1).clone(), U256::from(1000), 0, None).unwrap();
5338        let mut best = pool.best_transactions();
5339        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5340        assert_eq!(t0.id(), tx0.id());
5341        assert!(best.next().is_none());
5342
5343        // tx2 should be put in the pending subpool, and tx1 should be promoted to pending
5344        pool.add_transaction((*tx2).clone(), U256::MAX, 0, None).unwrap();
5345
5346        let mut best = pool.best_transactions();
5347
5348        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5349        let t1 = best.next().expect("tx1 should be put in the pending subpool");
5350        let t2 = best.next().expect("tx2 should be put in the pending subpool");
5351        assert_eq!(t0.id(), tx0.id());
5352        assert_eq!(t1.id(), tx1.id());
5353        assert_eq!(t2.id(), tx2.id());
5354
5355        // tx3 should be put in the pending subpool,
5356        pool.add_transaction((*tx3).clone(), U256::MAX, 0, None).unwrap();
5357        let mut best = pool.best_transactions();
5358        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5359        let t1 = best.next().expect("tx1 should be put in the pending subpool");
5360        let t2 = best.next().expect("tx2 should be put in the pending subpool");
5361        let t3 = best.next().expect("tx3 should be put in the pending subpool");
5362        assert_eq!(t0.id(), tx0.id());
5363        assert_eq!(t1.id(), tx1.id());
5364        assert_eq!(t2.id(), tx2.id());
5365        assert_eq!(t3.id(), tx3.id());
5366    }
5367
5368    #[test]
5369    fn test_non_4844_blob_fee_bit_invariant() {
5370        let mut f = MockTransactionFactory::default();
5371        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5372
5373        let non_4844_tx = MockTransaction::eip1559().set_max_fee(200).inc_limit();
5374        let validated = f.validated(non_4844_tx.clone());
5375
5376        assert!(!non_4844_tx.is_eip4844());
5377        pool.add_transaction(validated.clone(), U256::from(10_000), 0, None).unwrap();
5378
5379        // Core invariant: Non-4844 transactions must ALWAYS have ENOUGH_BLOB_FEE_CAP_BLOCK bit
5380        let tx_meta = pool.all_transactions.txs.get(validated.id()).unwrap();
5381        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
5382        assert_eq!(tx_meta.subpool, SubPool::Pending);
5383    }
5384
5385    #[test]
5386    fn test_blob_fee_enforcement_only_applies_to_eip4844() {
5387        let mut f = MockTransactionFactory::default();
5388        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5389
5390        // Set blob fee higher than EIP-4844 tx can afford
5391        let mut block_info = pool.block_info();
5392        block_info.pending_blob_fee = Some(160);
5393        block_info.pending_basefee = 100;
5394        pool.set_block_info(block_info);
5395
5396        let eip4844_tx = MockTransaction::eip4844()
5397            .with_sender(address!("0x000000000000000000000000000000000000000a"))
5398            .with_max_fee(200)
5399            .with_blob_fee(150) // Less than block blob fee (160)
5400            .inc_limit();
5401
5402        let non_4844_tx = MockTransaction::eip1559()
5403            .with_sender(address!("0x000000000000000000000000000000000000000b"))
5404            .set_max_fee(200)
5405            .inc_limit();
5406
5407        let validated_4844 = f.validated(eip4844_tx);
5408        let validated_non_4844 = f.validated(non_4844_tx);
5409
5410        pool.add_transaction(validated_4844.clone(), U256::from(10_000), 0, None).unwrap();
5411        pool.add_transaction(validated_non_4844.clone(), U256::from(10_000), 0, None).unwrap();
5412
5413        let tx_4844_meta = pool.all_transactions.txs.get(validated_4844.id()).unwrap();
5414        let tx_non_4844_meta = pool.all_transactions.txs.get(validated_non_4844.id()).unwrap();
5415
5416        // EIP-4844: blob fee enforcement applies - insufficient blob fee removes bit
5417        assert!(!tx_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
5418        assert_eq!(tx_4844_meta.subpool, SubPool::Blob);
5419
5420        // Non-4844: blob fee enforcement does NOT apply - bit always remains true
5421        assert!(tx_non_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
5422        assert_eq!(tx_non_4844_meta.subpool, SubPool::Pending);
5423    }
5424
5425    #[test]
5426    fn test_basefee_decrease_preserves_non_4844_blob_fee_bit() {
5427        let mut f = MockTransactionFactory::default();
5428        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5429
5430        // Create non-4844 transaction with fee that initially can't afford high basefee
5431        let non_4844_tx = MockTransaction::eip1559()
5432            .with_sender(address!("0x000000000000000000000000000000000000000a"))
5433            .set_max_fee(500) // Can't afford basefee of 600
5434            .inc_limit();
5435
5436        // Set high basefee so transaction goes to BaseFee pool initially
5437        pool.update_basefee(600, |_| {});
5438
5439        let validated = f.validated(non_4844_tx);
5440        let tx_id = *validated.id();
5441        pool.add_transaction(validated, U256::from(10_000), 0, None).unwrap();
5442
5443        // Initially should be in BaseFee pool but STILL have blob fee bit (critical invariant)
5444        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
5445        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
5446        assert!(
5447            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
5448            "Non-4844 tx in BaseFee pool must retain ENOUGH_BLOB_FEE_CAP_BLOCK bit"
5449        );
5450
5451        // Decrease basefee - transaction should be promoted to Pending
5452        // This is where PR #18215 bug would manifest: blob fee bit incorrectly removed
5453        pool.update_basefee(400, |_| {});
5454
5455        // After basefee decrease: should be promoted to Pending with blob fee bit preserved
5456        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
5457        assert_eq!(
5458            tx_meta.subpool,
5459            SubPool::Pending,
5460            "Non-4844 tx should be promoted from BaseFee to Pending after basefee decrease"
5461        );
5462        assert!(
5463            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
5464            "Non-4844 tx must NEVER lose ENOUGH_BLOB_FEE_CAP_BLOCK bit during basefee promotion"
5465        );
5466        assert!(
5467            tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK),
5468            "Non-4844 tx should gain ENOUGH_FEE_CAP_BLOCK bit after basefee decrease"
5469        );
5470    }
5471
5472    /// Test for <https://github.com/paradigmxyz/reth/issues/17701>
5473    ///
5474    /// When a new transaction is added and its `updates` contain a same-sender transaction with
5475    /// a lower nonce, the lower-nonce tx must be added to the pending subpool *before* the
5476    /// higher-nonce tx. Otherwise, live `BestTransactions` iterators receive them out of order.
5477    #[test]
5478    fn best_transactions_nonce_order_on_balance_unlock() {
5479        let mut f = MockTransactionFactory::default();
5480        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5481
5482        let sender = Address::random();
5483        let on_chain_balance = U256::from(10_000);
5484
5485        // tx0: nonce 0, cheap — will go straight to pending
5486        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
5487        // tx1: nonce 1, very expensive — cumulative cost (tx0 + tx1) will exceed balance
5488        let tx1 = tx0.next().inc_limit().with_value(U256::from(on_chain_balance));
5489        // tx2: nonce 2
5490        let tx2 = tx1.next().inc_limit().with_value(U256::ZERO);
5491
5492        let v0 = f.validated(tx0);
5493        let v1 = f.validated(tx1);
5494        let v2 = f.validated(tx2);
5495
5496        // Add tx0 with limited balance — goes to pending (tx0 cost is small: 1 * 100 = 100)
5497        pool.add_transaction(v0, on_chain_balance, 0, None).unwrap();
5498
5499        // Create a live BestTransactions iterator that will receive new pending txs
5500        let mut best = pool.best_transactions();
5501
5502        // Drain tx0 from the iterator
5503        let first = best.next().expect("should yield tx0");
5504        assert_eq!(first.id().nonce, 0);
5505
5506        // Add tx1 with the same limited balance — cumulative cost exceeds balance, goes to
5507        // queued
5508        pool.add_transaction(v1, on_chain_balance, 0, None).unwrap();
5509
5510        // tx1 should be queued, nothing new in best
5511        assert!(best.next().is_none(), "tx1 should be queued, not pending");
5512
5513        // Now add tx2 with U256::MAX balance — tx2 goes to pending AND tx1 gets promoted.
5514        // The bug: tx2 was added to pending *before* tx1, so BestTransactions yielded tx2
5515        // first, violating nonce ordering.
5516        pool.add_transaction(v2, U256::MAX, 0, None).unwrap();
5517
5518        let t1 = best.next().expect("should yield a transaction");
5519        let t2 = best.next().expect("should yield a transaction");
5520
5521        // Correct order: tx1 (nonce 1) before tx2 (nonce 2)
5522        assert_eq!(
5523            t1.id().nonce,
5524            1,
5525            "first yielded tx should be nonce 1, got nonce {}",
5526            t1.id().nonce
5527        );
5528        assert_eq!(
5529            t2.id().nonce,
5530            2,
5531            "second yielded tx should be nonce 2, got nonce {}",
5532            t2.id().nonce
5533        );
5534    }
5535
5536    /// Gap-fill scenario: inserting a low-nonce transaction promotes a queued higher-nonce
5537    /// transaction. The new (lower-nonce) tx must appear before the promoted (higher-nonce)
5538    /// tx in the `BestTransactions` iterator.
5539    #[test]
5540    fn best_transactions_nonce_order_on_gap_fill() {
5541        let mut f = MockTransactionFactory::default();
5542        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5543
5544        let sender = Address::random();
5545        let balance = U256::MAX;
5546
5547        // tx0: nonce 0
5548        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
5549        // tx1: nonce 1
5550        let tx1 = tx0.next().inc_limit();
5551
5552        let v0 = f.validated(tx0);
5553        let v1 = f.validated(tx1);
5554
5555        // Add tx1 first — goes to queued because nonce 0 is missing (nonce gap)
5556        pool.add_transaction(v1, balance, 0, None).unwrap();
5557
5558        // Create a live BestTransactions iterator (currently empty — nothing pending)
5559        let mut best = pool.best_transactions();
5560        assert!(best.next().is_none(), "pool should have no pending txs yet");
5561
5562        // Add tx0 — fills the gap, tx1 gets promoted
5563        pool.add_transaction(v0, balance, 0, None).unwrap();
5564
5565        let t0 = best.next().expect("should yield a transaction");
5566        let t1 = best.next().expect("should yield a transaction");
5567
5568        assert_eq!(t0.id().nonce, 0, "first yielded tx should be nonce 0, got {}", t0.id().nonce);
5569        assert_eq!(t1.id().nonce, 1, "second yielded tx should be nonce 1, got {}", t1.id().nonce);
5570    }
5571
5572    /// Mixed scenario: inserting a mid-nonce transaction promotes both a lower-nonce tx
5573    /// (via balance update) and a higher-nonce tx (via gap fill). All three must appear
5574    /// in nonce order in `BestTransactions`.
5575    #[test]
5576    fn best_transactions_nonce_order_mixed_promotions() {
5577        let mut f = MockTransactionFactory::default();
5578        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5579
5580        let sender = Address::random();
5581        let low_balance = U256::from(10_000);
5582
5583        // tx0: nonce 0, cheap
5584        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
5585        // tx1: nonce 1, very expensive — will exceed balance
5586        let tx1 = tx0.next().inc_limit().with_value(U256::from(low_balance));
5587        // tx2: nonce 2
5588        let tx2 = tx1.next().inc_limit().with_value(U256::ZERO);
5589        // tx3: nonce 3
5590        let tx3 = tx2.next().inc_limit().with_value(U256::ZERO);
5591
5592        let v0 = f.validated(tx0);
5593        let v1 = f.validated(tx1);
5594        let v2 = f.validated(tx2);
5595        let v3 = f.validated(tx3);
5596
5597        // Add tx0 — goes to pending
5598        pool.add_transaction(v0, low_balance, 0, None).unwrap();
5599
5600        // Add tx1 — queued (cumulative cost exceeds balance)
5601        pool.add_transaction(v1, low_balance, 0, None).unwrap();
5602
5603        // Add tx3 — queued (nonce gap: tx2 is missing)
5604        pool.add_transaction(v3, low_balance, 0, None).unwrap();
5605
5606        let mut best = pool.best_transactions();
5607
5608        // Drain tx0
5609        let first = best.next().expect("should yield tx0");
5610        assert_eq!(first.id().nonce, 0);
5611        assert!(best.next().is_none(), "only tx0 should be pending");
5612
5613        // Add tx2 with U256::MAX balance — this should:
5614        //  - promote tx1 (lower-nonce, was queued due to balance)
5615        //  - add tx2 itself to pending
5616        //  - promote tx3 (higher-nonce, was queued due to nonce gap)
5617        pool.add_transaction(v2, U256::MAX, 0, None).unwrap();
5618
5619        let t1 = best.next().expect("should yield nonce 1");
5620        let t2 = best.next().expect("should yield nonce 2");
5621        let t3 = best.next().expect("should yield nonce 3");
5622
5623        assert_eq!(t1.id().nonce, 1, "expected nonce 1, got {}", t1.id().nonce);
5624        assert_eq!(t2.id().nonce, 2, "expected nonce 2, got {}", t2.id().nonce);
5625        assert_eq!(t3.id().nonce, 3, "expected nonce 3, got {}", t3.id().nonce);
5626    }
5627}