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