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