1use 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#[derive(Debug, Clone)]
27pub struct PendingPool<T: TransactionOrdering> {
28 ordering: T,
30 submission_id: u64,
34 by_id: OrdMap<TransactionId, PendingTransaction<T>>,
36 highest_nonces: FxHashMap<SenderId, PendingTransaction<T>>,
39 independent_transactions: FxHashMap<SenderId, PendingTransaction<T>>,
42 size_of: SizeTracker,
46 new_transaction_notifier: broadcast::Sender<PendingTransaction<T>>,
49}
50
51impl<T: TransactionOrdering> PendingPool<T> {
54 pub fn new(ordering: T) -> Self {
56 Self::with_buffer(ordering, 200)
57 }
58
59 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 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 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 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 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 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 pub(crate) fn update_blob_fee(
173 &mut self,
174 blob_fee: u128,
175 ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
176 let mut removed = Vec::new();
178
179 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 removed.push(Arc::clone(&tx.transaction));
189
190 '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 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 pub(crate) fn update_base_fee(
224 &mut self,
225 base_fee: u64,
226 ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
227 let mut removed = Vec::new();
229
230 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 removed.push(Arc::clone(&tx.transaction));
239
240 '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 tx.priority = self.ordering.priority(&tx.transaction.transaction, base_fee);
252
253 self.size_of += tx.transaction.size();
254 if highest.is_none() {
255 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 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 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 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 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 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 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 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 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 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 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 let original_length = self.len();
428 let mut removed = Vec::new();
429 let mut total_removed = 0;
430
431 let original_size = self.size();
433 let mut total_size = 0;
434
435 loop {
436 removed.clear();
438
439 let mut worst_transactions = self
441 .highest_nonces
442 .values()
443 .filter(|tx| remove_locals || !tx.transaction.is_local())
444 .collect::<Vec<_>>();
445
446 if worst_transactions.is_empty() {
448 return
449 }
450
451 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 let removal_candidates = excess_txs.max(excess_size_txs).max(1);
463
464 if removal_candidates < worst_transactions.len() {
465 worst_transactions.select_nth_unstable(removal_candidates);
467 worst_transactions.truncate(removal_candidates);
468 }
469 worst_transactions.sort_unstable();
470
471 for tx in worst_transactions {
473 if !limit.is_exceeded(original_length - total_removed, original_size - total_size) {
475 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 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 pub fn truncate_pool(
514 &mut self,
515 limit: SubPoolLimit,
516 ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
517 let mut removed = Vec::new();
518 if !self.exceeds(&limit) {
520 return removed
521 }
522
523 self.remove_to_limit(&limit, false, &mut removed);
525 if !self.exceeds(&limit) {
526 return removed
527 }
528
529 self.remove_to_limit(&limit, true, &mut removed);
532
533 removed
534 }
535
536 #[inline]
538 pub(crate) fn exceeds(&self, limit: &SubPoolLimit) -> bool {
539 limit.is_exceeded(self.len(), self.size())
540 }
541
542 pub(crate) fn size(&self) -> usize {
544 self.size_of.into()
545 }
546
547 pub(crate) fn len(&self) -> usize {
549 self.by_id.len()
550 }
551
552 pub const fn by_id(&self) -> &OrdMap<TransactionId, PendingTransaction<T>> {
554 &self.by_id
555 }
556
557 pub const fn independent_transactions(&self) -> &FxHashMap<SenderId, PendingTransaction<T>> {
559 &self.independent_transactions
560 }
561
562 pub fn new_transaction_receiver(&self) -> broadcast::Receiver<PendingTransaction<T>> {
564 self.new_transaction_notifier.subscribe()
565 }
566
567 #[cfg(test)]
569 pub(crate) fn is_empty(&self) -> bool {
570 self.by_id.is_empty()
571 }
572
573 pub(crate) fn contains(&self, id: &TransactionId) -> bool {
575 self.by_id.contains_key(id)
576 }
577
578 pub(crate) fn get_txs_by_sender(&self, sender: SenderId) -> Vec<TransactionId> {
580 self.iter_txs_by_sender(sender).copied().collect()
581 }
582
583 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 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 fn get(&self, id: &TransactionId) -> Option<&PendingTransaction<T>> {
608 self.by_id.get(id)
609 }
610
611 #[cfg(test)]
613 pub(crate) const fn independent(&self) -> &FxHashMap<SenderId, PendingTransaction<T>> {
614 &self.independent_transactions
615 }
616
617 #[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#[derive(Debug)]
673pub struct PendingTransaction<T: TransactionOrdering> {
674 pub submission_id: u64,
676 pub transaction: Arc<ValidPoolTransaction<T::Transaction>>,
678 pub priority: Priority<T::PriorityValue>,
680}
681
682impl<T: TransactionOrdering> PendingTransaction<T> {
683 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 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 {
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 assert!(pool2.contains(root_tx.id()));
784 assert!(!pool2.contains(descendant_tx.id()));
785 }
786
787 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 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 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 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 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 assert_eq!(
860 pool.highest_nonces.values().min().map(|tx| *tx.transaction.hash()),
861 Some(*t.hash())
862 );
863
864 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 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 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 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 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 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 let mut f = MockTransactionFactory::default();
922 let mut pool = PendingPool::new(MockOrdering::default());
923
924 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 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 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 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 let all_txs =
963 [a_txs.into_vec(), b_txs.into_vec(), c_txs.into_vec(), d_txs.into_vec()].concat();
964
965 for tx in all_txs {
967 pool.add_transaction(f.validated_arc(tx), 0);
968 }
969
970 pool.assert_invariants();
972
973 let pool_limit = SubPoolLimit { max_txs: 4, max_size: usize::MAX };
983
984 let removed = pool.truncate_pool(pool_limit);
986 pool.assert_invariants();
987 assert_eq!(removed.len(), expected_removed.len());
988
989 let removed =
991 removed.into_iter().map(|tx| (tx.sender(), tx.nonce())).collect::<HashSet<_>>();
992 assert_eq!(removed, expected_removed);
993
994 let pending = pool.all().collect::<Vec<_>>();
996 assert_eq!(pending.len(), expected_pending.len());
997
998 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 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 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 #[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) .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 assert!(pool.is_empty());
1104 assert_eq!(pool.len(), 0);
1105 assert_eq!(pool.size(), 0);
1106
1107 let removed = pool.truncate_pool(SubPoolLimit { max_txs: 10, max_size: 1000 });
1109 assert!(removed.is_empty());
1110
1111 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 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 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 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 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 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 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 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 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 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 let removed = pool.update_blob_fee(100);
1190 assert_eq!(removed.len(), 1);
1191 assert_eq!(removed[0].hash(), tx1.hash());
1192
1193 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 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 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 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 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 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}