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