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, FxHashSet};
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 mut non_local_senders = self.highest_nonces.len();
435
436 let mut unique_senders = self.highest_nonces.len();
439
440 let mut local_senders = FxHashSet::default();
442
443 let original_length = self.len();
445 let mut removed = Vec::new();
446 let mut total_removed = 0;
447
448 let original_size = self.size();
450 let mut total_size = 0;
451
452 loop {
453 let unique_removed = unique_senders - self.highest_nonces.len();
455
456 unique_senders = self.highest_nonces.len();
458 non_local_senders -= unique_removed;
459
460 removed.clear();
462
463 let mut worst_transactions = self.highest_nonces.values().collect::<Vec<_>>();
465
466 let current_len = original_length - total_removed;
471 let current_size = original_size - total_size;
472 let excess_txs = current_len.saturating_sub(limit.max_txs);
473 let avg_tx_size = (current_size / current_len.max(1)).max(1);
474 let excess_size_txs = current_size.saturating_sub(limit.max_size).div_ceil(avg_tx_size);
475 let removal_candidates = excess_txs.max(excess_size_txs).max(1) + local_senders.len();
479
480 if removal_candidates < worst_transactions.len() {
481 worst_transactions.select_nth_unstable(removal_candidates);
483 worst_transactions.truncate(removal_candidates);
484 }
485 worst_transactions.sort_unstable();
486
487 for tx in worst_transactions {
489 if !limit.is_exceeded(original_length - total_removed, original_size - total_size) ||
491 non_local_senders == 0
492 {
493 for id in &removed {
495 if let Some(tx) = self.remove_transaction(id) {
496 end_removed.push(tx);
497 }
498 }
499
500 return
501 }
502
503 if !remove_locals && tx.transaction.is_local() {
504 let sender_id = tx.transaction.sender_id();
505 if local_senders.insert(sender_id) {
506 non_local_senders -= 1;
507 }
508 continue
509 }
510
511 total_size += tx.transaction.size();
512 total_removed += 1;
513 removed.push(*tx.transaction.id());
514 }
515
516 for id in &removed {
518 if let Some(tx) = self.remove_transaction(id) {
519 end_removed.push(tx);
520 }
521 }
522
523 if !self.exceeds(limit) || non_local_senders == 0 {
526 return
527 }
528 }
529 }
530
531 pub fn truncate_pool(
542 &mut self,
543 limit: SubPoolLimit,
544 ) -> Vec<Arc<ValidPoolTransaction<T::Transaction>>> {
545 let mut removed = Vec::new();
546 if !self.exceeds(&limit) {
548 return removed
549 }
550
551 self.remove_to_limit(&limit, false, &mut removed);
553 if !self.exceeds(&limit) {
554 return removed
555 }
556
557 self.remove_to_limit(&limit, true, &mut removed);
560
561 removed
562 }
563
564 #[inline]
566 pub(crate) fn exceeds(&self, limit: &SubPoolLimit) -> bool {
567 limit.is_exceeded(self.len(), self.size())
568 }
569
570 pub(crate) fn size(&self) -> usize {
572 self.size_of.into()
573 }
574
575 pub(crate) fn len(&self) -> usize {
577 self.by_id.len()
578 }
579
580 pub const fn by_id(&self) -> &OrdMap<TransactionId, PendingTransaction<T>> {
582 &self.by_id
583 }
584
585 pub const fn independent_transactions(&self) -> &FxHashMap<SenderId, PendingTransaction<T>> {
587 &self.independent_transactions
588 }
589
590 pub fn new_transaction_receiver(&self) -> broadcast::Receiver<PendingTransaction<T>> {
592 self.new_transaction_notifier.subscribe()
593 }
594
595 #[cfg(test)]
597 pub(crate) fn is_empty(&self) -> bool {
598 self.by_id.is_empty()
599 }
600
601 pub(crate) fn contains(&self, id: &TransactionId) -> bool {
603 self.by_id.contains_key(id)
604 }
605
606 pub(crate) fn get_txs_by_sender(&self, sender: SenderId) -> Vec<TransactionId> {
608 self.iter_txs_by_sender(sender).copied().collect()
609 }
610
611 pub(crate) fn iter_txs_by_sender(
613 &self,
614 sender: SenderId,
615 ) -> impl Iterator<Item = &TransactionId> + '_ {
616 self.by_id
617 .range((sender.start_bound(), Unbounded))
618 .take_while(move |(other, _)| sender == other.sender)
619 .map(|(tx_id, _)| tx_id)
620 }
621
622 pub(crate) fn txs_by_sender(
625 &self,
626 sender: SenderId,
627 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T::Transaction>>> + '_ {
628 self.by_id
629 .range((sender.start_bound(), Unbounded))
630 .take_while(move |(other, _)| sender == other.sender)
631 .map(|(_, tx)| tx.transaction.clone())
632 }
633
634 fn get(&self, id: &TransactionId) -> Option<&PendingTransaction<T>> {
636 self.by_id.get(id)
637 }
638
639 #[cfg(test)]
641 pub(crate) const fn independent(&self) -> &FxHashMap<SenderId, PendingTransaction<T>> {
642 &self.independent_transactions
643 }
644
645 #[cfg(any(test, feature = "test-utils"))]
651 pub(crate) fn assert_invariants(&self) {
652 let mut senders = 0;
653 let mut txs = self.by_id.iter().peekable();
654 while let Some((lowest_id, lowest)) = txs.next() {
655 let sender = lowest_id.sender;
656 senders += 1;
657
658 let mut highest = lowest;
659 while let Some((_, tx)) = txs.next_if(|(id, _)| id.sender == sender) {
660 highest = tx;
661 }
662
663 let independent = self
664 .independent_transactions
665 .get(&sender)
666 .unwrap_or_else(|| panic!("no independent transaction tracked for {sender:?}"));
667 assert_eq!(
668 independent, lowest,
669 "independent transaction of {sender:?} does not match its lowest nonce pool entry"
670 );
671 let tracked_highest = self
672 .highest_nonces
673 .get(&sender)
674 .unwrap_or_else(|| panic!("no highest nonce tracked for {sender:?}"));
675 assert_eq!(
676 tracked_highest, highest,
677 "highest nonce of {sender:?} does not match its highest nonce pool entry"
678 );
679 }
680
681 assert_eq!(
682 self.independent_transactions.len(),
683 senders,
684 "independent_transactions tracks senders without transactions"
685 );
686 assert_eq!(
687 self.highest_nonces.len(),
688 senders,
689 "highest_nonces tracks senders without transactions"
690 );
691 assert_eq!(
692 self.size(),
693 self.by_id.values().map(|tx| tx.transaction.size()).sum::<usize>(),
694 "size_of does not match the size of all transactions"
695 );
696 }
697}
698
699#[derive(Debug)]
701pub struct PendingTransaction<T: TransactionOrdering> {
702 pub submission_id: u64,
704 pub transaction: Arc<ValidPoolTransaction<T::Transaction>>,
706 pub priority: Priority<T::PriorityValue>,
708}
709
710impl<T: TransactionOrdering> PendingTransaction<T> {
711 pub fn unlocks(&self) -> TransactionId {
713 self.transaction.transaction_id.descendant()
714 }
715}
716
717impl<T: TransactionOrdering> Clone for PendingTransaction<T> {
718 fn clone(&self) -> Self {
719 Self {
720 submission_id: self.submission_id,
721 transaction: Arc::clone(&self.transaction),
722 priority: self.priority.clone(),
723 }
724 }
725}
726
727impl<T: TransactionOrdering> Eq for PendingTransaction<T> {}
728
729impl<T: TransactionOrdering> PartialEq<Self> for PendingTransaction<T> {
730 fn eq(&self, other: &Self) -> bool {
731 self.cmp(other) == Ordering::Equal
732 }
733}
734
735impl<T: TransactionOrdering> PartialOrd<Self> for PendingTransaction<T> {
736 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
737 Some(self.cmp(other))
738 }
739}
740
741impl<T: TransactionOrdering> Ord for PendingTransaction<T> {
742 fn cmp(&self, other: &Self) -> Ordering {
743 self.priority
747 .cmp(&other.priority)
748 .then_with(|| other.submission_id.cmp(&self.submission_id))
749 }
750}
751
752#[cfg(test)]
753mod tests {
754
755 use super::*;
756 use crate::{
757 test_utils::{MockOrdering, MockTransaction, MockTransactionFactory, MockTransactionSet},
758 PoolTransaction,
759 };
760 use alloy_consensus::{Transaction, TxType};
761 use alloy_primitives::Address;
762 use std::collections::HashSet;
763
764 #[test]
765 fn test_enforce_basefee() {
766 let mut f = MockTransactionFactory::default();
767 let mut pool = PendingPool::new(MockOrdering::default());
768 let tx = f.validated_arc(MockTransaction::eip1559().inc_price());
769 pool.add_transaction(tx.clone(), 0);
770
771 assert!(pool.contains(tx.id()));
772 assert_eq!(pool.len(), 1);
773
774 let removed = pool.update_base_fee(0);
775 assert!(removed.is_empty());
776
777 let removed = pool.update_base_fee((tx.max_fee_per_gas() + 1) as u64);
778 assert_eq!(removed.len(), 1);
779 assert!(pool.is_empty());
780 }
781
782 #[test]
783 fn test_enforce_basefee_descendant() {
784 let mut f = MockTransactionFactory::default();
785 let mut pool = PendingPool::new(MockOrdering::default());
786 let t = MockTransaction::eip1559().inc_price_by(10);
787 let root_tx = f.validated_arc(t.clone());
788 pool.add_transaction(root_tx.clone(), 0);
789
790 let descendant_tx = f.validated_arc(t.inc_nonce().decr_price());
791 pool.add_transaction(descendant_tx.clone(), 0);
792
793 assert!(pool.contains(root_tx.id()));
794 assert!(pool.contains(descendant_tx.id()));
795 assert_eq!(pool.len(), 2);
796
797 assert_eq!(pool.independent_transactions.len(), 1);
798 assert_eq!(pool.highest_nonces.len(), 1);
799
800 let removed = pool.update_base_fee(0);
801 assert!(removed.is_empty());
802
803 {
806 let mut pool2 = pool.clone();
807 let removed = pool2.update_base_fee((descendant_tx.max_fee_per_gas() + 1) as u64);
808 assert_eq!(removed.len(), 1);
809 assert_eq!(pool2.len(), 1);
810 assert!(pool2.contains(root_tx.id()));
812 assert!(!pool2.contains(descendant_tx.id()));
813 }
814
815 let removed = pool.update_base_fee((root_tx.max_fee_per_gas() + 1) as u64);
817 assert_eq!(removed.len(), 2);
818 assert!(pool.is_empty());
819 pool.assert_invariants();
820 }
821
822 #[test]
823 fn test_enforce_basefee_tracks_highest_nonce_of_partially_removed_sender() {
824 let mut f = MockTransactionFactory::default();
825 let mut pool = PendingPool::new(MockOrdering::default());
826
827 let a = MockTransaction::eip1559().inc_price_by(20);
829 let a0 = f.validated_arc(a.clone());
830 let a1 = f.validated_arc(a.inc_nonce().rng_hash().decr_price_by(15));
831 let b = MockTransaction::eip1559().inc_price_by(20);
833 let b0 = f.validated_arc(b.clone());
834 let b1 = f.validated_arc(b.inc_nonce().rng_hash());
835
836 for tx in [&a0, &a1, &b0, &b1] {
837 pool.add_transaction(tx.clone(), 0);
838 }
839
840 let removed = pool.update_base_fee((a1.max_fee_per_gas() + 1) as u64);
841 assert_eq!(removed.len(), 1);
842 assert_eq!(removed[0].hash(), a1.hash());
843
844 pool.assert_invariants();
845 assert_eq!(pool.highest_nonces[&a0.sender_id()].transaction.nonce(), a0.nonce());
846 assert_eq!(pool.independent_transactions[&a0.sender_id()].transaction.nonce(), a0.nonce());
847 assert_eq!(pool.highest_nonces[&b1.sender_id()].transaction.nonce(), b1.nonce());
848 assert_eq!(pool.independent_transactions[&b0.sender_id()].transaction.nonce(), b0.nonce());
849 }
850
851 #[test]
852 fn test_enforce_blobfee_tracks_highest_nonce_of_partially_removed_sender() {
853 let mut f = MockTransactionFactory::default();
854 let mut pool = PendingPool::new(MockOrdering::default());
855
856 let a = MockTransaction::eip4844().with_blob_fee(150);
858 let a0 = f.validated_arc(a.clone());
859 let a1 = f.validated_arc(a.inc_nonce().rng_hash().with_blob_fee(50));
860 let b = MockTransaction::eip4844().with_blob_fee(150);
862 let b0 = f.validated_arc(b.clone());
863 let b1 = f.validated_arc(b.inc_nonce().rng_hash());
864
865 for tx in [&a0, &a1, &b0, &b1] {
866 pool.add_transaction(tx.clone(), 0);
867 }
868
869 let removed = pool.update_blob_fee(100);
870 assert_eq!(removed.len(), 1);
871 assert_eq!(removed[0].hash(), a1.hash());
872 pool.assert_invariants();
873 }
874
875 #[test]
876 fn evict_worst() {
877 let mut f = MockTransactionFactory::default();
878 let mut pool = PendingPool::new(MockOrdering::default());
879
880 let t = MockTransaction::eip1559();
881 pool.add_transaction(f.validated_arc(t.clone()), 0);
882
883 let t2 = MockTransaction::eip1559().inc_price_by(10);
884 pool.add_transaction(f.validated_arc(t2), 0);
885
886 assert_eq!(
888 pool.highest_nonces.values().min().map(|tx| *tx.transaction.hash()),
889 Some(*t.hash())
890 );
891
892 let removed = pool.truncate_pool(SubPoolLimit { max_txs: 1, max_size: usize::MAX });
894 assert_eq!(removed.len(), 1);
895 assert_eq!(removed[0].hash(), t.hash());
896 }
897
898 #[test]
899 fn correct_independent_descendants() {
900 let mut f = MockTransactionFactory::default();
902 let mut pool = PendingPool::new(MockOrdering::default());
903
904 let a_sender = Address::with_last_byte(0x0a);
905 let b_sender = Address::with_last_byte(0x0b);
906 let c_sender = Address::with_last_byte(0x0c);
907 let d_sender = Address::with_last_byte(0x0d);
908
909 let mut tx_set = MockTransactionSet::dependent(a_sender, 0, 4, TxType::Eip1559);
911 let a = tx_set.clone().into_vec();
912
913 let b = MockTransactionSet::dependent(b_sender, 0, 3, TxType::Eip1559).into_vec();
914 tx_set.extend(b.clone());
915
916 let c = MockTransactionSet::dependent(c_sender, 0, 3, TxType::Eip1559).into_vec();
918 tx_set.extend(c.clone());
919
920 let d = MockTransactionSet::dependent(d_sender, 0, 1, TxType::Eip1559).into_vec();
921 tx_set.extend(d.clone());
922
923 let all_txs = tx_set.into_vec();
925 for tx in all_txs {
926 pool.add_transaction(f.validated_arc(tx), 0);
927 }
928
929 pool.assert_invariants();
930
931 let expected_highest_nonces = [d[0].clone(), c[2].clone(), b[2].clone(), a[3].clone()]
934 .iter()
935 .map(|tx| (tx.sender(), tx.nonce()))
936 .collect::<HashSet<_>>();
937 let actual_highest_nonces = pool
938 .highest_nonces
939 .values()
940 .map(|tx| (tx.transaction.sender(), tx.transaction.nonce()))
941 .collect::<HashSet<_>>();
942 assert_eq!(expected_highest_nonces, actual_highest_nonces);
943 pool.assert_invariants();
944 }
945
946 #[test]
947 fn truncate_by_sender() {
948 let mut f = MockTransactionFactory::default();
950 let mut pool = PendingPool::new(MockOrdering::default());
951
952 let a = Address::with_last_byte(0x0a);
954 let b = Address::with_last_byte(0x0b);
955 let c = Address::with_last_byte(0x0c);
956 let d = Address::with_last_byte(0x0d);
957
958 let a_txs = MockTransactionSet::sequential_transactions_by_sender(a, 4, TxType::Eip1559);
960 let b_txs = MockTransactionSet::sequential_transactions_by_sender(b, 3, TxType::Eip1559);
961 let c_txs = MockTransactionSet::sequential_transactions_by_sender(c, 3, TxType::Eip1559);
962 let d_txs = MockTransactionSet::sequential_transactions_by_sender(d, 1, TxType::Eip1559);
963
964 let expected_pending = vec![
966 a_txs.transactions[0].clone(),
967 b_txs.transactions[0].clone(),
968 c_txs.transactions[0].clone(),
969 a_txs.transactions[1].clone(),
970 ]
971 .into_iter()
972 .map(|tx| (tx.sender(), tx.nonce()))
973 .collect::<HashSet<_>>();
974
975 let expected_removed = vec![
977 d_txs.transactions[0].clone(),
978 c_txs.transactions[2].clone(),
979 b_txs.transactions[2].clone(),
980 a_txs.transactions[3].clone(),
981 c_txs.transactions[1].clone(),
982 b_txs.transactions[1].clone(),
983 a_txs.transactions[2].clone(),
984 ]
985 .into_iter()
986 .map(|tx| (tx.sender(), tx.nonce()))
987 .collect::<HashSet<_>>();
988
989 let all_txs =
991 [a_txs.into_vec(), b_txs.into_vec(), c_txs.into_vec(), d_txs.into_vec()].concat();
992
993 for tx in all_txs {
995 pool.add_transaction(f.validated_arc(tx), 0);
996 }
997
998 pool.assert_invariants();
1000
1001 let pool_limit = SubPoolLimit { max_txs: 4, max_size: usize::MAX };
1011
1012 let removed = pool.truncate_pool(pool_limit);
1014 pool.assert_invariants();
1015 assert_eq!(removed.len(), expected_removed.len());
1016
1017 let removed =
1019 removed.into_iter().map(|tx| (tx.sender(), tx.nonce())).collect::<HashSet<_>>();
1020 assert_eq!(removed, expected_removed);
1021
1022 let pending = pool.all().collect::<Vec<_>>();
1024 assert_eq!(pending.len(), expected_pending.len());
1025
1026 let pending =
1028 pending.into_iter().map(|tx| (tx.sender(), tx.nonce())).collect::<HashSet<_>>();
1029 assert_eq!(pending, expected_pending);
1030 }
1031
1032 #[test]
1034 fn test_eligible_updates_promoted() {
1035 let mut pool = PendingPool::new(MockOrdering::default());
1036 let mut f = MockTransactionFactory::default();
1037
1038 let num_senders = 10;
1039
1040 let first_txs: Vec<_> = (0..num_senders) .map(|_| MockTransaction::eip1559())
1042 .collect();
1043 let second_txs: Vec<_> =
1044 first_txs.iter().map(|tx| tx.clone().rng_hash().inc_nonce()).collect();
1045
1046 for tx in first_txs {
1047 let valid_tx = f.validated(tx);
1048 pool.add_transaction(Arc::new(valid_tx), 0);
1049 }
1050
1051 let mut best = pool.best();
1052
1053 for _ in 0..num_senders {
1054 if let Some(tx) = best.next() {
1055 assert_eq!(tx.nonce(), 0);
1056 } else {
1057 panic!("cannot read one of first_txs");
1058 }
1059 }
1060
1061 for tx in second_txs {
1062 let valid_tx = f.validated(tx);
1063 pool.add_transaction(Arc::new(valid_tx), 0);
1064 }
1065
1066 for _ in 0..num_senders {
1067 if let Some(tx) = best.next() {
1068 assert_eq!(tx.nonce(), 1);
1069 } else {
1070 panic!("cannot read one of second_txs");
1071 }
1072 }
1073 }
1074
1075 #[test]
1076 fn test_empty_pool_behavior() {
1077 let mut pool = PendingPool::<MockOrdering>::new(MockOrdering::default());
1078
1079 assert!(pool.is_empty());
1081 assert_eq!(pool.len(), 0);
1082 assert_eq!(pool.size(), 0);
1083
1084 let removed = pool.truncate_pool(SubPoolLimit { max_txs: 10, max_size: 1000 });
1086 assert!(removed.is_empty());
1087
1088 assert!(pool.all().next().is_none());
1090 }
1091
1092 #[test]
1093 fn test_add_remove_transaction() {
1094 let mut f = MockTransactionFactory::default();
1095 let mut pool = PendingPool::new(MockOrdering::default());
1096
1097 let tx = f.validated_arc(MockTransaction::eip1559());
1099 pool.add_transaction(tx.clone(), 0);
1100 assert!(pool.contains(tx.id()));
1101 assert_eq!(pool.len(), 1);
1102
1103 let removed_tx = pool.remove_transaction(tx.id()).unwrap();
1105 assert_eq!(removed_tx.id(), tx.id());
1106 assert!(!pool.contains(tx.id()));
1107 assert_eq!(pool.len(), 0);
1108 }
1109
1110 #[test]
1111 fn test_reorder_on_basefee_update() {
1112 let mut f = MockTransactionFactory::default();
1113 let mut pool = PendingPool::new(MockOrdering::default());
1114
1115 let tx1 = f.validated_arc(MockTransaction::eip1559().inc_price());
1117 let tx2 = f.validated_arc(MockTransaction::eip1559().inc_price_by(20));
1118 pool.add_transaction(tx1.clone(), 0);
1119 pool.add_transaction(tx2.clone(), 0);
1120
1121 let mut best = pool.best();
1123 assert_eq!(best.next().unwrap().hash(), tx2.hash());
1124 assert_eq!(best.next().unwrap().hash(), tx1.hash());
1125
1126 let removed = pool.update_base_fee((tx1.max_fee_per_gas() + 1) as u64);
1128 assert_eq!(removed.len(), 1);
1129 assert_eq!(removed[0].hash(), tx1.hash());
1130
1131 assert_eq!(pool.len(), 1);
1133 assert!(pool.contains(tx2.id()));
1134 assert!(!pool.contains(tx1.id()));
1135 }
1136
1137 #[test]
1138 #[cfg(debug_assertions)]
1139 #[should_panic(expected = "transaction already included")]
1140 fn test_handle_duplicates() {
1141 let mut f = MockTransactionFactory::default();
1142 let mut pool = PendingPool::new(MockOrdering::default());
1143
1144 let tx = f.validated_arc(MockTransaction::eip1559());
1146 pool.add_transaction(tx.clone(), 0);
1147 assert!(pool.contains(tx.id()));
1148 assert_eq!(pool.len(), 1);
1149
1150 pool.add_transaction(tx, 0);
1152 }
1153
1154 #[test]
1155 fn test_update_blob_fee() {
1156 let mut f = MockTransactionFactory::default();
1157 let mut pool = PendingPool::new(MockOrdering::default());
1158
1159 let tx1 = f.validated_arc(MockTransaction::eip4844().set_blob_fee(50).clone());
1161 let tx2 = f.validated_arc(MockTransaction::eip4844().set_blob_fee(150).clone());
1162 pool.add_transaction(tx1.clone(), 0);
1163 pool.add_transaction(tx2.clone(), 0);
1164
1165 let removed = pool.update_blob_fee(100);
1167 assert_eq!(removed.len(), 1);
1168 assert_eq!(removed[0].hash(), tx1.hash());
1169
1170 assert!(pool.contains(tx2.id()));
1172 assert!(!pool.contains(tx1.id()));
1173 }
1174
1175 #[test]
1176 fn local_senders_tracking() {
1177 let mut f = MockTransactionFactory::default();
1178 let mut pool = PendingPool::new(MockOrdering::default());
1179
1180 let a = Address::with_last_byte(0x0a);
1182 let b = Address::with_last_byte(0x0b);
1183 let c = Address::with_last_byte(0x0c);
1184
1185 let a_txs = MockTransactionSet::sequential_transactions_by_sender(a, 11, TxType::Eip1559);
1191 let b_txs = MockTransactionSet::sequential_transactions_by_sender(b, 2, TxType::Eip1559);
1192 let c_txs = MockTransactionSet::sequential_transactions_by_sender(c, 2, TxType::Eip1559);
1193
1194 for tx in a_txs.into_vec() {
1196 let final_tx = Arc::new(f.validated_with_origin(crate::TransactionOrigin::Local, tx));
1197
1198 pool.add_transaction(final_tx, 0);
1199 }
1200
1201 let remaining_txs = [b_txs.into_vec(), c_txs.into_vec()].concat();
1203 for tx in remaining_txs {
1204 let final_tx = f.validated_arc(tx);
1205
1206 pool.add_transaction(final_tx, 0);
1207 }
1208
1209 pool.assert_invariants();
1211
1212 let pool_limit = SubPoolLimit { max_txs: 10, max_size: usize::MAX };
1213 pool.truncate_pool(pool_limit);
1214
1215 let sender_a = f.ids.sender_id(&a).unwrap();
1216 let sender_b = f.ids.sender_id(&b).unwrap();
1217 let sender_c = f.ids.sender_id(&c).unwrap();
1218
1219 assert_eq!(pool.get_txs_by_sender(sender_a).len(), 10);
1220 assert!(pool.get_txs_by_sender(sender_b).is_empty());
1221 assert!(pool.get_txs_by_sender(sender_c).is_empty());
1222 }
1223
1224 #[test]
1225 fn test_remove_non_highest_keeps_highest() {
1226 let mut f = MockTransactionFactory::default();
1227 let mut pool = PendingPool::new(MockOrdering::default());
1228 let sender = Address::with_last_byte(0xaa);
1229 let txs = MockTransactionSet::dependent(sender, 0, 3, TxType::Eip1559).into_vec();
1230 for tx in txs {
1231 pool.add_transaction(f.validated_arc(tx), 0);
1232 }
1233 pool.assert_invariants();
1234 let sender_id = f.ids.sender_id(&sender).unwrap();
1235 let mid_id = TransactionId::new(sender_id, 1);
1236 let _ = pool.remove_transaction(&mid_id);
1237 let highest = pool.highest_nonces.get(&sender_id).unwrap();
1238 assert_eq!(highest.transaction.nonce(), 2);
1239 pool.assert_invariants();
1240 }
1241
1242 #[test]
1243 fn test_cascade_removal_recomputes_highest() {
1244 let mut f = MockTransactionFactory::default();
1245 let mut pool = PendingPool::new(MockOrdering::default());
1246 let sender = Address::with_last_byte(0xbb);
1247 let txs = MockTransactionSet::dependent(sender, 0, 4, TxType::Eip1559).into_vec();
1248 for tx in txs {
1249 pool.add_transaction(f.validated_arc(tx), 0);
1250 }
1251 pool.assert_invariants();
1252 let sender_id = f.ids.sender_id(&sender).unwrap();
1253 let id3 = TransactionId::new(sender_id, 3);
1254 let _ = pool.remove_transaction(&id3);
1255 let highest = pool.highest_nonces.get(&sender_id).unwrap();
1256 assert_eq!(highest.transaction.nonce(), 2);
1257 let id2 = TransactionId::new(sender_id, 2);
1258 let _ = pool.remove_transaction(&id2);
1259 let highest = pool.highest_nonces.get(&sender_id).unwrap();
1260 assert_eq!(highest.transaction.nonce(), 1);
1261 pool.assert_invariants();
1262 }
1263
1264 #[test]
1265 fn test_remove_only_tx_clears_highest() {
1266 let mut f = MockTransactionFactory::default();
1267 let mut pool = PendingPool::new(MockOrdering::default());
1268 let sender = Address::with_last_byte(0xcc);
1269 let txs = MockTransactionSet::dependent(sender, 0, 1, TxType::Eip1559).into_vec();
1270 for tx in txs {
1271 pool.add_transaction(f.validated_arc(tx), 0);
1272 }
1273 pool.assert_invariants();
1274 let sender_id = f.ids.sender_id(&sender).unwrap();
1275 let id0 = TransactionId::new(sender_id, 0);
1276 let _ = pool.remove_transaction(&id0);
1277 assert!(!pool.highest_nonces.contains_key(&sender_id));
1278 pool.assert_invariants();
1279 }
1280}