Skip to main content

reth_transaction_pool/pool/
txpool.rs

1//! The internal transaction pool implementation.
2
3use crate::{
4    config::{LocalTransactionConfig, TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER},
5    error::{
6        Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
7        PoolError, PoolErrorKind,
8    },
9    identifier::{SenderId, TransactionId},
10    metrics::{AllTransactionsMetrics, TxPoolMetrics},
11    pool::{
12        best::BestTransactions,
13        blob::BlobTransactions,
14        parked::{BasefeeOrd, ParkedPool, QueuedOrd},
15        pending::PendingPool,
16        state::{SubPool, TxState},
17        update::{Destination, PoolUpdate, UpdateOutcome},
18        AddedPendingTransaction, AddedTransaction, OnNewCanonicalStateOutcome,
19    },
20    traits::{BestTransactionsAttributes, BlockInfo, PoolSize},
21    PoolConfig, PoolResult, PoolTransaction, PoolUpdateKind, PriceBumpConfig, TransactionOrdering,
22    ValidPoolTransaction, U256,
23};
24use alloy_consensus::constants::{
25    EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, KECCAK_EMPTY,
26    LEGACY_TX_TYPE_ID,
27};
28use alloy_eips::{
29    eip1559::{ETHEREUM_BLOCK_GAS_LIMIT_30M, MIN_PROTOCOL_BASE_FEE},
30    eip4844::BLOB_TX_MIN_BLOB_GASPRICE,
31};
32#[cfg(test)]
33use alloy_primitives::Address;
34use alloy_primitives::{
35    map::{AddressSet, B256Map, B256Set},
36    TxHash, B256,
37};
38use reth_primitives_traits::transaction::error::InvalidTransactionError;
39use rustc_hash::FxHashMap;
40use smallvec::SmallVec;
41#[cfg(test)]
42use std::collections::{HashMap, HashSet};
43use std::{
44    cmp::Ordering,
45    collections::{btree_map::Entry, hash_map, BTreeMap},
46    fmt,
47    ops::Bound::{Excluded, Unbounded},
48    sync::Arc,
49};
50use tracing::{trace, warn};
51
52#[cfg_attr(doc, aquamarine::aquamarine)]
53// TODO: Inlined diagram due to a bug in aquamarine library, should become an include when it's
54// fixed. See https://github.com/mersinvald/aquamarine/issues/50
55// include_mmd!("docs/mermaid/txpool.mmd")
56/// A pool that manages transactions.
57///
58/// This pool maintains the state of all transactions and stores them accordingly.
59///
60/// ```mermaid
61/// graph TB
62///   subgraph TxPool
63///     direction TB
64///     pool[(All Transactions)]
65///     subgraph Subpools
66///         direction TB
67///         B3[(Queued)]
68///         B1[(Pending)]
69///         B2[(Basefee)]
70///         B4[(Blob)]
71///     end
72///   end
73///   discard([discard])
74///   production([Block Production])
75///   new([New Block])
76///   A[Incoming Tx] --> B[Validation] -->|ins
77///   pool --> |if ready + blobfee too low| B4
78///   pool --> |if ready| B1
79///   pool --> |if ready + basfee too low| B2
80///   pool --> |nonce gap or lack of funds| B3
81///   pool --> |update| pool
82///   B1 --> |best| production
83///   B2 --> |worst| discard
84///   B3 --> |worst| discard
85///   B4 --> |worst| discard
86///   B1 --> |increased blob fee| B4
87///   B4 --> |decreased blob fee| B1
88///   B1 --> |increased base fee| B2
89///   B2 --> |decreased base fee| B1
90///   B3 --> |promote| B1
91///   B3 --> |promote| B2
92///   new --> |apply state changes| pool
93/// ```
94pub struct TxPool<T: TransactionOrdering> {
95    /// pending subpool
96    ///
97    /// Holds transactions that are ready to be executed on the current state.
98    pending_pool: PendingPool<T>,
99    /// Pool settings to enforce limits etc.
100    config: PoolConfig,
101    /// queued subpool
102    ///
103    /// Holds all parked transactions that depend on external changes from the sender:
104    ///
105    ///    - blocked by missing ancestor transaction (has nonce gaps)
106    ///    - sender lacks funds to pay for this transaction.
107    queued_pool: ParkedPool<QueuedOrd<T::Transaction>>,
108    /// base fee subpool
109    ///
110    /// Holds all parked transactions that currently violate the dynamic fee requirement but could
111    /// be moved to pending if the base fee changes in their favor (decreases) in future blocks.
112    basefee_pool: ParkedPool<BasefeeOrd<T::Transaction>>,
113    /// Blob transactions in the pool that are __not pending__.
114    ///
115    /// This means they either do not satisfy the dynamic fee requirement or the blob fee
116    /// requirement. These transactions can be moved to pending if the base fee or blob fee changes
117    /// in their favor (decreases) in future blocks. The transaction may need both the base fee and
118    /// blob fee to decrease to become executable.
119    blob_pool: BlobTransactions<T::Transaction>,
120    /// All transactions in the pool.
121    all_transactions: AllTransactions<T::Transaction>,
122    /// Transaction pool metrics
123    metrics: TxPoolMetrics,
124}
125
126// === impl TxPool ===
127
128impl<T: TransactionOrdering> TxPool<T> {
129    /// Create a new graph pool instance.
130    pub fn new(ordering: T, config: PoolConfig) -> Self {
131        Self {
132            pending_pool: PendingPool::with_buffer(
133                ordering,
134                config.max_new_pending_txs_notifications,
135            ),
136            queued_pool: Default::default(),
137            basefee_pool: Default::default(),
138            blob_pool: Default::default(),
139            all_transactions: AllTransactions::new(&config),
140            config,
141            metrics: Default::default(),
142        }
143    }
144
145    /// Retrieves the highest nonce for a specific sender from the transaction pool.
146    pub fn get_highest_nonce_by_sender(&self, sender: SenderId) -> Option<u64> {
147        self.all().txs_iter(sender).last().map(|(_, tx)| tx.transaction.nonce())
148    }
149
150    /// Retrieves the highest transaction (wrapped in an `Arc`) for a specific sender from the
151    /// transaction pool.
152    pub fn get_highest_transaction_by_sender(
153        &self,
154        sender: SenderId,
155    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
156        self.all().txs_iter(sender).last().map(|(_, tx)| Arc::clone(&tx.transaction))
157    }
158
159    /// Returns the transaction with the highest nonce that is executable given the on chain nonce.
160    ///
161    /// If the pool already tracks a higher nonce for the given sender, then this nonce is used
162    /// instead.
163    ///
164    /// Note: The next pending pooled transaction must have the on chain nonce.
165    pub(crate) fn get_highest_consecutive_transaction_by_sender(
166        &self,
167        mut on_chain: TransactionId,
168    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
169        let mut last_consecutive_tx = None;
170
171        // ensure this operates on the most recent
172        if let Some(current) = self.all_transactions.sender_info.get(&on_chain.sender) {
173            on_chain.nonce = on_chain.nonce.max(current.state_nonce);
174        }
175
176        let mut next_expected_nonce = on_chain.nonce;
177        for (id, tx) in self.all().descendant_txs_inclusive(&on_chain) {
178            if next_expected_nonce != id.nonce {
179                break
180            }
181            next_expected_nonce = id.next_nonce();
182            last_consecutive_tx = Some(tx);
183        }
184
185        last_consecutive_tx.map(|tx| Arc::clone(&tx.transaction))
186    }
187
188    /// Returns access to the [`AllTransactions`] container.
189    pub(crate) const fn all(&self) -> &AllTransactions<T::Transaction> {
190        &self.all_transactions
191    }
192
193    /// Returns all senders in the pool
194    pub(crate) fn unique_senders(&self) -> AddressSet {
195        self.all_transactions.txs.values().map(|tx| tx.transaction.sender()).collect()
196    }
197
198    /// Returns stats about the size of pool.
199    pub fn size(&self) -> PoolSize {
200        PoolSize {
201            pending: self.pending_pool.len(),
202            pending_size: self.pending_pool.size(),
203            basefee: self.basefee_pool.len(),
204            basefee_size: self.basefee_pool.size(),
205            queued: self.queued_pool.len(),
206            queued_size: self.queued_pool.size(),
207            blob: self.blob_pool.len(),
208            blob_size: self.blob_pool.size(),
209            total: self.all_transactions.len(),
210        }
211    }
212
213    /// Returns the currently tracked block values
214    pub const fn block_info(&self) -> BlockInfo {
215        BlockInfo {
216            block_gas_limit: self.all_transactions.block_gas_limit,
217            last_seen_block_hash: self.all_transactions.last_seen_block_hash,
218            last_seen_block_number: self.all_transactions.last_seen_block_number,
219            pending_basefee: self.all_transactions.pending_fees.base_fee,
220            pending_blob_fee: Some(self.all_transactions.pending_fees.blob_fee),
221        }
222    }
223
224    /// Updates the tracked blob fee
225    fn update_blob_fee<F>(
226        &mut self,
227        mut pending_blob_fee: u128,
228        base_fee_update: Ordering,
229        mut on_promoted: F,
230    ) where
231        F: FnMut(&Arc<ValidPoolTransaction<T::Transaction>>),
232    {
233        std::mem::swap(&mut self.all_transactions.pending_fees.blob_fee, &mut pending_blob_fee);
234        match (self.all_transactions.pending_fees.blob_fee.cmp(&pending_blob_fee), base_fee_update)
235        {
236            (Ordering::Equal, Ordering::Equal | Ordering::Greater) => {
237                // fee unchanged, nothing to update
238            }
239            (Ordering::Greater, Ordering::Equal | Ordering::Greater) => {
240                // increased blob fee: recheck pending pool and remove all that are no longer valid
241                let removed =
242                    self.pending_pool.update_blob_fee(self.all_transactions.pending_fees.blob_fee);
243                for tx in removed {
244                    let to = {
245                        let tx =
246                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
247
248                        // the blob fee is too high now, unset the blob fee cap block flag
249                        tx.state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
250                        tx.subpool = tx.state.into();
251                        tx.subpool
252                    };
253                    self.add_transaction_to_subpool(to, tx);
254                }
255            }
256            (Ordering::Greater, Ordering::Less) => {
257                // The blob fee increased while the base fee decreased, so this update must both
258                // park transactions that no longer cover the blob fee and promote transactions
259                // that now cover both fees. Demote first so the promotion check sees the final
260                // fee state.
261                let removed =
262                    self.pending_pool.update_blob_fee(self.all_transactions.pending_fees.blob_fee);
263                for tx in removed {
264                    let to = {
265                        let tx =
266                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
267
268                        tx.state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
269                        tx.subpool = tx.state.into();
270                        tx.subpool
271                    };
272                    self.add_transaction_to_subpool(to, tx);
273                }
274
275                let removed =
276                    self.blob_pool.enforce_pending_fees(&self.all_transactions.pending_fees);
277                for tx in removed {
278                    let subpool = {
279                        let tx_meta =
280                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
281                        tx_meta.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
282                        tx_meta.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
283                        tx_meta.subpool = tx_meta.state.into();
284                        tx_meta.subpool
285                    };
286
287                    if subpool.is_pending() {
288                        on_promoted(&tx);
289                    }
290
291                    self.add_transaction_to_subpool(subpool, tx);
292                }
293            }
294            (Ordering::Less, _) | (Ordering::Equal, Ordering::Less) => {
295                // decreased blob/base fee: recheck blob pool and promote all that are now valid
296                let removed =
297                    self.blob_pool.enforce_pending_fees(&self.all_transactions.pending_fees);
298                for tx in removed {
299                    let subpool = {
300                        let tx_meta =
301                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
302                        tx_meta.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
303                        tx_meta.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
304                        tx_meta.subpool = tx_meta.state.into();
305                        tx_meta.subpool
306                    };
307
308                    if subpool.is_pending() {
309                        on_promoted(&tx);
310                    }
311
312                    self.add_transaction_to_subpool(subpool, tx);
313                }
314            }
315        }
316    }
317
318    /// Updates the tracked basefee
319    ///
320    /// Depending on the change in direction of the basefee, this will promote or demote
321    /// transactions from the basefee pool.
322    fn update_basefee<F>(&mut self, mut pending_basefee: u64, mut on_promoted: F) -> Ordering
323    where
324        F: FnMut(&Arc<ValidPoolTransaction<T::Transaction>>),
325    {
326        std::mem::swap(&mut self.all_transactions.pending_fees.base_fee, &mut pending_basefee);
327        match self.all_transactions.pending_fees.base_fee.cmp(&pending_basefee) {
328            Ordering::Equal => {
329                // fee unchanged, nothing to update
330                Ordering::Equal
331            }
332            Ordering::Greater => {
333                // increased base fee: recheck pending pool and remove all that are no longer valid
334                let removed =
335                    self.pending_pool.update_base_fee(self.all_transactions.pending_fees.base_fee);
336                for tx in removed {
337                    let to = {
338                        let tx =
339                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
340                        tx.state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
341                        tx.subpool = tx.state.into();
342                        tx.subpool
343                    };
344                    self.add_transaction_to_subpool(to, tx);
345                }
346
347                Ordering::Greater
348            }
349            Ordering::Less => {
350                // Base fee decreased: recheck BaseFee and promote.
351                // Invariants:
352                // - BaseFee contains only non-blob txs (blob txs live in Blob) and they already
353                //   have ENOUGH_BLOB_FEE_CAP_BLOCK.
354                // - PENDING_POOL_BITS = BASE_FEE_POOL_BITS | ENOUGH_FEE_CAP_BLOCK |
355                //   ENOUGH_BLOB_FEE_CAP_BLOCK.
356                // With the lower base fee they gain ENOUGH_FEE_CAP_BLOCK, so we can set the bit and
357                // insert directly into Pending (skip generic routing).
358                let current_base_fee = self.all_transactions.pending_fees.base_fee;
359                self.basefee_pool.enforce_basefee_with(current_base_fee, |tx| {
360                    // Update transaction state — guaranteed Pending by the invariants above
361                    let subpool = {
362                        let meta =
363                            self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set");
364                        meta.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
365                        meta.subpool = meta.state.into();
366                        meta.subpool
367                    };
368
369                    if subpool.is_pending() {
370                        on_promoted(&tx);
371                    }
372
373                    trace!(target: "txpool", hash=%tx.transaction.hash(), pool=?subpool, "Adding transaction to a subpool");
374                    match subpool {
375                        SubPool::Queued => self.queued_pool.add_transaction(tx),
376                        SubPool::Pending => {
377                            self.pending_pool.add_transaction(tx, current_base_fee);
378                        }
379                        SubPool::Blob => {
380                            self.blob_pool.add_transaction(tx);
381                        }
382                        SubPool::BaseFee => {
383                            // This should be unreachable as transactions from BaseFee pool with decreased
384                            // basefee are guaranteed to become Pending
385                            warn!(target: "txpool", "BaseFee transactions should become Pending after basefee decrease");
386                        }
387                    }
388                });
389
390                Ordering::Less
391            }
392        }
393    }
394
395    /// Sets the current block info for the pool.
396    ///
397    /// This will also apply updates to the pool based on the new base fee and blob fee.
398    ///
399    /// Returns the outcome containing any transactions that were promoted due to fee changes.
400    pub fn set_block_info(&mut self, info: BlockInfo) -> UpdateOutcome<T::Transaction> {
401        let mut outcome = UpdateOutcome::default();
402
403        // first update the subpools based on the new values, collecting promoted transactions
404        let basefee_ordering = self.update_basefee(info.pending_basefee, |tx| {
405            outcome.promoted.push(tx.clone());
406        });
407        if let Some(blob_fee) = info.pending_blob_fee {
408            self.update_blob_fee(blob_fee, basefee_ordering, |tx| {
409                outcome.promoted.push(tx.clone());
410            })
411        }
412        // then update tracked values
413        self.all_transactions.set_block_info(info);
414
415        outcome
416    }
417
418    /// Returns an iterator that yields transactions that are ready to be included in the block with
419    /// the tracked fees.
420    pub(crate) fn best_transactions(&self) -> BestTransactions<T> {
421        self.pending_pool.best()
422    }
423
424    /// Returns an iterator that yields transactions that are ready to be included in the block with
425    /// the given base fee and optional blob fee.
426    ///
427    /// If the provided attributes differ from the currently tracked fees, this will also include
428    /// transactions that are unlocked by the new fees, or exclude transactions that are no longer
429    /// valid with the new fees.
430    pub(crate) fn best_transactions_with_attributes(
431        &self,
432        best_transactions_attributes: BestTransactionsAttributes,
433    ) -> Box<dyn crate::traits::BestTransactions<Item = Arc<ValidPoolTransaction<T::Transaction>>>>
434    {
435        // First we need to check if the given base fee is different than what's currently being
436        // tracked
437        match best_transactions_attributes.basefee.cmp(&self.all_transactions.pending_fees.base_fee)
438        {
439            Ordering::Equal => {
440                // for EIP-4844 transactions we also need to check if the blob fee is now lower than
441                // what's currently being tracked, if so we need to include transactions from the
442                // blob pool that are valid with the lower blob fee
443                let new_blob_fee = best_transactions_attributes.blob_fee.unwrap_or_default();
444                match new_blob_fee.cmp(&(self.all_transactions.pending_fees.blob_fee as u64)) {
445                    Ordering::Less => {
446                        // it's possible that this swing unlocked more blob transactions
447                        let unlocked =
448                            self.blob_pool.satisfy_attributes(best_transactions_attributes);
449                        Box::new(self.pending_pool.best_with_unlocked_and_attributes(
450                            unlocked,
451                            best_transactions_attributes.basefee,
452                            new_blob_fee,
453                        ))
454                    }
455                    Ordering::Equal => Box::new(self.pending_pool.best()),
456                    Ordering::Greater => {
457                        // no additional transactions unlocked
458                        Box::new(self.pending_pool.best_with_basefee_and_blobfee(
459                            best_transactions_attributes.basefee,
460                            best_transactions_attributes.blob_fee.unwrap_or_default(),
461                        ))
462                    }
463                }
464            }
465            Ordering::Greater => {
466                // base fee increased, we need to check how the blob fee moved
467                let new_blob_fee = best_transactions_attributes.blob_fee.unwrap_or_default();
468                match new_blob_fee.cmp(&(self.all_transactions.pending_fees.blob_fee as u64)) {
469                    Ordering::Less => {
470                        // it's possible that this swing unlocked more blob transactions
471                        let unlocked =
472                            self.blob_pool.satisfy_attributes(best_transactions_attributes);
473                        Box::new(self.pending_pool.best_with_unlocked_and_attributes(
474                            unlocked,
475                            best_transactions_attributes.basefee,
476                            new_blob_fee,
477                        ))
478                    }
479                    Ordering::Equal | Ordering::Greater => {
480                        // no additional transactions unlocked
481                        Box::new(self.pending_pool.best_with_basefee_and_blobfee(
482                            best_transactions_attributes.basefee,
483                            new_blob_fee,
484                        ))
485                    }
486                }
487            }
488            Ordering::Less => {
489                // base fee decreased, we need to move transactions from the basefee + blob pool to
490                // the pending pool that might be unlocked by the lower base fee
491                let mut unlocked = self
492                    .basefee_pool
493                    .satisfy_base_fee_transactions(best_transactions_attributes.basefee);
494
495                // also include blob pool transactions that are now unlocked
496                unlocked.extend(self.blob_pool.satisfy_attributes(best_transactions_attributes));
497
498                Box::new(self.pending_pool.best_with_unlocked_and_attributes(
499                    unlocked,
500                    best_transactions_attributes.basefee,
501                    best_transactions_attributes.blob_fee.unwrap_or_default(),
502                ))
503            }
504        }
505    }
506
507    /// Returns all transactions from the pending sub-pool
508    pub(crate) fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
509        self.pending_pool.all().collect()
510    }
511    /// Returns an iterator over all transactions from the pending sub-pool
512    pub(crate) fn pending_transactions_iter(
513        &self,
514    ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
515        self.pending_pool.all()
516    }
517
518    /// Returns the number of transactions from the pending sub-pool
519    pub(crate) fn pending_transactions_count(&self) -> usize {
520        self.pending_pool.len()
521    }
522
523    /// Returns all pending transactions filtered by predicate
524    pub(crate) fn pending_transactions_with_predicate(
525        &self,
526        mut predicate: impl FnMut(&ValidPoolTransaction<T::Transaction>) -> bool,
527    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
528        self.pending_transactions_iter().filter(|tx| predicate(tx)).collect()
529    }
530
531    /// Returns all pending transactions for the specified sender
532    pub(crate) fn pending_txs_by_sender(
533        &self,
534        sender: SenderId,
535    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
536        self.pending_pool.txs_by_sender(sender).collect()
537    }
538
539    /// Returns all transactions from parked pools
540    pub(crate) fn queued_transactions(&self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
541        self.basefee_pool.all().chain(self.queued_pool.all()).chain(self.blob_pool.all()).collect()
542    }
543
544    /// Returns the number of transactions in parked pools
545    pub(crate) fn queued_transactions_count(&self) -> usize {
546        self.basefee_pool.len() + self.queued_pool.len() + self.blob_pool.len()
547    }
548
549    /// Returns queued and pending transactions for the specified sender
550    pub fn queued_and_pending_txs_by_sender(
551        &self,
552        sender: SenderId,
553    ) -> (SmallVec<[TransactionId; TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER]>, Vec<TransactionId>) {
554        (self.queued_pool.get_txs_by_sender(sender), self.pending_pool.get_txs_by_sender(sender))
555    }
556
557    /// Returns all queued transactions for the specified sender
558    pub(crate) fn queued_txs_by_sender(
559        &self,
560        sender: SenderId,
561    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
562        self.basefee_pool
563            .txs_by_sender(sender)
564            .chain(self.queued_pool.txs_by_sender(sender))
565            .chain(self.blob_pool.txs_by_sender(sender))
566            .collect()
567    }
568
569    /// Returns `true` if the transaction with the given hash is already included in this pool.
570    pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
571        self.all_transactions.contains(tx_hash)
572    }
573
574    /// Returns `true` if the transaction with the given id is already included in the given subpool
575    #[cfg(test)]
576    pub(crate) fn subpool_contains(&self, subpool: SubPool, id: &TransactionId) -> bool {
577        match subpool {
578            SubPool::Queued => self.queued_pool.contains(id),
579            SubPool::Pending => self.pending_pool.contains(id),
580            SubPool::BaseFee => self.basefee_pool.contains(id),
581            SubPool::Blob => self.blob_pool.contains(id),
582        }
583    }
584
585    /// Returns `true` if the pool is over its configured limits.
586    #[inline]
587    pub(crate) fn is_exceeded(&self) -> bool {
588        self.config.is_exceeded(self.size())
589    }
590
591    /// Returns the transaction for the given hash.
592    pub(crate) fn get(
593        &self,
594        tx_hash: &TxHash,
595    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
596        self.all_transactions.by_hash.get(tx_hash).cloned()
597    }
598
599    /// Returns transactions for the multiple given hashes, if they exist.
600    pub(crate) fn get_all(
601        &self,
602        txs: Vec<TxHash>,
603    ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
604        txs.into_iter().filter_map(|tx| self.get(&tx))
605    }
606
607    /// Returns all transactions sent from the given sender.
608    pub(crate) fn get_transactions_by_sender(
609        &self,
610        sender: SenderId,
611    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
612        self.all_transactions.txs_iter(sender).map(|(_, tx)| Arc::clone(&tx.transaction)).collect()
613    }
614
615    /// Returns a pending transaction sent by the given sender with the given nonce.
616    pub(crate) fn get_pending_transaction_by_sender_and_nonce(
617        &self,
618        sender: SenderId,
619        nonce: u64,
620    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
621        self.all_transactions
622            .txs_iter(sender)
623            .find(|(id, tx)| id.nonce == nonce && tx.subpool.is_pending())
624            .map(|(_, tx)| Arc::clone(&tx.transaction))
625    }
626
627    /// Updates only the pending fees without triggering subpool updates.
628    /// Returns the previous base fee and blob fee values.
629    const fn update_pending_fees_only(
630        &mut self,
631        mut new_base_fee: u64,
632        new_blob_fee: Option<u128>,
633    ) -> (u64, u128) {
634        std::mem::swap(&mut self.all_transactions.pending_fees.base_fee, &mut new_base_fee);
635
636        let prev_blob_fee = if let Some(mut blob_fee) = new_blob_fee {
637            std::mem::swap(&mut self.all_transactions.pending_fees.blob_fee, &mut blob_fee);
638            blob_fee
639        } else {
640            self.all_transactions.pending_fees.blob_fee
641        };
642
643        (new_base_fee, prev_blob_fee)
644    }
645
646    /// Applies fee-based promotion updates based on the previous fees.
647    ///
648    /// Records promoted transactions based on fee swings.
649    ///
650    /// Caution: This expects that the fees were previously already updated via
651    /// [`Self::update_pending_fees_only`].
652    fn apply_fee_updates(
653        &mut self,
654        prev_base_fee: u64,
655        prev_blob_fee: u128,
656        outcome: &mut UpdateOutcome<T::Transaction>,
657    ) {
658        let new_base_fee = self.all_transactions.pending_fees.base_fee;
659        let new_blob_fee = self.all_transactions.pending_fees.blob_fee;
660
661        if new_base_fee == prev_base_fee && new_blob_fee == prev_blob_fee {
662            // nothing to update
663            return;
664        }
665
666        // IMPORTANT:
667        // Restore previous fees so that the update fee functions correctly handle fee swings
668        self.all_transactions.pending_fees.base_fee = prev_base_fee;
669        self.all_transactions.pending_fees.blob_fee = prev_blob_fee;
670
671        let base_fee_ordering = self.update_basefee(new_base_fee, |tx| {
672            outcome.promoted.push(tx.clone());
673        });
674
675        self.update_blob_fee(new_blob_fee, base_fee_ordering, |tx| {
676            outcome.promoted.push(tx.clone());
677        });
678    }
679
680    /// Updates the transactions for the changed senders.
681    pub(crate) fn update_accounts(
682        &mut self,
683        changed_senders: FxHashMap<SenderId, SenderInfo>,
684    ) -> UpdateOutcome<T::Transaction> {
685        // Apply the state changes to the total set of transactions which triggers sub-pool updates.
686        let mut updates = self.all_transactions.update(&changed_senders);
687
688        // Process the sub-pool updates
689        let mut outcome = UpdateOutcome::default();
690        if self.config.enforce_tracked_nonce {
691            // Track the changed accounts only after the discards: a discard that removes the
692            // sender's last transaction also removes its info, and the tracked nonce must survive
693            // for `add_transaction` to reject a stale validation result.
694            #[expect(clippy::iter_with_drain, reason = "retain the scratch buffer allocation")]
695            self.process_updates(updates.drain(..), &mut outcome);
696            self.all_transactions.sender_info.extend(changed_senders);
697        } else {
698            self.all_transactions.sender_info.extend(changed_senders);
699            #[expect(clippy::iter_with_drain, reason = "retain the scratch buffer allocation")]
700            self.process_updates(updates.drain(..), &mut outcome);
701        }
702        self.all_transactions.update_buffer = updates;
703        // update the metrics after the update
704        self.update_size_metrics();
705        outcome
706    }
707
708    /// Updates the entire pool after a new block was mined.
709    ///
710    /// This removes all mined transactions, updates according to the new base fee and blob fee and
711    /// rechecks sender allowance based on the given changed sender infos.
712    pub(crate) fn on_canonical_state_change(
713        &mut self,
714        block_info: BlockInfo,
715        mined_transactions: Vec<TxHash>,
716        changed_senders: FxHashMap<SenderId, SenderInfo>,
717        _update_kind: PoolUpdateKind,
718    ) -> OnNewCanonicalStateOutcome<T::Transaction> {
719        // update block info
720        let block_hash = block_info.last_seen_block_hash;
721
722        // Remove all transaction that were included in the block
723        let mut removed_txs_count = 0;
724        for tx_hash in &mined_transactions {
725            if self.prune_transaction_by_hash(tx_hash).is_some() {
726                removed_txs_count += 1;
727            }
728        }
729
730        // Update removed transactions metric
731        self.metrics.removed_transactions.increment(removed_txs_count);
732
733        // Update fees internally first without triggering subpool updates based on fee movements
734        // This must happen before we update the changed so that all account updates use the new fee
735        // values, this way all changed accounts remain unaffected by the fee updates that are
736        // performed in next step and we don't collect promotions twice
737        let (prev_base_fee, prev_blob_fee) =
738            self.update_pending_fees_only(block_info.pending_basefee, block_info.pending_blob_fee);
739
740        // Now update accounts with the new fees already set
741        let mut outcome = self.update_accounts(changed_senders);
742
743        // Apply subpool updates based on fee changes
744        // This will record any additional promotions based on fee movements
745        self.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
746
747        // Update the rest of block info (without triggering fee updates again)
748        self.all_transactions.set_block_info(block_info);
749
750        self.update_transaction_type_metrics();
751        self.metrics.performed_state_updates.increment(1);
752
753        OnNewCanonicalStateOutcome {
754            block_hash,
755            mined: mined_transactions,
756            promoted: outcome.promoted,
757            discarded: outcome.discarded,
758        }
759    }
760
761    /// Update sub-pools size metrics.
762    pub(crate) fn update_size_metrics(&self) {
763        self.all_transactions.update_size_metrics();
764        let stats = self.size();
765        self.metrics.pending_pool_transactions.set(stats.pending as f64);
766        self.metrics.pending_pool_size_bytes.set(stats.pending_size as f64);
767        self.metrics.basefee_pool_transactions.set(stats.basefee as f64);
768        self.metrics.basefee_pool_size_bytes.set(stats.basefee_size as f64);
769        self.metrics.queued_pool_transactions.set(stats.queued as f64);
770        self.metrics.queued_pool_size_bytes.set(stats.queued_size as f64);
771        self.metrics.blob_pool_transactions.set(stats.blob as f64);
772        self.metrics.blob_pool_size_bytes.set(stats.blob_size as f64);
773        self.metrics.total_transactions.set(stats.total as f64);
774    }
775
776    /// Updates transaction type metrics for the entire pool.
777    pub(crate) fn update_transaction_type_metrics(&self) {
778        let counts = &self.all_transactions.tx_type_counts;
779        self.metrics.total_legacy_transactions.set(counts.legacy as f64);
780        self.metrics.total_eip2930_transactions.set(counts.eip2930 as f64);
781        self.metrics.total_eip1559_transactions.set(counts.eip1559 as f64);
782        self.metrics.total_eip4844_transactions.set(counts.eip4844 as f64);
783        self.metrics.total_eip7702_transactions.set(counts.eip7702 as f64);
784        self.metrics.total_other_transactions.set(counts.other as f64);
785    }
786
787    pub(crate) fn add_transaction(
788        &mut self,
789        tx: ValidPoolTransaction<T::Transaction>,
790        mut on_chain_balance: U256,
791        mut on_chain_nonce: u64,
792        on_chain_code_hash: Option<B256>,
793    ) -> PoolResult<AddedTransaction<T::Transaction>> {
794        if self.contains(tx.hash()) {
795            return Err(PoolError::new(*tx.hash(), PoolErrorKind::AlreadyImported))
796        }
797
798        // Validation reads state outside the pool lock, so its snapshot can predate the last
799        // canonical update. Opt-in because it assumes nonces only move forward, which a reorg
800        // breaks. Transactions exempt from the nonce check keep the validator's verdict.
801        if self.config.enforce_tracked_nonce &&
802            tx.transaction.requires_nonce_check() &&
803            let Some(info) = self.all_transactions.sender_info.get(&tx.sender_id()) &&
804            info.state_nonce > on_chain_nonce
805        {
806            // Below the tracked nonce the transaction was validated against outdated state;
807            // inserted as pending it would shadow the sender's executable transactions in the
808            // payload builder.
809            if tx.nonce() < info.state_nonce {
810                return Err(PoolError::new(
811                    *tx.hash(),
812                    InvalidPoolTransactionError::Consensus(
813                        InvalidTransactionError::NonceNotConsistent {
814                            tx: tx.nonce(),
815                            state: info.state_nonce,
816                        },
817                    ),
818                ))
819            }
820            on_chain_nonce = info.state_nonce;
821            on_chain_balance = info.balance;
822        }
823
824        self.validate_auth(&tx, on_chain_nonce, on_chain_code_hash)?;
825
826        // Update sender info with balance and nonce
827        self.all_transactions
828            .sender_info
829            .entry(tx.sender_id())
830            .or_default()
831            .update(on_chain_nonce, on_chain_balance);
832
833        match self.all_transactions.insert_tx(tx, on_chain_balance, on_chain_nonce) {
834            Ok(InsertOk { transaction, move_to, replaced_tx, mut updates, state }) => {
835                // Interleave update processing and new-tx insertion so that live
836                // `BestTransactions` iterators always receive transactions in nonce order.
837                // Updates are already in nonce-ascending order, so we split them around
838                // the new transaction's nonce:
839                //  1. Promote lower-nonce txs first  (e.g. balance-unlock scenario)
840                //  2. Add the new transaction
841                //  3. Promote higher-nonce txs last   (e.g. gap-fill scenario)
842                let new_nonce = transaction.id().nonce;
843                let split = updates.partition_point(|u| u.id.nonce < new_nonce);
844                let mut outcome = UpdateOutcome::default();
845                #[expect(clippy::iter_with_drain, reason = "retain the scratch buffer allocation")]
846                let mut drain = updates.drain(..);
847                self.process_updates(drain.by_ref().take(split), &mut outcome);
848                self.add_new_transaction(transaction.clone(), replaced_tx.clone(), move_to);
849                self.process_updates(drain, &mut outcome);
850                self.all_transactions.update_buffer = updates;
851                let UpdateOutcome { promoted, discarded } = outcome;
852                self.metrics.inserted_transactions.increment(1);
853
854                let replaced = replaced_tx.map(|(tx, _)| tx);
855
856                // This transaction was moved to the pending pool.
857                let res = if move_to.is_pending() {
858                    AddedTransaction::Pending(AddedPendingTransaction {
859                        transaction,
860                        promoted,
861                        discarded,
862                        replaced,
863                    })
864                } else {
865                    // Determine the specific queued reason based on the transaction state
866                    let queued_reason = state.determine_queued_reason(move_to);
867                    AddedTransaction::Parked {
868                        transaction,
869                        subpool: move_to,
870                        replaced,
871                        queued_reason,
872                        promoted,
873                    }
874                };
875
876                Ok(res)
877            }
878            Err(err) => {
879                // Update invalid transactions metric
880                self.metrics.invalid_transactions.increment(1);
881                match err {
882                    InsertErr::Underpriced { existing: _, transaction } => Err(PoolError::new(
883                        *transaction.hash(),
884                        PoolErrorKind::ReplacementUnderpriced,
885                    )),
886                    InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap } => {
887                        Err(PoolError::new(
888                            *transaction.hash(),
889                            PoolErrorKind::FeeCapBelowMinimumProtocolFeeCap(fee_cap),
890                        ))
891                    }
892                    InsertErr::ExceededSenderTransactionsCapacity { transaction } => {
893                        Err(PoolError::new(
894                            *transaction.hash(),
895                            PoolErrorKind::SpammerExceededCapacity(transaction.sender()),
896                        ))
897                    }
898                    InsertErr::TxGasLimitMoreThanAvailableBlockGas {
899                        transaction,
900                        block_gas_limit,
901                        tx_gas_limit,
902                    } => Err(PoolError::new(
903                        *transaction.hash(),
904                        PoolErrorKind::InvalidTransaction(
905                            InvalidPoolTransactionError::ExceedsGasLimit(
906                                tx_gas_limit,
907                                block_gas_limit,
908                            ),
909                        ),
910                    )),
911                    InsertErr::BlobTxHasNonceGap { transaction } => Err(PoolError::new(
912                        *transaction.hash(),
913                        PoolErrorKind::InvalidTransaction(
914                            Eip4844PoolTransactionError::Eip4844NonceGap.into(),
915                        ),
916                    )),
917                    InsertErr::Overdraft { transaction } => Err(PoolError::new(
918                        *transaction.hash(),
919                        PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Overdraft {
920                            cost: *transaction.cost(),
921                            balance: on_chain_balance,
922                        }),
923                    )),
924                    InsertErr::TxTypeConflict { transaction } => Err(PoolError::new(
925                        *transaction.hash(),
926                        PoolErrorKind::ExistingConflictingTransactionType(
927                            transaction.sender(),
928                            transaction.tx_type(),
929                        ),
930                    )),
931                }
932            }
933        }
934    }
935
936    /// Determines if the tx sender is delegated or has a  pending delegation, and if so, ensures
937    /// they have at most one configured amount of in-flight **executable** transactions (default at
938    /// most one), e.g. disallow stacked and nonce-gapped transactions from the account.
939    fn check_delegation_limit(
940        &self,
941        transaction: &ValidPoolTransaction<T::Transaction>,
942        on_chain_nonce: u64,
943        on_chain_code_hash: Option<B256>,
944    ) -> Result<(), PoolError> {
945        // Short circuit if the sender has neither delegation nor pending delegation.
946        if (on_chain_code_hash.is_none() || on_chain_code_hash == Some(KECCAK_EMPTY)) &&
947            !self.all_transactions.auths.contains_key(&transaction.sender_id())
948        {
949            return Ok(())
950        }
951
952        let mut txs_by_sender =
953            self.pending_pool.iter_txs_by_sender(transaction.sender_id()).peekable();
954
955        if txs_by_sender.peek().is_none() {
956            // Transaction with gapped nonce is not supported for delegated accounts
957            // but transaction can arrive out of order if more slots are allowed
958            // by default with a slot limit of 1 this will fail if the transaction's nonce >
959            // on_chain
960            let nonce_gap_distance = transaction.nonce().saturating_sub(on_chain_nonce);
961            if nonce_gap_distance >= self.config.max_inflight_delegated_slot_limit as u64 {
962                return Err(PoolError::new(
963                    *transaction.hash(),
964                    PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702(
965                        Eip7702PoolTransactionError::OutOfOrderTxFromDelegated,
966                    )),
967                ))
968            }
969            return Ok(())
970        }
971
972        let mut count = 0;
973        for id in txs_by_sender {
974            if id == &transaction.transaction_id {
975                // Transaction replacement is supported
976                return Ok(())
977            }
978            count += 1;
979        }
980
981        if count < self.config.max_inflight_delegated_slot_limit {
982            // account still has an available slot
983            return Ok(())
984        }
985
986        Err(PoolError::new(
987            *transaction.hash(),
988            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702(
989                Eip7702PoolTransactionError::InflightTxLimitReached,
990            )),
991        ))
992    }
993
994    /// This verifies that the transaction complies with code authorization
995    /// restrictions brought by EIP-7702 transaction type:
996    /// 1. Any account with a deployed delegation or an in-flight authorization to deploy a
997    ///    delegation will only be allowed a certain amount of transaction slots (default 1) instead
998    ///    of the standard limit. This is due to the possibility of the account being sweeped by an
999    ///    unrelated account.
1000    /// 2. In case the pool is tracking a pending / queued transaction from a specific account, at
1001    ///    most the configured inflight delegation slot limit of in-flight transactions is allowed;
1002    ///    any additional delegated transactions from that account will be rejected.
1003    fn validate_auth(
1004        &self,
1005        transaction: &ValidPoolTransaction<T::Transaction>,
1006        on_chain_nonce: u64,
1007        on_chain_code_hash: Option<B256>,
1008    ) -> Result<(), PoolError> {
1009        // Ensure in-flight limit for delegated accounts or those with a pending authorization.
1010        self.check_delegation_limit(transaction, on_chain_nonce, on_chain_code_hash)?;
1011
1012        if let Some(authority_list) = &transaction.authority_ids {
1013            for sender_id in authority_list {
1014                // Ensure authority does not exceed the configured inflight delegation slot limit.
1015                if self
1016                    .all_transactions
1017                    .txs_iter(*sender_id)
1018                    .nth(self.config.max_inflight_delegated_slot_limit)
1019                    .is_some()
1020                {
1021                    return Err(PoolError::new(
1022                        *transaction.hash(),
1023                        PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702(
1024                            Eip7702PoolTransactionError::AuthorityReserved,
1025                        )),
1026                    ))
1027                }
1028            }
1029        }
1030
1031        Ok(())
1032    }
1033
1034    /// Maintenance task to apply a series of updates.
1035    ///
1036    /// This will move/discard the given transaction according to the `PoolUpdate`
1037    fn process_updates(
1038        &mut self,
1039        updates: impl IntoIterator<Item = PoolUpdate>,
1040        outcome: &mut UpdateOutcome<T::Transaction>,
1041    ) {
1042        let mut removed = 0;
1043        for PoolUpdate { id, current, destination } in updates {
1044            match destination {
1045                Destination::Discard => {
1046                    // remove the transaction from the pool and subpool
1047                    if let Some(tx) = self.prune_transaction_by_id(&id) {
1048                        outcome.discarded.push(tx);
1049                    }
1050                    removed += 1;
1051                }
1052                Destination::Pool(move_to) => {
1053                    debug_assert_ne!(&move_to, &current, "destination must be different");
1054                    let moved = self.move_transaction(current, move_to, &id);
1055                    if move_to.is_pending() &&
1056                        let Some(tx) = moved
1057                    {
1058                        trace!(target: "txpool", hash=%tx.transaction.hash(), "Promoted transaction to pending");
1059                        outcome.promoted.push(tx);
1060                    }
1061                }
1062            }
1063        }
1064
1065        if removed > 0 {
1066            self.metrics.removed_transactions.increment(removed);
1067        }
1068    }
1069
1070    /// Moves a transaction from one sub pool to another.
1071    ///
1072    /// This will remove the given transaction from one sub-pool and insert it into the other
1073    /// sub-pool.
1074    fn move_transaction(
1075        &mut self,
1076        from: SubPool,
1077        to: SubPool,
1078        id: &TransactionId,
1079    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1080        let tx = self.remove_from_subpool(from, id)?;
1081        self.add_transaction_to_subpool(to, tx.clone());
1082        Some(tx)
1083    }
1084
1085    /// Removes and returns all matching transactions from the pool.
1086    ///
1087    /// Note: this does not advance any descendants of the removed transactions and does not apply
1088    /// any additional updates.
1089    pub(crate) fn remove_transactions(
1090        &mut self,
1091        hashes: Vec<TxHash>,
1092    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
1093        let txs =
1094            hashes.into_iter().filter_map(|hash| self.remove_transaction_by_hash(&hash)).collect();
1095        self.update_size_metrics();
1096        txs
1097    }
1098
1099    /// Removes and returns all matching transactions and their descendants from the pool.
1100    pub(crate) fn remove_transactions_and_descendants(
1101        &mut self,
1102        hashes: Vec<TxHash>,
1103    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
1104        let mut removed = Vec::new();
1105        for hash in hashes {
1106            if let Some(tx) = self.remove_transaction_by_hash(&hash) {
1107                removed.push(tx.clone());
1108                self.remove_descendants(tx.id(), &mut removed);
1109            }
1110        }
1111        self.update_size_metrics();
1112        removed
1113    }
1114
1115    /// Removes all transactions from the given sender.
1116    pub(crate) fn remove_transactions_by_sender(
1117        &mut self,
1118        sender_id: SenderId,
1119    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
1120        let mut removed = Vec::new();
1121        let txs = self.get_transactions_by_sender(sender_id);
1122        for tx in txs {
1123            if let Some(tx) = self.remove_transaction(tx.id()) {
1124                removed.push(tx);
1125            }
1126        }
1127        self.update_size_metrics();
1128        removed
1129    }
1130
1131    /// Prunes and returns all matching transactions from the pool.
1132    ///
1133    /// This uses [`Self::prune_transaction_by_hash`] which does **not** park descendant
1134    /// transactions, so they remain in their current sub-pool and can be included in subsequent
1135    /// blocks.
1136    pub(crate) fn prune_transactions(
1137        &mut self,
1138        hashes: Vec<TxHash>,
1139    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
1140        let txs =
1141            hashes.into_iter().filter_map(|hash| self.prune_transaction_by_hash(&hash)).collect();
1142        self.update_size_metrics();
1143        txs
1144    }
1145
1146    /// Remove the transaction from the __entire__ pool.
1147    ///
1148    /// This includes the total set of transaction and the subpool it currently resides in.
1149    fn remove_transaction(
1150        &mut self,
1151        id: &TransactionId,
1152    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1153        let (tx, pool) = self.all_transactions.remove_transaction(id)?;
1154        self.remove_from_subpool(pool, tx.id())
1155    }
1156
1157    /// Remove the transaction from the entire pool via its hash. This includes the total set of
1158    /// transactions and the subpool it currently resides in.
1159    ///
1160    /// This treats the descendants as if this transaction is discarded and removing the transaction
1161    /// reduces a nonce gap.
1162    fn remove_transaction_by_hash(
1163        &mut self,
1164        tx_hash: &B256,
1165    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1166        let (tx, pool) = self.all_transactions.remove_transaction_by_hash(tx_hash)?;
1167
1168        // After a tx is removed, its descendants must become parked due to the nonce gap
1169        let mut updates = self.all_transactions.park_descendant_transactions(tx.id());
1170        #[expect(clippy::iter_with_drain, reason = "retain the scratch buffer allocation")]
1171        self.process_updates(updates.drain(..), &mut UpdateOutcome::default());
1172        self.all_transactions.update_buffer = updates;
1173        self.remove_from_subpool(pool, tx.id())
1174    }
1175
1176    /// This removes the transaction from the pool and advances any descendant state inside the
1177    /// subpool.
1178    ///
1179    /// This is intended to be used when a transaction is included in a block,
1180    /// [`Self::on_canonical_state_change`]. So its descendants will not change from pending to
1181    /// parked, just like what we do in `remove_transaction_by_hash`.
1182    fn prune_transaction_by_hash(
1183        &mut self,
1184        tx_hash: &B256,
1185    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1186        let (tx, pool) = self.all_transactions.remove_transaction_by_hash(tx_hash)?;
1187        self.remove_from_subpool(pool, tx.id())
1188    }
1189    /// This removes the transaction from the pool and advances any descendant state inside the
1190    /// subpool.
1191    ///
1192    /// This is intended to be used when we call [`Self::process_updates`].
1193    fn prune_transaction_by_id(
1194        &mut self,
1195        tx_id: &TransactionId,
1196    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1197        let (tx, pool) = self.all_transactions.remove_transaction_by_id(tx_id)?;
1198        self.remove_from_subpool(pool, tx.id())
1199    }
1200
1201    /// Removes the transaction from the given pool.
1202    ///
1203    /// Caution: this only removes the tx from the sub-pool and not from the pool itself
1204    fn remove_from_subpool(
1205        &mut self,
1206        pool: SubPool,
1207        tx: &TransactionId,
1208    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
1209        let tx = match pool {
1210            SubPool::Queued => self.queued_pool.remove_transaction(tx),
1211            SubPool::Pending => self.pending_pool.remove_transaction(tx),
1212            SubPool::BaseFee => self.basefee_pool.remove_transaction(tx),
1213            SubPool::Blob => self.blob_pool.remove_transaction(tx),
1214        };
1215
1216        if let Some(ref tx) = tx {
1217            // We trace here instead of in subpool structs directly, because the `ParkedPool` type
1218            // is generic and it would not be possible to distinguish whether a transaction is
1219            // being removed from the `BaseFee` pool, or the `Queued` pool.
1220            trace!(target: "txpool", hash=%tx.transaction.hash(), ?pool, "Removed transaction from a subpool");
1221        }
1222
1223        tx
1224    }
1225
1226    /// Removes _only_ the descendants of the given transaction from the __entire__ pool.
1227    ///
1228    /// All removed transactions are added to the `removed` vec.
1229    fn remove_descendants(
1230        &mut self,
1231        tx: &TransactionId,
1232        removed: &mut Vec<Arc<ValidPoolTransaction<T::Transaction>>>,
1233    ) {
1234        let mut id = *tx;
1235
1236        // this will essentially pop _all_ descendant transactions one by one
1237        loop {
1238            let descendant =
1239                self.all_transactions.descendant_txs_exclusive(&id).map(|(id, _)| *id).next();
1240            if let Some(descendant) = descendant {
1241                if let Some(tx) = self.remove_transaction(&descendant) {
1242                    removed.push(tx)
1243                }
1244                id = descendant;
1245            } else {
1246                return
1247            }
1248        }
1249    }
1250
1251    /// Inserts the transaction into the given sub-pool.
1252    fn add_transaction_to_subpool(
1253        &mut self,
1254        pool: SubPool,
1255        tx: Arc<ValidPoolTransaction<T::Transaction>>,
1256    ) {
1257        // We trace here instead of in structs directly, because the `ParkedPool` type is
1258        // generic and it would not be possible to distinguish whether a transaction is being
1259        // added to the `BaseFee` pool, or the `Queued` pool.
1260        trace!(target: "txpool", hash=%tx.transaction.hash(), ?pool, "Adding transaction to a subpool");
1261        match pool {
1262            SubPool::Queued => self.queued_pool.add_transaction(tx),
1263            SubPool::Pending => {
1264                self.pending_pool.add_transaction(tx, self.all_transactions.pending_fees.base_fee);
1265            }
1266            SubPool::BaseFee => {
1267                self.basefee_pool.add_transaction(tx);
1268            }
1269            SubPool::Blob => {
1270                self.blob_pool.add_transaction(tx);
1271            }
1272        }
1273    }
1274
1275    /// Inserts the transaction into the given sub-pool.
1276    /// Optionally, removes the replacement transaction.
1277    fn add_new_transaction(
1278        &mut self,
1279        transaction: Arc<ValidPoolTransaction<T::Transaction>>,
1280        replaced: Option<(Arc<ValidPoolTransaction<T::Transaction>>, SubPool)>,
1281        pool: SubPool,
1282    ) {
1283        if let Some((replaced, replaced_pool)) = replaced {
1284            // Remove the replaced transaction
1285            self.remove_from_subpool(replaced_pool, replaced.id());
1286        }
1287
1288        self.add_transaction_to_subpool(pool, transaction)
1289    }
1290
1291    /// Ensures that the transactions in the sub-pools are within the given bounds.
1292    ///
1293    /// If the current size exceeds the given bounds, the worst transactions are evicted from the
1294    /// pool and returned.
1295    ///
1296    /// This returns all transactions that were removed from the entire pool.
1297    pub(crate) fn discard_worst(&mut self) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
1298        let mut removed = Vec::new();
1299
1300        // Helper macro that discards the worst transactions for the pools
1301        macro_rules! discard_worst {
1302            ($this:ident, $removed:ident, [$($limit:ident => ($pool:ident, $metric:ident)),* $(,)*]) => {
1303                $ (
1304                while $this.$pool.exceeds(&$this.config.$limit)
1305                    {
1306                        trace!(
1307                            target: "txpool",
1308                            "discarding transactions from {}, limit: {:?}, curr size: {}, curr len: {}",
1309                            stringify!($pool),
1310                            $this.config.$limit,
1311                            $this.$pool.size(),
1312                            $this.$pool.len(),
1313                        );
1314
1315                        // 1. first remove the worst transaction from the subpool
1316                        let removed_from_subpool = $this.$pool.truncate_pool($this.config.$limit.clone());
1317
1318                        trace!(
1319                            target: "txpool",
1320                            "removed {} transactions from {}, limit: {:?}, curr size: {}, curr len: {}",
1321                            removed_from_subpool.len(),
1322                            stringify!($pool),
1323                            $this.config.$limit,
1324                            $this.$pool.size(),
1325                            $this.$pool.len()
1326                        );
1327                        $this.metrics.$metric.increment(removed_from_subpool.len() as u64);
1328
1329                        // 2. remove all transactions from the total set
1330                        for tx in removed_from_subpool {
1331                            $this.all_transactions.remove_transaction(tx.id());
1332
1333                            let id = *tx.id();
1334
1335                            // keep track of removed transaction
1336                            removed.push(tx);
1337
1338                            // 3. remove all its descendants from the entire pool
1339                            $this.remove_descendants(&id, &mut $removed);
1340                        }
1341                    }
1342
1343                )*
1344            };
1345        }
1346
1347        discard_worst!(
1348            self, removed, [
1349                pending_limit => (pending_pool, pending_transactions_evicted),
1350                basefee_limit => (basefee_pool, basefee_transactions_evicted),
1351                blob_limit    => (blob_pool, blob_transactions_evicted),
1352                queued_limit  => (queued_pool, queued_transactions_evicted),
1353            ]
1354        );
1355
1356        removed
1357    }
1358
1359    /// Number of transactions in the entire pool
1360    pub(crate) fn len(&self) -> usize {
1361        self.all_transactions.len()
1362    }
1363
1364    /// Whether the pool is empty
1365    pub(crate) fn is_empty(&self) -> bool {
1366        self.all_transactions.is_empty()
1367    }
1368
1369    /// Asserts all invariants of the  pool's:
1370    ///
1371    ///  - All maps are bijections (`by_id`, `by_hash`)
1372    ///  - Total size is equal to the sum of all sub-pools
1373    ///
1374    /// # Panics
1375    /// if any invariant is violated
1376    #[cfg(any(test, feature = "test-utils"))]
1377    pub fn assert_invariants(&self) {
1378        let size = self.size();
1379        let actual = size.basefee + size.pending + size.queued + size.blob;
1380        assert_eq!(
1381            size.total, actual,
1382            "total size must be equal to the sum of all sub-pools, basefee:{}, pending:{}, queued:{}, blob:{}",
1383            size.basefee, size.pending, size.queued, size.blob
1384        );
1385        self.all_transactions.assert_invariants();
1386        self.pending_pool.assert_invariants();
1387        self.basefee_pool.assert_invariants();
1388        self.queued_pool.assert_invariants();
1389        self.blob_pool.assert_invariants();
1390    }
1391}
1392
1393#[cfg(any(test, feature = "test-utils"))]
1394impl TxPool<crate::test_utils::MockOrdering> {
1395    /// Creates a mock instance for testing.
1396    pub fn mock() -> Self {
1397        Self::new(crate::test_utils::MockOrdering::default(), PoolConfig::default())
1398    }
1399}
1400
1401#[cfg(test)]
1402impl<T: TransactionOrdering> Drop for TxPool<T> {
1403    fn drop(&mut self) {
1404        self.assert_invariants();
1405    }
1406}
1407
1408impl<T: TransactionOrdering> TxPool<T> {
1409    /// Pending subpool
1410    pub const fn pending(&self) -> &PendingPool<T> {
1411        &self.pending_pool
1412    }
1413
1414    /// Base fee subpool
1415    pub const fn base_fee(&self) -> &ParkedPool<BasefeeOrd<T::Transaction>> {
1416        &self.basefee_pool
1417    }
1418
1419    /// Queued sub pool
1420    pub const fn queued(&self) -> &ParkedPool<QueuedOrd<T::Transaction>> {
1421        &self.queued_pool
1422    }
1423}
1424
1425impl<T: TransactionOrdering> fmt::Debug for TxPool<T> {
1426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1427        f.debug_struct("TxPool").field("config", &self.config).finish_non_exhaustive()
1428    }
1429}
1430
1431/// Minimum number of live senders before the changed-sender ratio can trigger a full update.
1432///
1433/// Account-update benchmarks put the paths near parity at 500 senders and favor a full traversal
1434/// from 1,000 senders at the ratio below.
1435const FULL_UPDATE_MIN_SENDERS: usize = 1_000;
1436
1437/// Run a full update when at least one in this many live senders changed.
1438const FULL_UPDATE_SENDER_RATIO: usize = 4;
1439
1440/// Container for _all_ transaction in the pool.
1441///
1442/// This is the sole entrypoint that's guarding all sub-pools, all sub-pool actions are always
1443/// derived from this set. Updates returned from this type must be applied to the sub-pools.
1444pub(crate) struct AllTransactions<T: PoolTransaction> {
1445    /// Minimum base fee required by the protocol.
1446    ///
1447    /// Transactions with a lower base fee will never be included by the chain
1448    minimal_protocol_basefee: u64,
1449    /// The max gas limit of the block
1450    block_gas_limit: u64,
1451    /// Max number of executable transaction slots guaranteed per account
1452    max_account_slots: usize,
1453    /// _All_ transactions identified by their hash.
1454    by_hash: B256Map<Arc<ValidPoolTransaction<T>>>,
1455    /// _All_ transaction in the pool sorted by their sender and nonce pair.
1456    txs: BTreeMap<TransactionId, PoolInternalTransaction<T>>,
1457    /// Contains the currently known information about the senders.
1458    ///
1459    /// With `PoolConfig::enforce_tracked_nonce` entries outlive a sender's transactions when an
1460    /// account update discards them, so a validation result computed against older state cannot
1461    /// regress the tracked nonce.
1462    sender_info: FxHashMap<SenderId, SenderInfo>,
1463    /// Tracks the number of transactions by sender that are currently in the pool.
1464    tx_counter: FxHashMap<SenderId, usize>,
1465    /// The current block number the pool keeps track of.
1466    last_seen_block_number: u64,
1467    /// The current block hash the pool keeps track of.
1468    last_seen_block_hash: B256,
1469    /// Expected blob and base fee for the pending block.
1470    pending_fees: PendingFees,
1471    /// Snapshot of [`Self::pending_fees`] recorded after the most recent all-transactions pass in
1472    /// [`Self::update`].
1473    ///
1474    /// The all-transactions pass calls [`Self::update_txs`] over every entry in [`Self::txs`].
1475    /// This field is initialized to the default fees because an empty pool trivially reflects
1476    /// them. It is not necessarily the immediately preceding fee value: partial sender updates
1477    /// and fee changes do not modify it.
1478    last_full_update_fees: PendingFees,
1479    /// Configured price bump settings for replacements
1480    price_bumps: PriceBumpConfig,
1481    /// How to handle [`TransactionOrigin::Local`](crate::TransactionOrigin) transactions.
1482    local_transactions_config: LocalTransactionConfig,
1483    /// All accounts with a pooled authorization
1484    auths: FxHashMap<SenderId, B256Set>,
1485    /// Number of transactions in the pool by transaction type, tracked incrementally so metrics
1486    /// updates don't require iterating the entire pool.
1487    tx_type_counts: TxTypeCounts,
1488    /// Scratch space for sub-pool changes. Update methods take this buffer and return it to
1489    /// `TxPool`, which drains the updates and restores the empty buffer for reuse.
1490    update_buffer: Vec<PoolUpdate>,
1491    /// All Transactions metrics
1492    metrics: AllTransactionsMetrics,
1493}
1494
1495impl<T: PoolTransaction> AllTransactions<T> {
1496    /// Create a new instance
1497    fn new(config: &PoolConfig) -> Self {
1498        Self {
1499            max_account_slots: config.max_account_slots,
1500            price_bumps: config.price_bumps,
1501            local_transactions_config: config.local_transactions_config.clone(),
1502            minimal_protocol_basefee: config.minimal_protocol_basefee,
1503            block_gas_limit: config.gas_limit,
1504            ..Default::default()
1505        }
1506    }
1507
1508    /// Returns an iterator over all _unique_ hashes in the pool
1509    #[expect(dead_code)]
1510    pub(crate) fn hashes_iter(&self) -> impl Iterator<Item = TxHash> + '_ {
1511        self.by_hash.keys().copied()
1512    }
1513
1514    /// Returns an iterator over all transactions in the pool
1515    pub(crate) fn transactions_iter(
1516        &self,
1517    ) -> impl Iterator<Item = &Arc<ValidPoolTransaction<T>>> + '_ {
1518        self.by_hash.values()
1519    }
1520
1521    /// Returns if the transaction for the given hash is already included in this pool
1522    pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
1523        self.by_hash.contains_key(tx_hash)
1524    }
1525
1526    /// Returns the internal transaction with additional metadata
1527    pub(crate) fn get(&self, id: &TransactionId) -> Option<&PoolInternalTransaction<T>> {
1528        self.txs.get(id)
1529    }
1530
1531    /// Increments the transaction counter for the sender
1532    pub(crate) fn tx_inc(&mut self, sender: SenderId) {
1533        let count = self.tx_counter.entry(sender).or_default();
1534        *count += 1;
1535        self.metrics.all_transactions_by_all_senders.increment(1.0);
1536    }
1537
1538    /// Decrements the transaction counter for the sender
1539    pub(crate) fn tx_decr(&mut self, sender: SenderId) {
1540        if let hash_map::Entry::Occupied(mut entry) = self.tx_counter.entry(sender) {
1541            let count = entry.get_mut();
1542            if *count == 1 {
1543                entry.remove();
1544                self.sender_info.remove(&sender);
1545                self.metrics.all_transactions_by_all_senders.decrement(1.0);
1546                return
1547            }
1548            *count -= 1;
1549            self.metrics.all_transactions_by_all_senders.decrement(1.0);
1550        }
1551    }
1552
1553    /// Updates the block specific info
1554    fn set_block_info(&mut self, block_info: BlockInfo) {
1555        let BlockInfo {
1556            block_gas_limit,
1557            last_seen_block_hash,
1558            last_seen_block_number,
1559            pending_basefee,
1560            pending_blob_fee,
1561        } = block_info;
1562        self.last_seen_block_number = last_seen_block_number;
1563        self.last_seen_block_hash = last_seen_block_hash;
1564
1565        self.pending_fees.base_fee = pending_basefee;
1566        self.metrics.base_fee.set(pending_basefee as f64);
1567
1568        self.block_gas_limit = block_gas_limit;
1569
1570        if let Some(pending_blob_fee) = pending_blob_fee {
1571            self.pending_fees.blob_fee = pending_blob_fee;
1572            self.metrics.blob_base_fee.set(pending_blob_fee as f64);
1573        }
1574    }
1575
1576    /// Updates the size metrics
1577    pub(crate) fn update_size_metrics(&self) {
1578        self.metrics.all_transactions_by_hash.set(self.by_hash.len() as f64);
1579        self.metrics.all_transactions_by_id.set(self.txs.len() as f64);
1580    }
1581
1582    /// Rechecks all transactions in the pool against the changes.
1583    ///
1584    /// Possible changes are:
1585    ///
1586    /// For all transactions:
1587    ///   - decreased basefee: promotes from `basefee` to `pending` sub-pool.
1588    ///   - increased basefee: demotes from `pending` to `basefee` sub-pool.
1589    ///
1590    /// Individually:
1591    ///   - decreased sender allowance: demote from (`basefee`|`pending`) to `queued`.
1592    ///   - increased sender allowance: promote from `queued` to
1593    ///       - `pending` if basefee condition is met.
1594    ///       - `basefee` if basefee condition is _not_ met.
1595    ///
1596    /// Additionally, this will also update the `cumulative_gas_used` for transactions of a sender
1597    /// that got transaction included in the block.
1598    pub(crate) fn update(
1599        &mut self,
1600        changed_accounts: &FxHashMap<SenderId, SenderInfo>,
1601    ) -> Vec<PoolUpdate> {
1602        let mut updates = std::mem::take(&mut self.update_buffer);
1603        let pending_fees = self.pending_fees;
1604
1605        let update_all = self.last_full_update_fees != self.pending_fees ||
1606            self.should_update_all_senders(changed_accounts.len());
1607
1608        if update_all {
1609            Self::update_txs(pending_fees, changed_accounts, &mut updates, self.txs.iter_mut());
1610            self.last_full_update_fees = self.pending_fees;
1611        } else {
1612            // Fee eligibility is unchanged, while nonce gaps, ancestors, and cumulative cost are
1613            // sender-local; only transactions from changed accounts can require updates.
1614            for sender in changed_accounts.keys() {
1615                let range = TransactionId::new(*sender, 0)..=TransactionId::new(*sender, u64::MAX);
1616                Self::update_txs(
1617                    pending_fees,
1618                    changed_accounts,
1619                    &mut updates,
1620                    self.txs.range_mut(range),
1621                );
1622            }
1623        }
1624
1625        updates
1626    }
1627
1628    /// Returns whether one full traversal is preferable to a range lookup per changed sender.
1629    fn should_update_all_senders(&self, changed_sender_count: usize) -> bool {
1630        let pool_sender_count = self.tx_counter.len();
1631        changed_sender_count >= pool_sender_count ||
1632            (pool_sender_count >= FULL_UPDATE_MIN_SENDERS &&
1633                changed_sender_count >= pool_sender_count.div_ceil(FULL_UPDATE_SENDER_RATIO))
1634    }
1635
1636    /// Updates the given transactions, which must be ordered by [`TransactionId`] and must start
1637    /// at the lowest tracked nonce of the first sender they contain.
1638    ///
1639    /// Records a [`PoolUpdate`] for every transaction whose sub-pool changed.
1640    fn update_txs<'a, I>(
1641        pending_fees: PendingFees,
1642        changed_accounts: &FxHashMap<SenderId, SenderInfo>,
1643        updates: &mut Vec<PoolUpdate>,
1644        txs: I,
1645    ) where
1646        T: 'a,
1647        I: Iterator<Item = (&'a TransactionId, &'a mut PoolInternalTransaction<T>)>,
1648    {
1649        let mut iter = txs.peekable();
1650
1651        // Loop over all individual senders and update all affected transactions.
1652        // One sender may have up to `max_account_slots` transactions here, which means, worst case
1653        // `max_accounts_slots` need to be updated, for example if the first transaction is blocked
1654        // due to too low base fee.
1655        // However, we don't have to necessarily check every transaction of a sender. If no updates
1656        // are possible (nonce gap) then we can skip to the next sender.
1657
1658        // The `unique_sender` loop will process the first transaction of all senders, update its
1659        // state and internally update all consecutive transactions
1660        'transactions: while let Some((id, tx)) = iter.next() {
1661            macro_rules! next_sender {
1662                ($iter:ident) => {
1663                    'this: while let Some((peek, _)) = iter.peek() {
1664                        if peek.sender != id.sender {
1665                            break 'this
1666                        }
1667                        iter.next();
1668                    }
1669                };
1670            }
1671
1672            // track the balance if the sender was changed in the block
1673            // check if this is a changed account
1674            let changed_balance = if let Some(info) = changed_accounts.get(&id.sender) {
1675                // discard all transactions with a nonce lower than the current state nonce
1676                if id.nonce < info.state_nonce {
1677                    updates.push(PoolUpdate {
1678                        id: *tx.transaction.id(),
1679                        current: tx.subpool,
1680                        destination: Destination::Discard,
1681                    });
1682                    continue 'transactions
1683                }
1684
1685                let ancestor = TransactionId::ancestor(id.nonce, info.state_nonce, id.sender);
1686                // If there's no ancestor then this is the next transaction.
1687                if ancestor.is_none() {
1688                    tx.state.insert(TxState::NO_NONCE_GAPS);
1689                    tx.state.insert(TxState::NO_PARKED_ANCESTORS);
1690                    tx.cumulative_cost = U256::ZERO;
1691                    if tx.transaction.cost() > &info.balance {
1692                        // sender lacks sufficient funds to pay for this transaction
1693                        tx.state.remove(TxState::ENOUGH_BALANCE);
1694                    } else {
1695                        tx.state.insert(TxState::ENOUGH_BALANCE);
1696                    }
1697                }
1698
1699                Some(&info.balance)
1700            } else {
1701                None
1702            };
1703
1704            // If there's a nonce gap, we can shortcircuit, because there's nothing to update yet.
1705            if tx.state.has_nonce_gap() {
1706                next_sender!(iter);
1707                continue 'transactions
1708            }
1709
1710            // Since this is the first transaction of the sender, it has no parked ancestors
1711            tx.state.insert(TxState::NO_PARKED_ANCESTORS);
1712
1713            // Update the first transaction of this sender.
1714            Self::update_tx_fees(pending_fees, tx);
1715            // Track if the transaction's sub-pool changed.
1716            Self::record_subpool_update(updates, tx);
1717
1718            // Track blocking transactions.
1719            let mut has_parked_ancestor = !tx.state.is_pending();
1720
1721            let mut cumulative_cost = tx.next_cumulative_cost();
1722
1723            // the next expected nonce after this transaction: nonce + 1
1724            let mut next_nonce_in_line = tx.transaction.nonce().saturating_add(1);
1725
1726            // Update all consecutive transaction of this sender
1727            while let Some((peek, tx)) = iter.peek_mut() {
1728                if peek.sender != id.sender {
1729                    // Found the next sender we need to check
1730                    continue 'transactions
1731                }
1732
1733                if tx.transaction.nonce() == next_nonce_in_line {
1734                    // no longer nonce gapped
1735                    tx.state.insert(TxState::NO_NONCE_GAPS);
1736                } else {
1737                    // can short circuit if there's still a nonce gap
1738                    next_sender!(iter);
1739                    continue 'transactions
1740                }
1741
1742                // update for next iteration of this sender's loop
1743                next_nonce_in_line = next_nonce_in_line.saturating_add(1);
1744
1745                // update cumulative cost
1746                tx.cumulative_cost = cumulative_cost;
1747                // Update for next transaction
1748                cumulative_cost = tx.next_cumulative_cost();
1749
1750                // If the account changed in the block, check the balance.
1751                if let Some(changed_balance) = changed_balance {
1752                    if &cumulative_cost > changed_balance {
1753                        // sender lacks sufficient funds to pay for this transaction
1754                        tx.state.remove(TxState::ENOUGH_BALANCE);
1755                    } else {
1756                        tx.state.insert(TxState::ENOUGH_BALANCE);
1757                    }
1758                }
1759
1760                // Update ancestor condition.
1761                if has_parked_ancestor {
1762                    tx.state.remove(TxState::NO_PARKED_ANCESTORS);
1763                } else {
1764                    tx.state.insert(TxState::NO_PARKED_ANCESTORS);
1765                }
1766
1767                // Update and record sub-pool changes.
1768                Self::update_tx_fees(pending_fees, tx);
1769                Self::record_subpool_update(updates, tx);
1770                has_parked_ancestor = !tx.state.is_pending();
1771
1772                // Advance iterator
1773                iter.next();
1774            }
1775        }
1776    }
1777
1778    /// This will update the transaction's `subpool` based on its state.
1779    ///
1780    /// If the sub-pool derived from the state differs from the current pool, it will record a
1781    /// `PoolUpdate` for this transaction to move it to the new sub-pool.
1782    fn record_subpool_update(updates: &mut Vec<PoolUpdate>, tx: &mut PoolInternalTransaction<T>) {
1783        let current_pool = tx.subpool;
1784        tx.subpool = tx.state.into();
1785        if current_pool != tx.subpool {
1786            updates.push(PoolUpdate {
1787                id: *tx.transaction.id(),
1788                current: current_pool,
1789                destination: tx.subpool.into(),
1790            })
1791        }
1792    }
1793
1794    /// Rechecks the transaction's dynamic fee conditions.
1795    fn update_tx_fees(pending_fees: PendingFees, tx: &mut PoolInternalTransaction<T>) {
1796        match tx.transaction.max_fee_per_gas().cmp(&(pending_fees.base_fee as u128)) {
1797            Ordering::Greater | Ordering::Equal => {
1798                tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK);
1799            }
1800            Ordering::Less => {
1801                tx.state.remove(TxState::ENOUGH_FEE_CAP_BLOCK);
1802            }
1803        }
1804
1805        match tx.transaction.max_fee_per_blob_gas() {
1806            Some(blob_fee_cap) if blob_fee_cap < pending_fees.blob_fee => {
1807                tx.state.remove(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
1808            }
1809            _ => {
1810                tx.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK);
1811            }
1812        }
1813    }
1814
1815    /// Returns an iterator over all transactions for the given sender, starting with the lowest
1816    /// nonce
1817    pub(crate) fn txs_iter(
1818        &self,
1819        sender: SenderId,
1820    ) -> impl Iterator<Item = (&TransactionId, &PoolInternalTransaction<T>)> + '_ {
1821        self.txs
1822            .range((sender.start_bound(), Unbounded))
1823            .take_while(move |(other, _)| sender == other.sender)
1824    }
1825
1826    /// Returns a mutable iterator over all transactions for the given sender, starting with the
1827    /// lowest nonce
1828    #[cfg(test)]
1829    #[expect(dead_code)]
1830    pub(crate) fn txs_iter_mut(
1831        &mut self,
1832        sender: SenderId,
1833    ) -> impl Iterator<Item = (&TransactionId, &mut PoolInternalTransaction<T>)> + '_ {
1834        self.txs
1835            .range_mut((sender.start_bound(), Unbounded))
1836            .take_while(move |(other, _)| sender == other.sender)
1837    }
1838
1839    /// Returns all transactions that _follow_ after the given id and have the same sender.
1840    ///
1841    /// NOTE: The range is _exclusive_
1842    pub(crate) fn descendant_txs_exclusive<'a, 'b: 'a>(
1843        &'a self,
1844        id: &'b TransactionId,
1845    ) -> impl Iterator<Item = (&'a TransactionId, &'a PoolInternalTransaction<T>)> + 'a {
1846        self.txs.range((Excluded(id), Unbounded)).take_while(|(other, _)| id.sender == other.sender)
1847    }
1848
1849    /// Returns all transactions that _follow_ after the given id but have the same sender.
1850    ///
1851    /// NOTE: The range is _inclusive_: if the transaction that belongs to `id` it will be the
1852    /// first value.
1853    pub(crate) fn descendant_txs_inclusive<'a, 'b: 'a>(
1854        &'a self,
1855        id: &'b TransactionId,
1856    ) -> impl Iterator<Item = (&'a TransactionId, &'a PoolInternalTransaction<T>)> + 'a {
1857        self.txs.range(id..).take_while(|(other, _)| id.sender == other.sender)
1858    }
1859
1860    /// Returns all mutable transactions that _follow_ after the given id but have the same sender.
1861    ///
1862    /// NOTE: The range is _inclusive_: if the transaction that belongs to `id` it field be the
1863    /// first value.
1864    pub(crate) fn descendant_txs_mut<'a, 'b: 'a>(
1865        &'a mut self,
1866        id: &'b TransactionId,
1867    ) -> impl Iterator<Item = (&'a TransactionId, &'a mut PoolInternalTransaction<T>)> + 'a {
1868        self.txs.range_mut(id..).take_while(|(other, _)| id.sender == other.sender)
1869    }
1870
1871    /// Removes a transaction from the set using its hash.
1872    pub(crate) fn remove_transaction_by_hash(
1873        &mut self,
1874        tx_hash: &B256,
1875    ) -> Option<(Arc<ValidPoolTransaction<T>>, SubPool)> {
1876        let tx = self.by_hash.remove(tx_hash)?;
1877        let internal = self.txs.remove(&tx.transaction_id)?;
1878        self.remove_auths(&internal);
1879        self.tx_type_counts.dec(internal.transaction.transaction.ty());
1880        // decrement the counter for the sender.
1881        self.tx_decr(tx.sender_id());
1882        Some((tx, internal.subpool))
1883    }
1884
1885    /// Removes a transaction from the set using its id.
1886    ///
1887    /// This is intended for processing updates after state changes.
1888    pub(crate) fn remove_transaction_by_id(
1889        &mut self,
1890        tx_id: &TransactionId,
1891    ) -> Option<(Arc<ValidPoolTransaction<T>>, SubPool)> {
1892        let internal = self.txs.remove(tx_id)?;
1893        let tx = self.by_hash.remove(internal.transaction.hash())?;
1894        self.remove_auths(&internal);
1895        self.tx_type_counts.dec(internal.transaction.transaction.ty());
1896        // decrement the counter for the sender.
1897        self.tx_decr(tx.sender_id());
1898        Some((tx, internal.subpool))
1899    }
1900
1901    /// If a tx is removed (_not_ mined), all descendants are set to parked due to the nonce gap
1902    pub(crate) fn park_descendant_transactions(
1903        &mut self,
1904        tx_id: &TransactionId,
1905    ) -> Vec<PoolUpdate> {
1906        let mut updates = std::mem::take(&mut self.update_buffer);
1907
1908        for (id, tx) in self.descendant_txs_mut(tx_id) {
1909            let current_pool = tx.subpool;
1910
1911            tx.state.remove(TxState::NO_NONCE_GAPS);
1912
1913            // update the pool based on the state.
1914            tx.subpool = tx.state.into();
1915
1916            // check if anything changed.
1917            if current_pool != tx.subpool {
1918                updates.push(PoolUpdate {
1919                    id: *id,
1920                    current: current_pool,
1921                    destination: tx.subpool.into(),
1922                })
1923            }
1924        }
1925
1926        updates
1927    }
1928
1929    /// Removes a transaction from the set.
1930    ///
1931    /// This will _not_ trigger additional updates, because descendants without nonce gaps are
1932    /// already in the pending pool, and this transaction will be the first transaction of the
1933    /// sender in this pool.
1934    pub(crate) fn remove_transaction(
1935        &mut self,
1936        id: &TransactionId,
1937    ) -> Option<(Arc<ValidPoolTransaction<T>>, SubPool)> {
1938        let internal = self.txs.remove(id)?;
1939
1940        // decrement the counter for the sender.
1941        self.tx_decr(internal.transaction.sender_id());
1942        self.tx_type_counts.dec(internal.transaction.transaction.ty());
1943
1944        let result =
1945            self.by_hash.remove(internal.transaction.hash()).map(|tx| (tx, internal.subpool));
1946
1947        self.remove_auths(&internal);
1948
1949        result
1950    }
1951
1952    /// Removes any pending auths for the given transaction.
1953    ///
1954    /// This is a noop for non EIP-7702 transactions.
1955    fn remove_auths(&mut self, tx: &PoolInternalTransaction<T>) {
1956        let Some(auths) = &tx.transaction.authority_ids else { return };
1957
1958        let tx_hash = tx.transaction.hash();
1959        for auth in auths {
1960            if let Some(list) = self.auths.get_mut(auth) {
1961                list.remove(tx_hash);
1962                if list.is_empty() {
1963                    self.auths.remove(auth);
1964                }
1965            }
1966        }
1967    }
1968
1969    /// Checks if the given transaction's type conflicts with an existing transaction.
1970    ///
1971    /// See also [`ValidPoolTransaction::tx_type_conflicts_with`].
1972    ///
1973    /// Caution: This assumes that mutually exclusive invariant is always true for the same sender.
1974    #[inline]
1975    fn contains_conflicting_transaction(&self, tx: &ValidPoolTransaction<T>) -> bool {
1976        self.txs_iter(tx.transaction_id.sender)
1977            .next()
1978            .is_some_and(|(_, existing)| tx.tx_type_conflicts_with(&existing.transaction))
1979    }
1980
1981    /// Additional checks for a new transaction.
1982    ///
1983    /// This will enforce all additional rules in the context of this pool, such as:
1984    ///   - Spam protection: reject new non-local transaction from a sender that exhausted its slot
1985    ///     capacity.
1986    ///   - Gas limit: reject transactions if they exceed a block's maximum gas.
1987    ///   - Ensures transaction types are not conflicting for the sender: blob vs normal
1988    ///     transactions are mutually exclusive for the same sender.
1989    fn ensure_valid(
1990        &self,
1991        transaction: ValidPoolTransaction<T>,
1992        on_chain_nonce: u64,
1993    ) -> Result<ValidPoolTransaction<T>, InsertErr<T>> {
1994        if !self.local_transactions_config.is_local(transaction.origin, transaction.sender_ref()) {
1995            let current_txs =
1996                self.tx_counter.get(&transaction.sender_id()).copied().unwrap_or_default();
1997
1998            // Reject transactions if sender's capacity is exceeded.
1999            // If transaction's nonce matches on-chain nonce always let it through
2000            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 changed_sender_update_matches_full_update() {
3582        let senders = [
3583            Address::with_last_byte(0x0a),
3584            Address::with_last_byte(0x0b),
3585            Address::with_last_byte(0x0c),
3586        ];
3587        let starting_nonces = [5, 11, 17];
3588
3589        let build_pool = || {
3590            let mut f = MockTransactionFactory::default();
3591            let mut pool = AllTransactions::default();
3592            pool.pending_fees.base_fee = 1;
3593
3594            for (sender, starting_nonce) in senders.into_iter().zip(starting_nonces) {
3595                for nonce in starting_nonce..starting_nonce + 3 {
3596                    let tx = MockTransaction::eip1559()
3597                        .with_sender(sender)
3598                        .with_nonce(nonce)
3599                        .inc_price_by(10)
3600                        .rng_hash();
3601                    pool.insert_tx(f.validated(tx), U256::from(1_000_000), starting_nonce).unwrap();
3602                }
3603            }
3604
3605            // The fee differs from the initial marker, so this takes the all-transactions path.
3606            pool.update(&Default::default());
3607            let sender_ids = senders.map(|sender| f.ids.sender_id(&sender).unwrap());
3608            (pool, sender_ids)
3609        };
3610
3611        let (mut changed_senders_only, sender_ids) = build_pool();
3612        let (mut full_update, full_update_sender_ids) = build_pool();
3613        assert_eq!(sender_ids, full_update_sender_ids);
3614
3615        let mut changed = FxHashMap::default();
3616        changed.insert(
3617            sender_ids[0],
3618            SenderInfo { state_nonce: starting_nonces[0] + 1, balance: U256::from(1_000_000) },
3619        );
3620        changed.insert(
3621            sender_ids[1],
3622            SenderInfo { state_nonce: starting_nonces[1], balance: U256::ZERO },
3623        );
3624
3625        let mut changed_sender_updates = changed_senders_only.update(&changed);
3626        // Force the reference pool through the all-transactions path with the same pending fees.
3627        full_update.last_full_update_fees.base_fee =
3628            full_update.last_full_update_fees.base_fee.saturating_add(1);
3629        let mut full_updates = full_update.update(&changed);
3630
3631        let update_key = |update: &PoolUpdate| {
3632            let destination = match &update.destination {
3633                Destination::Discard => None,
3634                Destination::Pool(pool) => Some(*pool),
3635            };
3636            (update.id, update.current, destination)
3637        };
3638        changed_sender_updates.sort_unstable_by_key(|update| update.id);
3639        full_updates.sort_unstable_by_key(|update| update.id);
3640        assert_eq!(
3641            changed_sender_updates.iter().map(update_key).collect::<Vec<_>>(),
3642            full_updates.iter().map(update_key).collect::<Vec<_>>()
3643        );
3644
3645        let metadata = |pool: &AllTransactions<MockTransaction>| {
3646            pool.txs
3647                .iter()
3648                .map(|(id, tx)| (*id, tx.state, tx.subpool, tx.cumulative_cost))
3649                .collect::<Vec<_>>()
3650        };
3651        assert_eq!(metadata(&changed_senders_only), metadata(&full_update));
3652    }
3653
3654    #[test]
3655    fn update_visits_every_sender_when_the_base_fee_moved() {
3656        let mut f = MockTransactionFactory::default();
3657        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3658
3659        let tx = MockTransaction::eip1559().inc_price_by(10);
3660        let validated = f.validated(tx.clone());
3661        let id = *validated.id();
3662        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3663
3664        pool.all_transactions.update(&Default::default());
3665        assert!(pool
3666            .all_transactions
3667            .txs
3668            .get(&id)
3669            .unwrap()
3670            .state
3671            .contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3672
3673        // raising the base fee past the tx must still reach it even though no account changed,
3674        // otherwise the fast path would leave a stale fee bit behind
3675        pool.all_transactions.pending_fees.base_fee = (tx.max_fee_per_gas() + 1) as u64;
3676        pool.all_transactions.update(&Default::default());
3677
3678        assert!(
3679            !pool
3680                .all_transactions
3681                .txs
3682                .get(&id)
3683                .unwrap()
3684                .state
3685                .contains(TxState::ENOUGH_FEE_CAP_BLOCK),
3686            "fee change was not applied to an unchanged sender"
3687        );
3688    }
3689
3690    #[test]
3691    fn blob_fee_change_records_full_update() {
3692        let mut pool = AllTransactions::<MockTransaction>::default();
3693        pool.pending_fees.blob_fee += 1;
3694
3695        pool.update(&Default::default());
3696
3697        assert_eq!(pool.last_full_update_fees, pool.pending_fees);
3698    }
3699
3700    #[test]
3701    fn gap_fill_rechecks_descendant_fee_eligibility() {
3702        let mut f = MockTransactionFactory::default();
3703        let mut pool = AllTransactions::default();
3704        let sender = Address::with_last_byte(0x0d);
3705        let balance = U256::MAX;
3706
3707        pool.pending_fees.base_fee = 100;
3708        let descendant = MockTransaction::eip1559()
3709            .with_sender(sender)
3710            .with_nonce(1)
3711            .with_gas_limit(21_000)
3712            .with_max_fee(150)
3713            .with_priority_fee(1)
3714            .rng_hash();
3715        let descendant = f.validated(descendant);
3716        let descendant_id = *descendant.id();
3717        pool.insert_tx(descendant, balance, 0).unwrap();
3718        pool.update(&Default::default());
3719
3720        // The fee increase cannot affect a nonce-gapped transaction yet, but closing its gap must
3721        // evaluate it against the current fee.
3722        pool.pending_fees.base_fee = 200;
3723        pool.update(&Default::default());
3724        assert!(pool.get(&descendant_id).unwrap().state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3725
3726        let predecessor = MockTransaction::eip1559()
3727            .with_sender(sender)
3728            .with_nonce(0)
3729            .with_gas_limit(21_000)
3730            .with_max_fee(250)
3731            .with_priority_fee(1)
3732            .rng_hash();
3733        let InsertOk { move_to, .. } =
3734            pool.insert_tx(f.validated(predecessor), balance, 0).unwrap();
3735
3736        assert_eq!(move_to, SubPool::Pending);
3737        let descendant = pool.get(&descendant_id).unwrap();
3738        assert_eq!(descendant.subpool, SubPool::BaseFee);
3739        assert!(!descendant.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3740        assert!(descendant.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
3741    }
3742
3743    #[test]
3744    fn base_fee_update_unparks_all_descendants() {
3745        let mut f = MockTransactionFactory::default();
3746        let mut pool = AllTransactions::default();
3747        let sender = Address::with_last_byte(0x0e);
3748        let mut ids = Vec::new();
3749
3750        pool.pending_fees.base_fee = 200;
3751        for nonce in 0..3 {
3752            let tx = MockTransaction::eip1559()
3753                .with_sender(sender)
3754                .with_nonce(nonce)
3755                .with_gas_limit(21_000)
3756                .with_max_fee(150)
3757                .with_priority_fee(1)
3758                .rng_hash();
3759            let tx = f.validated(tx);
3760            ids.push(*tx.id());
3761            pool.insert_tx(tx, U256::MAX, 0).unwrap();
3762        }
3763
3764        pool.pending_fees.base_fee = 100;
3765        pool.update(&Default::default());
3766
3767        for id in ids {
3768            assert_eq!(pool.get(&id).unwrap().subpool, SubPool::Pending);
3769        }
3770    }
3771
3772    #[test]
3773    fn fee_update_keeps_descendants_of_underpriced_transaction_parked() {
3774        let mut f = MockTransactionFactory::default();
3775        let mut pool = AllTransactions::default();
3776        let sender = Address::with_last_byte(0x10);
3777        let fee_caps = [150, 50, 150];
3778        let mut ids = Vec::new();
3779
3780        pool.pending_fees.base_fee = 200;
3781        for (nonce, fee_cap) in fee_caps.into_iter().enumerate() {
3782            let tx = MockTransaction::eip1559()
3783                .with_sender(sender)
3784                .with_nonce(nonce as u64)
3785                .with_gas_limit(21_000)
3786                .with_max_fee(fee_cap)
3787                .with_priority_fee(1)
3788                .rng_hash();
3789            let tx = f.validated(tx);
3790            ids.push(*tx.id());
3791            pool.insert_tx(tx, U256::MAX, 0).unwrap();
3792        }
3793
3794        pool.pending_fees.base_fee = 100;
3795        pool.update(&Default::default());
3796
3797        assert_eq!(pool.get(&ids[0]).unwrap().subpool, SubPool::Pending);
3798        assert_eq!(pool.get(&ids[1]).unwrap().subpool, SubPool::BaseFee);
3799        assert_eq!(pool.get(&ids[2]).unwrap().subpool, SubPool::Queued);
3800    }
3801
3802    #[test]
3803    fn blob_fee_update_unparks_all_descendants() {
3804        let mut f = MockTransactionFactory::default();
3805        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3806        let sender = Address::with_last_byte(0x0f);
3807        let mut block_info = pool.block_info();
3808        block_info.pending_blob_fee = Some(200);
3809        pool.set_block_info(block_info);
3810
3811        for nonce in 0..3 {
3812            let tx = MockTransaction::eip4844()
3813                .with_sender(sender)
3814                .with_nonce(nonce)
3815                .with_gas_limit(21_000)
3816                .with_max_fee(1_000)
3817                .with_priority_fee(1)
3818                .with_blob_fee(150)
3819                .rng_hash();
3820            pool.add_transaction(f.validated(tx), U256::MAX, 0, None).unwrap();
3821        }
3822        assert_eq!(pool.blob_pool.len(), 3);
3823
3824        block_info.pending_blob_fee = Some(100);
3825        pool.on_canonical_state_change(
3826            block_info,
3827            Vec::new(),
3828            FxHashMap::default(),
3829            PoolUpdateKind::Commit,
3830        );
3831
3832        let nonces = pool.best_transactions().map(|tx| tx.nonce()).collect::<Vec<_>>();
3833        assert_eq!(nonces, vec![0, 1, 2]);
3834    }
3835
3836    #[test]
3837    fn update_basefee_subpools() {
3838        let mut f = MockTransactionFactory::default();
3839        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3840
3841        let tx = MockTransaction::eip1559().inc_price_by(10);
3842        let validated = f.validated(tx.clone());
3843        let id = *validated.id();
3844        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3845
3846        assert_eq!(pool.pending_pool.len(), 1);
3847
3848        pool.update_basefee((tx.max_fee_per_gas() + 1) as u64, |_| {});
3849
3850        assert!(pool.pending_pool.is_empty());
3851        assert_eq!(pool.basefee_pool.len(), 1);
3852
3853        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3854    }
3855
3856    #[test]
3857    fn update_basefee_subpools_setting_block_info() {
3858        let mut f = MockTransactionFactory::default();
3859        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3860
3861        let tx = MockTransaction::eip1559().inc_price_by(10);
3862        let validated = f.validated(tx.clone());
3863        let id = *validated.id();
3864        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3865
3866        assert_eq!(pool.pending_pool.len(), 1);
3867
3868        // use set_block_info for the basefee update
3869        let mut block_info = pool.block_info();
3870        block_info.pending_basefee = (tx.max_fee_per_gas() + 1) as u64;
3871        pool.set_block_info(block_info);
3872
3873        assert!(pool.pending_pool.is_empty());
3874        assert_eq!(pool.basefee_pool.len(), 1);
3875
3876        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3877    }
3878
3879    #[test]
3880    fn basefee_decrease_promotes_affordable_and_keeps_unaffordable() {
3881        let mut f = MockTransactionFactory::default();
3882        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3883
3884        // Create transactions that will be in basefee pool (can't afford initial high fee)
3885        // Use different senders to avoid nonce gap issues
3886        let sender_a = Address::with_last_byte(0x0a);
3887        let sender_b = Address::with_last_byte(0x0b);
3888        let sender_c = Address::with_last_byte(0x0c);
3889
3890        let tx1 = MockTransaction::eip1559()
3891            .set_sender(sender_a)
3892            .set_nonce(0)
3893            .set_max_fee(500)
3894            .inc_limit();
3895        let tx2 = MockTransaction::eip1559()
3896            .set_sender(sender_b)
3897            .set_nonce(0)
3898            .set_max_fee(600)
3899            .inc_limit();
3900        let tx3 = MockTransaction::eip1559()
3901            .set_sender(sender_c)
3902            .set_nonce(0)
3903            .set_max_fee(400)
3904            .inc_limit();
3905
3906        // Set high initial basefee so transactions go to basefee pool
3907        let mut block_info = pool.block_info();
3908        block_info.pending_basefee = 700;
3909        pool.set_block_info(block_info);
3910
3911        let validated1 = f.validated(tx1);
3912        let validated2 = f.validated(tx2);
3913        let validated3 = f.validated(tx3);
3914        let id1 = *validated1.id();
3915        let id2 = *validated2.id();
3916        let id3 = *validated3.id();
3917
3918        // Add transactions - they should go to basefee pool due to high basefee
3919        // All transactions have nonce 0 from different senders, so on_chain_nonce should be 0 for
3920        // all
3921        pool.add_transaction(validated1, U256::from(10_000), 0, None).unwrap();
3922        pool.add_transaction(validated2, U256::from(10_000), 0, None).unwrap();
3923        pool.add_transaction(validated3, U256::from(10_000), 0, None).unwrap();
3924
3925        // Debug: Check where transactions ended up
3926        println!("Basefee pool len: {}", pool.basefee_pool.len());
3927        println!("Pending pool len: {}", pool.pending_pool.len());
3928        println!("tx1 subpool: {:?}", pool.all_transactions.txs.get(&id1).unwrap().subpool);
3929        println!("tx2 subpool: {:?}", pool.all_transactions.txs.get(&id2).unwrap().subpool);
3930        println!("tx3 subpool: {:?}", pool.all_transactions.txs.get(&id3).unwrap().subpool);
3931
3932        // Verify they're in basefee pool
3933        assert_eq!(pool.basefee_pool.len(), 3);
3934        assert_eq!(pool.pending_pool.len(), 0);
3935        assert_eq!(pool.all_transactions.txs.get(&id1).unwrap().subpool, SubPool::BaseFee);
3936        assert_eq!(pool.all_transactions.txs.get(&id2).unwrap().subpool, SubPool::BaseFee);
3937        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3938
3939        // Now decrease basefee to trigger the zero-allocation optimization
3940        let mut block_info = pool.block_info();
3941        block_info.pending_basefee = 450; // tx1 (500) and tx2 (600) can now afford it, tx3 (400)
3942                                          // cannot
3943        pool.set_block_info(block_info);
3944
3945        // Verify the optimization worked correctly:
3946        // - tx1 and tx2 should be promoted to pending (mathematical certainty)
3947        // - tx3 should remain in basefee pool
3948        // - All state transitions should be correct
3949        assert_eq!(pool.basefee_pool.len(), 1);
3950        assert_eq!(pool.pending_pool.len(), 2);
3951
3952        // tx3 should still be in basefee pool (fee 400 < basefee 450)
3953        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3954
3955        // tx1 and tx2 should be in pending pool with correct state bits
3956        let tx1_meta = pool.all_transactions.txs.get(&id1).unwrap();
3957        let tx2_meta = pool.all_transactions.txs.get(&id2).unwrap();
3958        assert_eq!(tx1_meta.subpool, SubPool::Pending);
3959        assert_eq!(tx2_meta.subpool, SubPool::Pending);
3960        assert!(tx1_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3961        assert!(tx2_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3962
3963        // Verify that best_transactions returns the promoted transactions
3964        let best: Vec<_> = pool.best_transactions().take(3).collect();
3965        assert_eq!(best.len(), 2); // Only tx1 and tx2 should be returned
3966        assert!(best.iter().any(|tx| tx.id() == &id1));
3967        assert!(best.iter().any(|tx| tx.id() == &id2));
3968    }
3969
3970    #[test]
3971    fn apply_fee_updates_records_promotions_after_basefee_drop() {
3972        let mut f = MockTransactionFactory::default();
3973        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3974
3975        let tx = MockTransaction::eip1559()
3976            .with_gas_limit(21_000)
3977            .with_max_fee(500)
3978            .with_priority_fee(1);
3979        let validated = f.validated(tx);
3980        let id = *validated.id();
3981        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3982
3983        assert_eq!(pool.pending_pool.len(), 1);
3984
3985        // Raise base fee beyond the transaction's cap so it gets parked in BaseFee pool.
3986        pool.update_basefee(600, |_| {});
3987        assert!(pool.pending_pool.is_empty());
3988        assert_eq!(pool.basefee_pool.len(), 1);
3989
3990        let prev_base_fee = 600;
3991        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3992
3993        // Simulate the canonical state path updating pending fees before applying promotions.
3994        pool.all_transactions.pending_fees.base_fee = 400;
3995
3996        let mut outcome = UpdateOutcome::default();
3997        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3998
3999        assert_eq!(pool.pending_pool.len(), 1);
4000        assert!(pool.basefee_pool.is_empty());
4001        assert_eq!(outcome.promoted.len(), 1);
4002        assert_eq!(outcome.promoted[0].id(), &id);
4003        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
4004        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
4005
4006        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4007        assert_eq!(tx_meta.subpool, SubPool::Pending);
4008        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4009    }
4010
4011    #[test]
4012    fn update_blob_fee_demotes_and_promotes_when_base_fee_falls() {
4013        let mut f = MockTransactionFactory::default();
4014        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4015
4016        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4017        let initial_base_fee = 600;
4018        pool.all_transactions.pending_fees.base_fee = initial_base_fee;
4019
4020        let tx_to_demote = MockTransaction::eip4844()
4021            .with_max_fee(700)
4022            .with_priority_fee(1)
4023            .with_blob_fee(initial_blob_fee + 100);
4024        let validated = f.validated(tx_to_demote);
4025        let demoted_id = *validated.id();
4026        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4027
4028        let tx_to_promote = MockTransaction::eip4844()
4029            .with_max_fee(500)
4030            .with_priority_fee(1)
4031            .with_blob_fee(initial_blob_fee + 300);
4032        let validated = f.validated(tx_to_promote);
4033        let promoted_id = *validated.id();
4034        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4035
4036        assert_eq!(pool.pending_pool.len(), 1);
4037        assert_eq!(pool.blob_pool.len(), 1);
4038
4039        // The lower base fee makes one transaction affordable while the higher blob fee makes the
4040        // other unaffordable, so the same update must both promote and demote.
4041        let raised_blob_fee = initial_blob_fee + 200;
4042        pool.all_transactions.pending_fees.base_fee = 400;
4043        let mut promoted = Vec::new();
4044        pool.update_blob_fee(raised_blob_fee, Ordering::Less, |tx| promoted.push(*tx.id()));
4045
4046        assert_eq!(pool.pending_pool.len(), 1);
4047        assert_eq!(pool.blob_pool.len(), 1);
4048        assert_eq!(promoted, vec![promoted_id]);
4049
4050        assert_eq!(pool.all_transactions.txs.get(&demoted_id).unwrap().subpool, SubPool::Blob);
4051        assert_eq!(pool.all_transactions.txs.get(&promoted_id).unwrap().subpool, SubPool::Pending);
4052    }
4053
4054    #[test]
4055    fn apply_fee_updates_records_promotions_after_blob_fee_drop() {
4056        let mut f = MockTransactionFactory::default();
4057        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4058
4059        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4060
4061        let tx = MockTransaction::eip4844().with_blob_fee(initial_blob_fee + 100);
4062        let validated = f.validated(tx.clone());
4063        let id = *validated.id();
4064        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4065
4066        assert_eq!(pool.pending_pool.len(), 1);
4067
4068        // Raise blob fee beyond the transaction's cap so it gets parked in Blob pool.
4069        let increased_blob_fee = tx.max_fee_per_blob_gas().unwrap() + 200;
4070        pool.update_blob_fee(increased_blob_fee, Ordering::Equal, |_| {});
4071        assert!(pool.pending_pool.is_empty());
4072        assert_eq!(pool.blob_pool.len(), 1);
4073
4074        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
4075        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4076
4077        // Simulate the canonical state path updating pending fees before applying promotions.
4078        pool.all_transactions.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap();
4079
4080        let mut outcome = UpdateOutcome::default();
4081        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
4082
4083        assert_eq!(pool.pending_pool.len(), 1);
4084        assert!(pool.blob_pool.is_empty());
4085        assert_eq!(outcome.promoted.len(), 1);
4086        assert_eq!(outcome.promoted[0].id(), &id);
4087        assert_eq!(pool.all_transactions.pending_fees.base_fee, prev_base_fee);
4088        assert_eq!(pool.all_transactions.pending_fees.blob_fee, tx.max_fee_per_blob_gas().unwrap());
4089
4090        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4091        assert_eq!(tx_meta.subpool, SubPool::Pending);
4092        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4093        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4094    }
4095
4096    #[test]
4097    fn apply_fee_updates_promotes_blob_after_basefee_drop() {
4098        let mut f = MockTransactionFactory::default();
4099        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4100
4101        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4102
4103        let tx = MockTransaction::eip4844()
4104            .with_max_fee(500)
4105            .with_priority_fee(1)
4106            .with_blob_fee(initial_blob_fee + 100);
4107        let validated = f.validated(tx);
4108        let id = *validated.id();
4109        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4110
4111        assert_eq!(pool.pending_pool.len(), 1);
4112
4113        // Raise base fee beyond the transaction's cap so it gets parked in Blob pool.
4114        let high_base_fee = 600;
4115        pool.update_basefee(high_base_fee, |_| {});
4116        assert!(pool.pending_pool.is_empty());
4117        assert_eq!(pool.blob_pool.len(), 1);
4118
4119        let prev_base_fee = high_base_fee;
4120        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4121
4122        // Simulate applying a lower base fee while keeping blob fee unchanged.
4123        pool.all_transactions.pending_fees.base_fee = 400;
4124
4125        let mut outcome = UpdateOutcome::default();
4126        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
4127
4128        assert_eq!(pool.pending_pool.len(), 1);
4129        assert!(pool.blob_pool.is_empty());
4130        assert_eq!(outcome.promoted.len(), 1);
4131        assert_eq!(outcome.promoted[0].id(), &id);
4132        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
4133        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
4134
4135        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4136        assert_eq!(tx_meta.subpool, SubPool::Pending);
4137        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4138        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4139    }
4140
4141    #[test]
4142    fn queued_transactions_include_blob_pool() {
4143        let mut f = MockTransactionFactory::default();
4144        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4145
4146        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4147        let tx = MockTransaction::eip4844()
4148            .with_max_fee(500)
4149            .with_priority_fee(1)
4150            .with_blob_fee(initial_blob_fee + 100);
4151        let validated = f.validated(tx);
4152        let id = *validated.id();
4153        let sender = validated.sender_id();
4154        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4155
4156        // Raise the base fee beyond the transaction's cap so it gets parked in the blob pool.
4157        pool.update_basefee(600, |_| {});
4158        assert_eq!(pool.blob_pool.len(), 1);
4159
4160        let queued = pool.queued_transactions();
4161        assert_eq!(queued.len(), 1);
4162        assert_eq!(queued[0].id(), &id);
4163
4164        let by_sender = pool.queued_txs_by_sender(sender);
4165        assert_eq!(by_sender.len(), 1);
4166        assert_eq!(by_sender[0].id(), &id);
4167    }
4168
4169    #[test]
4170    fn apply_fee_updates_demotes_after_basefee_rise() {
4171        let mut f = MockTransactionFactory::default();
4172        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4173
4174        let tx = MockTransaction::eip1559()
4175            .with_gas_limit(21_000)
4176            .with_max_fee(400)
4177            .with_priority_fee(1);
4178        let validated = f.validated(tx);
4179        let id = *validated.id();
4180        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
4181
4182        assert_eq!(pool.pending_pool.len(), 1);
4183
4184        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
4185        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
4186
4187        // Simulate canonical path raising the base fee beyond the transaction's cap.
4188        let new_base_fee = prev_base_fee + 1_000;
4189        pool.all_transactions.pending_fees.base_fee = new_base_fee;
4190
4191        let mut outcome = UpdateOutcome::default();
4192        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
4193
4194        assert!(pool.pending_pool.is_empty());
4195        assert_eq!(pool.basefee_pool.len(), 1);
4196        assert!(outcome.promoted.is_empty());
4197        assert_eq!(pool.all_transactions.pending_fees.base_fee, new_base_fee);
4198        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
4199
4200        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
4201        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
4202        assert!(!tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
4203    }
4204
4205    #[test]
4206    fn get_highest_transaction_by_sender_and_nonce() {
4207        // Set up a mock transaction factory and a new transaction pool.
4208        let mut f = MockTransactionFactory::default();
4209        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4210
4211        // Create a mock transaction and add it to the pool.
4212        let tx = MockTransaction::eip1559();
4213        pool.add_transaction(f.validated(tx.clone()), U256::from(1_000), 0, None).unwrap();
4214
4215        // Create another mock transaction with an incremented price.
4216        let tx1 = tx.inc_price().next();
4217
4218        // Validate the second mock transaction and add it to the pool.
4219        let tx1_validated = f.validated(tx1.clone());
4220        pool.add_transaction(tx1_validated, U256::from(1_000), 0, None).unwrap();
4221
4222        // Ensure that the calculated next nonce for the sender matches the expected value.
4223        assert_eq!(
4224            pool.get_highest_nonce_by_sender(f.ids.sender_id(&tx.sender()).unwrap()),
4225            Some(1)
4226        );
4227
4228        // Retrieve the highest transaction by sender.
4229        let highest_tx = pool
4230            .get_highest_transaction_by_sender(f.ids.sender_id(&tx.sender()).unwrap())
4231            .expect("Failed to retrieve highest transaction");
4232
4233        // Validate that the retrieved highest transaction matches the expected transaction.
4234        assert_eq!(highest_tx.as_ref().transaction, tx1);
4235    }
4236
4237    #[test]
4238    fn get_highest_consecutive_transaction_by_sender() {
4239        // Set up a mock transaction factory and a new transaction pool.
4240        let mut pool = TxPool::new(MockOrdering::default(), PoolConfig::default());
4241        let mut f = MockTransactionFactory::default();
4242
4243        // Create transactions with nonces 0, 1, 2, 4, 5.
4244        let sender = Address::random();
4245        let txs: Vec<_> = vec![0, 1, 2, 4, 5, 8, 9];
4246        for nonce in txs {
4247            let mut mock_tx = MockTransaction::eip1559();
4248            mock_tx.set_sender(sender);
4249            mock_tx.set_nonce(nonce);
4250
4251            let validated_tx = f.validated(mock_tx);
4252            pool.add_transaction(validated_tx, U256::from(1000), 0, None).unwrap();
4253        }
4254
4255        // Get last consecutive transaction
4256        let sender_id = f.ids.sender_id(&sender).unwrap();
4257        let next_tx =
4258            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(0));
4259        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(2), "Expected nonce 2 for on-chain nonce 0");
4260
4261        let next_tx =
4262            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(4));
4263        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 4");
4264
4265        let next_tx =
4266            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
4267        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 5");
4268
4269        // update the tracked nonce
4270        let mut info = SenderInfo::default();
4271        info.update(8, U256::ZERO);
4272        pool.all_transactions.sender_info.insert(sender_id, info);
4273        let next_tx =
4274            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
4275        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(9), "Expected nonce 9 for on-chain nonce 8");
4276    }
4277
4278    #[test]
4279    fn discard_nonce_too_low() {
4280        let mut f = MockTransactionFactory::default();
4281        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4282
4283        let tx = MockTransaction::eip1559().inc_price_by(10);
4284        let validated = f.validated(tx.clone());
4285        let id = *validated.id();
4286        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4287
4288        let next = tx.next();
4289        let validated = f.validated(next.clone());
4290        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4291
4292        assert_eq!(pool.pending_pool.len(), 2);
4293
4294        let mut changed_senders = HashMap::default();
4295        changed_senders.insert(
4296            id.sender,
4297            SenderInfo { state_nonce: next.nonce(), balance: U256::from(1_000) },
4298        );
4299        let outcome = pool.update_accounts(changed_senders);
4300        assert_eq!(outcome.discarded.len(), 1);
4301        assert_eq!(pool.pending_pool.len(), 1);
4302    }
4303
4304    /// Pool with `enforce_tracked_nonce` enabled.
4305    fn stale_validation_pool() -> TxPool<MockOrdering> {
4306        TxPool::new(
4307            MockOrdering::default(),
4308            PoolConfig { enforce_tracked_nonce: true, ..Default::default() },
4309        )
4310    }
4311
4312    #[test]
4313    fn stale_validation_accepted_by_default() {
4314        let mut f = MockTransactionFactory::default();
4315        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4316
4317        let template = MockTransaction::eip1559();
4318        let first = f.validated(template.clone().with_nonce(0).rng_hash());
4319        let sender = first.sender_id();
4320        pool.add_transaction(first, U256::from(1_000), 0, None).unwrap();
4321
4322        let changed = FxHashMap::from_iter([(
4323            sender,
4324            SenderInfo { state_nonce: 1, balance: U256::from(1_000) },
4325        )]);
4326        let outcome = pool.update_accounts(changed);
4327        assert_eq!(outcome.discarded.len(), 1);
4328        // discarding the sender's last transaction drops its info
4329        assert!(!pool.all_transactions.sender_info.contains_key(&sender));
4330
4331        // the validation snapshot is trusted as is
4332        let stale = f.validated(template.with_nonce(0).rng_hash());
4333        pool.add_transaction(stale, U256::from(1_000), 0, None).unwrap();
4334        assert_eq!(pool.pending_pool.len(), 1);
4335        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 0);
4336    }
4337
4338    #[test]
4339    fn stale_validation_does_not_regress_sender_state() {
4340        let mut f = MockTransactionFactory::default();
4341        let mut pool = stale_validation_pool();
4342
4343        let template = MockTransaction::eip1559();
4344        let first = f.validated(template.clone().with_nonce(0).rng_hash());
4345        let sender = first.sender_id();
4346        pool.add_transaction(first, U256::from(1_000), 0, None).unwrap();
4347
4348        let mut changed_senders = HashMap::default();
4349        changed_senders.insert(sender, SenderInfo { state_nonce: 1, balance: U256::from(1_000) });
4350        let outcome = pool.update_accounts(changed_senders);
4351        assert_eq!(outcome.discarded.len(), 1);
4352        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4353
4354        // Stale validation for the next nonce remains pending.
4355        let current = f.validated(template.clone().with_nonce(1).rng_hash());
4356        pool.add_transaction(current, U256::from(1_000), 0, None).unwrap();
4357        assert_eq!(pool.pending_pool.len(), 1);
4358        assert_eq!(pool.queued_pool.len(), 0);
4359        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4360
4361        let stale = f.validated(template.with_nonce(0).rng_hash());
4362        let stale_hash = *stale.hash();
4363        let err = pool.add_transaction(stale, U256::from(1_000), 0, None).unwrap_err();
4364        assert!(matches!(
4365            err.kind,
4366            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
4367                InvalidTransactionError::NonceNotConsistent { tx: 0, state: 1 }
4368            ))
4369        ));
4370        assert!(!pool.contains(&stale_hash));
4371        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4372    }
4373
4374    #[test]
4375    fn stale_validation_resubmission_after_commit() {
4376        let mut f = MockTransactionFactory::default();
4377        let mut pool = stale_validation_pool();
4378        let block_info = pool.block_info();
4379
4380        let template = MockTransaction::eip1559();
4381        let tx0 = template.clone().with_nonce(0).rng_hash();
4382        let tx1 = template.clone().with_nonce(1).rng_hash();
4383        let first = f.validated(tx0.clone());
4384        let sender = first.sender_id();
4385        pool.add_transaction(first, U256::from(1_000), 0, None).unwrap();
4386        pool.add_transaction(f.validated(tx1.clone()), U256::from(1_000), 0, None).unwrap();
4387        assert_eq!(pool.pending_pool.len(), 2);
4388
4389        // Nonce 0 is mined while resubmissions of both nonces are still being validated.
4390        let changed = FxHashMap::from_iter([(
4391            sender,
4392            SenderInfo { state_nonce: 1, balance: U256::from(1_000) },
4393        )]);
4394        pool.on_canonical_state_change(
4395            block_info,
4396            vec![*tx0.get_hash()],
4397            changed,
4398            PoolUpdateKind::Commit,
4399        );
4400        assert_eq!(pool.pending_pool.len(), 1);
4401
4402        // The resubmitted mined nonce is rejected instead of shadowing nonce 1.
4403        let retry = f.validated(template.with_nonce(0).rng_hash());
4404        let err = pool.add_transaction(retry, U256::from(1_000), 0, None).unwrap_err();
4405        assert!(matches!(
4406            err.kind,
4407            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
4408                InvalidTransactionError::NonceNotConsistent { tx: 0, state: 1 }
4409            ))
4410        ));
4411
4412        // The replacement of the executable nonce is accepted using the tracked nonce.
4413        let replacement = f.validated(tx1.inc_price_by(10).rng_hash());
4414        let added = pool.add_transaction(replacement, U256::from(1_000), 0, None).unwrap();
4415        assert!(added.as_pending().is_some());
4416        assert!(added.replaced().is_some());
4417        assert_eq!(pool.pending_pool.len(), 1);
4418        assert_eq!(pool.all_transactions.sender_info[&sender].state_nonce, 1);
4419        let next =
4420            pool.get_highest_consecutive_transaction_by_sender(sender.into_transaction_id(1));
4421        assert_eq!(next.map(|tx| tx.nonce()), Some(1));
4422    }
4423
4424    #[test]
4425    fn stale_validation_uses_tracked_balance() {
4426        let mut f = MockTransactionFactory::default();
4427        let mut pool = stale_validation_pool();
4428
4429        // each transaction costs 1_500_000
4430        let template = MockTransaction::eip1559().with_gas_price(50).with_gas_limit(30_000);
4431        let first = f.validated(template.clone().with_nonce(0).rng_hash());
4432        let sender = first.sender_id();
4433        pool.add_transaction(first, U256::from(10_000_000), 0, None).unwrap();
4434        assert_eq!(pool.pending_pool.len(), 1);
4435
4436        // The commit mines nonce 0 and drains the balance below the cost of the next transaction.
4437        let changed = FxHashMap::from_iter([(
4438            sender,
4439            SenderInfo { state_nonce: 1, balance: U256::from(1_000) },
4440        )]);
4441        pool.update_accounts(changed);
4442
4443        // The stale snapshot still reports the old balance, the tracked balance applies.
4444        let next = f.validated(template.with_nonce(1).rng_hash());
4445        let id = *next.id();
4446        pool.add_transaction(next, U256::from(10_000_000), 0, None).unwrap();
4447        assert_eq!(pool.pending_pool.len(), 0);
4448        assert_eq!(pool.queued_pool.len(), 1);
4449        let state = pool.all_transactions.get(&id).unwrap().state;
4450        assert!(state.contains(TxState::NO_NONCE_GAPS));
4451        assert!(!state.contains(TxState::ENOUGH_BALANCE));
4452        assert_eq!(pool.all_transactions.sender_info[&sender].balance, U256::from(1_000));
4453    }
4454
4455    #[test]
4456    fn discard_with_large_blob_txs() {
4457        // init tracing
4458        reth_tracing::init_test_tracing();
4459
4460        // this test adds large txs to the parked pool, then attempting to discard worst
4461        let mut f = MockTransactionFactory::default();
4462        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4463        let default_limits = pool.config.blob_limit;
4464
4465        // create a chain of transactions by sender A
4466        // make sure they are all one over half the limit
4467        let a_sender = Address::with_last_byte(0x0a);
4468
4469        // set the base fee of the pool
4470        let mut block_info = pool.block_info();
4471        block_info.pending_blob_fee = Some(100);
4472        block_info.pending_basefee = 100;
4473
4474        // update
4475        pool.set_block_info(block_info);
4476
4477        // 2 txs, that should put the pool over the size limit but not max txs
4478        let a_txs = MockTransactionSet::dependent(a_sender, 0, 2, TxType::Eip4844)
4479            .into_iter()
4480            .map(|mut tx| {
4481                tx.set_size(default_limits.max_size / 2 + 1);
4482                tx.set_max_fee((block_info.pending_basefee - 1).into());
4483                tx
4484            })
4485            .collect::<Vec<_>>();
4486
4487        // add all the transactions to the parked pool
4488        for tx in a_txs {
4489            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
4490        }
4491
4492        // truncate the pool, it should remove at least one transaction
4493        let removed = pool.discard_worst();
4494        assert_eq!(removed.len(), 1);
4495    }
4496
4497    #[test]
4498    fn discard_with_parked_large_txs() {
4499        // init tracing
4500        reth_tracing::init_test_tracing();
4501
4502        // this test adds large txs to the parked pool, then attempting to discard worst
4503        let mut f = MockTransactionFactory::default();
4504        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4505        let default_limits = pool.config.queued_limit;
4506
4507        // create a chain of transactions by sender A
4508        // make sure they are all one over half the limit
4509        let a_sender = Address::with_last_byte(0x0a);
4510
4511        // set the base fee of the pool
4512        let pool_base_fee = 100;
4513        pool.update_basefee(pool_base_fee, |_| {});
4514
4515        // 2 txs, that should put the pool over the size limit but not max txs
4516        let a_txs = MockTransactionSet::dependent(a_sender, 0, 3, TxType::Eip1559)
4517            .into_iter()
4518            .map(|mut tx| {
4519                tx.set_size(default_limits.max_size / 2 + 1);
4520                tx.set_max_fee((pool_base_fee - 1).into());
4521                tx
4522            })
4523            .collect::<Vec<_>>();
4524
4525        // add all the transactions to the parked pool
4526        for tx in a_txs {
4527            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
4528        }
4529
4530        // truncate the pool, it should remove at least one transaction
4531        let removed = pool.discard_worst();
4532        assert_eq!(removed.len(), 1);
4533    }
4534
4535    #[test]
4536    fn discard_at_capacity() {
4537        let mut f = MockTransactionFactory::default();
4538        let queued_limit = SubPoolLimit::new(1000, usize::MAX);
4539        let mut pool =
4540            TxPool::new(MockOrdering::default(), PoolConfig { queued_limit, ..Default::default() });
4541
4542        // insert a bunch of transactions into the queued pool
4543        for _ in 0..queued_limit.max_txs {
4544            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
4545            let validated = f.validated(tx);
4546            let _id = *validated.id();
4547            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4548        }
4549
4550        let size = pool.size();
4551        assert_eq!(size.queued, queued_limit.max_txs);
4552
4553        for _ in 0..queued_limit.max_txs {
4554            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
4555            let validated = f.validated(tx);
4556            let _id = *validated.id();
4557            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4558
4559            pool.discard_worst();
4560            pool.assert_invariants();
4561            assert!(pool.size().queued <= queued_limit.max_txs);
4562        }
4563    }
4564
4565    #[test]
4566    fn discard_blobs_at_capacity() {
4567        let mut f = MockTransactionFactory::default();
4568        let blob_limit = SubPoolLimit::new(1000, usize::MAX);
4569        let mut pool =
4570            TxPool::new(MockOrdering::default(), PoolConfig { blob_limit, ..Default::default() });
4571        pool.all_transactions.pending_fees.blob_fee = 10000;
4572        // insert a bunch of transactions into the queued pool
4573        for _ in 0..blob_limit.max_txs {
4574            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
4575            let validated = f.validated(tx);
4576            let _id = *validated.id();
4577            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4578        }
4579
4580        let size = pool.size();
4581        assert_eq!(size.blob, blob_limit.max_txs);
4582
4583        for _ in 0..blob_limit.max_txs {
4584            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
4585            let validated = f.validated(tx);
4586            let _id = *validated.id();
4587            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
4588
4589            pool.discard_worst();
4590            pool.assert_invariants();
4591            assert!(pool.size().blob <= blob_limit.max_txs);
4592        }
4593    }
4594
4595    #[test]
4596    fn reuse_update_buffer_across_pool_operations() {
4597        let mut f = MockTransactionFactory::default();
4598        let mut pool = TxPool::new(
4599            MockOrdering::default(),
4600            PoolConfig { max_account_slots: 128, ..Default::default() },
4601        );
4602        let mut tx = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4603        let first = f.validated(tx.clone());
4604        for _ in 0..80 {
4605            pool.add_transaction(f.validated(tx.clone()), U256::MAX, 0, None).unwrap();
4606            tx = tx.next();
4607        }
4608        assert_eq!(pool.all_transactions.update_buffer.capacity(), 0);
4609
4610        let changed = |balance, state_nonce| {
4611            FxHashMap::from_iter([(first.sender_id(), SenderInfo { balance, state_nonce })])
4612        };
4613        let outcome = pool.update_accounts(changed(U256::ZERO, 0));
4614        assert!(outcome.promoted.is_empty());
4615        assert!(outcome.discarded.is_empty());
4616        assert_eq!(pool.queued_pool.len(), 80);
4617        let capacity = pool.all_transactions.update_buffer.capacity();
4618        assert!(capacity >= 80);
4619        let buffer = pool.all_transactions.update_buffer.as_ptr();
4620
4621        for _ in 0..2 {
4622            let outcome = pool.update_accounts(changed(U256::MAX, 0));
4623            assert_eq!(outcome.promoted.len(), 80);
4624            assert!(outcome.discarded.is_empty());
4625            assert_eq!(pool.pending_pool.len(), 80);
4626
4627            let outcome = pool.update_accounts(FxHashMap::default());
4628            assert!(outcome.promoted.is_empty());
4629            assert!(outcome.discarded.is_empty());
4630
4631            pool.remove_transaction_by_hash(first.hash()).unwrap();
4632            assert_eq!(pool.queued_pool.len(), 79);
4633
4634            // A rejected replacement must leave the reusable allocation available.
4635            let replacement = f.validated(first.transaction.next());
4636            assert!(pool.add_transaction(replacement, U256::MAX, 0, None).is_err());
4637            assert_eq!(pool.all_transactions.update_buffer.as_ptr(), buffer);
4638
4639            let added = pool.add_transaction(first.clone(), U256::MAX, 0, None).unwrap();
4640            let AddedTransaction::Pending(added) = added else { panic!("expected pending") };
4641            assert_eq!(added.promoted.len(), 79);
4642            assert!(added.discarded.is_empty());
4643            assert_eq!(pool.pending_pool.len(), 80);
4644
4645            pool.update_accounts(changed(U256::ZERO, 0));
4646            assert_eq!(pool.queued_pool.len(), 80);
4647            assert!(pool.all_transactions.update_buffer.is_empty());
4648            assert_eq!(pool.all_transactions.update_buffer.capacity(), capacity);
4649            assert_eq!(pool.all_transactions.update_buffer.as_ptr(), buffer);
4650            pool.assert_invariants();
4651        }
4652
4653        let outcome = pool.update_accounts(changed(U256::MAX, 80));
4654        assert_eq!(outcome.discarded.len(), 80);
4655        assert!(outcome.promoted.is_empty());
4656        assert!(pool.is_empty());
4657        assert!(pool.all_transactions.update_buffer.is_empty());
4658        assert_eq!(pool.all_transactions.update_buffer.as_ptr(), buffer);
4659    }
4660
4661    #[test]
4662    fn account_updates_sender_balance() {
4663        let mut on_chain_balance = U256::from(100);
4664        let on_chain_nonce = 0;
4665        let mut f = MockTransactionFactory::default();
4666        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4667
4668        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4669        let tx_1 = tx_0.next();
4670        let tx_2 = tx_1.next();
4671
4672        // Create 3 transactions
4673        let v0 = f.validated(tx_0);
4674        let v1 = f.validated(tx_1);
4675        let v2 = f.validated(tx_2);
4676
4677        let _res =
4678            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4679        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4680        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4681
4682        // The sender does not have enough balance to put all txs into pending.
4683        assert_eq!(1, pool.pending_transactions().len());
4684        assert_eq!(2, pool.queued_transactions().len());
4685
4686        // Simulate new block arrival - and chain balance increase.
4687        let mut updated_accounts = HashMap::default();
4688        on_chain_balance = U256::from(300);
4689        updated_accounts.insert(
4690            v0.sender_id(),
4691            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
4692        );
4693        pool.update_accounts(updated_accounts.clone());
4694
4695        assert_eq!(3, pool.pending_transactions().len());
4696        assert!(pool.queued_transactions().is_empty());
4697
4698        // Simulate new block arrival - and chain balance decrease.
4699        updated_accounts.entry(v0.sender_id()).and_modify(|v| v.balance = U256::ONE);
4700        pool.update_accounts(updated_accounts);
4701
4702        assert!(pool.pending_transactions().is_empty());
4703        assert_eq!(3, pool.queued_transactions().len());
4704    }
4705
4706    #[test]
4707    fn account_updates_nonce_gap() {
4708        let on_chain_balance = U256::from(10_000);
4709        let mut on_chain_nonce = 0;
4710        let mut f = MockTransactionFactory::default();
4711        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4712
4713        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4714        let tx_1 = tx_0.next();
4715        let tx_2 = tx_1.next();
4716
4717        // Create 3 transactions
4718        let v0 = f.validated(tx_0);
4719        let v1 = f.validated(tx_1);
4720        let v2 = f.validated(tx_2);
4721
4722        // Add first 2 to the pool
4723        let _res =
4724            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4725        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4726
4727        assert!(pool.queued_transactions().is_empty());
4728        assert_eq!(2, pool.pending_transactions().len());
4729
4730        // Remove first (nonce 0)
4731        pool.remove_transaction_by_hash(v0.hash());
4732
4733        // Now add transaction with nonce 2
4734        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4735
4736        // v1 and v2 should both be in the queue now.
4737        assert_eq!(2, pool.queued_transactions().len());
4738        assert!(pool.pending_transactions().is_empty());
4739
4740        // Simulate new block arrival - and chain nonce increasing.
4741        let mut updated_accounts = HashMap::default();
4742        on_chain_nonce += 1;
4743        updated_accounts.insert(
4744            v0.sender_id(),
4745            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
4746        );
4747        pool.update_accounts(updated_accounts);
4748
4749        // 'pending' now).
4750        assert!(pool.queued_transactions().is_empty());
4751        assert_eq!(2, pool.pending_transactions().len());
4752    }
4753    #[test]
4754    fn test_transaction_removal() {
4755        let on_chain_balance = U256::from(10_000);
4756        let on_chain_nonce = 0;
4757        let mut f = MockTransactionFactory::default();
4758        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4759
4760        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4761        let tx_1 = tx_0.next();
4762
4763        // Create 2 transactions
4764        let v0 = f.validated(tx_0);
4765        let v1 = f.validated(tx_1);
4766
4767        // Add them to the pool
4768        let _res =
4769            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4770        let _res =
4771            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4772
4773        assert_eq!(0, pool.queued_transactions().len());
4774        assert_eq!(2, pool.pending_transactions().len());
4775
4776        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
4777        pool.remove_transaction(v0.id());
4778        // assert the second transaction is really at the top of the queue
4779        let pool_txs = pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>();
4780        assert_eq!(vec![v1.nonce()], pool_txs);
4781    }
4782    #[test]
4783    fn test_remove_transactions() {
4784        let on_chain_balance = U256::from(10_000);
4785        let on_chain_nonce = 0;
4786        let mut f = MockTransactionFactory::default();
4787        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4788
4789        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4790        let tx_1 = tx_0.next();
4791        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4792        let tx_3 = tx_2.next();
4793
4794        // Create 4 transactions
4795        let v0 = f.validated(tx_0);
4796        let v1 = f.validated(tx_1);
4797        let v2 = f.validated(tx_2);
4798        let v3 = f.validated(tx_3);
4799
4800        // Add them to the pool
4801        let _res =
4802            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4803        let _res =
4804            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4805        let _res =
4806            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4807        let _res =
4808            pool.add_transaction(v3.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4809
4810        assert_eq!(0, pool.queued_transactions().len());
4811        assert_eq!(4, pool.pending_transactions().len());
4812
4813        pool.remove_transactions(vec![*v0.hash(), *v2.hash()]);
4814
4815        assert_eq!(2, pool.queued_transactions().len());
4816        assert!(pool.pending_transactions().is_empty());
4817        assert!(pool.contains(v1.hash()));
4818        assert!(pool.contains(v3.hash()));
4819    }
4820
4821    #[test]
4822    fn test_remove_transactions_middle_pending_hash() {
4823        let on_chain_balance = U256::from(10_000);
4824        let on_chain_nonce = 0;
4825        let mut f = MockTransactionFactory::default();
4826        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4827
4828        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4829        let tx_1 = tx_0.next();
4830        let tx_2 = tx_1.next();
4831        let tx_3 = tx_2.next();
4832
4833        // Create 4 transactions
4834        let v0 = f.validated(tx_0);
4835        let v1 = f.validated(tx_1);
4836        let v2 = f.validated(tx_2);
4837        let v3 = f.validated(tx_3);
4838
4839        // Add them to the pool
4840        let _res = pool.add_transaction(v0, on_chain_balance, on_chain_nonce, None).unwrap();
4841        let _res =
4842            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4843        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4844        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4845
4846        assert_eq!(0, pool.queued_transactions().len());
4847        assert_eq!(4, pool.pending_transactions().len());
4848
4849        let mut removed_txs = pool.remove_transactions(vec![*v1.hash()]);
4850        assert_eq!(1, removed_txs.len());
4851
4852        assert_eq!(2, pool.queued_transactions().len());
4853        assert_eq!(1, pool.pending_transactions().len());
4854
4855        // reinsert
4856        let removed_tx = removed_txs.pop().unwrap();
4857        let v1 = f.validated(removed_tx.transaction.clone());
4858        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4859        assert_eq!(0, pool.queued_transactions().len());
4860        assert_eq!(4, pool.pending_transactions().len());
4861    }
4862
4863    #[test]
4864    fn test_remove_transactions_and_descendants() {
4865        let on_chain_balance = U256::from(10_000);
4866        let on_chain_nonce = 0;
4867        let mut f = MockTransactionFactory::default();
4868        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4869
4870        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4871        let tx_1 = tx_0.next();
4872        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4873        let tx_3 = tx_2.next();
4874        let tx_4 = tx_3.next();
4875
4876        // Create 5 transactions
4877        let v0 = f.validated(tx_0);
4878        let v1 = f.validated(tx_1);
4879        let v2 = f.validated(tx_2);
4880        let v3 = f.validated(tx_3);
4881        let v4 = f.validated(tx_4);
4882
4883        // Add them to the pool
4884        let _res =
4885            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4886        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4887        let _res =
4888            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4889        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4890        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
4891
4892        assert_eq!(0, pool.queued_transactions().len());
4893        assert_eq!(5, pool.pending_transactions().len());
4894
4895        pool.remove_transactions_and_descendants(vec![*v0.hash(), *v2.hash()]);
4896
4897        assert_eq!(0, pool.queued_transactions().len());
4898        assert_eq!(0, pool.pending_transactions().len());
4899    }
4900    #[test]
4901    fn test_remove_descendants() {
4902        let on_chain_balance = U256::from(10_000);
4903        let on_chain_nonce = 0;
4904        let mut f = MockTransactionFactory::default();
4905        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4906
4907        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4908        let tx_1 = tx_0.next();
4909        let tx_2 = tx_1.next();
4910        let tx_3 = tx_2.next();
4911
4912        // Create 4 transactions
4913        let v0 = f.validated(tx_0);
4914        let v1 = f.validated(tx_1);
4915        let v2 = f.validated(tx_2);
4916        let v3 = f.validated(tx_3);
4917
4918        // Add them to the  pool
4919        let _res =
4920            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4921        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4922        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4923        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4924
4925        assert_eq!(0, pool.queued_transactions().len());
4926        assert_eq!(4, pool.pending_transactions().len());
4927
4928        let mut removed = Vec::new();
4929        pool.remove_transaction(v0.id());
4930        pool.remove_descendants(v0.id(), &mut removed);
4931
4932        assert_eq!(0, pool.queued_transactions().len());
4933        assert_eq!(0, pool.pending_transactions().len());
4934        assert_eq!(3, removed.len());
4935    }
4936    #[test]
4937    fn test_remove_transactions_by_sender() {
4938        let on_chain_balance = U256::from(10_000);
4939        let on_chain_nonce = 0;
4940        let mut f = MockTransactionFactory::default();
4941        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4942
4943        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4944        let tx_1 = tx_0.next();
4945        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4946        let tx_3 = tx_2.next();
4947        let tx_4 = tx_3.next();
4948
4949        // Create 5 transactions
4950        let v0 = f.validated(tx_0);
4951        let v1 = f.validated(tx_1);
4952        let v2 = f.validated(tx_2);
4953        let v3 = f.validated(tx_3);
4954        let v4 = f.validated(tx_4);
4955
4956        // Add them to the pool
4957        let _res =
4958            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4959        let _res =
4960            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4961        let _res =
4962            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4963        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4964        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
4965
4966        assert_eq!(0, pool.queued_transactions().len());
4967        assert_eq!(5, pool.pending_transactions().len());
4968
4969        pool.remove_transactions_by_sender(v2.sender_id());
4970
4971        assert_eq!(0, pool.queued_transactions().len());
4972        assert_eq!(2, pool.pending_transactions().len());
4973        assert!(pool.contains(v0.hash()));
4974        assert!(pool.contains(v1.hash()));
4975    }
4976    #[test]
4977    fn wrong_best_order_of_transactions() {
4978        let on_chain_balance = U256::from(10_000);
4979        let mut on_chain_nonce = 0;
4980        let mut f = MockTransactionFactory::default();
4981        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4982
4983        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4984        let tx_1 = tx_0.next();
4985        let tx_2 = tx_1.next();
4986        let tx_3 = tx_2.next();
4987
4988        // Create 4 transactions
4989        let v0 = f.validated(tx_0);
4990        let v1 = f.validated(tx_1);
4991        let v2 = f.validated(tx_2);
4992        let v3 = f.validated(tx_3);
4993
4994        // Add first 2 to the pool
4995        let _res =
4996            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4997        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4998
4999        assert_eq!(0, pool.queued_transactions().len());
5000        assert_eq!(2, pool.pending_transactions().len());
5001
5002        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
5003        pool.remove_transaction(v0.id());
5004
5005        // Now add transaction with nonce 2
5006        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
5007
5008        // v2 is in the queue now. v1 is still in 'pending'.
5009        assert_eq!(1, pool.queued_transactions().len());
5010        assert_eq!(1, pool.pending_transactions().len());
5011
5012        // Simulate new block arrival - and chain nonce increasing.
5013        let mut updated_accounts = HashMap::default();
5014        on_chain_nonce += 1;
5015        updated_accounts.insert(
5016            v0.sender_id(),
5017            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
5018        );
5019        pool.update_accounts(updated_accounts);
5020
5021        // Transactions are not changed (IMHO - this is a bug, as transaction v2 should be in the
5022        // 'pending' now).
5023        assert_eq!(0, pool.queued_transactions().len());
5024        assert_eq!(2, pool.pending_transactions().len());
5025
5026        // Add transaction v3 - it 'unclogs' everything.
5027        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
5028        assert_eq!(0, pool.queued_transactions().len());
5029        assert_eq!(3, pool.pending_transactions().len());
5030
5031        // It should have returned transactions in order (v1, v2, v3 - as there is nothing blocking
5032        // them).
5033        assert_eq!(
5034            pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>(),
5035            vec![1, 2, 3]
5036        );
5037    }
5038
5039    #[test]
5040    fn test_best_with_attributes() {
5041        let on_chain_balance = U256::MAX;
5042        let on_chain_nonce = 0;
5043        let mut f = MockTransactionFactory::default();
5044        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5045
5046        let base_fee: u128 = 100;
5047        let blob_fee: u128 = 100;
5048
5049        // set base fee and blob fee.
5050        let mut block_info = pool.block_info();
5051        block_info.pending_basefee = base_fee as u64;
5052        block_info.pending_blob_fee = Some(blob_fee);
5053        pool.set_block_info(block_info);
5054
5055        // Insert transactions with varying max_fee_per_gas and max_fee_per_blob_gas.
5056        let tx1 = MockTransaction::eip4844()
5057            .with_sender(Address::with_last_byte(1))
5058            .with_max_fee(base_fee + 10)
5059            .with_blob_fee(blob_fee + 10);
5060        let tx2 = MockTransaction::eip4844()
5061            .with_sender(Address::with_last_byte(2))
5062            .with_max_fee(base_fee + 10)
5063            .with_blob_fee(blob_fee);
5064        let tx3 = MockTransaction::eip4844()
5065            .with_sender(Address::with_last_byte(3))
5066            .with_max_fee(base_fee)
5067            .with_blob_fee(blob_fee + 10);
5068        let tx4 = MockTransaction::eip4844()
5069            .with_sender(Address::with_last_byte(4))
5070            .with_max_fee(base_fee)
5071            .with_blob_fee(blob_fee);
5072        let tx5 = MockTransaction::eip4844()
5073            .with_sender(Address::with_last_byte(5))
5074            .with_max_fee(base_fee)
5075            .with_blob_fee(blob_fee - 10);
5076        let tx6 = MockTransaction::eip4844()
5077            .with_sender(Address::with_last_byte(6))
5078            .with_max_fee(base_fee - 10)
5079            .with_blob_fee(blob_fee);
5080        let tx7 = MockTransaction::eip4844()
5081            .with_sender(Address::with_last_byte(7))
5082            .with_max_fee(base_fee - 10)
5083            .with_blob_fee(blob_fee - 10);
5084
5085        for tx in vec![
5086            tx1.clone(),
5087            tx2.clone(),
5088            tx3.clone(),
5089            tx4.clone(),
5090            tx5.clone(),
5091            tx6.clone(),
5092            tx7.clone(),
5093        ] {
5094            pool.add_transaction(f.validated(tx), on_chain_balance, on_chain_nonce, None).unwrap();
5095        }
5096
5097        let base_fee = base_fee as u64;
5098        let blob_fee = blob_fee as u64;
5099
5100        let cases = vec![
5101            // 1. Base fee increase, blob fee increase
5102            (BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee + 5)), vec![tx1.clone()]),
5103            // 2. Base fee increase, blob fee not change
5104            (
5105                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee)),
5106                vec![tx1.clone(), tx2.clone()],
5107            ),
5108            // 3. Base fee increase, blob fee decrease
5109            (
5110                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee - 5)),
5111                vec![tx1.clone(), tx2.clone()],
5112            ),
5113            // 4. Base fee not change, blob fee increase
5114            (
5115                BestTransactionsAttributes::new(base_fee, Some(blob_fee + 5)),
5116                vec![tx1.clone(), tx3.clone()],
5117            ),
5118            // 5. Base fee not change, blob fee not change
5119            (
5120                BestTransactionsAttributes::new(base_fee, Some(blob_fee)),
5121                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()],
5122            ),
5123            // 6. Base fee not change, blob fee decrease
5124            (
5125                BestTransactionsAttributes::new(base_fee, Some(blob_fee - 10)),
5126                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()],
5127            ),
5128            // 7. Base fee decrease, blob fee increase
5129            (
5130                BestTransactionsAttributes::new(base_fee - 5, Some(blob_fee + 5)),
5131                vec![tx1.clone(), tx3.clone()],
5132            ),
5133            // 8. Base fee decrease, blob fee not change
5134            (
5135                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee)),
5136                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx6.clone()],
5137            ),
5138            // 9. Base fee decrease, blob fee decrease
5139            (
5140                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee - 10)),
5141                vec![tx1, tx2, tx5, tx3, tx4, tx6, tx7],
5142            ),
5143        ];
5144
5145        for (idx, (attribute, expected)) in cases.into_iter().enumerate() {
5146            let mut best = pool.best_transactions_with_attributes(attribute);
5147
5148            for (tx_idx, expected_tx) in expected.into_iter().enumerate() {
5149                let tx = best.next().expect("Transaction should be returned");
5150                assert_eq!(
5151                    tx.transaction,
5152                    expected_tx,
5153                    "Failed tx {} in case {}",
5154                    tx_idx + 1,
5155                    idx + 1
5156                );
5157            }
5158
5159            // No more transactions should be returned
5160            assert!(best.next().is_none());
5161        }
5162    }
5163
5164    #[test]
5165    fn test_pending_ordering() {
5166        let mut f = MockTransactionFactory::default();
5167        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5168
5169        let tx_0 = MockTransaction::eip1559().with_nonce(1).set_gas_price(100).inc_limit();
5170        let tx_1 = tx_0.next();
5171
5172        let v0 = f.validated(tx_0);
5173        let v1 = f.validated(tx_1);
5174
5175        // nonce gap, tx should be queued
5176        pool.add_transaction(v0.clone(), U256::MAX, 0, None).unwrap();
5177        assert_eq!(1, pool.queued_transactions().len());
5178
5179        // nonce gap is closed on-chain, both transactions should be moved to pending
5180        pool.add_transaction(v1, U256::MAX, 1, None).unwrap();
5181
5182        assert_eq!(2, pool.pending_transactions().len());
5183        assert_eq!(0, pool.queued_transactions().len());
5184
5185        assert_eq!(
5186            pool.pending_pool.independent().get(&v0.sender_id()).unwrap().transaction.nonce(),
5187            v0.nonce()
5188        );
5189    }
5190
5191    // <https://github.com/paradigmxyz/reth/issues/12286>
5192    #[test]
5193    fn one_sender_one_independent_transaction() {
5194        let mut on_chain_balance = U256::from(4_999); // only enough for 4 txs
5195        let mut on_chain_nonce = 40;
5196        let mut f = MockTransactionFactory::default();
5197        let mut pool = TxPool::mock();
5198        let mut submitted_txs = Vec::new();
5199
5200        // We use a "template" because we want all txs to have the same sender.
5201        let template =
5202            MockTransaction::eip1559().inc_price().inc_limit().with_value(U256::from(1_001));
5203
5204        // Add 8 txs. Because the balance is only sufficient for 4, so the last 4 will be
5205        // Queued.
5206        for tx_nonce in 40..48 {
5207            let tx = f.validated(template.clone().with_nonce(tx_nonce).rng_hash());
5208            submitted_txs.push(*tx.id());
5209            pool.add_transaction(tx, on_chain_balance, on_chain_nonce, None).unwrap();
5210        }
5211
5212        // A block is mined with two txs (so nonce is changed from 40 to 42).
5213        // Now the balance gets so high that it's enough to execute alltxs.
5214        on_chain_balance = U256::from(999_999);
5215        on_chain_nonce = 42;
5216        pool.remove_transaction(&submitted_txs[0]);
5217        pool.remove_transaction(&submitted_txs[1]);
5218
5219        // Add 4 txs.
5220        for tx_nonce in 48..52 {
5221            pool.add_transaction(
5222                f.validated(template.clone().with_nonce(tx_nonce).rng_hash()),
5223                on_chain_balance,
5224                on_chain_nonce,
5225                None,
5226            )
5227            .unwrap();
5228        }
5229
5230        let best_txs: Vec<_> = pool.pending().best().map(|tx| *tx.id()).collect();
5231        assert_eq!(best_txs.len(), 10); // 8 - 2 + 4 = 10
5232
5233        assert_eq!(pool.pending_pool.independent().len(), 1);
5234    }
5235
5236    #[test]
5237    fn test_insertion_disorder() {
5238        let mut f = MockTransactionFactory::default();
5239        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5240
5241        let sender = address!("0x1234567890123456789012345678901234567890");
5242        let tx0 = f.validated_arc(
5243            MockTransaction::legacy().with_sender(sender).with_nonce(0).with_gas_price(10),
5244        );
5245        let tx1 = f.validated_arc(
5246            MockTransaction::eip1559()
5247                .with_sender(sender)
5248                .with_nonce(1)
5249                .with_gas_limit(1000)
5250                .with_gas_price(10),
5251        );
5252        let tx2 = f.validated_arc(
5253            MockTransaction::legacy().with_sender(sender).with_nonce(2).with_gas_price(10),
5254        );
5255        let tx3 = f.validated_arc(
5256            MockTransaction::legacy().with_sender(sender).with_nonce(3).with_gas_price(10),
5257        );
5258
5259        // tx0 should be put in the pending subpool
5260        pool.add_transaction((*tx0).clone(), U256::from(1000), 0, None).unwrap();
5261        let mut best = pool.best_transactions();
5262        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5263        assert_eq!(t0.id(), tx0.id());
5264        // tx1 should be put in the queued subpool due to insufficient sender balance
5265        pool.add_transaction((*tx1).clone(), U256::from(1000), 0, None).unwrap();
5266        let mut best = pool.best_transactions();
5267        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5268        assert_eq!(t0.id(), tx0.id());
5269        assert!(best.next().is_none());
5270
5271        // tx2 should be put in the pending subpool, and tx1 should be promoted to pending
5272        pool.add_transaction((*tx2).clone(), U256::MAX, 0, None).unwrap();
5273
5274        let mut best = pool.best_transactions();
5275
5276        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5277        let t1 = best.next().expect("tx1 should be put in the pending subpool");
5278        let t2 = best.next().expect("tx2 should be put in the pending subpool");
5279        assert_eq!(t0.id(), tx0.id());
5280        assert_eq!(t1.id(), tx1.id());
5281        assert_eq!(t2.id(), tx2.id());
5282
5283        // tx3 should be put in the pending subpool,
5284        pool.add_transaction((*tx3).clone(), U256::MAX, 0, None).unwrap();
5285        let mut best = pool.best_transactions();
5286        let t0 = best.next().expect("tx0 should be put in the pending subpool");
5287        let t1 = best.next().expect("tx1 should be put in the pending subpool");
5288        let t2 = best.next().expect("tx2 should be put in the pending subpool");
5289        let t3 = best.next().expect("tx3 should be put in the pending subpool");
5290        assert_eq!(t0.id(), tx0.id());
5291        assert_eq!(t1.id(), tx1.id());
5292        assert_eq!(t2.id(), tx2.id());
5293        assert_eq!(t3.id(), tx3.id());
5294    }
5295
5296    #[test]
5297    fn test_non_4844_blob_fee_bit_invariant() {
5298        let mut f = MockTransactionFactory::default();
5299        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5300
5301        let non_4844_tx = MockTransaction::eip1559().set_max_fee(200).inc_limit();
5302        let validated = f.validated(non_4844_tx.clone());
5303
5304        assert!(!non_4844_tx.is_eip4844());
5305        pool.add_transaction(validated.clone(), U256::from(10_000), 0, None).unwrap();
5306
5307        // Core invariant: Non-4844 transactions must ALWAYS have ENOUGH_BLOB_FEE_CAP_BLOCK bit
5308        let tx_meta = pool.all_transactions.txs.get(validated.id()).unwrap();
5309        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
5310        assert_eq!(tx_meta.subpool, SubPool::Pending);
5311    }
5312
5313    #[test]
5314    fn test_blob_fee_enforcement_only_applies_to_eip4844() {
5315        let mut f = MockTransactionFactory::default();
5316        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5317
5318        // Set blob fee higher than EIP-4844 tx can afford
5319        let mut block_info = pool.block_info();
5320        block_info.pending_blob_fee = Some(160);
5321        block_info.pending_basefee = 100;
5322        pool.set_block_info(block_info);
5323
5324        let eip4844_tx = MockTransaction::eip4844()
5325            .with_sender(Address::with_last_byte(0x0a))
5326            .with_max_fee(200)
5327            .with_blob_fee(150) // Less than block blob fee (160)
5328            .inc_limit();
5329
5330        let non_4844_tx = MockTransaction::eip1559()
5331            .with_sender(Address::with_last_byte(0x0b))
5332            .set_max_fee(200)
5333            .inc_limit();
5334
5335        let validated_4844 = f.validated(eip4844_tx);
5336        let validated_non_4844 = f.validated(non_4844_tx);
5337
5338        pool.add_transaction(validated_4844.clone(), U256::from(10_000), 0, None).unwrap();
5339        pool.add_transaction(validated_non_4844.clone(), U256::from(10_000), 0, None).unwrap();
5340
5341        let tx_4844_meta = pool.all_transactions.txs.get(validated_4844.id()).unwrap();
5342        let tx_non_4844_meta = pool.all_transactions.txs.get(validated_non_4844.id()).unwrap();
5343
5344        // EIP-4844: blob fee enforcement applies - insufficient blob fee removes bit
5345        assert!(!tx_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
5346        assert_eq!(tx_4844_meta.subpool, SubPool::Blob);
5347
5348        // Non-4844: blob fee enforcement does NOT apply - bit always remains true
5349        assert!(tx_non_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
5350        assert_eq!(tx_non_4844_meta.subpool, SubPool::Pending);
5351    }
5352
5353    #[test]
5354    fn test_basefee_decrease_preserves_non_4844_blob_fee_bit() {
5355        let mut f = MockTransactionFactory::default();
5356        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5357
5358        // Create non-4844 transaction with fee that initially can't afford high basefee
5359        let non_4844_tx = MockTransaction::eip1559()
5360            .with_sender(Address::with_last_byte(0x0a))
5361            .set_max_fee(500) // Can't afford basefee of 600
5362            .inc_limit();
5363
5364        // Set high basefee so transaction goes to BaseFee pool initially
5365        pool.update_basefee(600, |_| {});
5366
5367        let validated = f.validated(non_4844_tx);
5368        let tx_id = *validated.id();
5369        pool.add_transaction(validated, U256::from(10_000), 0, None).unwrap();
5370
5371        // Initially should be in BaseFee pool but STILL have blob fee bit (critical invariant)
5372        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
5373        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
5374        assert!(
5375            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
5376            "Non-4844 tx in BaseFee pool must retain ENOUGH_BLOB_FEE_CAP_BLOCK bit"
5377        );
5378
5379        // Decrease basefee - transaction should be promoted to Pending
5380        // This is where PR #18215 bug would manifest: blob fee bit incorrectly removed
5381        pool.update_basefee(400, |_| {});
5382
5383        // After basefee decrease: should be promoted to Pending with blob fee bit preserved
5384        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
5385        assert_eq!(
5386            tx_meta.subpool,
5387            SubPool::Pending,
5388            "Non-4844 tx should be promoted from BaseFee to Pending after basefee decrease"
5389        );
5390        assert!(
5391            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
5392            "Non-4844 tx must NEVER lose ENOUGH_BLOB_FEE_CAP_BLOCK bit during basefee promotion"
5393        );
5394        assert!(
5395            tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK),
5396            "Non-4844 tx should gain ENOUGH_FEE_CAP_BLOCK bit after basefee decrease"
5397        );
5398    }
5399
5400    /// Test for <https://github.com/paradigmxyz/reth/issues/17701>
5401    ///
5402    /// When a new transaction is added and its `updates` contain a same-sender transaction with
5403    /// a lower nonce, the lower-nonce tx must be added to the pending subpool *before* the
5404    /// higher-nonce tx. Otherwise, live `BestTransactions` iterators receive them out of order.
5405    #[test]
5406    fn best_transactions_nonce_order_on_balance_unlock() {
5407        let mut f = MockTransactionFactory::default();
5408        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5409
5410        let sender = Address::random();
5411        let on_chain_balance = U256::from(10_000);
5412
5413        // tx0: nonce 0, cheap — will go straight to pending
5414        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
5415        // tx1: nonce 1, very expensive — cumulative cost (tx0 + tx1) will exceed balance
5416        let tx1 = tx0.next().inc_limit().with_value(on_chain_balance);
5417        // tx2: nonce 2
5418        let tx2 = tx1.next().inc_limit().with_value(U256::ZERO);
5419
5420        let v0 = f.validated(tx0);
5421        let v1 = f.validated(tx1);
5422        let v2 = f.validated(tx2);
5423
5424        // Add tx0 with limited balance — goes to pending (tx0 cost is small: 1 * 100 = 100)
5425        pool.add_transaction(v0, on_chain_balance, 0, None).unwrap();
5426
5427        // Create a live BestTransactions iterator that will receive new pending txs
5428        let mut best = pool.best_transactions();
5429
5430        // Drain tx0 from the iterator
5431        let first = best.next().expect("should yield tx0");
5432        assert_eq!(first.id().nonce, 0);
5433
5434        // Add tx1 with the same limited balance — cumulative cost exceeds balance, goes to
5435        // queued
5436        pool.add_transaction(v1, on_chain_balance, 0, None).unwrap();
5437
5438        // tx1 should be queued, nothing new in best
5439        assert!(best.next().is_none(), "tx1 should be queued, not pending");
5440
5441        // Now add tx2 with U256::MAX balance — tx2 goes to pending AND tx1 gets promoted.
5442        // The bug: tx2 was added to pending *before* tx1, so BestTransactions yielded tx2
5443        // first, violating nonce ordering.
5444        pool.add_transaction(v2, U256::MAX, 0, None).unwrap();
5445
5446        let t1 = best.next().expect("should yield a transaction");
5447        let t2 = best.next().expect("should yield a transaction");
5448
5449        // Correct order: tx1 (nonce 1) before tx2 (nonce 2)
5450        assert_eq!(
5451            t1.id().nonce,
5452            1,
5453            "first yielded tx should be nonce 1, got nonce {}",
5454            t1.id().nonce
5455        );
5456        assert_eq!(
5457            t2.id().nonce,
5458            2,
5459            "second yielded tx should be nonce 2, got nonce {}",
5460            t2.id().nonce
5461        );
5462    }
5463
5464    /// Gap-fill scenario: inserting a low-nonce transaction promotes a queued higher-nonce
5465    /// transaction. The new (lower-nonce) tx must appear before the promoted (higher-nonce)
5466    /// tx in the `BestTransactions` iterator.
5467    #[test]
5468    fn best_transactions_nonce_order_on_gap_fill() {
5469        let mut f = MockTransactionFactory::default();
5470        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5471
5472        let sender = Address::random();
5473        let balance = U256::MAX;
5474
5475        // tx0: nonce 0
5476        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
5477        // tx1: nonce 1
5478        let tx1 = tx0.next().inc_limit();
5479
5480        let v0 = f.validated(tx0);
5481        let v1 = f.validated(tx1);
5482
5483        // Add tx1 first — goes to queued because nonce 0 is missing (nonce gap)
5484        pool.add_transaction(v1, balance, 0, None).unwrap();
5485
5486        // Create a live BestTransactions iterator (currently empty — nothing pending)
5487        let mut best = pool.best_transactions();
5488        assert!(best.next().is_none(), "pool should have no pending txs yet");
5489
5490        // Add tx0 — fills the gap, tx1 gets promoted
5491        pool.add_transaction(v0, balance, 0, None).unwrap();
5492
5493        let t0 = best.next().expect("should yield a transaction");
5494        let t1 = best.next().expect("should yield a transaction");
5495
5496        assert_eq!(t0.id().nonce, 0, "first yielded tx should be nonce 0, got {}", t0.id().nonce);
5497        assert_eq!(t1.id().nonce, 1, "second yielded tx should be nonce 1, got {}", t1.id().nonce);
5498    }
5499
5500    /// Mixed scenario: inserting a mid-nonce transaction promotes both a lower-nonce tx
5501    /// (via balance update) and a higher-nonce tx (via gap fill). All three must appear
5502    /// in nonce order in `BestTransactions`.
5503    #[test]
5504    fn best_transactions_nonce_order_mixed_promotions() {
5505        let mut f = MockTransactionFactory::default();
5506        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
5507
5508        let sender = Address::random();
5509        let low_balance = U256::from(10_000);
5510
5511        // tx0: nonce 0, cheap
5512        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
5513        // tx1: nonce 1, very expensive — will exceed balance
5514        let tx1 = tx0.next().inc_limit().with_value(low_balance);
5515        // tx2: nonce 2
5516        let tx2 = tx1.next().inc_limit().with_value(U256::ZERO);
5517        // tx3: nonce 3
5518        let tx3 = tx2.next().inc_limit().with_value(U256::ZERO);
5519
5520        let v0 = f.validated(tx0);
5521        let v1 = f.validated(tx1);
5522        let v2 = f.validated(tx2);
5523        let v3 = f.validated(tx3);
5524
5525        // Add tx0 — goes to pending
5526        pool.add_transaction(v0, low_balance, 0, None).unwrap();
5527
5528        // Add tx1 — queued (cumulative cost exceeds balance)
5529        pool.add_transaction(v1, low_balance, 0, None).unwrap();
5530
5531        // Add tx3 — queued (nonce gap: tx2 is missing)
5532        pool.add_transaction(v3, low_balance, 0, None).unwrap();
5533
5534        let mut best = pool.best_transactions();
5535
5536        // Drain tx0
5537        let first = best.next().expect("should yield tx0");
5538        assert_eq!(first.id().nonce, 0);
5539        assert!(best.next().is_none(), "only tx0 should be pending");
5540
5541        // Add tx2 with U256::MAX balance — this should:
5542        //  - promote tx1 (lower-nonce, was queued due to balance)
5543        //  - add tx2 itself to pending
5544        //  - promote tx3 (higher-nonce, was queued due to nonce gap)
5545        pool.add_transaction(v2, U256::MAX, 0, None).unwrap();
5546
5547        let t1 = best.next().expect("should yield nonce 1");
5548        let t2 = best.next().expect("should yield nonce 2");
5549        let t3 = best.next().expect("should yield nonce 3");
5550
5551        assert_eq!(t1.id().nonce, 1, "expected nonce 1, got {}", t1.id().nonce);
5552        assert_eq!(t2.id().nonce, 2, "expected nonce 2, got {}", t2.id().nonce);
5553        assert_eq!(t3.id().nonce, 3, "expected nonce 3, got {}", t3.id().nonce);
5554    }
5555}