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_conflicting_type_normal_to_blob() {
3109        let on_chain_balance = U256::from(10_000);
3110        let on_chain_nonce = 0;
3111        let mut f = MockTransactionFactory::default();
3112        let mut pool = AllTransactions::default();
3113        let tx = MockTransaction::eip1559().inc_price().inc_limit();
3114        let first = f.validated(tx.clone());
3115        pool.insert_tx(first, on_chain_balance, on_chain_nonce).unwrap();
3116        let tx = MockTransaction::eip4844().set_sender(tx.sender()).inc_price_by(100).inc_limit();
3117        let blob = f.validated(tx);
3118        let err = pool.insert_tx(blob, on_chain_balance, on_chain_nonce).unwrap_err();
3119        assert!(matches!(err, InsertErr::TxTypeConflict { .. }), "{err:?}");
3120    }
3121
3122    #[test]
3123    fn insert_conflicting_type_blob_to_normal() {
3124        let on_chain_balance = U256::from(10_000);
3125        let on_chain_nonce = 0;
3126        let mut f = MockTransactionFactory::default();
3127        let mut pool = AllTransactions::default();
3128        let tx = MockTransaction::eip4844().inc_price().inc_limit();
3129        let first = f.validated(tx.clone());
3130        pool.insert_tx(first, on_chain_balance, on_chain_nonce).unwrap();
3131        let tx = MockTransaction::eip1559().set_sender(tx.sender()).inc_price_by(100).inc_limit();
3132        let tx = f.validated(tx);
3133        let err = pool.insert_tx(tx, on_chain_balance, on_chain_nonce).unwrap_err();
3134        assert!(matches!(err, InsertErr::TxTypeConflict { .. }), "{err:?}");
3135    }
3136
3137    // insert nonce then nonce - 1
3138    #[test]
3139    fn insert_previous() {
3140        let on_chain_balance = U256::ZERO;
3141        let on_chain_nonce = 0;
3142        let mut f = MockTransactionFactory::default();
3143        let mut pool = AllTransactions::default();
3144        let tx = MockTransaction::eip1559().inc_nonce().inc_price().inc_limit();
3145        let first = f.validated(tx.clone());
3146        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce);
3147
3148        let first_in_pool = pool.get(first.id()).unwrap();
3149
3150        // has nonce gap
3151        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3152
3153        let prev = f.validated(tx.prev());
3154        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3155            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3156
3157        // no updates since still in queued pool
3158        assert!(updates.is_empty());
3159        assert!(replaced_tx.is_none());
3160        assert!(state.contains(TxState::NO_NONCE_GAPS));
3161        assert_eq!(move_to, SubPool::Queued);
3162
3163        let first_in_pool = pool.get(first.id()).unwrap();
3164        // has non nonce gap
3165        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3166    }
3167
3168    // insert nonce then nonce - 1
3169    #[test]
3170    fn insert_with_updates() {
3171        let on_chain_balance = U256::from(10_000);
3172        let on_chain_nonce = 0;
3173        let mut f = MockTransactionFactory::default();
3174        let mut pool = AllTransactions::default();
3175        let tx = MockTransaction::eip1559().inc_nonce().set_gas_price(100).inc_limit();
3176        let first = f.validated(tx.clone());
3177        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
3178
3179        let first_in_pool = pool.get(first.id()).unwrap();
3180        // has nonce gap
3181        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3182        assert_eq!(SubPool::Queued, first_in_pool.subpool);
3183
3184        let prev = f.validated(tx.prev());
3185        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3186            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3187
3188        // updated previous tx
3189        assert_eq!(updates.len(), 1);
3190        assert!(replaced_tx.is_none());
3191        assert!(state.contains(TxState::NO_NONCE_GAPS));
3192        assert_eq!(move_to, SubPool::Pending);
3193
3194        let first_in_pool = pool.get(first.id()).unwrap();
3195        // has non nonce gap
3196        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3197        assert_eq!(SubPool::Pending, first_in_pool.subpool);
3198    }
3199
3200    #[test]
3201    fn insert_previous_blocking() {
3202        let on_chain_balance = U256::from(1_000);
3203        let on_chain_nonce = 0;
3204        let mut f = MockTransactionFactory::default();
3205        let mut pool = AllTransactions::default();
3206        pool.pending_fees.base_fee = pool.minimal_protocol_basefee.checked_add(1).unwrap();
3207        let tx = MockTransaction::eip1559().inc_nonce().inc_limit();
3208        let first = f.validated(tx.clone());
3209
3210        let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce);
3211
3212        let first_in_pool = pool.get(first.id()).unwrap();
3213
3214        assert!(tx.get_gas_price() < pool.pending_fees.base_fee as u128);
3215        // has nonce gap
3216        assert!(!first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3217
3218        let prev = f.validated(tx.prev());
3219        let InsertOk { updates, replaced_tx, state, move_to, .. } =
3220            pool.insert_tx(prev, on_chain_balance, on_chain_nonce).unwrap();
3221
3222        assert!(!state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3223        // no updates since still in queued pool
3224        assert!(updates.is_empty());
3225        assert!(replaced_tx.is_none());
3226        assert!(state.contains(TxState::NO_NONCE_GAPS));
3227        assert_eq!(move_to, SubPool::BaseFee);
3228
3229        let first_in_pool = pool.get(first.id()).unwrap();
3230        // has non nonce gap
3231        assert!(first_in_pool.state.contains(TxState::NO_NONCE_GAPS));
3232    }
3233
3234    #[test]
3235    fn rejects_spammer() {
3236        let on_chain_balance = U256::from(1_000);
3237        let on_chain_nonce = 0;
3238        let mut f = MockTransactionFactory::default();
3239        let mut pool = AllTransactions::default();
3240
3241        let mut tx = MockTransaction::eip1559();
3242        let unblocked_tx = tx.clone();
3243        for _ in 0..pool.max_account_slots {
3244            tx = tx.next();
3245            pool.insert_tx(f.validated(tx.clone()), on_chain_balance, on_chain_nonce).unwrap();
3246        }
3247
3248        assert_eq!(
3249            pool.max_account_slots,
3250            pool.tx_count(f.ids.sender_id(tx.get_sender()).unwrap())
3251        );
3252
3253        let err =
3254            pool.insert_tx(f.validated(tx.next()), on_chain_balance, on_chain_nonce).unwrap_err();
3255        assert!(matches!(err, InsertErr::ExceededSenderTransactionsCapacity { .. }));
3256
3257        assert!(pool
3258            .insert_tx(f.validated(unblocked_tx), on_chain_balance, on_chain_nonce)
3259            .is_ok());
3260    }
3261
3262    #[test]
3263    fn allow_local_spamming() {
3264        let on_chain_balance = U256::from(1_000);
3265        let on_chain_nonce = 0;
3266        let mut f = MockTransactionFactory::default();
3267        let mut pool = AllTransactions::default();
3268
3269        let mut tx = MockTransaction::eip1559();
3270        for _ in 0..pool.max_account_slots {
3271            tx = tx.next();
3272            pool.insert_tx(
3273                f.validated_with_origin(TransactionOrigin::Local, tx.clone()),
3274                on_chain_balance,
3275                on_chain_nonce,
3276            )
3277            .unwrap();
3278        }
3279
3280        assert_eq!(
3281            pool.max_account_slots,
3282            pool.tx_count(f.ids.sender_id(tx.get_sender()).unwrap())
3283        );
3284
3285        pool.insert_tx(
3286            f.validated_with_origin(TransactionOrigin::Local, tx.next()),
3287            on_chain_balance,
3288            on_chain_nonce,
3289        )
3290        .unwrap();
3291    }
3292
3293    #[test]
3294    fn reject_tx_over_gas_limit() {
3295        let on_chain_balance = U256::from(1_000);
3296        let on_chain_nonce = 0;
3297        let mut f = MockTransactionFactory::default();
3298        let mut pool = AllTransactions::default();
3299
3300        let tx = MockTransaction::eip1559().with_gas_limit(30_000_001);
3301
3302        assert!(matches!(
3303            pool.insert_tx(f.validated(tx), on_chain_balance, on_chain_nonce),
3304            Err(InsertErr::TxGasLimitMoreThanAvailableBlockGas { .. })
3305        ));
3306    }
3307
3308    #[test]
3309    fn test_tx_equal_gas_limit() {
3310        let on_chain_balance = U256::from(1_000);
3311        let on_chain_nonce = 0;
3312        let mut f = MockTransactionFactory::default();
3313        let mut pool = AllTransactions::default();
3314
3315        let tx = MockTransaction::eip1559().with_gas_limit(30_000_000);
3316
3317        let InsertOk { state, .. } =
3318            pool.insert_tx(f.validated(tx), on_chain_balance, on_chain_nonce).unwrap();
3319        assert!(state.contains(TxState::NOT_TOO_MUCH_GAS));
3320    }
3321
3322    #[test]
3323    fn update_basefee_subpools() {
3324        let mut f = MockTransactionFactory::default();
3325        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3326
3327        let tx = MockTransaction::eip1559().inc_price_by(10);
3328        let validated = f.validated(tx.clone());
3329        let id = *validated.id();
3330        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3331
3332        assert_eq!(pool.pending_pool.len(), 1);
3333
3334        pool.update_basefee((tx.max_fee_per_gas() + 1) as u64, |_| {});
3335
3336        assert!(pool.pending_pool.is_empty());
3337        assert_eq!(pool.basefee_pool.len(), 1);
3338
3339        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3340    }
3341
3342    #[test]
3343    fn update_basefee_subpools_setting_block_info() {
3344        let mut f = MockTransactionFactory::default();
3345        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3346
3347        let tx = MockTransaction::eip1559().inc_price_by(10);
3348        let validated = f.validated(tx.clone());
3349        let id = *validated.id();
3350        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3351
3352        assert_eq!(pool.pending_pool.len(), 1);
3353
3354        // use set_block_info for the basefee update
3355        let mut block_info = pool.block_info();
3356        block_info.pending_basefee = (tx.max_fee_per_gas() + 1) as u64;
3357        pool.set_block_info(block_info);
3358
3359        assert!(pool.pending_pool.is_empty());
3360        assert_eq!(pool.basefee_pool.len(), 1);
3361
3362        assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee)
3363    }
3364
3365    #[test]
3366    fn basefee_decrease_promotes_affordable_and_keeps_unaffordable() {
3367        use alloy_primitives::address;
3368        let mut f = MockTransactionFactory::default();
3369        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3370
3371        // Create transactions that will be in basefee pool (can't afford initial high fee)
3372        // Use different senders to avoid nonce gap issues
3373        let sender_a = address!("0x000000000000000000000000000000000000000a");
3374        let sender_b = address!("0x000000000000000000000000000000000000000b");
3375        let sender_c = address!("0x000000000000000000000000000000000000000c");
3376
3377        let tx1 = MockTransaction::eip1559()
3378            .set_sender(sender_a)
3379            .set_nonce(0)
3380            .set_max_fee(500)
3381            .inc_limit();
3382        let tx2 = MockTransaction::eip1559()
3383            .set_sender(sender_b)
3384            .set_nonce(0)
3385            .set_max_fee(600)
3386            .inc_limit();
3387        let tx3 = MockTransaction::eip1559()
3388            .set_sender(sender_c)
3389            .set_nonce(0)
3390            .set_max_fee(400)
3391            .inc_limit();
3392
3393        // Set high initial basefee so transactions go to basefee pool
3394        let mut block_info = pool.block_info();
3395        block_info.pending_basefee = 700;
3396        pool.set_block_info(block_info);
3397
3398        let validated1 = f.validated(tx1);
3399        let validated2 = f.validated(tx2);
3400        let validated3 = f.validated(tx3);
3401        let id1 = *validated1.id();
3402        let id2 = *validated2.id();
3403        let id3 = *validated3.id();
3404
3405        // Add transactions - they should go to basefee pool due to high basefee
3406        // All transactions have nonce 0 from different senders, so on_chain_nonce should be 0 for
3407        // all
3408        pool.add_transaction(validated1, U256::from(10_000), 0, None).unwrap();
3409        pool.add_transaction(validated2, U256::from(10_000), 0, None).unwrap();
3410        pool.add_transaction(validated3, U256::from(10_000), 0, None).unwrap();
3411
3412        // Debug: Check where transactions ended up
3413        println!("Basefee pool len: {}", pool.basefee_pool.len());
3414        println!("Pending pool len: {}", pool.pending_pool.len());
3415        println!("tx1 subpool: {:?}", pool.all_transactions.txs.get(&id1).unwrap().subpool);
3416        println!("tx2 subpool: {:?}", pool.all_transactions.txs.get(&id2).unwrap().subpool);
3417        println!("tx3 subpool: {:?}", pool.all_transactions.txs.get(&id3).unwrap().subpool);
3418
3419        // Verify they're in basefee pool
3420        assert_eq!(pool.basefee_pool.len(), 3);
3421        assert_eq!(pool.pending_pool.len(), 0);
3422        assert_eq!(pool.all_transactions.txs.get(&id1).unwrap().subpool, SubPool::BaseFee);
3423        assert_eq!(pool.all_transactions.txs.get(&id2).unwrap().subpool, SubPool::BaseFee);
3424        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3425
3426        // Now decrease basefee to trigger the zero-allocation optimization
3427        let mut block_info = pool.block_info();
3428        block_info.pending_basefee = 450; // tx1 (500) and tx2 (600) can now afford it, tx3 (400) cannot
3429        pool.set_block_info(block_info);
3430
3431        // Verify the optimization worked correctly:
3432        // - tx1 and tx2 should be promoted to pending (mathematical certainty)
3433        // - tx3 should remain in basefee pool
3434        // - All state transitions should be correct
3435        assert_eq!(pool.basefee_pool.len(), 1);
3436        assert_eq!(pool.pending_pool.len(), 2);
3437
3438        // tx3 should still be in basefee pool (fee 400 < basefee 450)
3439        assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee);
3440
3441        // tx1 and tx2 should be in pending pool with correct state bits
3442        let tx1_meta = pool.all_transactions.txs.get(&id1).unwrap();
3443        let tx2_meta = pool.all_transactions.txs.get(&id2).unwrap();
3444        assert_eq!(tx1_meta.subpool, SubPool::Pending);
3445        assert_eq!(tx2_meta.subpool, SubPool::Pending);
3446        assert!(tx1_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3447        assert!(tx2_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3448
3449        // Verify that best_transactions returns the promoted transactions
3450        let best: Vec<_> = pool.best_transactions().take(3).collect();
3451        assert_eq!(best.len(), 2); // Only tx1 and tx2 should be returned
3452        assert!(best.iter().any(|tx| tx.id() == &id1));
3453        assert!(best.iter().any(|tx| tx.id() == &id2));
3454    }
3455
3456    #[test]
3457    fn apply_fee_updates_records_promotions_after_basefee_drop() {
3458        let mut f = MockTransactionFactory::default();
3459        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3460
3461        let tx = MockTransaction::eip1559()
3462            .with_gas_limit(21_000)
3463            .with_max_fee(500)
3464            .with_priority_fee(1);
3465        let validated = f.validated(tx);
3466        let id = *validated.id();
3467        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3468
3469        assert_eq!(pool.pending_pool.len(), 1);
3470
3471        // Raise base fee beyond the transaction's cap so it gets parked in BaseFee pool.
3472        pool.update_basefee(600, |_| {});
3473        assert!(pool.pending_pool.is_empty());
3474        assert_eq!(pool.basefee_pool.len(), 1);
3475
3476        let prev_base_fee = 600;
3477        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3478
3479        // Simulate the canonical state path updating pending fees before applying promotions.
3480        pool.all_transactions.pending_fees.base_fee = 400;
3481
3482        let mut outcome = UpdateOutcome::default();
3483        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3484
3485        assert_eq!(pool.pending_pool.len(), 1);
3486        assert!(pool.basefee_pool.is_empty());
3487        assert_eq!(outcome.promoted.len(), 1);
3488        assert_eq!(outcome.promoted[0].id(), &id);
3489        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
3490        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
3491
3492        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3493        assert_eq!(tx_meta.subpool, SubPool::Pending);
3494        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3495    }
3496
3497    #[test]
3498    fn apply_fee_updates_records_promotions_after_blob_fee_drop() {
3499        let mut f = MockTransactionFactory::default();
3500        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3501
3502        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3503
3504        let tx = MockTransaction::eip4844().with_blob_fee(initial_blob_fee + 100);
3505        let validated = f.validated(tx.clone());
3506        let id = *validated.id();
3507        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3508
3509        assert_eq!(pool.pending_pool.len(), 1);
3510
3511        // Raise blob fee beyond the transaction's cap so it gets parked in Blob pool.
3512        let increased_blob_fee = tx.max_fee_per_blob_gas().unwrap() + 200;
3513        pool.update_blob_fee(increased_blob_fee, Ordering::Equal, |_| {});
3514        assert!(pool.pending_pool.is_empty());
3515        assert_eq!(pool.blob_pool.len(), 1);
3516
3517        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
3518        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3519
3520        // Simulate the canonical state path updating pending fees before applying promotions.
3521        pool.all_transactions.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap();
3522
3523        let mut outcome = UpdateOutcome::default();
3524        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3525
3526        assert_eq!(pool.pending_pool.len(), 1);
3527        assert!(pool.blob_pool.is_empty());
3528        assert_eq!(outcome.promoted.len(), 1);
3529        assert_eq!(outcome.promoted[0].id(), &id);
3530        assert_eq!(pool.all_transactions.pending_fees.base_fee, prev_base_fee);
3531        assert_eq!(pool.all_transactions.pending_fees.blob_fee, tx.max_fee_per_blob_gas().unwrap());
3532
3533        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3534        assert_eq!(tx_meta.subpool, SubPool::Pending);
3535        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
3536        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3537    }
3538
3539    #[test]
3540    fn apply_fee_updates_promotes_blob_after_basefee_drop() {
3541        let mut f = MockTransactionFactory::default();
3542        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3543
3544        let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3545
3546        let tx = MockTransaction::eip4844()
3547            .with_max_fee(500)
3548            .with_priority_fee(1)
3549            .with_blob_fee(initial_blob_fee + 100);
3550        let validated = f.validated(tx);
3551        let id = *validated.id();
3552        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3553
3554        assert_eq!(pool.pending_pool.len(), 1);
3555
3556        // Raise base fee beyond the transaction's cap so it gets parked in Blob pool.
3557        let high_base_fee = 600;
3558        pool.update_basefee(high_base_fee, |_| {});
3559        assert!(pool.pending_pool.is_empty());
3560        assert_eq!(pool.blob_pool.len(), 1);
3561
3562        let prev_base_fee = high_base_fee;
3563        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3564
3565        // Simulate applying a lower base fee while keeping blob fee unchanged.
3566        pool.all_transactions.pending_fees.base_fee = 400;
3567
3568        let mut outcome = UpdateOutcome::default();
3569        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3570
3571        assert_eq!(pool.pending_pool.len(), 1);
3572        assert!(pool.blob_pool.is_empty());
3573        assert_eq!(outcome.promoted.len(), 1);
3574        assert_eq!(outcome.promoted[0].id(), &id);
3575        assert_eq!(pool.all_transactions.pending_fees.base_fee, 400);
3576        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
3577
3578        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3579        assert_eq!(tx_meta.subpool, SubPool::Pending);
3580        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
3581        assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3582    }
3583
3584    #[test]
3585    fn apply_fee_updates_demotes_after_basefee_rise() {
3586        let mut f = MockTransactionFactory::default();
3587        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3588
3589        let tx = MockTransaction::eip1559()
3590            .with_gas_limit(21_000)
3591            .with_max_fee(400)
3592            .with_priority_fee(1);
3593        let validated = f.validated(tx);
3594        let id = *validated.id();
3595        pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap();
3596
3597        assert_eq!(pool.pending_pool.len(), 1);
3598
3599        let prev_base_fee = pool.all_transactions.pending_fees.base_fee;
3600        let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee;
3601
3602        // Simulate canonical path raising the base fee beyond the transaction's cap.
3603        let new_base_fee = prev_base_fee + 1_000;
3604        pool.all_transactions.pending_fees.base_fee = new_base_fee;
3605
3606        let mut outcome = UpdateOutcome::default();
3607        pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome);
3608
3609        assert!(pool.pending_pool.is_empty());
3610        assert_eq!(pool.basefee_pool.len(), 1);
3611        assert!(outcome.promoted.is_empty());
3612        assert_eq!(pool.all_transactions.pending_fees.base_fee, new_base_fee);
3613        assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee);
3614
3615        let tx_meta = pool.all_transactions.txs.get(&id).unwrap();
3616        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
3617        assert!(!tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK));
3618    }
3619
3620    #[test]
3621    fn get_highest_transaction_by_sender_and_nonce() {
3622        // Set up a mock transaction factory and a new transaction pool.
3623        let mut f = MockTransactionFactory::default();
3624        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3625
3626        // Create a mock transaction and add it to the pool.
3627        let tx = MockTransaction::eip1559();
3628        pool.add_transaction(f.validated(tx.clone()), U256::from(1_000), 0, None).unwrap();
3629
3630        // Create another mock transaction with an incremented price.
3631        let tx1 = tx.inc_price().next();
3632
3633        // Validate the second mock transaction and add it to the pool.
3634        let tx1_validated = f.validated(tx1.clone());
3635        pool.add_transaction(tx1_validated, U256::from(1_000), 0, None).unwrap();
3636
3637        // Ensure that the calculated next nonce for the sender matches the expected value.
3638        assert_eq!(
3639            pool.get_highest_nonce_by_sender(f.ids.sender_id(&tx.sender()).unwrap()),
3640            Some(1)
3641        );
3642
3643        // Retrieve the highest transaction by sender.
3644        let highest_tx = pool
3645            .get_highest_transaction_by_sender(f.ids.sender_id(&tx.sender()).unwrap())
3646            .expect("Failed to retrieve highest transaction");
3647
3648        // Validate that the retrieved highest transaction matches the expected transaction.
3649        assert_eq!(highest_tx.as_ref().transaction, tx1);
3650    }
3651
3652    #[test]
3653    fn get_highest_consecutive_transaction_by_sender() {
3654        // Set up a mock transaction factory and a new transaction pool.
3655        let mut pool = TxPool::new(MockOrdering::default(), PoolConfig::default());
3656        let mut f = MockTransactionFactory::default();
3657
3658        // Create transactions with nonces 0, 1, 2, 4, 5.
3659        let sender = Address::random();
3660        let txs: Vec<_> = vec![0, 1, 2, 4, 5, 8, 9];
3661        for nonce in txs {
3662            let mut mock_tx = MockTransaction::eip1559();
3663            mock_tx.set_sender(sender);
3664            mock_tx.set_nonce(nonce);
3665
3666            let validated_tx = f.validated(mock_tx);
3667            pool.add_transaction(validated_tx, U256::from(1000), 0, None).unwrap();
3668        }
3669
3670        // Get last consecutive transaction
3671        let sender_id = f.ids.sender_id(&sender).unwrap();
3672        let next_tx =
3673            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(0));
3674        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(2), "Expected nonce 2 for on-chain nonce 0");
3675
3676        let next_tx =
3677            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(4));
3678        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 4");
3679
3680        let next_tx =
3681            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
3682        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(5), "Expected nonce 5 for on-chain nonce 5");
3683
3684        // update the tracked nonce
3685        let mut info = SenderInfo::default();
3686        info.update(8, U256::ZERO);
3687        pool.all_transactions.sender_info.insert(sender_id, info);
3688        let next_tx =
3689            pool.get_highest_consecutive_transaction_by_sender(sender_id.into_transaction_id(5));
3690        assert_eq!(next_tx.map(|tx| tx.nonce()), Some(9), "Expected nonce 9 for on-chain nonce 8");
3691    }
3692
3693    #[test]
3694    fn discard_nonce_too_low() {
3695        let mut f = MockTransactionFactory::default();
3696        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3697
3698        let tx = MockTransaction::eip1559().inc_price_by(10);
3699        let validated = f.validated(tx.clone());
3700        let id = *validated.id();
3701        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3702
3703        let next = tx.next();
3704        let validated = f.validated(next.clone());
3705        pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3706
3707        assert_eq!(pool.pending_pool.len(), 2);
3708
3709        let mut changed_senders = HashMap::default();
3710        changed_senders.insert(
3711            id.sender,
3712            SenderInfo { state_nonce: next.nonce(), balance: U256::from(1_000) },
3713        );
3714        let outcome = pool.update_accounts(changed_senders);
3715        assert_eq!(outcome.discarded.len(), 1);
3716        assert_eq!(pool.pending_pool.len(), 1);
3717    }
3718
3719    #[test]
3720    fn discard_with_large_blob_txs() {
3721        // init tracing
3722        reth_tracing::init_test_tracing();
3723
3724        // this test adds large txs to the parked pool, then attempting to discard worst
3725        let mut f = MockTransactionFactory::default();
3726        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3727        let default_limits = pool.config.blob_limit;
3728
3729        // create a chain of transactions by sender A
3730        // make sure they are all one over half the limit
3731        let a_sender = address!("0x000000000000000000000000000000000000000a");
3732
3733        // set the base fee of the pool
3734        let mut block_info = pool.block_info();
3735        block_info.pending_blob_fee = Some(100);
3736        block_info.pending_basefee = 100;
3737
3738        // update
3739        pool.set_block_info(block_info);
3740
3741        // 2 txs, that should put the pool over the size limit but not max txs
3742        let a_txs = MockTransactionSet::dependent(a_sender, 0, 2, TxType::Eip4844)
3743            .into_iter()
3744            .map(|mut tx| {
3745                tx.set_size(default_limits.max_size / 2 + 1);
3746                tx.set_max_fee((block_info.pending_basefee - 1).into());
3747                tx
3748            })
3749            .collect::<Vec<_>>();
3750
3751        // add all the transactions to the parked pool
3752        for tx in a_txs {
3753            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
3754        }
3755
3756        // truncate the pool, it should remove at least one transaction
3757        let removed = pool.discard_worst();
3758        assert_eq!(removed.len(), 1);
3759    }
3760
3761    #[test]
3762    fn discard_with_parked_large_txs() {
3763        // init tracing
3764        reth_tracing::init_test_tracing();
3765
3766        // this test adds large txs to the parked pool, then attempting to discard worst
3767        let mut f = MockTransactionFactory::default();
3768        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3769        let default_limits = pool.config.queued_limit;
3770
3771        // create a chain of transactions by sender A
3772        // make sure they are all one over half the limit
3773        let a_sender = address!("0x000000000000000000000000000000000000000a");
3774
3775        // set the base fee of the pool
3776        let pool_base_fee = 100;
3777        pool.update_basefee(pool_base_fee, |_| {});
3778
3779        // 2 txs, that should put the pool over the size limit but not max txs
3780        let a_txs = MockTransactionSet::dependent(a_sender, 0, 3, TxType::Eip1559)
3781            .into_iter()
3782            .map(|mut tx| {
3783                tx.set_size(default_limits.max_size / 2 + 1);
3784                tx.set_max_fee((pool_base_fee - 1).into());
3785                tx
3786            })
3787            .collect::<Vec<_>>();
3788
3789        // add all the transactions to the parked pool
3790        for tx in a_txs {
3791            pool.add_transaction(f.validated(tx), U256::from(1_000), 0, None).unwrap();
3792        }
3793
3794        // truncate the pool, it should remove at least one transaction
3795        let removed = pool.discard_worst();
3796        assert_eq!(removed.len(), 1);
3797    }
3798
3799    #[test]
3800    fn discard_at_capacity() {
3801        let mut f = MockTransactionFactory::default();
3802        let queued_limit = SubPoolLimit::new(1000, usize::MAX);
3803        let mut pool =
3804            TxPool::new(MockOrdering::default(), PoolConfig { queued_limit, ..Default::default() });
3805
3806        // insert a bunch of transactions into the queued pool
3807        for _ in 0..queued_limit.max_txs {
3808            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
3809            let validated = f.validated(tx.clone());
3810            let _id = *validated.id();
3811            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3812        }
3813
3814        let size = pool.size();
3815        assert_eq!(size.queued, queued_limit.max_txs);
3816
3817        for _ in 0..queued_limit.max_txs {
3818            let tx = MockTransaction::eip1559().inc_price_by(10).inc_nonce();
3819            let validated = f.validated(tx.clone());
3820            let _id = *validated.id();
3821            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3822
3823            pool.discard_worst();
3824            pool.assert_invariants();
3825            assert!(pool.size().queued <= queued_limit.max_txs);
3826        }
3827    }
3828
3829    #[test]
3830    fn discard_blobs_at_capacity() {
3831        let mut f = MockTransactionFactory::default();
3832        let blob_limit = SubPoolLimit::new(1000, usize::MAX);
3833        let mut pool =
3834            TxPool::new(MockOrdering::default(), PoolConfig { blob_limit, ..Default::default() });
3835        pool.all_transactions.pending_fees.blob_fee = 10000;
3836        // insert a bunch of transactions into the queued pool
3837        for _ in 0..blob_limit.max_txs {
3838            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
3839            let validated = f.validated(tx.clone());
3840            let _id = *validated.id();
3841            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3842        }
3843
3844        let size = pool.size();
3845        assert_eq!(size.blob, blob_limit.max_txs);
3846
3847        for _ in 0..blob_limit.max_txs {
3848            let tx = MockTransaction::eip4844().inc_price_by(100).with_blob_fee(100);
3849            let validated = f.validated(tx.clone());
3850            let _id = *validated.id();
3851            pool.add_transaction(validated, U256::from(1_000), 0, None).unwrap();
3852
3853            pool.discard_worst();
3854            pool.assert_invariants();
3855            assert!(pool.size().blob <= blob_limit.max_txs);
3856        }
3857    }
3858
3859    #[test]
3860    fn account_updates_sender_balance() {
3861        let mut on_chain_balance = U256::from(100);
3862        let on_chain_nonce = 0;
3863        let mut f = MockTransactionFactory::default();
3864        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3865
3866        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3867        let tx_1 = tx_0.next();
3868        let tx_2 = tx_1.next();
3869
3870        // Create 3 transactions
3871        let v0 = f.validated(tx_0);
3872        let v1 = f.validated(tx_1);
3873        let v2 = f.validated(tx_2);
3874
3875        let _res =
3876            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3877        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
3878        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
3879
3880        // The sender does not have enough balance to put all txs into pending.
3881        assert_eq!(1, pool.pending_transactions().len());
3882        assert_eq!(2, pool.queued_transactions().len());
3883
3884        // Simulate new block arrival - and chain balance increase.
3885        let mut updated_accounts = HashMap::default();
3886        on_chain_balance = U256::from(300);
3887        updated_accounts.insert(
3888            v0.sender_id(),
3889            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
3890        );
3891        pool.update_accounts(updated_accounts.clone());
3892
3893        assert_eq!(3, pool.pending_transactions().len());
3894        assert!(pool.queued_transactions().is_empty());
3895
3896        // Simulate new block arrival - and chain balance decrease.
3897        updated_accounts.entry(v0.sender_id()).and_modify(|v| v.balance = U256::from(1));
3898        pool.update_accounts(updated_accounts);
3899
3900        assert!(pool.pending_transactions().is_empty());
3901        assert_eq!(3, pool.queued_transactions().len());
3902    }
3903
3904    #[test]
3905    fn account_updates_nonce_gap() {
3906        let on_chain_balance = U256::from(10_000);
3907        let mut on_chain_nonce = 0;
3908        let mut f = MockTransactionFactory::default();
3909        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3910
3911        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3912        let tx_1 = tx_0.next();
3913        let tx_2 = tx_1.next();
3914
3915        // Create 3 transactions
3916        let v0 = f.validated(tx_0);
3917        let v1 = f.validated(tx_1);
3918        let v2 = f.validated(tx_2);
3919
3920        // Add first 2 to the pool
3921        let _res =
3922            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3923        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
3924
3925        assert!(pool.queued_transactions().is_empty());
3926        assert_eq!(2, pool.pending_transactions().len());
3927
3928        // Remove first (nonce 0)
3929        pool.remove_transaction_by_hash(v0.hash());
3930
3931        // Now add transaction with nonce 2
3932        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
3933
3934        // v1 and v2 should both be in the queue now.
3935        assert_eq!(2, pool.queued_transactions().len());
3936        assert!(pool.pending_transactions().is_empty());
3937
3938        // Simulate new block arrival - and chain nonce increasing.
3939        let mut updated_accounts = HashMap::default();
3940        on_chain_nonce += 1;
3941        updated_accounts.insert(
3942            v0.sender_id(),
3943            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
3944        );
3945        pool.update_accounts(updated_accounts);
3946
3947        // 'pending' now).
3948        assert!(pool.queued_transactions().is_empty());
3949        assert_eq!(2, pool.pending_transactions().len());
3950    }
3951    #[test]
3952    fn test_transaction_removal() {
3953        let on_chain_balance = U256::from(10_000);
3954        let on_chain_nonce = 0;
3955        let mut f = MockTransactionFactory::default();
3956        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
3957
3958        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3959        let tx_1 = tx_0.next();
3960
3961        // Create 2 transactions
3962        let v0 = f.validated(tx_0);
3963        let v1 = f.validated(tx_1);
3964
3965        // Add them to the pool
3966        let _res =
3967            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3968        let _res =
3969            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
3970
3971        assert_eq!(0, pool.queued_transactions().len());
3972        assert_eq!(2, pool.pending_transactions().len());
3973
3974        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
3975        pool.remove_transaction(v0.id());
3976        // assert the second transaction is really at the top of the queue
3977        let pool_txs = pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>();
3978        assert_eq!(vec![v1.nonce()], pool_txs);
3979    }
3980    #[test]
3981    fn test_remove_transactions() {
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        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
3990        let tx_3 = tx_2.next();
3991
3992        // Create 4 transactions
3993        let v0 = f.validated(tx_0);
3994        let v1 = f.validated(tx_1);
3995        let v2 = f.validated(tx_2);
3996        let v3 = f.validated(tx_3);
3997
3998        // Add them to the pool
3999        let _res =
4000            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4001        let _res =
4002            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4003        let _res =
4004            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4005        let _res =
4006            pool.add_transaction(v3.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4007
4008        assert_eq!(0, pool.queued_transactions().len());
4009        assert_eq!(4, pool.pending_transactions().len());
4010
4011        pool.remove_transactions(vec![*v0.hash(), *v2.hash()]);
4012
4013        assert_eq!(2, pool.queued_transactions().len());
4014        assert!(pool.pending_transactions().is_empty());
4015        assert!(pool.contains(v1.hash()));
4016        assert!(pool.contains(v3.hash()));
4017    }
4018
4019    #[test]
4020    fn test_remove_transactions_middle_pending_hash() {
4021        let on_chain_balance = U256::from(10_000);
4022        let on_chain_nonce = 0;
4023        let mut f = MockTransactionFactory::default();
4024        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4025
4026        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4027        let tx_1 = tx_0.next();
4028        let tx_2 = tx_1.next();
4029        let tx_3 = tx_2.next();
4030
4031        // Create 4 transactions
4032        let v0 = f.validated(tx_0);
4033        let v1 = f.validated(tx_1);
4034        let v2 = f.validated(tx_2);
4035        let v3 = f.validated(tx_3);
4036
4037        // Add them to the pool
4038        let _res = pool.add_transaction(v0, on_chain_balance, on_chain_nonce, None).unwrap();
4039        let _res =
4040            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4041        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4042        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4043
4044        assert_eq!(0, pool.queued_transactions().len());
4045        assert_eq!(4, pool.pending_transactions().len());
4046
4047        let mut removed_txs = pool.remove_transactions(vec![*v1.hash()]);
4048        assert_eq!(1, removed_txs.len());
4049
4050        assert_eq!(2, pool.queued_transactions().len());
4051        assert_eq!(1, pool.pending_transactions().len());
4052
4053        // reinsert
4054        let removed_tx = removed_txs.pop().unwrap();
4055        let v1 = f.validated(removed_tx.transaction.clone());
4056        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4057        assert_eq!(0, pool.queued_transactions().len());
4058        assert_eq!(4, pool.pending_transactions().len());
4059    }
4060
4061    #[test]
4062    fn test_remove_transactions_and_descendants() {
4063        let on_chain_balance = U256::from(10_000);
4064        let on_chain_nonce = 0;
4065        let mut f = MockTransactionFactory::default();
4066        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4067
4068        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4069        let tx_1 = tx_0.next();
4070        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4071        let tx_3 = tx_2.next();
4072        let tx_4 = tx_3.next();
4073
4074        // Create 5 transactions
4075        let v0 = f.validated(tx_0);
4076        let v1 = f.validated(tx_1);
4077        let v2 = f.validated(tx_2);
4078        let v3 = f.validated(tx_3);
4079        let v4 = f.validated(tx_4);
4080
4081        // Add them to the pool
4082        let _res =
4083            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4084        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4085        let _res =
4086            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4087        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4088        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
4089
4090        assert_eq!(0, pool.queued_transactions().len());
4091        assert_eq!(5, pool.pending_transactions().len());
4092
4093        pool.remove_transactions_and_descendants(vec![*v0.hash(), *v2.hash()]);
4094
4095        assert_eq!(0, pool.queued_transactions().len());
4096        assert_eq!(0, pool.pending_transactions().len());
4097    }
4098    #[test]
4099    fn test_remove_descendants() {
4100        let on_chain_balance = U256::from(10_000);
4101        let on_chain_nonce = 0;
4102        let mut f = MockTransactionFactory::default();
4103        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4104
4105        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4106        let tx_1 = tx_0.next();
4107        let tx_2 = tx_1.next();
4108        let tx_3 = tx_2.next();
4109
4110        // Create 4 transactions
4111        let v0 = f.validated(tx_0);
4112        let v1 = f.validated(tx_1);
4113        let v2 = f.validated(tx_2);
4114        let v3 = f.validated(tx_3);
4115
4116        // Add them to the  pool
4117        let _res =
4118            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4119        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4120        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4121        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4122
4123        assert_eq!(0, pool.queued_transactions().len());
4124        assert_eq!(4, pool.pending_transactions().len());
4125
4126        let mut removed = Vec::new();
4127        pool.remove_transaction(v0.id());
4128        pool.remove_descendants(v0.id(), &mut removed);
4129
4130        assert_eq!(0, pool.queued_transactions().len());
4131        assert_eq!(0, pool.pending_transactions().len());
4132        assert_eq!(3, removed.len());
4133    }
4134    #[test]
4135    fn test_remove_transactions_by_sender() {
4136        let on_chain_balance = U256::from(10_000);
4137        let on_chain_nonce = 0;
4138        let mut f = MockTransactionFactory::default();
4139        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4140
4141        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4142        let tx_1 = tx_0.next();
4143        let tx_2 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4144        let tx_3 = tx_2.next();
4145        let tx_4 = tx_3.next();
4146
4147        // Create 5 transactions
4148        let v0 = f.validated(tx_0);
4149        let v1 = f.validated(tx_1);
4150        let v2 = f.validated(tx_2);
4151        let v3 = f.validated(tx_3);
4152        let v4 = f.validated(tx_4);
4153
4154        // Add them to the pool
4155        let _res =
4156            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4157        let _res =
4158            pool.add_transaction(v1.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4159        let _res =
4160            pool.add_transaction(v2.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4161        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4162        let _res = pool.add_transaction(v4, on_chain_balance, on_chain_nonce, None).unwrap();
4163
4164        assert_eq!(0, pool.queued_transactions().len());
4165        assert_eq!(5, pool.pending_transactions().len());
4166
4167        pool.remove_transactions_by_sender(v2.sender_id());
4168
4169        assert_eq!(0, pool.queued_transactions().len());
4170        assert_eq!(2, pool.pending_transactions().len());
4171        assert!(pool.contains(v0.hash()));
4172        assert!(pool.contains(v1.hash()));
4173    }
4174    #[test]
4175    fn wrong_best_order_of_transactions() {
4176        let on_chain_balance = U256::from(10_000);
4177        let mut on_chain_nonce = 0;
4178        let mut f = MockTransactionFactory::default();
4179        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4180
4181        let tx_0 = MockTransaction::eip1559().set_gas_price(100).inc_limit();
4182        let tx_1 = tx_0.next();
4183        let tx_2 = tx_1.next();
4184        let tx_3 = tx_2.next();
4185
4186        // Create 4 transactions
4187        let v0 = f.validated(tx_0);
4188        let v1 = f.validated(tx_1);
4189        let v2 = f.validated(tx_2);
4190        let v3 = f.validated(tx_3);
4191
4192        // Add first 2 to the pool
4193        let _res =
4194            pool.add_transaction(v0.clone(), on_chain_balance, on_chain_nonce, None).unwrap();
4195        let _res = pool.add_transaction(v1, on_chain_balance, on_chain_nonce, None).unwrap();
4196
4197        assert_eq!(0, pool.queued_transactions().len());
4198        assert_eq!(2, pool.pending_transactions().len());
4199
4200        // Remove first (nonce 0) - simulating that it was taken to be a part of the block.
4201        pool.remove_transaction(v0.id());
4202
4203        // Now add transaction with nonce 2
4204        let _res = pool.add_transaction(v2, on_chain_balance, on_chain_nonce, None).unwrap();
4205
4206        // v2 is in the queue now. v1 is still in 'pending'.
4207        assert_eq!(1, pool.queued_transactions().len());
4208        assert_eq!(1, pool.pending_transactions().len());
4209
4210        // Simulate new block arrival - and chain nonce increasing.
4211        let mut updated_accounts = HashMap::default();
4212        on_chain_nonce += 1;
4213        updated_accounts.insert(
4214            v0.sender_id(),
4215            SenderInfo { state_nonce: on_chain_nonce, balance: on_chain_balance },
4216        );
4217        pool.update_accounts(updated_accounts);
4218
4219        // Transactions are not changed (IMHO - this is a bug, as transaction v2 should be in the
4220        // 'pending' now).
4221        assert_eq!(0, pool.queued_transactions().len());
4222        assert_eq!(2, pool.pending_transactions().len());
4223
4224        // Add transaction v3 - it 'unclogs' everything.
4225        let _res = pool.add_transaction(v3, on_chain_balance, on_chain_nonce, None).unwrap();
4226        assert_eq!(0, pool.queued_transactions().len());
4227        assert_eq!(3, pool.pending_transactions().len());
4228
4229        // It should have returned transactions in order (v1, v2, v3 - as there is nothing blocking
4230        // them).
4231        assert_eq!(
4232            pool.best_transactions().map(|x| x.id().nonce).collect::<Vec<_>>(),
4233            vec![1, 2, 3]
4234        );
4235    }
4236
4237    #[test]
4238    fn test_best_with_attributes() {
4239        let on_chain_balance = U256::MAX;
4240        let on_chain_nonce = 0;
4241        let mut f = MockTransactionFactory::default();
4242        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4243
4244        let base_fee: u128 = 100;
4245        let blob_fee: u128 = 100;
4246
4247        // set base fee and blob fee.
4248        let mut block_info = pool.block_info();
4249        block_info.pending_basefee = base_fee as u64;
4250        block_info.pending_blob_fee = Some(blob_fee);
4251        pool.set_block_info(block_info);
4252
4253        // Insert transactions with varying max_fee_per_gas and max_fee_per_blob_gas.
4254        let tx1 = MockTransaction::eip4844()
4255            .with_sender(Address::with_last_byte(1))
4256            .with_max_fee(base_fee + 10)
4257            .with_blob_fee(blob_fee + 10);
4258        let tx2 = MockTransaction::eip4844()
4259            .with_sender(Address::with_last_byte(2))
4260            .with_max_fee(base_fee + 10)
4261            .with_blob_fee(blob_fee);
4262        let tx3 = MockTransaction::eip4844()
4263            .with_sender(Address::with_last_byte(3))
4264            .with_max_fee(base_fee)
4265            .with_blob_fee(blob_fee + 10);
4266        let tx4 = MockTransaction::eip4844()
4267            .with_sender(Address::with_last_byte(4))
4268            .with_max_fee(base_fee)
4269            .with_blob_fee(blob_fee);
4270        let tx5 = MockTransaction::eip4844()
4271            .with_sender(Address::with_last_byte(5))
4272            .with_max_fee(base_fee)
4273            .with_blob_fee(blob_fee - 10);
4274        let tx6 = MockTransaction::eip4844()
4275            .with_sender(Address::with_last_byte(6))
4276            .with_max_fee(base_fee - 10)
4277            .with_blob_fee(blob_fee);
4278        let tx7 = MockTransaction::eip4844()
4279            .with_sender(Address::with_last_byte(7))
4280            .with_max_fee(base_fee - 10)
4281            .with_blob_fee(blob_fee - 10);
4282
4283        for tx in vec![
4284            tx1.clone(),
4285            tx2.clone(),
4286            tx3.clone(),
4287            tx4.clone(),
4288            tx5.clone(),
4289            tx6.clone(),
4290            tx7.clone(),
4291        ] {
4292            pool.add_transaction(f.validated(tx.clone()), on_chain_balance, on_chain_nonce, None)
4293                .unwrap();
4294        }
4295
4296        let base_fee = base_fee as u64;
4297        let blob_fee = blob_fee as u64;
4298
4299        let cases = vec![
4300            // 1. Base fee increase, blob fee increase
4301            (BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee + 5)), vec![tx1.clone()]),
4302            // 2. Base fee increase, blob fee not change
4303            (
4304                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee)),
4305                vec![tx1.clone(), tx2.clone()],
4306            ),
4307            // 3. Base fee increase, blob fee decrease
4308            (
4309                BestTransactionsAttributes::new(base_fee + 5, Some(blob_fee - 5)),
4310                vec![tx1.clone(), tx2.clone()],
4311            ),
4312            // 4. Base fee not change, blob fee increase
4313            (
4314                BestTransactionsAttributes::new(base_fee, Some(blob_fee + 5)),
4315                vec![tx1.clone(), tx3.clone()],
4316            ),
4317            // 5. Base fee not change, blob fee not change
4318            (
4319                BestTransactionsAttributes::new(base_fee, Some(blob_fee)),
4320                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()],
4321            ),
4322            // 6. Base fee not change, blob fee decrease
4323            (
4324                BestTransactionsAttributes::new(base_fee, Some(blob_fee - 10)),
4325                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx5.clone()],
4326            ),
4327            // 7. Base fee decrease, blob fee increase
4328            (
4329                BestTransactionsAttributes::new(base_fee - 5, Some(blob_fee + 5)),
4330                vec![tx1.clone(), tx3.clone()],
4331            ),
4332            // 8. Base fee decrease, blob fee not change
4333            (
4334                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee)),
4335                vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone(), tx6.clone()],
4336            ),
4337            // 9. Base fee decrease, blob fee decrease
4338            (
4339                BestTransactionsAttributes::new(base_fee - 10, Some(blob_fee - 10)),
4340                vec![tx1, tx2, tx5, tx3, tx4, tx6, tx7],
4341            ),
4342        ];
4343
4344        for (idx, (attribute, expected)) in cases.into_iter().enumerate() {
4345            let mut best = pool.best_transactions_with_attributes(attribute);
4346
4347            for (tx_idx, expected_tx) in expected.into_iter().enumerate() {
4348                let tx = best.next().expect("Transaction should be returned");
4349                assert_eq!(
4350                    tx.transaction,
4351                    expected_tx,
4352                    "Failed tx {} in case {}",
4353                    tx_idx + 1,
4354                    idx + 1
4355                );
4356            }
4357
4358            // No more transactions should be returned
4359            assert!(best.next().is_none());
4360        }
4361    }
4362
4363    #[test]
4364    fn test_pending_ordering() {
4365        let mut f = MockTransactionFactory::default();
4366        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4367
4368        let tx_0 = MockTransaction::eip1559().with_nonce(1).set_gas_price(100).inc_limit();
4369        let tx_1 = tx_0.next();
4370
4371        let v0 = f.validated(tx_0);
4372        let v1 = f.validated(tx_1);
4373
4374        // nonce gap, tx should be queued
4375        pool.add_transaction(v0.clone(), U256::MAX, 0, None).unwrap();
4376        assert_eq!(1, pool.queued_transactions().len());
4377
4378        // nonce gap is closed on-chain, both transactions should be moved to pending
4379        pool.add_transaction(v1, U256::MAX, 1, None).unwrap();
4380
4381        assert_eq!(2, pool.pending_transactions().len());
4382        assert_eq!(0, pool.queued_transactions().len());
4383
4384        assert_eq!(
4385            pool.pending_pool.independent().get(&v0.sender_id()).unwrap().transaction.nonce(),
4386            v0.nonce()
4387        );
4388    }
4389
4390    // <https://github.com/paradigmxyz/reth/issues/12286>
4391    #[test]
4392    fn one_sender_one_independent_transaction() {
4393        let mut on_chain_balance = U256::from(4_999); // only enough for 4 txs
4394        let mut on_chain_nonce = 40;
4395        let mut f = MockTransactionFactory::default();
4396        let mut pool = TxPool::mock();
4397        let mut submitted_txs = Vec::new();
4398
4399        // We use a "template" because we want all txs to have the same sender.
4400        let template =
4401            MockTransaction::eip1559().inc_price().inc_limit().with_value(U256::from(1_001));
4402
4403        // Add 8 txs. Because the balance is only sufficient for 4, so the last 4 will be
4404        // Queued.
4405        for tx_nonce in 40..48 {
4406            let tx = f.validated(template.clone().with_nonce(tx_nonce).rng_hash());
4407            submitted_txs.push(*tx.id());
4408            pool.add_transaction(tx, on_chain_balance, on_chain_nonce, None).unwrap();
4409        }
4410
4411        // A block is mined with two txs (so nonce is changed from 40 to 42).
4412        // Now the balance gets so high that it's enough to execute alltxs.
4413        on_chain_balance = U256::from(999_999);
4414        on_chain_nonce = 42;
4415        pool.remove_transaction(&submitted_txs[0]);
4416        pool.remove_transaction(&submitted_txs[1]);
4417
4418        // Add 4 txs.
4419        for tx_nonce in 48..52 {
4420            pool.add_transaction(
4421                f.validated(template.clone().with_nonce(tx_nonce).rng_hash()),
4422                on_chain_balance,
4423                on_chain_nonce,
4424                None,
4425            )
4426            .unwrap();
4427        }
4428
4429        let best_txs: Vec<_> = pool.pending().best().map(|tx| *tx.id()).collect();
4430        assert_eq!(best_txs.len(), 10); // 8 - 2 + 4 = 10
4431
4432        assert_eq!(pool.pending_pool.independent().len(), 1);
4433    }
4434
4435    #[test]
4436    fn test_insertion_disorder() {
4437        let mut f = MockTransactionFactory::default();
4438        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4439
4440        let sender = address!("0x1234567890123456789012345678901234567890");
4441        let tx0 = f.validated_arc(
4442            MockTransaction::legacy().with_sender(sender).with_nonce(0).with_gas_price(10),
4443        );
4444        let tx1 = f.validated_arc(
4445            MockTransaction::eip1559()
4446                .with_sender(sender)
4447                .with_nonce(1)
4448                .with_gas_limit(1000)
4449                .with_gas_price(10),
4450        );
4451        let tx2 = f.validated_arc(
4452            MockTransaction::legacy().with_sender(sender).with_nonce(2).with_gas_price(10),
4453        );
4454        let tx3 = f.validated_arc(
4455            MockTransaction::legacy().with_sender(sender).with_nonce(3).with_gas_price(10),
4456        );
4457
4458        // tx0 should be put in the pending subpool
4459        pool.add_transaction((*tx0).clone(), U256::from(1000), 0, None).unwrap();
4460        let mut best = pool.best_transactions();
4461        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4462        assert_eq!(t0.id(), tx0.id());
4463        // tx1 should be put in the queued subpool due to insufficient sender balance
4464        pool.add_transaction((*tx1).clone(), U256::from(1000), 0, None).unwrap();
4465        let mut best = pool.best_transactions();
4466        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4467        assert_eq!(t0.id(), tx0.id());
4468        assert!(best.next().is_none());
4469
4470        // tx2 should be put in the pending subpool, and tx1 should be promoted to pending
4471        pool.add_transaction((*tx2).clone(), U256::MAX, 0, None).unwrap();
4472
4473        let mut best = pool.best_transactions();
4474
4475        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4476        let t1 = best.next().expect("tx1 should be put in the pending subpool");
4477        let t2 = best.next().expect("tx2 should be put in the pending subpool");
4478        assert_eq!(t0.id(), tx0.id());
4479        assert_eq!(t1.id(), tx1.id());
4480        assert_eq!(t2.id(), tx2.id());
4481
4482        // tx3 should be put in the pending subpool,
4483        pool.add_transaction((*tx3).clone(), U256::MAX, 0, None).unwrap();
4484        let mut best = pool.best_transactions();
4485        let t0 = best.next().expect("tx0 should be put in the pending subpool");
4486        let t1 = best.next().expect("tx1 should be put in the pending subpool");
4487        let t2 = best.next().expect("tx2 should be put in the pending subpool");
4488        let t3 = best.next().expect("tx3 should be put in the pending subpool");
4489        assert_eq!(t0.id(), tx0.id());
4490        assert_eq!(t1.id(), tx1.id());
4491        assert_eq!(t2.id(), tx2.id());
4492        assert_eq!(t3.id(), tx3.id());
4493    }
4494
4495    #[test]
4496    fn test_non_4844_blob_fee_bit_invariant() {
4497        let mut f = MockTransactionFactory::default();
4498        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4499
4500        let non_4844_tx = MockTransaction::eip1559().set_max_fee(200).inc_limit();
4501        let validated = f.validated(non_4844_tx.clone());
4502
4503        assert!(!non_4844_tx.is_eip4844());
4504        pool.add_transaction(validated.clone(), U256::from(10_000), 0, None).unwrap();
4505
4506        // Core invariant: Non-4844 transactions must ALWAYS have ENOUGH_BLOB_FEE_CAP_BLOCK bit
4507        let tx_meta = pool.all_transactions.txs.get(validated.id()).unwrap();
4508        assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4509        assert_eq!(tx_meta.subpool, SubPool::Pending);
4510    }
4511
4512    #[test]
4513    fn test_blob_fee_enforcement_only_applies_to_eip4844() {
4514        let mut f = MockTransactionFactory::default();
4515        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4516
4517        // Set blob fee higher than EIP-4844 tx can afford
4518        let mut block_info = pool.block_info();
4519        block_info.pending_blob_fee = Some(160);
4520        block_info.pending_basefee = 100;
4521        pool.set_block_info(block_info);
4522
4523        let eip4844_tx = MockTransaction::eip4844()
4524            .with_sender(address!("0x000000000000000000000000000000000000000a"))
4525            .with_max_fee(200)
4526            .with_blob_fee(150) // Less than block blob fee (160)
4527            .inc_limit();
4528
4529        let non_4844_tx = MockTransaction::eip1559()
4530            .with_sender(address!("0x000000000000000000000000000000000000000b"))
4531            .set_max_fee(200)
4532            .inc_limit();
4533
4534        let validated_4844 = f.validated(eip4844_tx);
4535        let validated_non_4844 = f.validated(non_4844_tx);
4536
4537        pool.add_transaction(validated_4844.clone(), U256::from(10_000), 0, None).unwrap();
4538        pool.add_transaction(validated_non_4844.clone(), U256::from(10_000), 0, None).unwrap();
4539
4540        let tx_4844_meta = pool.all_transactions.txs.get(validated_4844.id()).unwrap();
4541        let tx_non_4844_meta = pool.all_transactions.txs.get(validated_non_4844.id()).unwrap();
4542
4543        // EIP-4844: blob fee enforcement applies - insufficient blob fee removes bit
4544        assert!(!tx_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4545        assert_eq!(tx_4844_meta.subpool, SubPool::Blob);
4546
4547        // Non-4844: blob fee enforcement does NOT apply - bit always remains true
4548        assert!(tx_non_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK));
4549        assert_eq!(tx_non_4844_meta.subpool, SubPool::Pending);
4550    }
4551
4552    #[test]
4553    fn test_basefee_decrease_preserves_non_4844_blob_fee_bit() {
4554        let mut f = MockTransactionFactory::default();
4555        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4556
4557        // Create non-4844 transaction with fee that initially can't afford high basefee
4558        let non_4844_tx = MockTransaction::eip1559()
4559            .with_sender(address!("0x000000000000000000000000000000000000000a"))
4560            .set_max_fee(500) // Can't afford basefee of 600
4561            .inc_limit();
4562
4563        // Set high basefee so transaction goes to BaseFee pool initially
4564        pool.update_basefee(600, |_| {});
4565
4566        let validated = f.validated(non_4844_tx);
4567        let tx_id = *validated.id();
4568        pool.add_transaction(validated, U256::from(10_000), 0, None).unwrap();
4569
4570        // Initially should be in BaseFee pool but STILL have blob fee bit (critical invariant)
4571        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
4572        assert_eq!(tx_meta.subpool, SubPool::BaseFee);
4573        assert!(
4574            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
4575            "Non-4844 tx in BaseFee pool must retain ENOUGH_BLOB_FEE_CAP_BLOCK bit"
4576        );
4577
4578        // Decrease basefee - transaction should be promoted to Pending
4579        // This is where PR #18215 bug would manifest: blob fee bit incorrectly removed
4580        pool.update_basefee(400, |_| {});
4581
4582        // After basefee decrease: should be promoted to Pending with blob fee bit preserved
4583        let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap();
4584        assert_eq!(
4585            tx_meta.subpool,
4586            SubPool::Pending,
4587            "Non-4844 tx should be promoted from BaseFee to Pending after basefee decrease"
4588        );
4589        assert!(
4590            tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK),
4591            "Non-4844 tx must NEVER lose ENOUGH_BLOB_FEE_CAP_BLOCK bit during basefee promotion"
4592        );
4593        assert!(
4594            tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK),
4595            "Non-4844 tx should gain ENOUGH_FEE_CAP_BLOCK bit after basefee decrease"
4596        );
4597    }
4598
4599    /// Test for <https://github.com/paradigmxyz/reth/issues/17701>
4600    ///
4601    /// When a new transaction is added and its `updates` contain a same-sender transaction with
4602    /// a lower nonce, the lower-nonce tx must be added to the pending subpool *before* the
4603    /// higher-nonce tx. Otherwise, live `BestTransactions` iterators receive them out of order.
4604    #[test]
4605    fn best_transactions_nonce_order_on_balance_unlock() {
4606        let mut f = MockTransactionFactory::default();
4607        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4608
4609        let sender = Address::random();
4610        let on_chain_balance = U256::from(10_000);
4611
4612        // tx0: nonce 0, cheap — will go straight to pending
4613        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
4614        // tx1: nonce 1, very expensive — cumulative cost (tx0 + tx1) will exceed balance
4615        let tx1 = tx0.next().inc_limit().with_value(U256::from(on_chain_balance));
4616        // tx2: nonce 2
4617        let tx2 = tx1.next().inc_limit().with_value(U256::ZERO);
4618
4619        let v0 = f.validated(tx0);
4620        let v1 = f.validated(tx1);
4621        let v2 = f.validated(tx2);
4622
4623        // Add tx0 with limited balance — goes to pending (tx0 cost is small: 1 * 100 = 100)
4624        pool.add_transaction(v0, on_chain_balance, 0, None).unwrap();
4625
4626        // Create a live BestTransactions iterator that will receive new pending txs
4627        let mut best = pool.best_transactions();
4628
4629        // Drain tx0 from the iterator
4630        let first = best.next().expect("should yield tx0");
4631        assert_eq!(first.id().nonce, 0);
4632
4633        // Add tx1 with the same limited balance — cumulative cost exceeds balance, goes to
4634        // queued
4635        pool.add_transaction(v1, on_chain_balance, 0, None).unwrap();
4636
4637        // tx1 should be queued, nothing new in best
4638        assert!(best.next().is_none(), "tx1 should be queued, not pending");
4639
4640        // Now add tx2 with U256::MAX balance — tx2 goes to pending AND tx1 gets promoted.
4641        // The bug: tx2 was added to pending *before* tx1, so BestTransactions yielded tx2
4642        // first, violating nonce ordering.
4643        pool.add_transaction(v2, U256::MAX, 0, None).unwrap();
4644
4645        let t1 = best.next().expect("should yield a transaction");
4646        let t2 = best.next().expect("should yield a transaction");
4647
4648        // Correct order: tx1 (nonce 1) before tx2 (nonce 2)
4649        assert_eq!(
4650            t1.id().nonce,
4651            1,
4652            "first yielded tx should be nonce 1, got nonce {}",
4653            t1.id().nonce
4654        );
4655        assert_eq!(
4656            t2.id().nonce,
4657            2,
4658            "second yielded tx should be nonce 2, got nonce {}",
4659            t2.id().nonce
4660        );
4661    }
4662
4663    /// Gap-fill scenario: inserting a low-nonce transaction promotes a queued higher-nonce
4664    /// transaction. The new (lower-nonce) tx must appear before the promoted (higher-nonce)
4665    /// tx in the `BestTransactions` iterator.
4666    #[test]
4667    fn best_transactions_nonce_order_on_gap_fill() {
4668        let mut f = MockTransactionFactory::default();
4669        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4670
4671        let sender = Address::random();
4672        let balance = U256::MAX;
4673
4674        // tx0: nonce 0
4675        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
4676        // tx1: nonce 1
4677        let tx1 = tx0.next().inc_limit();
4678
4679        let v0 = f.validated(tx0);
4680        let v1 = f.validated(tx1);
4681
4682        // Add tx1 first — goes to queued because nonce 0 is missing (nonce gap)
4683        pool.add_transaction(v1, balance, 0, None).unwrap();
4684
4685        // Create a live BestTransactions iterator (currently empty — nothing pending)
4686        let mut best = pool.best_transactions();
4687        assert!(best.next().is_none(), "pool should have no pending txs yet");
4688
4689        // Add tx0 — fills the gap, tx1 gets promoted
4690        pool.add_transaction(v0, balance, 0, None).unwrap();
4691
4692        let t0 = best.next().expect("should yield a transaction");
4693        let t1 = best.next().expect("should yield a transaction");
4694
4695        assert_eq!(t0.id().nonce, 0, "first yielded tx should be nonce 0, got {}", t0.id().nonce);
4696        assert_eq!(t1.id().nonce, 1, "second yielded tx should be nonce 1, got {}", t1.id().nonce);
4697    }
4698
4699    /// Mixed scenario: inserting a mid-nonce transaction promotes both a lower-nonce tx
4700    /// (via balance update) and a higher-nonce tx (via gap fill). All three must appear
4701    /// in nonce order in `BestTransactions`.
4702    #[test]
4703    fn best_transactions_nonce_order_mixed_promotions() {
4704        let mut f = MockTransactionFactory::default();
4705        let mut pool = TxPool::new(MockOrdering::default(), Default::default());
4706
4707        let sender = Address::random();
4708        let low_balance = U256::from(10_000);
4709
4710        // tx0: nonce 0, cheap
4711        let tx0 = MockTransaction::eip1559().with_sender(sender).set_gas_price(100).inc_limit();
4712        // tx1: nonce 1, very expensive — will exceed balance
4713        let tx1 = tx0.next().inc_limit().with_value(U256::from(low_balance));
4714        // tx2: nonce 2
4715        let tx2 = tx1.next().inc_limit().with_value(U256::ZERO);
4716        // tx3: nonce 3
4717        let tx3 = tx2.next().inc_limit().with_value(U256::ZERO);
4718
4719        let v0 = f.validated(tx0);
4720        let v1 = f.validated(tx1);
4721        let v2 = f.validated(tx2);
4722        let v3 = f.validated(tx3);
4723
4724        // Add tx0 — goes to pending
4725        pool.add_transaction(v0, low_balance, 0, None).unwrap();
4726
4727        // Add tx1 — queued (cumulative cost exceeds balance)
4728        pool.add_transaction(v1, low_balance, 0, None).unwrap();
4729
4730        // Add tx3 — queued (nonce gap: tx2 is missing)
4731        pool.add_transaction(v3, low_balance, 0, None).unwrap();
4732
4733        let mut best = pool.best_transactions();
4734
4735        // Drain tx0
4736        let first = best.next().expect("should yield tx0");
4737        assert_eq!(first.id().nonce, 0);
4738        assert!(best.next().is_none(), "only tx0 should be pending");
4739
4740        // Add tx2 with U256::MAX balance — this should:
4741        //  - promote tx1 (lower-nonce, was queued due to balance)
4742        //  - add tx2 itself to pending
4743        //  - promote tx3 (higher-nonce, was queued due to nonce gap)
4744        pool.add_transaction(v2, U256::MAX, 0, None).unwrap();
4745
4746        let t1 = best.next().expect("should yield nonce 1");
4747        let t2 = best.next().expect("should yield nonce 2");
4748        let t3 = best.next().expect("should yield nonce 3");
4749
4750        assert_eq!(t1.id().nonce, 1, "expected nonce 1, got {}", t1.id().nonce);
4751        assert_eq!(t2.id().nonce, 2, "expected nonce 2, got {}", t2.id().nonce);
4752        assert_eq!(t3.id().nonce, 3, "expected nonce 3, got {}", t3.id().nonce);
4753    }
4754}