Skip to main content

reth_transaction_pool/pool/
pending.rs

1//! Pending transactions
2
3use crate::{
4    identifier::{SenderId, TransactionId},
5    pool::{
6        best::{BestTransactions, BestTransactionsWithFees},
7        size::SizeTracker,
8    },
9    Priority, SubPoolLimit, TransactionOrdering, ValidPoolTransaction,
10};
11use imbl::OrdMap;
12use rustc_hash::FxHashMap;
13use std::{cmp::Ordering, collections::hash_map::Entry, ops::Bound::Unbounded, sync::Arc};
14use tokio::sync::broadcast;
15
16/// A pool of validated and gapless transactions that are ready to be executed on the current state
17/// and are waiting to be included in a block.
18///
19/// This pool distinguishes between `independent` transactions and pending transactions. A
20/// transaction is `independent`, if it is in the pending pool, and it has the current on chain
21/// nonce of the sender. Meaning `independent` transactions can be executed right away, other
22/// pending transactions depend on at least one `independent` transaction.
23///
24/// Once an `independent` transaction was executed it *unlocks* the next nonce, if this transaction
25/// is also pending, then this will be moved to the `independent` queue.
26#[derive(Debug, Clone)]
27pub struct PendingPool<T: TransactionOrdering> {
28    /// How to order transactions.
29    ordering: T,
30    /// Keeps track of transactions inserted in the pool.
31    ///
32    /// This way we can determine when transactions were submitted to the pool.
33    submission_id: u64,
34    /// _All_ Transactions that are currently inside the pool grouped by their identifier.
35    by_id: OrdMap<TransactionId, PendingTransaction<T>>,
36    /// The highest nonce transactions for each sender - like the `independent` set, but the
37    /// highest instead of lowest nonce.
38    highest_nonces: FxHashMap<SenderId, PendingTransaction<T>>,
39    /// Independent transactions that can be included directly and don't require other
40    /// transactions.
41    independent_transactions: FxHashMap<SenderId, PendingTransaction<T>>,
42    /// Keeps track of the size of this pool.
43    ///
44    /// See also [`reth_primitives_traits::InMemorySize::size`].
45    size_of: SizeTracker,
46    /// Used to broadcast new transactions that have been added to the `PendingPool` to existing
47    /// `static_files` of this pool.
48    new_transaction_notifier: broadcast::Sender<PendingTransaction<T>>,
49}
50
51// === impl PendingPool ===
52
53impl<T: TransactionOrdering> PendingPool<T> {
54    /// Create a new pending pool instance.
55    pub fn new(ordering: T) -> Self {
56        Self::with_buffer(ordering, 200)
57    }
58
59    /// Create a new pool instance with the given buffer capacity.
60    pub fn with_buffer(ordering: T, buffer_capacity: usize) -> Self {
61        let (new_transaction_notifier, _) = broadcast::channel(buffer_capacity);
62        Self {
63            ordering,
64            submission_id: 0,
65            by_id: Default::default(),
66            independent_transactions: Default::default(),
67            highest_nonces: Default::default(),
68            size_of: Default::default(),
69            new_transaction_notifier,
70        }
71    }
72
73    /// Clear all transactions from the pool without resetting other values.
74    /// Used for atomic reordering during basefee update.
75    ///
76    /// # Returns
77    ///
78    /// Returns all transactions by id.
79    fn clear_transactions(&mut self) -> OrdMap<TransactionId, PendingTransaction<T>> {
80        self.independent_transactions.clear();
81        self.highest_nonces.clear();
82        self.size_of.reset();
83        std::mem::take(&mut self.by_id)
84    }
85
86    /// Returns an iterator over all transactions that are _currently_ ready.
87    ///
88    /// 1. The iterator _always_ returns transactions in order: it never returns a transaction with
89    ///    an unsatisfied dependency and only returns them if dependency transaction were yielded
90    ///    previously. In other words: the nonces of transactions with the same sender will _always_
91    ///    increase by exactly 1.
92    ///
93    /// The order of transactions which satisfy (1.) is determined by their computed priority: a
94    /// transaction with a higher priority is returned before a transaction with a lower priority.
95    ///
96    /// If two transactions have the same priority score, then the transactions which spent more
97    /// time in pool (were added earlier) are returned first.
98    ///
99    /// NOTE: while this iterator returns transaction that pool considers valid at this point, they
100    /// could potentially become invalid at point of execution. Therefore, this iterator
101    /// provides a way to mark transactions that the consumer of this iterator considers invalid. In
102    /// which case the transaction's subgraph is also automatically marked invalid, See (1.).
103    /// Invalid transactions are skipped.
104    pub fn best(&self) -> BestTransactions<T> {
105        BestTransactions {
106            all: self.by_id.clone(),
107            independent: self.independent_transactions.values().cloned().collect(),
108            invalid: Default::default(),
109            new_transaction_receiver: Some(self.new_transaction_notifier.subscribe()),
110            last_priority: None,
111            skip_blobs: false,
112            allow_updates_out_of_order: false,
113        }
114    }
115
116    /// Same as `best` but only returns transactions that satisfy the given basefee and blobfee.
117    pub(crate) fn best_with_basefee_and_blobfee(
118        &self,
119        base_fee: u64,
120        base_fee_per_blob_gas: u64,
121    ) -> BestTransactionsWithFees<T> {
122        BestTransactionsWithFees { best: self.best(), base_fee, base_fee_per_blob_gas }
123    }
124
125    /// Same as `best` but also includes the given unlocked transactions.
126    ///
127    /// This mimics the [`Self::add_transaction`] method, but does not insert the transactions into
128    /// pool but only into the returned iterator.
129    ///
130    /// Note: this does not insert the unlocked transactions into the pool.
131    ///
132    /// # Panics
133    ///
134    /// if the transaction is already included
135    pub(crate) fn best_with_unlocked_and_attributes(
136        &self,
137        unlocked: Vec<Arc<ValidPoolTransaction<T::Transaction>>>,
138        base_fee: u64,
139        base_fee_per_blob_gas: u64,
140    ) -> BestTransactionsWithFees<T> {
141        let mut best = self.best();
142        for (submission_id, tx) in (self.submission_id + 1..).zip(unlocked) {
143            debug_assert!(!best.all.contains_key(tx.id()), "transaction already included");
144            let priority = self.ordering.priority(&tx.transaction, base_fee);
145            let tx_id = *tx.id();
146            let transaction = PendingTransaction { submission_id, transaction: tx, priority };
147            if best.ancestor(&tx_id).is_none() {
148                best.independent.insert(transaction.clone());
149            }
150            best.all.insert(tx_id, transaction);
151        }
152
153        BestTransactionsWithFees { best, base_fee, base_fee_per_blob_gas }
154    }
155
156    /// Returns an iterator over all transactions in the pool
157    pub(crate) fn all(
158        &self,
159    ) -> impl ExactSizeIterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
160        self.by_id.values().map(|tx| tx.transaction.clone())
161    }
162
163    /// Updates the pool with the new blob fee. Removes
164    /// from the subpool all transactions and their dependents that no longer satisfy the given
165    /// blob fee (`tx.max_blob_fee < blob_fee`).
166    ///
167    /// Note: the transactions are not returned in a particular order.
168    ///
169    /// # Returns
170    ///
171    /// Removed transactions that no longer satisfy the blob fee.
172    pub(crate) fn update_blob_fee(
173        &mut self,
174        blob_fee: u128,
175    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
176        // Create a collection for removed transactions.
177        let mut removed = Vec::new();
178
179        // Drain and iterate over all transactions.
180        let mut transactions_iter = self.clear_transactions().into_iter().peekable();
181        let mut highest: Option<TransactionId> = None;
182        while let Some((id, tx)) = transactions_iter.next() {
183            self.flush_highest_nonce(&mut highest, Some(id.sender));
184            if tx.transaction.is_eip4844() && tx.transaction.max_fee_per_blob_gas() < Some(blob_fee)
185            {
186                // Add this tx to the removed collection since it no longer satisfies the blob fee
187                // condition. Decrease the total pool size.
188                removed.push(Arc::clone(&tx.transaction));
189
190                // Remove all dependent transactions. Draining the rest of the sender here is what
191                // keeps the tracked `highest` correct, see `flush_highest_nonce`.
192                'this: while let Some((next_id, next_tx)) = transactions_iter.peek() {
193                    if next_id.sender != id.sender {
194                        break 'this
195                    }
196                    removed.push(Arc::clone(&next_tx.transaction));
197                    transactions_iter.next();
198                }
199            } else {
200                self.size_of += tx.transaction.size();
201                if highest.is_none() {
202                    // first kept transaction of this sender, hence the independent one
203                    self.independent_transactions.insert(id.sender, tx.clone());
204                }
205                highest = Some(id);
206                self.by_id.insert(id, tx);
207            }
208        }
209        self.flush_highest_nonce(&mut highest, None);
210
211        removed
212    }
213
214    /// Updates the pool with the new base fee. Reorders transactions by new priorities. Removes
215    /// from the subpool all transactions and their dependents that no longer satisfy the given
216    /// base fee (`tx.fee < base_fee`).
217    ///
218    /// Note: the transactions are not returned in a particular order.
219    ///
220    /// # Returns
221    ///
222    /// Removed transactions that no longer satisfy the base fee.
223    pub(crate) fn update_base_fee(
224        &mut self,
225        base_fee: u64,
226    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
227        // Create a collection for removed transactions.
228        let mut removed = Vec::new();
229
230        // Drain and iterate over all transactions.
231        let mut transactions_iter = self.clear_transactions().into_iter().peekable();
232        let mut highest: Option<TransactionId> = None;
233        while let Some((id, mut tx)) = transactions_iter.next() {
234            self.flush_highest_nonce(&mut highest, Some(id.sender));
235            if tx.transaction.max_fee_per_gas() < base_fee as u128 {
236                // Add this tx to the removed collection since it no longer satisfies the base fee
237                // condition. Decrease the total pool size.
238                removed.push(Arc::clone(&tx.transaction));
239
240                // Remove all dependent transactions. Draining the rest of the sender here is what
241                // keeps the tracked `highest` correct, see `flush_highest_nonce`.
242                'this: while let Some((next_id, next_tx)) = transactions_iter.peek() {
243                    if next_id.sender != id.sender {
244                        break 'this
245                    }
246                    removed.push(Arc::clone(&next_tx.transaction));
247                    transactions_iter.next();
248                }
249            } else {
250                // Re-insert the transaction with new priority.
251                tx.priority = self.ordering.priority(&tx.transaction.transaction, base_fee);
252
253                self.size_of += tx.transaction.size();
254                if highest.is_none() {
255                    // first kept transaction of this sender, hence the independent one
256                    self.independent_transactions.insert(id.sender, tx.clone());
257                }
258                highest = Some(id);
259                self.by_id.insert(id, tx);
260            }
261        }
262        self.flush_highest_nonce(&mut highest, None);
263
264        removed
265    }
266
267    /// Writes the transaction tracked by a rebuild loop (`update_base_fee`, `update_blob_fee`) to
268    /// `highest_nonces` if the loop has moved on to `next_sender`.
269    ///
270    /// The pool is drained in ascending `(sender, nonce)` order, so the highest kept nonce of a
271    /// sender is only known once the drain reaches the next sender or the end of the pool. This
272    /// relies on the loops removing all remaining transactions of a sender once one of them no
273    /// longer satisfies the fee, otherwise the tracked transaction would not be the highest kept
274    /// one.
275    fn flush_highest_nonce(
276        &mut self,
277        tracked: &mut Option<TransactionId>,
278        next_sender: Option<SenderId>,
279    ) {
280        if let Some(id) = tracked.take_if(|id| Some(id.sender) != next_sender) &&
281            let Some(tx) = self.by_id.get(&id).cloned()
282        {
283            self.highest_nonces.insert(id.sender, tx);
284        }
285    }
286
287    /// Updates the independent transaction and highest nonces set, assuming the given transaction
288    /// is being _added_ to the pool.
289    fn update_independents_and_highest_nonces(&mut self, tx: &PendingTransaction<T>) {
290        match self.highest_nonces.entry(tx.transaction.sender_id()) {
291            Entry::Occupied(mut entry) => {
292                if entry.get().transaction.nonce() < tx.transaction.nonce() {
293                    *entry.get_mut() = tx.clone();
294                }
295            }
296            Entry::Vacant(entry) => {
297                entry.insert(tx.clone());
298            }
299        }
300        match self.independent_transactions.entry(tx.transaction.sender_id()) {
301            Entry::Occupied(mut entry) => {
302                if entry.get().transaction.nonce() > tx.transaction.nonce() {
303                    *entry.get_mut() = tx.clone();
304                }
305            }
306            Entry::Vacant(entry) => {
307                entry.insert(tx.clone());
308            }
309        }
310    }
311
312    /// Adds a new transactions to the pending queue.
313    ///
314    /// # Panics
315    ///
316    /// if the transaction is already included
317    pub fn add_transaction(
318        &mut self,
319        tx: Arc<ValidPoolTransaction<T::Transaction>>,
320        base_fee: u64,
321    ) {
322        debug_assert!(
323            !self.contains(tx.id()),
324            "transaction already included {:?}",
325            self.get(tx.id()).unwrap().transaction
326        );
327
328        // keep track of size
329        self.size_of += tx.size();
330
331        let tx_id = *tx.id();
332
333        let submission_id = self.next_id();
334        let priority = self.ordering.priority(&tx.transaction, base_fee);
335        let tx = PendingTransaction { submission_id, transaction: tx, priority };
336
337        self.update_independents_and_highest_nonces(&tx);
338
339        // send the new transaction to any existing pendingpool static file iterators
340        if self.new_transaction_notifier.receiver_count() > 0 {
341            let _ = self.new_transaction_notifier.send(tx.clone());
342        }
343
344        self.by_id.insert(tx_id, tx);
345    }
346
347    /// Removes the transaction from the pool.
348    ///
349    /// Note: If the transaction has a descendant transaction
350    /// it will advance it to the best queue.
351    pub(crate) fn remove_transaction(
352        &mut self,
353        id: &TransactionId,
354    ) -> Option<Arc<ValidPoolTransaction<T::Transaction>>> {
355        if let Some(lowest) = self.independent_transactions.get(&id.sender) &&
356            lowest.transaction.nonce() == id.nonce
357        {
358            self.independent_transactions.remove(&id.sender);
359            // mark the next as independent if it exists
360            if let Some(unlocked) = self.get(&id.descendant()) {
361                self.independent_transactions.insert(id.sender, unlocked.clone());
362            }
363        }
364
365        let tx = self.by_id.remove(id)?;
366        self.size_of -= tx.transaction.size();
367
368        match self.highest_nonces.entry(id.sender) {
369            Entry::Occupied(mut entry) => {
370                if entry.get().transaction.nonce() == id.nonce {
371                    // we just removed the tx with the highest nonce for this sender, find the
372                    // highest remaining tx from that sender
373                    if let Some((_, new_highest)) = self
374                        .by_id
375                        .range((
376                            id.sender.start_bound(),
377                            std::ops::Bound::Included(TransactionId::new(id.sender, u64::MAX)),
378                        ))
379                        .next_back()
380                    {
381                        // insert the new highest nonce for this sender
382                        entry.insert(new_highest.clone());
383                    } else {
384                        entry.remove();
385                    }
386                }
387            }
388            Entry::Vacant(_) => {
389                debug_assert!(
390                    false,
391                    "removed transaction without a tracked highest nonce {:?}",
392                    id
393                );
394            }
395        }
396
397        Some(tx.transaction)
398    }
399
400    const fn next_id(&mut self) -> u64 {
401        let id = self.submission_id;
402        self.submission_id = self.submission_id.wrapping_add(1);
403        id
404    }
405
406    /// Traverses the pool, starting at the highest nonce set, removing the transactions which
407    /// would put the pool under the specified limits.
408    ///
409    /// This attempts to remove transactions by roughly the same amount for each sender. This is
410    /// done by removing the highest-nonce transactions for each sender.
411    ///
412    /// If the `remove_locals` flag is unset, transactions will be removed per-sender until a
413    /// local transaction is the highest nonce transaction for that sender. If all senders have a
414    /// local highest-nonce transaction, the pool will not be truncated further.
415    ///
416    /// Otherwise, if the `remove_locals` flag is set, transactions will be removed per-sender
417    /// until the pool is under the given limits.
418    ///
419    /// Any removed transactions will be added to the `end_removed` vector.
420    pub fn remove_to_limit(
421        &mut self,
422        limit: &SubPoolLimit,
423        remove_locals: bool,
424        end_removed: &mut Vec<Arc<ValidPoolTransaction<T::Transaction>>>,
425    ) {
426        // keep track of transactions to remove and how many have been removed so far
427        let original_length = self.len();
428        let mut removed = Vec::new();
429        let mut total_removed = 0;
430
431        // track total `size` of transactions to remove
432        let original_size = self.size();
433        let mut total_size = 0;
434
435        loop {
436            // we can reuse the temp array
437            removed.clear();
438
439            // we prefer removing transactions with lower ordering
440            let mut worst_transactions = self
441                .highest_nonces
442                .values()
443                .filter(|tx| remove_locals || !tx.transaction.is_local())
444                .collect::<Vec<_>>();
445
446            // no eligible transactions left to remove
447            if worst_transactions.is_empty() {
448                return
449            }
450
451            // Each pass removes at most one transaction per sender (its highest nonce), so only
452            // the worst few senders can be relevant in this pass. Selecting them is O(n)
453            // instead of sorting all senders. The estimate may fall short for size-based limits, in
454            // which case the outer loop runs another pass.
455            let current_len = original_length - total_removed;
456            let current_size = original_size - total_size;
457            let excess_txs = current_len.saturating_sub(limit.max_txs);
458            let avg_tx_size = (current_size / current_len.max(1)).max(1);
459            let excess_size_txs = current_size.saturating_sub(limit.max_size).div_ceil(avg_tx_size);
460            // Number of worst senders to consider for removal in this pass: enough to cover the
461            // count and (estimated) size excess.
462            let removal_candidates = excess_txs.max(excess_size_txs).max(1);
463
464            if removal_candidates < worst_transactions.len() {
465                // keep only the `removal_candidates` worst senders, in O(n) without a full sort
466                worst_transactions.select_nth_unstable(removal_candidates);
467                worst_transactions.truncate(removal_candidates);
468            }
469            worst_transactions.sort_unstable();
470
471            // loop through the highest nonces set, removing transactions until we reach the limit
472            for tx in worst_transactions {
473                // return early if the pool is under limits
474                if !limit.is_exceeded(original_length - total_removed, original_size - total_size) {
475                    // need to remove remaining transactions before exiting
476                    for id in &removed {
477                        if let Some(tx) = self.remove_transaction(id) {
478                            end_removed.push(tx);
479                        }
480                    }
481
482                    return
483                }
484
485                total_size += tx.transaction.size();
486                total_removed += 1;
487                removed.push(*tx.transaction.id());
488            }
489
490            // remove the transactions from this iteration
491            for id in &removed {
492                if let Some(tx) = self.remove_transaction(id) {
493                    end_removed.push(tx);
494                }
495            }
496
497            if !self.exceeds(limit) {
498                return
499            }
500        }
501    }
502
503    /// Truncates the pool to the given [`SubPoolLimit`], removing transactions until the subpool
504    /// limits are met.
505    ///
506    /// This attempts to remove transactions by roughly the same amount for each sender. For more
507    /// information on this exact process see docs for
508    /// [`remove_to_limit`](PendingPool::remove_to_limit).
509    ///
510    /// This first truncates all of the non-local transactions in the pool. If the subpool is still
511    /// not under the limit, this truncates the entire pool, including non-local transactions. The
512    /// removed transactions are returned.
513    pub fn truncate_pool(
514        &mut self,
515        limit: SubPoolLimit,
516    ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
517        let mut removed = Vec::new();
518        // return early if the pool is already under the limits
519        if !self.exceeds(&limit) {
520            return removed
521        }
522
523        // first truncate only non-local transactions, returning if the pool end up under the limit
524        self.remove_to_limit(&limit, false, &mut removed);
525        if !self.exceeds(&limit) {
526            return removed
527        }
528
529        // now repeat for local transactions, since local transactions must be removed now for the
530        // pool to be under the limit
531        self.remove_to_limit(&limit, true, &mut removed);
532
533        removed
534    }
535
536    /// Returns true if the pool exceeds the given limit
537    #[inline]
538    pub(crate) fn exceeds(&self, limit: &SubPoolLimit) -> bool {
539        limit.is_exceeded(self.len(), self.size())
540    }
541
542    /// The reported size of all transactions in this pool.
543    pub(crate) fn size(&self) -> usize {
544        self.size_of.into()
545    }
546
547    /// Number of transactions in the entire pool
548    pub(crate) fn len(&self) -> usize {
549        self.by_id.len()
550    }
551
552    /// All transactions grouped by id
553    pub const fn by_id(&self) -> &OrdMap<TransactionId, PendingTransaction<T>> {
554        &self.by_id
555    }
556
557    /// Independent transactions
558    pub const fn independent_transactions(&self) -> &FxHashMap<SenderId, PendingTransaction<T>> {
559        &self.independent_transactions
560    }
561
562    /// Subscribes to new transactions
563    pub fn new_transaction_receiver(&self) -> broadcast::Receiver<PendingTransaction<T>> {
564        self.new_transaction_notifier.subscribe()
565    }
566
567    /// Whether the pool is empty
568    #[cfg(test)]
569    pub(crate) fn is_empty(&self) -> bool {
570        self.by_id.is_empty()
571    }
572
573    /// Returns `true` if the transaction with the given id is already included in this pool.
574    pub(crate) fn contains(&self, id: &TransactionId) -> bool {
575        self.by_id.contains_key(id)
576    }
577
578    /// Get transactions by sender
579    pub(crate) fn get_txs_by_sender(&self, sender: SenderId) -> Vec<TransactionId> {
580        self.iter_txs_by_sender(sender).copied().collect()
581    }
582
583    /// Returns an iterator over all transaction with the sender id
584    pub(crate) fn iter_txs_by_sender(
585        &self,
586        sender: SenderId,
587    ) -> impl Iterator<Item = &TransactionId> + '_ {
588        self.by_id
589            .range((sender.start_bound(), Unbounded))
590            .take_while(move |(other, _)| sender == other.sender)
591            .map(|(tx_id, _)| tx_id)
592    }
593
594    /// Returns an iterator over all transactions for the given sender, using a `BTree` range
595    /// query.
596    pub(crate) fn txs_by_sender(
597        &self,
598        sender: SenderId,
599    ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
600        self.by_id
601            .range((sender.start_bound(), Unbounded))
602            .take_while(move |(other, _)| sender == other.sender)
603            .map(|(_, tx)| tx.transaction.clone())
604    }
605
606    /// Retrieves a transaction with the given ID from the pool, if it exists.
607    fn get(&self, id: &TransactionId) -> Option<&PendingTransaction<T>> {
608        self.by_id.get(id)
609    }
610
611    /// Returns a reference to the independent transactions in the pool
612    #[cfg(test)]
613    pub(crate) const fn independent(&self) -> &FxHashMap<SenderId, PendingTransaction<T>> {
614        &self.independent_transactions
615    }
616
617    /// Asserts that the per-sender side maps are consistent with `by_id`.
618    ///
619    /// Every sender in `by_id` must be tracked with its lowest nonce transaction in
620    /// `independent_transactions` and its highest nonce transaction in `highest_nonces`, no other
621    /// sender may be tracked, and the tracked size must match the size of all transactions.
622    #[cfg(any(test, feature = "test-utils"))]
623    pub(crate) fn assert_invariants(&self) {
624        let mut senders = 0;
625        let mut txs = self.by_id.iter().peekable();
626        while let Some((lowest_id, lowest)) = txs.next() {
627            let sender = lowest_id.sender;
628            senders += 1;
629
630            let mut highest = lowest;
631            while let Some((_, tx)) = txs.next_if(|(id, _)| id.sender == sender) {
632                highest = tx;
633            }
634
635            let independent = self
636                .independent_transactions
637                .get(&sender)
638                .unwrap_or_else(|| panic!("no independent transaction tracked for {sender:?}"));
639            assert_eq!(
640                independent, lowest,
641                "independent transaction of {sender:?} does not match its lowest nonce pool entry"
642            );
643            let tracked_highest = self
644                .highest_nonces
645                .get(&sender)
646                .unwrap_or_else(|| panic!("no highest nonce tracked for {sender:?}"));
647            assert_eq!(
648                tracked_highest, highest,
649                "highest nonce of {sender:?} does not match its highest nonce pool entry"
650            );
651        }
652
653        assert_eq!(
654            self.independent_transactions.len(),
655            senders,
656            "independent_transactions tracks senders without transactions"
657        );
658        assert_eq!(
659            self.highest_nonces.len(),
660            senders,
661            "highest_nonces tracks senders without transactions"
662        );
663        assert_eq!(
664            self.size(),
665            self.by_id.values().map(|tx| tx.transaction.size()).sum::<usize>(),
666            "size_of does not match the size of all transactions"
667        );
668    }
669}
670
671/// A transaction that is ready to be included in a block.
672#[derive(Debug)]
673pub struct PendingTransaction<T: TransactionOrdering> {
674    /// Identifier that tags when transaction was submitted in the pool.
675    pub submission_id: u64,
676    /// Actual transaction.
677    pub transaction: Arc<ValidPoolTransaction<T::Transaction>>,
678    /// The priority value assigned by the used `Ordering` function.
679    pub priority: Priority<T::PriorityValue>,
680}
681
682impl<T: TransactionOrdering> PendingTransaction<T> {
683    /// The next transaction of the sender: `nonce + 1`
684    pub fn unlocks(&self) -> TransactionId {
685        self.transaction.transaction_id.descendant()
686    }
687}
688
689impl<T: TransactionOrdering> Clone for PendingTransaction<T> {
690    fn clone(&self) -> Self {
691        Self {
692            submission_id: self.submission_id,
693            transaction: Arc::clone(&self.transaction),
694            priority: self.priority.clone(),
695        }
696    }
697}
698
699impl<T: TransactionOrdering> Eq for PendingTransaction<T> {}
700
701impl<T: TransactionOrdering> PartialEq<Self> for PendingTransaction<T> {
702    fn eq(&self, other: &Self) -> bool {
703        self.cmp(other) == Ordering::Equal
704    }
705}
706
707impl<T: TransactionOrdering> PartialOrd<Self> for PendingTransaction<T> {
708    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
709        Some(self.cmp(other))
710    }
711}
712
713impl<T: TransactionOrdering> Ord for PendingTransaction<T> {
714    fn cmp(&self, other: &Self) -> Ordering {
715        // This compares by `priority` and only if two tx have the exact same priority this compares
716        // the unique `submission_id`. This ensures that transactions with same priority are not
717        // equal, so they're not replaced in the set
718        self.priority
719            .cmp(&other.priority)
720            .then_with(|| other.submission_id.cmp(&self.submission_id))
721    }
722}
723
724#[cfg(test)]
725mod tests {
726
727    use super::*;
728    use crate::{
729        test_utils::{MockOrdering, MockTransaction, MockTransactionFactory, MockTransactionSet},
730        PoolTransaction, TransactionOrigin,
731    };
732    use alloy_consensus::{Transaction, TxType};
733    use alloy_primitives::Address;
734    use std::collections::HashSet;
735
736    #[test]
737    fn test_enforce_basefee() {
738        let mut f = MockTransactionFactory::default();
739        let mut pool = PendingPool::new(MockOrdering::default());
740        let tx = f.validated_arc(MockTransaction::eip1559().inc_price());
741        pool.add_transaction(tx.clone(), 0);
742
743        assert!(pool.contains(tx.id()));
744        assert_eq!(pool.len(), 1);
745
746        let removed = pool.update_base_fee(0);
747        assert!(removed.is_empty());
748
749        let removed = pool.update_base_fee((tx.max_fee_per_gas() + 1) as u64);
750        assert_eq!(removed.len(), 1);
751        assert!(pool.is_empty());
752    }
753
754    #[test]
755    fn test_enforce_basefee_descendant() {
756        let mut f = MockTransactionFactory::default();
757        let mut pool = PendingPool::new(MockOrdering::default());
758        let t = MockTransaction::eip1559().inc_price_by(10);
759        let root_tx = f.validated_arc(t.clone());
760        pool.add_transaction(root_tx.clone(), 0);
761
762        let descendant_tx = f.validated_arc(t.inc_nonce().decr_price());
763        pool.add_transaction(descendant_tx.clone(), 0);
764
765        assert!(pool.contains(root_tx.id()));
766        assert!(pool.contains(descendant_tx.id()));
767        assert_eq!(pool.len(), 2);
768
769        assert_eq!(pool.independent_transactions.len(), 1);
770        assert_eq!(pool.highest_nonces.len(), 1);
771
772        let removed = pool.update_base_fee(0);
773        assert!(removed.is_empty());
774
775        // two dependent tx in the pool with decreasing fee
776
777        {
778            let mut pool2 = pool.clone();
779            let removed = pool2.update_base_fee((descendant_tx.max_fee_per_gas() + 1) as u64);
780            assert_eq!(removed.len(), 1);
781            assert_eq!(pool2.len(), 1);
782            // descendant got popped
783            assert!(pool2.contains(root_tx.id()));
784            assert!(!pool2.contains(descendant_tx.id()));
785        }
786
787        // remove root transaction via fee
788        let removed = pool.update_base_fee((root_tx.max_fee_per_gas() + 1) as u64);
789        assert_eq!(removed.len(), 2);
790        assert!(pool.is_empty());
791        pool.assert_invariants();
792    }
793
794    #[test]
795    fn test_enforce_basefee_tracks_highest_nonce_of_partially_removed_sender() {
796        let mut f = MockTransactionFactory::default();
797        let mut pool = PendingPool::new(MockOrdering::default());
798
799        // sender a keeps its first transaction but its descendant is priced out
800        let a = MockTransaction::eip1559().inc_price_by(20);
801        let a0 = f.validated_arc(a.clone());
802        let a1 = f.validated_arc(a.inc_nonce().rng_hash().decr_price_by(15));
803        // sender b keeps both of its transactions
804        let b = MockTransaction::eip1559().inc_price_by(20);
805        let b0 = f.validated_arc(b.clone());
806        let b1 = f.validated_arc(b.inc_nonce().rng_hash());
807
808        for tx in [&a0, &a1, &b0, &b1] {
809            pool.add_transaction(tx.clone(), 0);
810        }
811
812        let removed = pool.update_base_fee((a1.max_fee_per_gas() + 1) as u64);
813        assert_eq!(removed.len(), 1);
814        assert_eq!(removed[0].hash(), a1.hash());
815
816        pool.assert_invariants();
817        assert_eq!(pool.highest_nonces[&a0.sender_id()].transaction.nonce(), a0.nonce());
818        assert_eq!(pool.independent_transactions[&a0.sender_id()].transaction.nonce(), a0.nonce());
819        assert_eq!(pool.highest_nonces[&b1.sender_id()].transaction.nonce(), b1.nonce());
820        assert_eq!(pool.independent_transactions[&b0.sender_id()].transaction.nonce(), b0.nonce());
821    }
822
823    #[test]
824    fn test_enforce_blobfee_tracks_highest_nonce_of_partially_removed_sender() {
825        let mut f = MockTransactionFactory::default();
826        let mut pool = PendingPool::new(MockOrdering::default());
827
828        // sender a keeps its first transaction but its descendant is priced out
829        let a = MockTransaction::eip4844().with_blob_fee(150);
830        let a0 = f.validated_arc(a.clone());
831        let a1 = f.validated_arc(a.inc_nonce().rng_hash().with_blob_fee(50));
832        // sender b keeps both of its transactions
833        let b = MockTransaction::eip4844().with_blob_fee(150);
834        let b0 = f.validated_arc(b.clone());
835        let b1 = f.validated_arc(b.inc_nonce().rng_hash());
836
837        for tx in [&a0, &a1, &b0, &b1] {
838            pool.add_transaction(tx.clone(), 0);
839        }
840
841        let removed = pool.update_blob_fee(100);
842        assert_eq!(removed.len(), 1);
843        assert_eq!(removed[0].hash(), a1.hash());
844        pool.assert_invariants();
845    }
846
847    #[test]
848    fn evict_worst() {
849        let mut f = MockTransactionFactory::default();
850        let mut pool = PendingPool::new(MockOrdering::default());
851
852        let t = MockTransaction::eip1559();
853        pool.add_transaction(f.validated_arc(t.clone()), 0);
854
855        let t2 = MockTransaction::eip1559().inc_price_by(10);
856        pool.add_transaction(f.validated_arc(t2), 0);
857
858        // First transaction should be evicted.
859        assert_eq!(
860            pool.highest_nonces.values().min().map(|tx| *tx.transaction.hash()),
861            Some(*t.hash())
862        );
863
864        // truncate pool with max size = 1, ensure it's the same transaction
865        let removed = pool.truncate_pool(SubPoolLimit { max_txs: 1, max_size: usize::MAX });
866        assert_eq!(removed.len(), 1);
867        assert_eq!(removed[0].hash(), t.hash());
868    }
869
870    #[test]
871    fn correct_independent_descendants() {
872        // this test ensures that we set the right highest nonces set for each sender
873        let mut f = MockTransactionFactory::default();
874        let mut pool = PendingPool::new(MockOrdering::default());
875
876        let a_sender = Address::with_last_byte(0x0a);
877        let b_sender = Address::with_last_byte(0x0b);
878        let c_sender = Address::with_last_byte(0x0c);
879        let d_sender = Address::with_last_byte(0x0d);
880
881        // create a chain of transactions by sender A, B, C
882        let mut tx_set = MockTransactionSet::dependent(a_sender, 0, 4, TxType::Eip1559);
883        let a = tx_set.clone().into_vec();
884
885        let b = MockTransactionSet::dependent(b_sender, 0, 3, TxType::Eip1559).into_vec();
886        tx_set.extend(b.clone());
887
888        // C has the same number of txs as B
889        let c = MockTransactionSet::dependent(c_sender, 0, 3, TxType::Eip1559).into_vec();
890        tx_set.extend(c.clone());
891
892        let d = MockTransactionSet::dependent(d_sender, 0, 1, TxType::Eip1559).into_vec();
893        tx_set.extend(d.clone());
894
895        // add all the transactions to the pool
896        let all_txs = tx_set.into_vec();
897        for tx in all_txs {
898            pool.add_transaction(f.validated_arc(tx), 0);
899        }
900
901        pool.assert_invariants();
902
903        // the independent set is the roots of each of these tx chains, these are the highest
904        // nonces for each sender
905        let expected_highest_nonces = [d[0].clone(), c[2].clone(), b[2].clone(), a[3].clone()]
906            .iter()
907            .map(|tx| (tx.sender(), tx.nonce()))
908            .collect::<HashSet<_>>();
909        let actual_highest_nonces = pool
910            .highest_nonces
911            .values()
912            .map(|tx| (tx.transaction.sender(), tx.transaction.nonce()))
913            .collect::<HashSet<_>>();
914        assert_eq!(expected_highest_nonces, actual_highest_nonces);
915        pool.assert_invariants();
916    }
917
918    #[test]
919    fn truncate_by_sender() {
920        // This test ensures that transactions are removed from the pending pool by sender.
921        let mut f = MockTransactionFactory::default();
922        let mut pool = PendingPool::new(MockOrdering::default());
923
924        // Addresses for simulated senders A, B, C, and D.
925        let a = Address::with_last_byte(0x0a);
926        let b = Address::with_last_byte(0x0b);
927        let c = Address::with_last_byte(0x0c);
928        let d = Address::with_last_byte(0x0d);
929
930        // Create transaction chains for senders A, B, C, and D.
931        let a_txs = MockTransactionSet::sequential_transactions_by_sender(a, 4, TxType::Eip1559);
932        let b_txs = MockTransactionSet::sequential_transactions_by_sender(b, 3, TxType::Eip1559);
933        let c_txs = MockTransactionSet::sequential_transactions_by_sender(c, 3, TxType::Eip1559);
934        let d_txs = MockTransactionSet::sequential_transactions_by_sender(d, 1, TxType::Eip1559);
935
936        // Set up expected pending transactions.
937        let expected_pending = vec![
938            a_txs.transactions[0].clone(),
939            b_txs.transactions[0].clone(),
940            c_txs.transactions[0].clone(),
941            a_txs.transactions[1].clone(),
942        ]
943        .into_iter()
944        .map(|tx| (tx.sender(), tx.nonce()))
945        .collect::<HashSet<_>>();
946
947        // Set up expected removed transactions.
948        let expected_removed = vec![
949            d_txs.transactions[0].clone(),
950            c_txs.transactions[2].clone(),
951            b_txs.transactions[2].clone(),
952            a_txs.transactions[3].clone(),
953            c_txs.transactions[1].clone(),
954            b_txs.transactions[1].clone(),
955            a_txs.transactions[2].clone(),
956        ]
957        .into_iter()
958        .map(|tx| (tx.sender(), tx.nonce()))
959        .collect::<HashSet<_>>();
960
961        // Consolidate all transactions into a single vector.
962        let all_txs =
963            [a_txs.into_vec(), b_txs.into_vec(), c_txs.into_vec(), d_txs.into_vec()].concat();
964
965        // Add all the transactions to the pool.
966        for tx in all_txs {
967            pool.add_transaction(f.validated_arc(tx), 0);
968        }
969
970        // Sanity check, ensuring everything is consistent.
971        pool.assert_invariants();
972
973        // Define the maximum total transactions to be 4, removing transactions for each sender.
974        // Expected order of removal:
975        // * d1, c3, b3, a4
976        // * c2, b2, a3
977        //
978        // Remaining transactions:
979        // * a1, a2
980        // * b1
981        // * c1
982        let pool_limit = SubPoolLimit { max_txs: 4, max_size: usize::MAX };
983
984        // Truncate the pool based on the defined limit.
985        let removed = pool.truncate_pool(pool_limit);
986        pool.assert_invariants();
987        assert_eq!(removed.len(), expected_removed.len());
988
989        // Get the set of removed transactions and compare with the expected set.
990        let removed =
991            removed.into_iter().map(|tx| (tx.sender(), tx.nonce())).collect::<HashSet<_>>();
992        assert_eq!(removed, expected_removed);
993
994        // Retrieve the current pending transactions after truncation.
995        let pending = pool.all().collect::<Vec<_>>();
996        assert_eq!(pending.len(), expected_pending.len());
997
998        // Get the set of pending transactions and compare with the expected set.
999        let pending =
1000            pending.into_iter().map(|tx| (tx.sender(), tx.nonce())).collect::<HashSet<_>>();
1001        assert_eq!(pending, expected_pending);
1002    }
1003
1004    #[test]
1005    fn truncate_skips_local_heads_ranked_below_non_local() {
1006        let mut f = MockTransactionFactory::default();
1007        let mut pool = PendingPool::new(MockOrdering::default());
1008
1009        // many local senders that rank below every non-local sender
1010        let num_locals = 3_000;
1011        for _ in 0..num_locals {
1012            let tx = f.validated_with_origin(TransactionOrigin::Local, MockTransaction::eip1559());
1013            pool.add_transaction(Arc::new(tx), 0);
1014        }
1015
1016        let non_locals: Vec<_> = (1..=3u128)
1017            .map(|i| {
1018                let tx = f.validated_arc(MockTransaction::eip1559().inc_price_by(10 * i));
1019                pool.add_transaction(tx.clone(), 0);
1020                tx
1021            })
1022            .collect();
1023        pool.assert_invariants();
1024
1025        let removed =
1026            pool.truncate_pool(SubPoolLimit { max_txs: num_locals + 2, max_size: usize::MAX });
1027        pool.assert_invariants();
1028
1029        assert_eq!(removed.len(), 1);
1030        assert_eq!(removed[0].hash(), non_locals[0].hash());
1031        assert_eq!(pool.len(), num_locals + 2);
1032    }
1033
1034    #[test]
1035    fn truncate_removes_non_local_head_above_local_tx() {
1036        let mut f = MockTransactionFactory::default();
1037        let mut pool = PendingPool::new(MockOrdering::default());
1038
1039        // the sender's highest nonce tx is non-local, so it is removed even though a local tx
1040        // of the same sender stays behind
1041        let local = MockTransaction::eip1559();
1042        let local_tx = f.validated_with_origin(TransactionOrigin::Local, local.clone());
1043        let head_tx = f.validated_arc(local.inc_nonce().rng_hash());
1044        pool.add_transaction(Arc::new(local_tx), 0);
1045        pool.add_transaction(head_tx.clone(), 0);
1046
1047        let removed = pool.truncate_pool(SubPoolLimit { max_txs: 1, max_size: usize::MAX });
1048        pool.assert_invariants();
1049
1050        assert_eq!(removed.len(), 1);
1051        assert_eq!(removed[0].hash(), head_tx.hash());
1052        assert_eq!(pool.len(), 1);
1053    }
1054
1055    // <https://github.com/paradigmxyz/reth/issues/12340>
1056    #[test]
1057    fn test_eligible_updates_promoted() {
1058        let mut pool = PendingPool::new(MockOrdering::default());
1059        let mut f = MockTransactionFactory::default();
1060
1061        let num_senders = 10;
1062
1063        let first_txs: Vec<_> = (0..num_senders) //
1064            .map(|_| MockTransaction::eip1559())
1065            .collect();
1066        let second_txs: Vec<_> =
1067            first_txs.iter().map(|tx| tx.clone().rng_hash().inc_nonce()).collect();
1068
1069        for tx in first_txs {
1070            let valid_tx = f.validated(tx);
1071            pool.add_transaction(Arc::new(valid_tx), 0);
1072        }
1073
1074        let mut best = pool.best();
1075
1076        for _ in 0..num_senders {
1077            if let Some(tx) = best.next() {
1078                assert_eq!(tx.nonce(), 0);
1079            } else {
1080                panic!("cannot read one of first_txs");
1081            }
1082        }
1083
1084        for tx in second_txs {
1085            let valid_tx = f.validated(tx);
1086            pool.add_transaction(Arc::new(valid_tx), 0);
1087        }
1088
1089        for _ in 0..num_senders {
1090            if let Some(tx) = best.next() {
1091                assert_eq!(tx.nonce(), 1);
1092            } else {
1093                panic!("cannot read one of second_txs");
1094            }
1095        }
1096    }
1097
1098    #[test]
1099    fn test_empty_pool_behavior() {
1100        let mut pool = PendingPool::<MockOrdering>::new(MockOrdering::default());
1101
1102        // Ensure the pool is empty
1103        assert!(pool.is_empty());
1104        assert_eq!(pool.len(), 0);
1105        assert_eq!(pool.size(), 0);
1106
1107        // Verify that attempting to truncate an empty pool does not panic and returns an empty vec
1108        let removed = pool.truncate_pool(SubPoolLimit { max_txs: 10, max_size: 1000 });
1109        assert!(removed.is_empty());
1110
1111        // Verify that retrieving transactions from an empty pool yields nothing
1112        assert!(pool.all().next().is_none());
1113    }
1114
1115    #[test]
1116    fn test_add_remove_transaction() {
1117        let mut f = MockTransactionFactory::default();
1118        let mut pool = PendingPool::new(MockOrdering::default());
1119
1120        // Add a transaction and check if it's in the pool
1121        let tx = f.validated_arc(MockTransaction::eip1559());
1122        pool.add_transaction(tx.clone(), 0);
1123        assert!(pool.contains(tx.id()));
1124        assert_eq!(pool.len(), 1);
1125
1126        // Remove the transaction and ensure it's no longer in the pool
1127        let removed_tx = pool.remove_transaction(tx.id()).unwrap();
1128        assert_eq!(removed_tx.id(), tx.id());
1129        assert!(!pool.contains(tx.id()));
1130        assert_eq!(pool.len(), 0);
1131    }
1132
1133    #[test]
1134    fn test_reorder_on_basefee_update() {
1135        let mut f = MockTransactionFactory::default();
1136        let mut pool = PendingPool::new(MockOrdering::default());
1137
1138        // Add two transactions with different fees
1139        let tx1 = f.validated_arc(MockTransaction::eip1559().inc_price());
1140        let tx2 = f.validated_arc(MockTransaction::eip1559().inc_price_by(20));
1141        pool.add_transaction(tx1.clone(), 0);
1142        pool.add_transaction(tx2.clone(), 0);
1143
1144        // Ensure the transactions are in the correct order
1145        let mut best = pool.best();
1146        assert_eq!(best.next().unwrap().hash(), tx2.hash());
1147        assert_eq!(best.next().unwrap().hash(), tx1.hash());
1148
1149        // Update the base fee to a value higher than tx1's fee, causing it to be removed
1150        let removed = pool.update_base_fee((tx1.max_fee_per_gas() + 1) as u64);
1151        assert_eq!(removed.len(), 1);
1152        assert_eq!(removed[0].hash(), tx1.hash());
1153
1154        // Verify that only tx2 remains in the pool
1155        assert_eq!(pool.len(), 1);
1156        assert!(pool.contains(tx2.id()));
1157        assert!(!pool.contains(tx1.id()));
1158    }
1159
1160    #[test]
1161    #[cfg(debug_assertions)]
1162    #[should_panic(expected = "transaction already included")]
1163    fn test_handle_duplicates() {
1164        let mut f = MockTransactionFactory::default();
1165        let mut pool = PendingPool::new(MockOrdering::default());
1166
1167        // Add the same transaction twice and ensure it only appears once
1168        let tx = f.validated_arc(MockTransaction::eip1559());
1169        pool.add_transaction(tx.clone(), 0);
1170        assert!(pool.contains(tx.id()));
1171        assert_eq!(pool.len(), 1);
1172
1173        // Attempt to add the same transaction again, which should be ignored
1174        pool.add_transaction(tx, 0);
1175    }
1176
1177    #[test]
1178    fn test_update_blob_fee() {
1179        let mut f = MockTransactionFactory::default();
1180        let mut pool = PendingPool::new(MockOrdering::default());
1181
1182        // Add transactions with varying blob fees
1183        let tx1 = f.validated_arc(MockTransaction::eip4844().set_blob_fee(50).clone());
1184        let tx2 = f.validated_arc(MockTransaction::eip4844().set_blob_fee(150).clone());
1185        pool.add_transaction(tx1.clone(), 0);
1186        pool.add_transaction(tx2.clone(), 0);
1187
1188        // Update the blob fee to a value that causes tx1 to be removed
1189        let removed = pool.update_blob_fee(100);
1190        assert_eq!(removed.len(), 1);
1191        assert_eq!(removed[0].hash(), tx1.hash());
1192
1193        // Verify that only tx2 remains in the pool
1194        assert!(pool.contains(tx2.id()));
1195        assert!(!pool.contains(tx1.id()));
1196    }
1197
1198    #[test]
1199    fn local_senders_tracking() {
1200        let mut f = MockTransactionFactory::default();
1201        let mut pool = PendingPool::new(MockOrdering::default());
1202
1203        // Addresses for simulated senders A, B, C
1204        let a = Address::with_last_byte(0x0a);
1205        let b = Address::with_last_byte(0x0b);
1206        let c = Address::with_last_byte(0x0c);
1207
1208        // sender A (local) - 11+ transactions (large enough to keep limit exceeded)
1209        // sender B (external) - 2 transactions
1210        // sender C (external) - 2 transactions
1211
1212        // Create transaction chains for senders A, B, C
1213        let a_txs = MockTransactionSet::sequential_transactions_by_sender(a, 11, TxType::Eip1559);
1214        let b_txs = MockTransactionSet::sequential_transactions_by_sender(b, 2, TxType::Eip1559);
1215        let c_txs = MockTransactionSet::sequential_transactions_by_sender(c, 2, TxType::Eip1559);
1216
1217        // create local txs for sender A
1218        for tx in a_txs.into_vec() {
1219            let final_tx = Arc::new(f.validated_with_origin(crate::TransactionOrigin::Local, tx));
1220
1221            pool.add_transaction(final_tx, 0);
1222        }
1223
1224        // create external txs for senders B and C
1225        let remaining_txs = [b_txs.into_vec(), c_txs.into_vec()].concat();
1226        for tx in remaining_txs {
1227            let final_tx = f.validated_arc(tx);
1228
1229            pool.add_transaction(final_tx, 0);
1230        }
1231
1232        // Sanity check, ensuring everything is consistent.
1233        pool.assert_invariants();
1234
1235        let pool_limit = SubPoolLimit { max_txs: 10, max_size: usize::MAX };
1236        pool.truncate_pool(pool_limit);
1237
1238        let sender_a = f.ids.sender_id(&a).unwrap();
1239        let sender_b = f.ids.sender_id(&b).unwrap();
1240        let sender_c = f.ids.sender_id(&c).unwrap();
1241
1242        assert_eq!(pool.get_txs_by_sender(sender_a).len(), 10);
1243        assert!(pool.get_txs_by_sender(sender_b).is_empty());
1244        assert!(pool.get_txs_by_sender(sender_c).is_empty());
1245    }
1246
1247    #[test]
1248    fn test_remove_non_highest_keeps_highest() {
1249        let mut f = MockTransactionFactory::default();
1250        let mut pool = PendingPool::new(MockOrdering::default());
1251        let sender = Address::with_last_byte(0xaa);
1252        let txs = MockTransactionSet::dependent(sender, 0, 3, TxType::Eip1559).into_vec();
1253        for tx in txs {
1254            pool.add_transaction(f.validated_arc(tx), 0);
1255        }
1256        pool.assert_invariants();
1257        let sender_id = f.ids.sender_id(&sender).unwrap();
1258        let mid_id = TransactionId::new(sender_id, 1);
1259        let _ = pool.remove_transaction(&mid_id);
1260        let highest = pool.highest_nonces.get(&sender_id).unwrap();
1261        assert_eq!(highest.transaction.nonce(), 2);
1262        pool.assert_invariants();
1263    }
1264
1265    #[test]
1266    fn test_cascade_removal_recomputes_highest() {
1267        let mut f = MockTransactionFactory::default();
1268        let mut pool = PendingPool::new(MockOrdering::default());
1269        let sender = Address::with_last_byte(0xbb);
1270        let txs = MockTransactionSet::dependent(sender, 0, 4, TxType::Eip1559).into_vec();
1271        for tx in txs {
1272            pool.add_transaction(f.validated_arc(tx), 0);
1273        }
1274        pool.assert_invariants();
1275        let sender_id = f.ids.sender_id(&sender).unwrap();
1276        let id3 = TransactionId::new(sender_id, 3);
1277        let _ = pool.remove_transaction(&id3);
1278        let highest = pool.highest_nonces.get(&sender_id).unwrap();
1279        assert_eq!(highest.transaction.nonce(), 2);
1280        let id2 = TransactionId::new(sender_id, 2);
1281        let _ = pool.remove_transaction(&id2);
1282        let highest = pool.highest_nonces.get(&sender_id).unwrap();
1283        assert_eq!(highest.transaction.nonce(), 1);
1284        pool.assert_invariants();
1285    }
1286
1287    #[test]
1288    fn test_remove_only_tx_clears_highest() {
1289        let mut f = MockTransactionFactory::default();
1290        let mut pool = PendingPool::new(MockOrdering::default());
1291        let sender = Address::with_last_byte(0xcc);
1292        let txs = MockTransactionSet::dependent(sender, 0, 1, TxType::Eip1559).into_vec();
1293        for tx in txs {
1294            pool.add_transaction(f.validated_arc(tx), 0);
1295        }
1296        pool.assert_invariants();
1297        let sender_id = f.ids.sender_id(&sender).unwrap();
1298        let id0 = TransactionId::new(sender_id, 0);
1299        let _ = pool.remove_transaction(&id0);
1300        assert!(!pool.highest_nonces.contains_key(&sender_id));
1301        pool.assert_invariants();
1302    }
1303}