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