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