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