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