Skip to main content

reth_transaction_pool/pool/
txpool.rs

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