1use crate::{metrics::AA2dPoolMetrics, transaction::TempoPooledTransaction};
3use alloy_primitives::{
4 Address, B256, TxHash, U256,
5 map::{AddressMap, HashMap, HashSet, U256Map},
6};
7use reth_primitives_traits::transaction::error::InvalidTransactionError;
8use reth_tracing::tracing::trace;
9use reth_transaction_pool::{
10 BestTransactions, CoinbaseTipOrdering, GetPooledTransactionLimit, PoolResult, PoolTransaction,
11 PriceBumpConfig, Priority, SubPool, SubPoolLimit, TransactionOrdering, TransactionOrigin,
12 ValidPoolTransaction,
13 error::{InvalidPoolTransactionError, PoolError, PoolErrorKind},
14 pool::{AddedPendingTransaction, AddedTransaction, QueuedReason, pending::PendingTransaction},
15};
16use revm::database::BundleAccount;
17use std::{
18 collections::{
19 BTreeMap, BTreeSet,
20 Bound::{Excluded, Unbounded},
21 btree_map::Entry,
22 hash_map,
23 },
24 sync::{
25 Arc,
26 atomic::{AtomicBool, Ordering},
27 },
28};
29use tempo_chainspec::hardfork::TempoHardfork;
30use tempo_precompiles::NONCE_PRECOMPILE_ADDRESS;
31use tokio::sync::broadcast;
32
33type TxOrdering = CoinbaseTipOrdering<TempoPooledTransaction>;
34#[derive(Debug)]
46pub struct AA2dPool {
47 submission_id: u64,
51 independent_transactions: HashMap<AASequenceId, PendingTransaction<TxOrdering>>,
53 by_id: BTreeMap<AA2dTransactionId, Arc<AA2dInternalTransaction>>,
55 by_hash: HashMap<TxHash, Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
57 expiring_nonce_txs: HashMap<B256, PendingTransaction<TxOrdering>>,
60 slot_to_expiring_nonce_hash: U256Map<B256>,
64 slot_to_seq_id: U256Map<AASequenceId>,
72 config: AA2dPoolConfig,
74 metrics: AA2dPoolMetrics,
76 by_eviction_order: BTreeSet<EvictionKey>,
82 txs_by_sender: AddressMap<usize>,
87 new_transaction_notifier: broadcast::Sender<PendingTransaction<TxOrdering>>,
89}
90
91impl Default for AA2dPool {
92 fn default() -> Self {
93 Self::new(AA2dPoolConfig::default())
94 }
95}
96
97impl AA2dPool {
98 fn expiring_nonce_hash(
99 transaction: &Arc<ValidPoolTransaction<TempoPooledTransaction>>,
100 ) -> B256 {
101 transaction
102 .transaction
103 .expiring_nonce_hash()
104 .expect("expiring nonce tx must be AA")
105 }
106
107 pub fn new(config: AA2dPoolConfig) -> Self {
109 let (new_transaction_notifier, _) = broadcast::channel(200);
110 Self {
111 submission_id: 0,
112 independent_transactions: Default::default(),
113 by_id: Default::default(),
114 by_hash: Default::default(),
115 expiring_nonce_txs: Default::default(),
116 slot_to_expiring_nonce_hash: Default::default(),
117 slot_to_seq_id: Default::default(),
118 config,
119 metrics: AA2dPoolMetrics::default(),
120 by_eviction_order: Default::default(),
121 txs_by_sender: Default::default(),
122 new_transaction_notifier,
123 }
124 }
125
126 fn notify_new_pending(&self, tx: &PendingTransaction<TxOrdering>) {
128 if self.new_transaction_notifier.receiver_count() > 0 {
129 let _ = self.new_transaction_notifier.send(tx.clone());
130 }
131 }
132
133 fn update_metrics(&self) {
135 let (pending, queued) = self.pending_and_queued_txn_count();
136 let total = self.by_id.len() + self.expiring_nonce_txs.len();
137 self.metrics.set_transaction_counts(total, pending, queued);
138 }
139
140 pub(crate) fn add_transaction(
150 &mut self,
151 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
152 on_chain_nonce: u64,
153 hardfork: tempo_chainspec::hardfork::TempoHardfork,
154 ) -> PoolResult<AddedTransaction<TempoPooledTransaction>> {
155 debug_assert!(
156 transaction.transaction.is_aa(),
157 "only AA transactions are supported"
158 );
159 if self.contains(transaction.hash()) {
160 return Err(PoolError::new(
161 *transaction.hash(),
162 PoolErrorKind::AlreadyImported,
163 ));
164 }
165
166 if hardfork.is_t1() && transaction.transaction.is_expiring_nonce() {
169 return self.add_expiring_nonce_transaction(transaction, hardfork);
170 }
171
172 let tx_id = transaction
173 .transaction
174 .aa_transaction_id()
175 .expect("Transaction added to AA2D pool must be an AA transaction");
176
177 if transaction.nonce() < on_chain_nonce {
178 return Err(PoolError::new(
180 *transaction.hash(),
181 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
182 InvalidTransactionError::NonceNotConsistent {
183 tx: transaction.nonce(),
184 state: on_chain_nonce,
185 },
186 )),
187 ));
188 }
189
190 let tx = Arc::new(AA2dInternalTransaction {
192 inner: PendingTransaction {
193 submission_id: self.next_id(),
194 priority: CoinbaseTipOrdering::default()
195 .priority(&transaction.transaction, hardfork.base_fee()),
196 transaction: transaction.clone(),
197 },
198 is_pending: AtomicBool::new(false),
199 });
200
201 let sender = transaction.sender();
204 let replaced = match self.by_id.entry(tx_id) {
205 Entry::Occupied(mut entry) => {
206 if entry
208 .get()
209 .inner
210 .transaction
211 .is_underpriced(&tx.inner.transaction, &self.config.price_bump_config)
212 {
213 return Err(PoolError::new(
214 *transaction.hash(),
215 PoolErrorKind::ReplacementUnderpriced,
216 ));
217 }
218
219 Some(entry.insert(Arc::clone(&tx)))
220 }
221 Entry::Vacant(entry) => {
222 let sender_count = self.txs_by_sender.get(&sender).copied().unwrap_or(0);
224 if sender_count >= self.config.max_txs_per_sender {
225 return Err(PoolError::new(
226 *transaction.hash(),
227 PoolErrorKind::SpammerExceededCapacity(sender),
228 ));
229 }
230
231 entry.insert(Arc::clone(&tx));
232 *self.txs_by_sender.entry(sender).or_insert(0) += 1;
234 None
235 }
236 };
237
238 if transaction.transaction.is_aa_2d() {
242 self.record_2d_slot(&transaction.transaction);
243 }
244
245 if let Some(replaced) = &replaced {
247 self.by_hash.remove(replaced.inner.transaction.hash());
250 let replaced_key = EvictionKey::new(Arc::clone(replaced), tx_id);
252 self.by_eviction_order.remove(&replaced_key);
253 }
254
255 self.by_hash
257 .insert(*tx.inner.transaction.hash(), tx.inner.transaction.clone());
258
259 let mut promoted = Vec::new();
261 let mut inserted_as_pending = false;
263 let on_chain_id = AA2dTransactionId::new(tx_id.seq_id, on_chain_nonce);
265 let mut next_nonce = on_chain_id.nonce;
267
268 for (existing_id, existing_tx) in self.descendant_txs(&on_chain_id) {
271 if existing_id.nonce == next_nonce {
272 match existing_id.nonce.cmp(&tx_id.nonce) {
273 std::cmp::Ordering::Less => {
274 }
276 std::cmp::Ordering::Equal => {
277 existing_tx.set_pending(true);
278 inserted_as_pending = true;
279 }
280 std::cmp::Ordering::Greater => {
281 let was_pending = existing_tx.set_pending(true);
283 if !was_pending {
284 promoted.push(existing_tx.inner.clone());
285 }
286 }
287 }
288 next_nonce = existing_id.nonce.saturating_add(1);
290 } else {
291 break;
293 }
294 }
295
296 self.metrics.inc_inserted();
298
299 let new_tx_eviction_key = EvictionKey::new(Arc::clone(&tx), tx_id);
301 self.by_eviction_order.insert(new_tx_eviction_key);
302
303 if inserted_as_pending {
304 if !promoted.is_empty() {
305 self.metrics.inc_promoted(promoted.len());
306 }
307 if tx_id.nonce == on_chain_nonce {
309 self.independent_transactions
310 .insert(tx_id.seq_id, tx.inner.clone());
311 }
312
313 self.notify_new_pending(&tx.inner);
315 for promoted_tx in &promoted {
316 self.notify_new_pending(promoted_tx);
317 }
318
319 return Ok(AddedTransaction::Pending(AddedPendingTransaction {
320 transaction,
321 replaced: replaced.map(|tx| tx.inner.transaction.clone()),
322 promoted: promoted.into_iter().map(|tx| tx.transaction).collect(),
323 discarded: self.discard(),
324 }));
325 }
326
327 let _ = self.discard();
329
330 Ok(AddedTransaction::Parked {
331 transaction,
332 replaced: replaced.map(|tx| tx.inner.transaction.clone()),
333 subpool: SubPool::Queued,
334 queued_reason: Some(QueuedReason::NonceGap),
335 })
336 }
337
338 fn add_expiring_nonce_transaction(
344 &mut self,
345 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
346 hardfork: TempoHardfork,
347 ) -> PoolResult<AddedTransaction<TempoPooledTransaction>> {
348 let tx_hash = *transaction.hash();
349 let expiring_nonce_hash = Self::expiring_nonce_hash(&transaction);
350
351 if self.expiring_nonce_txs.contains_key(&expiring_nonce_hash) {
353 return Err(PoolError::new(tx_hash, PoolErrorKind::AlreadyImported));
354 }
355
356 let sender = transaction.sender();
358 let sender_count = self.txs_by_sender.get(&sender).copied().unwrap_or(0);
359 if sender_count >= self.config.max_txs_per_sender {
360 return Err(PoolError::new(
361 tx_hash,
362 PoolErrorKind::SpammerExceededCapacity(sender),
363 ));
364 }
365
366 let pending_tx = PendingTransaction {
368 submission_id: self.next_id(),
369 priority: CoinbaseTipOrdering::default()
370 .priority(&transaction.transaction, hardfork.base_fee()),
371 transaction: transaction.clone(),
372 };
373
374 self.notify_new_pending(&pending_tx);
376
377 self.expiring_nonce_txs
379 .insert(expiring_nonce_hash, pending_tx);
380 if let Some(slot) = transaction.transaction.expiring_nonce_slot() {
381 self.slot_to_expiring_nonce_hash
382 .insert(slot, expiring_nonce_hash);
383 }
384 self.by_hash.insert(tx_hash, transaction.clone());
385
386 *self.txs_by_sender.entry(sender).or_insert(0) += 1;
388
389 trace!(target: "txpool", hash = %tx_hash, "Added expiring nonce transaction");
390
391 self.update_metrics();
392
393 Ok(AddedTransaction::Pending(AddedPendingTransaction {
395 transaction,
396 replaced: None,
397 promoted: vec![],
398 discarded: self.discard(),
399 }))
400 }
401
402 pub(crate) fn pending_and_queued_txn_count(&self) -> (usize, usize) {
404 let (pending_2d, queued_2d) = self.by_id.values().fold((0, 0), |mut acc, tx| {
405 if tx.is_pending() {
406 acc.0 += 1;
407 } else {
408 acc.1 += 1;
409 }
410 acc
411 });
412 let expiring_pending = self.expiring_nonce_txs.len();
414 (pending_2d + expiring_pending, queued_2d)
415 }
416
417 pub(crate) fn get_transactions_by_origin_iter(
419 &self,
420 origin: TransactionOrigin,
421 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
422 let regular = self
423 .by_id
424 .values()
425 .filter(move |tx| tx.inner.transaction.origin == origin)
426 .map(|tx| tx.inner.transaction.clone());
427 let expiring = self
428 .expiring_nonce_txs
429 .values()
430 .filter(move |tx| tx.transaction.origin == origin)
431 .map(|tx| tx.transaction.clone());
432 regular.chain(expiring)
433 }
434
435 pub(crate) fn get_pending_transactions_by_origin_iter(
437 &self,
438 origin: TransactionOrigin,
439 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
440 let regular = self
441 .by_id
442 .values()
443 .filter(move |tx| tx.is_pending() && tx.inner.transaction.origin == origin)
444 .map(|tx| tx.inner.transaction.clone());
445 let expiring = self
447 .expiring_nonce_txs
448 .values()
449 .filter(move |tx| tx.transaction.origin == origin)
450 .map(|tx| tx.transaction.clone());
451 regular.chain(expiring)
452 }
453
454 pub(crate) fn get_transactions_by_sender_iter(
456 &self,
457 sender: Address,
458 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
459 let regular = self
460 .by_id
461 .values()
462 .filter(move |tx| tx.inner.transaction.sender() == sender)
463 .map(|tx| tx.inner.transaction.clone());
464 let expiring = self
465 .expiring_nonce_txs
466 .values()
467 .filter(move |tx| tx.transaction.sender() == sender)
468 .map(|tx| tx.transaction.clone());
469 regular.chain(expiring)
470 }
471
472 pub(crate) fn all_transaction_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
474 self.by_hash.keys().copied()
475 }
476
477 pub(crate) fn queued_transactions(
479 &self,
480 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
481 self.by_id
482 .values()
483 .filter(|tx| !tx.is_pending())
484 .map(|tx| tx.inner.transaction.clone())
485 }
486
487 pub(crate) fn pending_transactions(
489 &self,
490 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
491 let regular_pending = self
493 .by_id
494 .values()
495 .filter(|tx| tx.is_pending())
496 .map(|tx| tx.inner.transaction.clone());
497 let expiring_pending = self
498 .expiring_nonce_txs
499 .values()
500 .map(|tx| tx.transaction.clone());
501 regular_pending.chain(expiring_pending)
502 }
503
504 #[allow(clippy::mutable_key_type)]
506 pub(crate) fn best_transactions(&self) -> BestAA2dTransactions {
507 let mut independent: BTreeSet<_> =
509 self.independent_transactions.values().cloned().collect();
510 independent.extend(self.expiring_nonce_txs.values().cloned());
512
513 BestAA2dTransactions {
514 independent,
515 by_id: self
516 .by_id
517 .iter()
518 .filter(|(_, tx)| tx.is_pending())
519 .map(|(id, tx)| (*id, tx.inner.clone()))
520 .collect(),
521 invalid: Default::default(),
522 new_transaction_receiver: Some(self.new_transaction_notifier.subscribe()),
523 last_priority: None,
524 }
525 }
526
527 pub(crate) fn get(
529 &self,
530 tx_hash: &TxHash,
531 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
532 self.by_hash.get(tx_hash).cloned()
533 }
534
535 pub(crate) fn get_all<'a, I>(
537 &self,
538 tx_hashes: I,
539 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
540 where
541 I: Iterator<Item = &'a TxHash> + 'a,
542 {
543 let mut ret = Vec::new();
544 for tx_hash in tx_hashes {
545 if let Some(tx) = self.get(tx_hash) {
546 ret.push(tx);
547 }
548 }
549 ret
550 }
551
552 pub(crate) fn append_pooled_transaction_elements<'a>(
559 &self,
560 tx_hashes: impl IntoIterator<Item = &'a TxHash>,
561 limit: GetPooledTransactionLimit,
562 accumulated_size: &mut usize,
563 out: &mut Vec<<TempoPooledTransaction as PoolTransaction>::Pooled>,
564 ) {
565 for tx_hash in tx_hashes {
566 let Some(tx) = self.by_hash.get(tx_hash) else {
567 continue;
568 };
569
570 let encoded_len = tx.transaction.encoded_length();
571 let Some(pooled) = tx.transaction.clone_into_pooled().ok() else {
572 continue;
573 };
574
575 *accumulated_size += encoded_len;
576 out.push(pooled.into_inner());
577
578 if limit.exceeds(*accumulated_size) {
579 break;
580 }
581 }
582 }
583
584 pub(crate) fn senders_iter(&self) -> impl Iterator<Item = &Address> {
586 let regular = self
587 .by_id
588 .values()
589 .map(|tx| tx.inner.transaction.sender_ref());
590 let expiring = self
591 .expiring_nonce_txs
592 .values()
593 .map(|tx| tx.transaction.sender_ref());
594 regular.chain(expiring)
595 }
596
597 fn descendant_txs<'a, 'b: 'a>(
602 &'a self,
603 id: &'b AA2dTransactionId,
604 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a Arc<AA2dInternalTransaction>)> + 'a {
605 self.by_id
606 .range(id..)
607 .take_while(|(other, _)| id.seq_id == other.seq_id)
608 }
609
610 fn descendant_txs_exclusive<'a, 'b: 'a>(
614 &'a self,
615 id: &'b AA2dTransactionId,
616 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a Arc<AA2dInternalTransaction>)> + 'a {
617 self.by_id
618 .range((Excluded(id), Unbounded))
619 .take_while(|(other, _)| id.seq_id == other.seq_id)
620 }
621
622 fn remove_transaction_by_id(
626 &mut self,
627 id: &AA2dTransactionId,
628 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
629 let tx = self.by_id.remove(id)?;
630
631 let eviction_key = EvictionKey::new(Arc::clone(&tx), *id);
633 self.by_eviction_order.remove(&eviction_key);
634
635 if self.by_id.range(id.seq_id.range()).next().is_none()
637 && let Some(slot) = tx.inner.transaction.transaction.nonce_key_slot()
638 {
639 self.slot_to_seq_id.remove(&slot);
640 }
641
642 self.remove_independent(id);
643 let removed_tx = tx.inner.transaction.clone();
644 self.by_hash.remove(removed_tx.hash());
645
646 self.decrement_sender_count(removed_tx.sender());
648
649 Some(removed_tx)
650 }
651
652 fn decrement_sender_count(&mut self, sender: Address) {
654 if let hash_map::Entry::Occupied(mut entry) = self.txs_by_sender.entry(sender) {
655 let count = entry.get_mut();
656 *count -= 1;
657 if *count == 0 {
658 entry.remove();
659 }
660 }
661 }
662
663 fn remove_independent(
665 &mut self,
666 id: &AA2dTransactionId,
667 ) -> Option<PendingTransaction<TxOrdering>> {
668 match self.independent_transactions.entry(id.seq_id) {
670 hash_map::Entry::Occupied(entry) => {
671 if entry.get().transaction.nonce() == id.nonce {
673 return Some(entry.remove());
674 }
675 }
676 hash_map::Entry::Vacant(_) => {}
677 };
678 None
679 }
680
681 pub(crate) fn remove_transactions<'a, I>(
686 &mut self,
687 tx_hashes: I,
688 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
689 where
690 I: Iterator<Item = &'a TxHash> + 'a,
691 {
692 let mut txs = Vec::new();
693 let mut seq_ids_to_demote: HashMap<AASequenceId, u64> = HashMap::default();
694
695 for tx_hash in tx_hashes {
696 if let Some((tx, seq_id)) = self.remove_transaction_by_hash_no_demote(tx_hash) {
697 if let Some(id) = seq_id {
698 seq_ids_to_demote
699 .entry(id.seq_id)
700 .and_modify(|min_nonce| {
701 if id.nonce < *min_nonce {
702 *min_nonce = id.nonce;
703 }
704 })
705 .or_insert(id.nonce);
706 }
707 txs.push(tx);
708 }
709 }
710
711 for (seq_id, min_nonce) in seq_ids_to_demote {
713 self.demote_from_nonce(&seq_id, min_nonce);
714 }
715
716 txs
717 }
718
719 fn remove_transaction_by_hash(
724 &mut self,
725 tx_hash: &B256,
726 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
727 let (tx, id) = self.remove_transaction_by_hash_no_demote(tx_hash)?;
728
729 if let Some(id) = id {
731 self.demote_descendants(&id);
732 }
733
734 Some(tx)
735 }
736
737 fn remove_transaction_by_hash_no_demote(
741 &mut self,
742 tx_hash: &B256,
743 ) -> Option<(
744 Arc<ValidPoolTransaction<TempoPooledTransaction>>,
745 Option<AA2dTransactionId>,
746 )> {
747 let tx = self.by_hash.remove(tx_hash)?;
748
749 if tx.transaction.is_expiring_nonce() {
751 let tx = self.remove_expiring_nonce_tx(&Self::expiring_nonce_hash(&tx))?;
752 return Some((tx, None));
753 }
754
755 let id = tx
757 .transaction
758 .aa_transaction_id()
759 .expect("is AA transaction");
760 self.remove_transaction_by_id(&id)?;
761
762 Some((tx, Some(id)))
763 }
764
765 fn demote_descendants(&mut self, id: &AA2dTransactionId) {
770 self.demote_from_nonce(&id.seq_id, id.nonce);
771 }
772
773 fn demote_from_nonce(&self, seq_id: &AASequenceId, min_nonce: u64) {
778 let start_id = AA2dTransactionId::new(*seq_id, min_nonce);
779 for (_, tx) in self
780 .by_id
781 .range((Excluded(&start_id), Unbounded))
782 .take_while(|(other, _)| *seq_id == other.seq_id)
783 {
784 tx.set_pending(false);
785 }
786 }
787
788 pub(crate) fn remove_transactions_and_descendants<'a, I>(
791 &mut self,
792 hashes: I,
793 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
794 where
795 I: Iterator<Item = &'a TxHash> + 'a,
796 {
797 let mut removed = Vec::new();
798 for hash in hashes {
799 if let Some(tx) = self.remove_transaction_by_hash(hash) {
800 let id = tx.transaction.aa_transaction_id();
801 removed.push(tx);
802 if let Some(id) = id {
803 self.remove_descendants(&id, &mut removed);
804 }
805 }
806 }
807 removed
808 }
809
810 pub(crate) fn remove_transactions_by_sender(
812 &mut self,
813 sender_id: Address,
814 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
815 let mut removed = Vec::new();
816 let txs = self
817 .get_transactions_by_sender_iter(sender_id)
818 .collect::<Vec<_>>();
819 for tx in txs {
820 if tx.transaction.is_expiring_nonce() {
821 if let Some(tx) = self.remove_expiring_nonce_tx(&Self::expiring_nonce_hash(&tx)) {
822 removed.push(tx);
823 }
824 } else if let Some(tx) = tx
825 .transaction
826 .aa_transaction_id()
827 .and_then(|id| self.remove_transaction_by_id(&id))
828 {
829 removed.push(tx);
830 }
831 }
832 removed
833 }
834
835 fn remove_descendants(
839 &mut self,
840 tx: &AA2dTransactionId,
841 removed: &mut Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
842 ) {
843 let mut id = *tx;
844
845 loop {
847 let descendant = self.descendant_txs_exclusive(&id).map(|(id, _)| *id).next();
848 if let Some(descendant) = descendant {
849 if let Some(tx) = self.remove_transaction_by_id(&descendant) {
850 removed.push(tx)
851 }
852 id = descendant;
853 } else {
854 return;
855 }
856 }
857 }
858
859 #[allow(clippy::type_complexity)]
865 pub(crate) fn on_nonce_changes(
866 &mut self,
867 on_chain_ids: HashMap<AASequenceId, u64>,
868 ) -> (
869 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
870 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
871 ) {
872 trace!(target: "txpool::2d", ?on_chain_ids, "processing nonce changes");
873
874 let mut promoted = Vec::new();
875 let mut mined_ids = Vec::new();
876
877 'changes: for (sender_id, on_chain_nonce) in on_chain_ids {
879 let mut iter = self
880 .by_id
881 .range_mut((sender_id.start_bound(), Unbounded))
882 .take_while(move |(other, _)| sender_id == other.seq_id)
883 .peekable();
884
885 let Some(mut current) = iter.next() else {
886 continue;
887 };
888
889 'mined: loop {
891 if current.0.nonce < on_chain_nonce {
892 mined_ids.push(*current.0);
893 let Some(next) = iter.next() else {
894 continue 'changes;
895 };
896 current = next;
897 } else {
898 break 'mined;
899 }
900 }
901
902 let mut next_nonce = on_chain_nonce;
904 for (existing_id, existing_tx) in std::iter::once(current).chain(iter) {
905 if existing_id.nonce == next_nonce {
906 let was_pending = existing_tx.set_pending(true);
908 if !was_pending {
909 promoted.push(existing_tx.inner.transaction.clone());
910 }
911
912 if existing_id.nonce == on_chain_nonce {
913 self.independent_transactions
915 .insert(existing_id.seq_id, existing_tx.inner.clone());
916 }
917
918 next_nonce = next_nonce.saturating_add(1);
919 } else {
920 existing_tx.set_pending(false);
922 }
923 }
924
925 if next_nonce == on_chain_nonce {
929 self.independent_transactions.remove(&sender_id);
930 }
931 }
932
933 let mut mined = Vec::with_capacity(mined_ids.len());
935 for id in mined_ids {
936 if let Some(removed) = self.remove_transaction_by_id(&id) {
937 mined.push(removed);
938 }
939 }
940
941 (promoted, mined)
942 }
943
944 fn discard(&mut self) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
956 let mut removed = Vec::new();
957
958 let (pending_count, queued_count) = self.pending_and_queued_txn_count();
960
961 if queued_count > self.config.queued_limit.max_txs {
963 let queued_excess = queued_count - self.config.queued_limit.max_txs;
964 removed.extend(self.evict_lowest_priority(queued_excess, false));
965 }
966
967 if pending_count > self.config.pending_limit.max_txs {
969 let pending_excess = pending_count - self.config.pending_limit.max_txs;
970 removed.extend(self.evict_lowest_priority(pending_excess, true));
971 }
972
973 if !removed.is_empty() {
974 self.metrics.inc_removed(removed.len());
975 }
976
977 removed
978 }
979
980 fn evict_lowest_priority(
986 &mut self,
987 count: usize,
988 evict_pending: bool,
989 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
990 if count == 0 {
991 return vec![];
992 }
993
994 let mut removed = Vec::with_capacity(count);
995
996 if evict_pending {
997 for _ in 0..count {
999 if let Some(tx) = self.evict_one_pending() {
1000 removed.push(tx);
1001 } else {
1002 break;
1003 }
1004 }
1005 } else {
1006 let to_remove: Vec<_> = self
1008 .by_eviction_order
1009 .iter()
1010 .filter(|key| !key.is_pending())
1011 .map(|key| key.tx_id)
1012 .take(count)
1013 .collect();
1014
1015 for id in to_remove {
1016 if let Some(tx) = self.remove_transaction_by_id(&id) {
1017 removed.push(tx);
1018 }
1019 }
1020 }
1021
1022 removed
1023 }
1024
1025 fn evict_one_pending(&mut self) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1028 let worst_2d = self
1029 .by_eviction_order
1030 .iter()
1031 .find(|key| key.is_pending())
1032 .map(|key| (key.tx_id, key.priority().clone(), key.submission_id()));
1033
1034 let worst_expiring = self
1035 .expiring_nonce_txs
1036 .iter()
1037 .min_by(|a, b| {
1038 a.1.priority
1039 .cmp(&b.1.priority)
1040 .then_with(|| b.1.submission_id.cmp(&a.1.submission_id))
1041 })
1042 .map(|(hash, tx)| (*hash, tx.priority.clone(), tx.submission_id));
1043
1044 match (worst_2d, worst_expiring) {
1045 (Some((id, pri_2d, sid_2d)), Some((hash, pri_exp, sid_exp))) => {
1046 let evict_expiring = pri_exp
1048 .cmp(&pri_2d)
1049 .then_with(|| sid_2d.cmp(&sid_exp))
1050 .is_le();
1051 if evict_expiring {
1052 self.remove_expiring_nonce_tx(&hash)
1053 } else {
1054 self.evict_2d_pending_tx(&id)
1055 }
1056 }
1057 (Some((id, ..)), None) => self.evict_2d_pending_tx(&id),
1058 (None, Some((hash, ..))) => self.remove_expiring_nonce_tx(&hash),
1059 (None, None) => None,
1060 }
1061 }
1062
1063 fn evict_2d_pending_tx(
1065 &mut self,
1066 id: &AA2dTransactionId,
1067 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1068 let tx = self.remove_transaction_by_id(id)?;
1069 self.demote_descendants(id);
1070 Some(tx)
1071 }
1072
1073 fn remove_expiring_nonce_tx(
1077 &mut self,
1078 expiring_hash: &B256,
1079 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1080 let pending_tx = self.expiring_nonce_txs.remove(expiring_hash)?;
1081 let tx_hash = *pending_tx.transaction.hash();
1082 self.by_hash.remove(&tx_hash);
1083 if let Some(slot) = pending_tx.transaction.transaction.expiring_nonce_slot() {
1084 self.slot_to_expiring_nonce_hash.remove(&slot);
1085 }
1086 self.decrement_sender_count(pending_tx.transaction.sender());
1087 Some(pending_tx.transaction)
1088 }
1089
1090 pub fn metrics(&self) -> &AA2dPoolMetrics {
1092 &self.metrics
1093 }
1094
1095 pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
1097 self.by_hash.contains_key(tx_hash)
1098 }
1099
1100 pub(crate) fn pooled_transactions_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
1102 self.by_hash
1103 .values()
1104 .filter(|tx| tx.propagate)
1105 .map(|tx| *tx.hash())
1106 }
1107
1108 pub(crate) fn pooled_transactions_iter(
1110 &self,
1111 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1112 self.by_hash.values().filter(|tx| tx.propagate).cloned()
1113 }
1114
1115 const fn next_id(&mut self) -> u64 {
1116 let id = self.submission_id;
1117 self.submission_id = self.submission_id.wrapping_add(1);
1118 id
1119 }
1120
1121 fn record_2d_slot(&mut self, transaction: &TempoPooledTransaction) {
1123 let address = transaction.sender();
1124 let nonce_key = transaction.nonce_key().unwrap_or_default();
1125 let Some(slot) = transaction.nonce_key_slot() else {
1126 return;
1127 };
1128
1129 trace!(target: "txpool::2d", ?address, ?nonce_key, "recording 2d nonce slot");
1130 let seq_id = AASequenceId::new(address, nonce_key);
1131
1132 if self.slot_to_seq_id.insert(slot, seq_id).is_none() {
1133 self.metrics.inc_nonce_key_count(1);
1134 }
1135 }
1136
1137 #[expect(clippy::type_complexity)]
1139 pub(crate) fn on_state_updates(
1140 &mut self,
1141 state: &AddressMap<BundleAccount>,
1142 ) -> (
1143 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
1144 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
1145 ) {
1146 let mut changes = HashMap::default();
1147 let mut included_expiring_nonce_hashes = Vec::new();
1148
1149 for (account, state) in state {
1150 if account == &NONCE_PRECOMPILE_ADDRESS {
1151 for (slot, value) in state.storage.iter() {
1153 if let Some(seq_id) = self.slot_to_seq_id.get(slot) {
1154 changes.insert(*seq_id, value.present_value.saturating_to());
1155 }
1156 if !value.present_value.is_zero()
1159 && let Some(expiring_nonce_hash) =
1160 self.slot_to_expiring_nonce_hash.get(slot)
1161 {
1162 included_expiring_nonce_hashes.push(*expiring_nonce_hash);
1163 }
1164 }
1165 }
1166 let nonce = state
1167 .account_info()
1168 .map(|info| info.nonce)
1169 .unwrap_or_default();
1170 changes.insert(AASequenceId::new(*account, U256::ZERO), nonce);
1171 }
1172
1173 let (promoted, mut mined) = self.on_nonce_changes(changes);
1174
1175 for expiring_nonce_hash in included_expiring_nonce_hashes {
1177 if let Some(tx) = self.remove_expiring_nonce_tx(&expiring_nonce_hash) {
1178 mined.push(tx);
1179 }
1180 }
1181
1182 if !promoted.is_empty() {
1184 self.metrics.inc_promoted(promoted.len());
1185 }
1186 if !mined.is_empty() {
1187 self.metrics.inc_removed(mined.len());
1188 }
1189 self.update_metrics();
1190
1191 (promoted, mined)
1192 }
1193
1194 #[cfg(test)]
1196 pub(crate) fn assert_invariants(&self) {
1197 assert!(
1199 self.independent_transactions.len() <= self.by_id.len(),
1200 "independent_transactions.len() ({}) > by_id.len() ({})",
1201 self.independent_transactions.len(),
1202 self.by_id.len()
1203 );
1204 assert_eq!(
1206 self.by_id.len() + self.expiring_nonce_txs.len(),
1207 self.by_hash.len(),
1208 "by_id.len() ({}) + expiring_nonce_txs.len() ({}) != by_hash.len() ({})",
1209 self.by_id.len(),
1210 self.expiring_nonce_txs.len(),
1211 self.by_hash.len()
1212 );
1213
1214 for (seq_id, independent_tx) in &self.independent_transactions {
1216 let tx_id = independent_tx
1217 .transaction
1218 .transaction
1219 .aa_transaction_id()
1220 .expect("Independent transaction must have AA transaction ID");
1221 assert!(
1222 self.by_id.contains_key(&tx_id),
1223 "Independent transaction {tx_id:?} not in by_id"
1224 );
1225 assert_eq!(
1226 seq_id, &tx_id.seq_id,
1227 "Independent transactions sequence ID {seq_id:?} does not match transaction sequence ID {tx_id:?}"
1228 );
1229
1230 let tx_in_pool = self.by_id.get(&tx_id).unwrap();
1232 assert!(
1233 tx_in_pool.is_pending(),
1234 "Independent transaction {tx_id:?} is not pending"
1235 );
1236
1237 assert_eq!(
1239 independent_tx.transaction.hash(),
1240 tx_in_pool.inner.transaction.hash(),
1241 "Independent transaction hash mismatch for {tx_id:?}"
1242 );
1243 }
1244
1245 let mut seen_senders = std::collections::HashSet::new();
1247 for id in self.independent_transactions.keys() {
1248 assert!(
1249 seen_senders.insert(*id),
1250 "Duplicate sender {id:?} in independent transactions"
1251 );
1252 }
1253
1254 for (hash, tx) in &self.by_hash {
1256 assert_eq!(
1258 tx.hash(),
1259 hash,
1260 "Hash mismatch in by_hash: expected {:?}, got {:?}",
1261 hash,
1262 tx.hash()
1263 );
1264
1265 if tx.transaction.is_expiring_nonce() {
1267 assert!(
1268 self.expiring_nonce_txs
1269 .contains_key(&Self::expiring_nonce_hash(tx)),
1270 "Expiring nonce transaction with hash {hash:?} in by_hash but not in expiring_nonce_txs"
1271 );
1272 continue;
1273 }
1274
1275 let id = tx
1277 .transaction
1278 .aa_transaction_id()
1279 .expect("Transaction in pool should be AA transaction");
1280 assert!(
1281 self.by_id.contains_key(&id),
1282 "Transaction with hash {hash:?} in by_hash but not in by_id"
1283 );
1284
1285 let tx_in_by_id = &self.by_id.get(&id).unwrap().inner.transaction;
1287 assert_eq!(
1288 tx.hash(),
1289 tx_in_by_id.hash(),
1290 "Transaction hash mismatch between by_hash and by_id for id {id:?}"
1291 );
1292 }
1293
1294 for (id, tx) in &self.by_id {
1296 let hash = tx.inner.transaction.hash();
1298 assert!(
1299 self.by_hash.contains_key(hash),
1300 "Transaction with id {id:?} in by_id but not in by_hash"
1301 );
1302
1303 let tx_id = tx
1305 .inner
1306 .transaction
1307 .transaction
1308 .aa_transaction_id()
1309 .expect("Transaction in pool should be AA transaction");
1310 assert_eq!(
1311 &tx_id, id,
1312 "Transaction ID mismatch: expected {id:?}, got {tx_id:?}"
1313 );
1314
1315 if let Some(independent_tx) = self.independent_transactions.get(&id.seq_id)
1317 && independent_tx.transaction.hash() == tx.inner.transaction.hash()
1318 {
1319 assert!(
1320 tx.is_pending(),
1321 "Transaction {id:?} is in independent set but not pending"
1322 );
1323 }
1324 }
1325
1326 let (pending_count, queued_count) = self.pending_and_queued_txn_count();
1329 assert_eq!(
1330 pending_count + queued_count,
1331 self.by_id.len() + self.expiring_nonce_txs.len(),
1332 "Pending ({}) + queued ({}) != total transactions (by_id: {} + expiring: {})",
1333 pending_count,
1334 queued_count,
1335 self.by_id.len(),
1336 self.expiring_nonce_txs.len()
1337 );
1338
1339 assert!(
1341 pending_count <= self.config.pending_limit.max_txs,
1342 "pending_count {} exceeds limit {}",
1343 pending_count,
1344 self.config.pending_limit.max_txs
1345 );
1346 assert!(
1347 queued_count <= self.config.queued_limit.max_txs,
1348 "queued_count {} exceeds limit {}",
1349 queued_count,
1350 self.config.queued_limit.max_txs
1351 );
1352
1353 for (hash, pending_tx) in &self.expiring_nonce_txs {
1355 let tx_hash = *pending_tx.transaction.hash();
1356 assert!(
1357 self.by_hash.contains_key(&tx_hash),
1358 "Expiring nonce tx {tx_hash:?} not in by_hash (expiring hash {hash:?})"
1359 );
1360 assert!(
1361 pending_tx.transaction.transaction.is_expiring_nonce(),
1362 "Transaction in expiring_nonce_txs is not an expiring nonce tx"
1363 );
1364 }
1365 }
1366}
1367
1368pub const DEFAULT_MAX_TXS_PER_SENDER: usize = 16;
1372
1373#[derive(Debug, Clone)]
1375pub struct AA2dPoolConfig {
1376 pub price_bump_config: PriceBumpConfig,
1378 pub pending_limit: SubPoolLimit,
1380 pub queued_limit: SubPoolLimit,
1382 pub max_txs_per_sender: usize,
1386}
1387
1388impl Default for AA2dPoolConfig {
1389 fn default() -> Self {
1390 Self {
1391 price_bump_config: PriceBumpConfig::default(),
1392 pending_limit: SubPoolLimit::default(),
1393 queued_limit: SubPoolLimit::default(),
1394 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
1395 }
1396 }
1397}
1398
1399#[derive(Debug)]
1400struct AA2dInternalTransaction {
1401 inner: PendingTransaction<CoinbaseTipOrdering<TempoPooledTransaction>>,
1405 is_pending: AtomicBool,
1413}
1414
1415impl AA2dInternalTransaction {
1416 fn is_pending(&self) -> bool {
1418 self.is_pending.load(Ordering::Relaxed)
1419 }
1420
1421 fn set_pending(&self, pending: bool) -> bool {
1423 self.is_pending.swap(pending, Ordering::Relaxed)
1424 }
1425}
1426
1427#[derive(Debug, Clone)]
1436struct EvictionKey {
1437 tx: Arc<AA2dInternalTransaction>,
1439 tx_id: AA2dTransactionId,
1443}
1444
1445impl EvictionKey {
1446 fn new(tx: Arc<AA2dInternalTransaction>, tx_id: AA2dTransactionId) -> Self {
1448 Self { tx, tx_id }
1449 }
1450
1451 fn priority(&self) -> &Priority<u128> {
1453 &self.tx.inner.priority
1454 }
1455
1456 fn submission_id(&self) -> u64 {
1458 self.tx.inner.submission_id
1459 }
1460
1461 fn is_pending(&self) -> bool {
1463 self.tx.is_pending()
1464 }
1465}
1466
1467impl PartialEq for EvictionKey {
1468 fn eq(&self, other: &Self) -> bool {
1469 self.submission_id() == other.submission_id()
1470 }
1471}
1472
1473impl Eq for EvictionKey {}
1474
1475impl Ord for EvictionKey {
1476 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1477 self.priority()
1479 .cmp(other.priority())
1480 .then_with(|| other.submission_id().cmp(&self.submission_id()))
1483 }
1484}
1485
1486impl PartialOrd for EvictionKey {
1487 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1488 Some(self.cmp(other))
1489 }
1490}
1491
1492const MAX_NEW_TRANSACTIONS_PER_BATCH: usize = 16;
1494
1495enum IncomingAA2dTransaction {
1498 Process(PendingTransaction<TxOrdering>),
1500 Stash(PendingTransaction<TxOrdering>),
1502}
1503
1504#[derive(Debug)]
1506pub(crate) struct BestAA2dTransactions {
1507 independent: BTreeSet<PendingTransaction<TxOrdering>>,
1509 by_id: BTreeMap<AA2dTransactionId, PendingTransaction<TxOrdering>>,
1511
1512 invalid: HashSet<AASequenceId>,
1514 new_transaction_receiver: Option<broadcast::Receiver<PendingTransaction<TxOrdering>>>,
1516 last_priority: Option<Priority<u128>>,
1518}
1519
1520impl BestAA2dTransactions {
1521 fn pop_best(&mut self) -> Option<(AA2dTransactionId, PendingTransaction<TxOrdering>)> {
1523 let tx = self.independent.pop_last()?;
1524 let id = tx
1525 .transaction
1526 .transaction
1527 .aa_transaction_id()
1528 .expect("Transaction in AA2D pool must be an AA transaction with valid nonce key");
1529 self.by_id.remove(&id);
1530 Some((id, tx))
1531 }
1532
1533 fn try_recv(&mut self) -> Option<IncomingAA2dTransaction> {
1535 loop {
1536 match self.new_transaction_receiver.as_mut()?.try_recv() {
1537 Ok(tx) => {
1538 if let Some(last_priority) = &self.last_priority
1539 && &tx.priority > last_priority
1540 {
1541 return Some(IncomingAA2dTransaction::Stash(tx));
1544 }
1545 return Some(IncomingAA2dTransaction::Process(tx));
1546 }
1547 Err(broadcast::error::TryRecvError::Lagged(_)) => {
1548 }
1550 Err(_) => return None,
1551 }
1552 }
1553 }
1554
1555 fn add_new_transactions(&mut self) {
1557 for _ in 0..MAX_NEW_TRANSACTIONS_PER_BATCH {
1558 if let Some(incoming) = self.try_recv() {
1559 let (tx, process) = match incoming {
1560 IncomingAA2dTransaction::Process(tx) => (tx, true),
1561 IncomingAA2dTransaction::Stash(tx) => (tx, false),
1562 };
1563 if tx.transaction.transaction.is_expiring_nonce() {
1564 if process {
1565 self.independent.insert(tx);
1567 }
1568 } else if let Some(id) = tx.transaction.transaction.aa_transaction_id() {
1569 if process {
1570 if !self.by_id.contains_key(&AA2dTransactionId::new(
1572 id.seq_id,
1573 id.nonce.saturating_sub(1),
1574 )) || id.nonce == 0
1575 {
1576 self.independent.insert(tx.clone());
1577 }
1578 }
1579 self.by_id.insert(id, tx);
1580 }
1581 } else {
1582 break;
1583 }
1584 }
1585 }
1586
1587 pub(crate) fn next_tx_and_priority(
1589 &mut self,
1590 ) -> Option<(
1591 Arc<ValidPoolTransaction<TempoPooledTransaction>>,
1592 Priority<u128>,
1593 )> {
1594 loop {
1595 self.add_new_transactions();
1596 let (id, best) = self.pop_best()?;
1597 if self.invalid.contains(&id.seq_id) {
1598 continue;
1599 }
1600 if !best.transaction.transaction.is_expiring_nonce()
1603 && let Some(unlocked) = self.by_id.get(&id.unlocks())
1604 {
1605 self.independent.insert(unlocked.clone());
1606 }
1607 if self.new_transaction_receiver.is_some() {
1608 self.last_priority = Some(best.priority.clone());
1609 }
1610 return Some((best.transaction, best.priority));
1611 }
1612 }
1613}
1614
1615impl Iterator for BestAA2dTransactions {
1616 type Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>;
1617
1618 fn next(&mut self) -> Option<Self::Item> {
1619 self.next_tx_and_priority().map(|(tx, _)| tx)
1620 }
1621}
1622
1623impl BestTransactions for BestAA2dTransactions {
1624 fn mark_invalid(&mut self, transaction: &Self::Item, _kind: &InvalidPoolTransactionError) {
1625 if transaction.transaction.is_expiring_nonce() {
1628 return;
1629 }
1630
1631 if let Some(id) = transaction.transaction.aa_transaction_id() {
1632 self.invalid.insert(id.seq_id);
1633 }
1634 }
1635
1636 fn no_updates(&mut self) {
1637 self.new_transaction_receiver.take();
1638 self.last_priority.take();
1639 }
1640
1641 fn set_skip_blobs(&mut self, _skip_blobs: bool) {}
1642}
1643
1644#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
1648pub struct AASequenceId {
1649 pub address: Address,
1651 pub nonce_key: U256,
1653}
1654
1655impl AASequenceId {
1656 pub const fn new(address: Address, nonce_key: U256) -> Self {
1658 Self { address, nonce_key }
1659 }
1660
1661 const fn start_bound(self) -> std::ops::Bound<AA2dTransactionId> {
1662 std::ops::Bound::Included(AA2dTransactionId::new(self, 0))
1663 }
1664
1665 const fn range(self) -> std::ops::RangeInclusive<AA2dTransactionId> {
1667 AA2dTransactionId::new(self, 0)..=AA2dTransactionId::new(self, u64::MAX)
1668 }
1669}
1670
1671#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
1675pub(crate) struct AA2dTransactionId {
1676 pub(crate) seq_id: AASequenceId,
1678 pub(crate) nonce: u64,
1680}
1681
1682impl AA2dTransactionId {
1683 pub(crate) const fn new(seq_id: AASequenceId, nonce: u64) -> Self {
1685 Self { seq_id, nonce }
1686 }
1687
1688 pub(crate) fn unlocks(&self) -> Self {
1690 Self::new(self.seq_id, self.nonce.saturating_add(1))
1691 }
1692}
1693
1694#[cfg(test)]
1695mod tests {
1696 use super::*;
1697 use crate::test_utils::{TxBuilder, wrap_valid_tx};
1698 use alloy_eips::eip2930::AccessList;
1699 use alloy_primitives::{Address, Bytes, Signature, TxKind, U256};
1700 use reth_primitives_traits::Recovered;
1701 use reth_transaction_pool::PoolTransaction;
1702 use std::collections::HashSet;
1703 use tempo_chainspec::hardfork::TempoHardfork;
1704 use tempo_primitives::{
1705 TempoTxEnvelope,
1706 transaction::{
1707 TempoTransaction,
1708 tempo_transaction::Call,
1709 tt_signature::{PrimitiveSignature, TempoSignature},
1710 tt_signed::AASigned,
1711 },
1712 };
1713
1714 #[test_case::test_case(U256::ZERO)]
1715 #[test_case::test_case(U256::random())]
1716 fn insert_pending(nonce_key: U256) {
1717 let mut pool = AA2dPool::default();
1718
1719 let sender = Address::random();
1721
1722 let tx = TxBuilder::aa(sender).nonce_key(nonce_key).build();
1724 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
1725
1726 let result = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
1728
1729 assert!(result.is_ok(), "Transaction should be added successfully");
1731 let added = result.unwrap();
1732 assert!(
1733 matches!(added, AddedTransaction::Pending(_)),
1734 "Transaction should be pending, got: {added:?}"
1735 );
1736
1737 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1739 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
1740 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1741
1742 pool.assert_invariants();
1743 }
1744
1745 #[test_case::test_case(U256::ZERO)]
1746 #[test_case::test_case(U256::random())]
1747 fn insert_with_nonce_gap_then_fill(nonce_key: U256) {
1748 let mut pool = AA2dPool::default();
1749
1750 let sender = Address::random();
1752
1753 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
1755 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
1756 let tx1_hash = *valid_tx1.hash();
1757
1758 let result1 = pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1);
1759
1760 assert!(
1762 result1.is_ok(),
1763 "Transaction 1 should be added successfully"
1764 );
1765 let added1 = result1.unwrap();
1766 assert!(
1767 matches!(
1768 added1,
1769 AddedTransaction::Parked {
1770 subpool: SubPool::Queued,
1771 ..
1772 }
1773 ),
1774 "Transaction 1 should be queued due to nonce gap, got: {added1:?}"
1775 );
1776
1777 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1779 assert_eq!(pending_count, 0, "Should have 0 pending transactions");
1780 assert_eq!(queued_count, 1, "Should have 1 queued transaction");
1781
1782 let seq_id = AASequenceId::new(sender, nonce_key);
1784 let tx1_id = AA2dTransactionId::new(seq_id, 1);
1785 assert!(
1786 !pool.independent_transactions.contains_key(&tx1_id.seq_id),
1787 "Transaction 1 should not be in independent set yet"
1788 );
1789
1790 pool.assert_invariants();
1791
1792 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
1794 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
1795 let tx0_hash = *valid_tx0.hash();
1796
1797 let result0 = pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1);
1798
1799 assert!(
1801 result0.is_ok(),
1802 "Transaction 0 should be added successfully"
1803 );
1804 let added0 = result0.unwrap();
1805
1806 match added0 {
1808 AddedTransaction::Pending(ref pending) => {
1809 assert_eq!(pending.transaction.hash(), &tx0_hash, "Should be tx0");
1810 assert_eq!(
1811 pending.promoted.len(),
1812 1,
1813 "Should have promoted 1 transaction"
1814 );
1815 assert_eq!(
1816 pending.promoted[0].hash(),
1817 &tx1_hash,
1818 "Should have promoted tx1"
1819 );
1820 }
1821 _ => panic!("Transaction 0 should be pending, got: {added0:?}"),
1822 }
1823
1824 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1826 assert_eq!(pending_count, 2, "Should have 2 pending transactions");
1827 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1828
1829 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1831 assert!(
1832 pool.by_id.get(&tx0_id).unwrap().is_pending(),
1833 "Transaction 0 should be pending"
1834 );
1835 assert!(
1836 pool.by_id.get(&tx1_id).unwrap().is_pending(),
1837 "Transaction 1 should be pending after promotion"
1838 );
1839
1840 assert!(
1842 pool.independent_transactions.contains_key(&tx0_id.seq_id),
1843 "Transaction 0 should be in independent set (at on-chain nonce)"
1844 );
1845
1846 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
1848 assert_eq!(
1849 independent_tx.transaction.hash(),
1850 &tx0_hash,
1851 "Independent transaction should be tx0, not tx1"
1852 );
1853
1854 pool.assert_invariants();
1855 }
1856
1857 #[test_case::test_case(U256::ZERO)]
1858 #[test_case::test_case(U256::random())]
1859 fn replace_pending_transaction(nonce_key: U256) {
1860 let mut pool = AA2dPool::default();
1861
1862 let sender = Address::random();
1864
1865 let tx_low = TxBuilder::aa(sender)
1867 .nonce_key(nonce_key)
1868 .max_priority_fee(1_000_000_000)
1869 .max_fee(2_000_000_000)
1870 .build();
1871 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
1872 let tx_low_hash = *valid_tx_low.hash();
1873
1874 let result_low = pool.add_transaction(Arc::new(valid_tx_low), 0, TempoHardfork::T1);
1875
1876 assert!(
1878 result_low.is_ok(),
1879 "Initial transaction should be added successfully"
1880 );
1881 let added_low = result_low.unwrap();
1882 assert!(
1883 matches!(added_low, AddedTransaction::Pending(_)),
1884 "Initial transaction should be pending"
1885 );
1886
1887 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1889 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
1890 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1891
1892 let seq_id = AASequenceId::new(sender, nonce_key);
1894 let tx_id = AA2dTransactionId::new(seq_id, 0);
1895 assert!(
1896 pool.independent_transactions.contains_key(&tx_id.seq_id),
1897 "Initial transaction should be in independent set"
1898 );
1899
1900 let independent_tx = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
1902 assert_eq!(
1903 independent_tx.transaction.hash(),
1904 &tx_low_hash,
1905 "Independent set should contain tx_low"
1906 );
1907
1908 pool.assert_invariants();
1909
1910 let tx_high = TxBuilder::aa(sender)
1913 .nonce_key(nonce_key)
1914 .max_priority_fee(1_200_000_000)
1915 .max_fee(2_400_000_000)
1916 .build();
1917 let valid_tx_high = wrap_valid_tx(tx_high, TransactionOrigin::Local);
1918 let tx_high_hash = *valid_tx_high.hash();
1919
1920 let result_high = pool.add_transaction(Arc::new(valid_tx_high), 0, TempoHardfork::T1);
1921
1922 assert!(
1924 result_high.is_ok(),
1925 "Replacement transaction should be added successfully"
1926 );
1927 let added_high = result_high.unwrap();
1928
1929 match added_high {
1931 AddedTransaction::Pending(ref pending) => {
1932 assert_eq!(
1933 pending.transaction.hash(),
1934 &tx_high_hash,
1935 "Should be tx_high"
1936 );
1937 assert!(
1938 pending.replaced.is_some(),
1939 "Should have replaced a transaction"
1940 );
1941 assert_eq!(
1942 pending.replaced.as_ref().unwrap().hash(),
1943 &tx_low_hash,
1944 "Should have replaced tx_low"
1945 );
1946 }
1947 _ => panic!("Replacement transaction should be pending, got: {added_high:?}"),
1948 }
1949
1950 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1952 assert_eq!(
1953 pending_count, 1,
1954 "Should still have 1 pending transaction after replacement"
1955 );
1956 assert_eq!(queued_count, 0, "Should still have 0 queued transactions");
1957
1958 assert!(
1960 !pool.contains(&tx_low_hash),
1961 "Old transaction should be removed from pool"
1962 );
1963
1964 assert!(
1966 pool.contains(&tx_high_hash),
1967 "New transaction should be in pool"
1968 );
1969
1970 assert!(
1972 pool.independent_transactions.contains_key(&tx_id.seq_id),
1973 "Transaction ID should still be in independent set"
1974 );
1975
1976 let independent_tx_after = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
1977 assert_eq!(
1978 independent_tx_after.transaction.hash(),
1979 &tx_high_hash,
1980 "Independent set should now contain tx_high (not tx_low)"
1981 );
1982
1983 let tx_in_pool = pool.by_id.get(&tx_id).unwrap();
1985 assert_eq!(
1986 tx_in_pool.inner.transaction.hash(),
1987 &tx_high_hash,
1988 "Transaction in by_id should be tx_high"
1989 );
1990 assert!(tx_in_pool.is_pending(), "Transaction should be pending");
1991
1992 pool.assert_invariants();
1993 }
1994
1995 #[test_case::test_case(U256::ZERO)]
1996 #[test_case::test_case(U256::random())]
1997 fn on_chain_nonce_update_with_gaps(nonce_key: U256) {
1998 let mut pool = AA2dPool::default();
1999
2000 let sender = Address::random();
2002
2003 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2008 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2009 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2010 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
2011 let tx6 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(6).build();
2012
2013 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2014 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
2015 let valid_tx3 = wrap_valid_tx(tx3, TransactionOrigin::Local);
2016 let valid_tx4 = wrap_valid_tx(tx4, TransactionOrigin::Local);
2017 let valid_tx6 = wrap_valid_tx(tx6, TransactionOrigin::Local);
2018
2019 let tx0_hash = *valid_tx0.hash();
2020 let tx1_hash = *valid_tx1.hash();
2021 let tx3_hash = *valid_tx3.hash();
2022 let tx4_hash = *valid_tx4.hash();
2023 let tx6_hash = *valid_tx6.hash();
2024
2025 pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1)
2027 .unwrap();
2028 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
2029 .unwrap();
2030 pool.add_transaction(Arc::new(valid_tx3), 0, TempoHardfork::T1)
2031 .unwrap();
2032 pool.add_transaction(Arc::new(valid_tx4), 0, TempoHardfork::T1)
2033 .unwrap();
2034 pool.add_transaction(Arc::new(valid_tx6), 0, TempoHardfork::T1)
2035 .unwrap();
2036
2037 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2039 assert_eq!(
2040 pending_count, 2,
2041 "Should have 2 pending transactions (0, 1)"
2042 );
2043 assert_eq!(
2044 queued_count, 3,
2045 "Should have 3 queued transactions (3, 4, 6)"
2046 );
2047
2048 let seq_id = AASequenceId::new(sender, nonce_key);
2050 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2051 assert!(
2052 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2053 "Transaction 0 should be in independent set"
2054 );
2055
2056 pool.assert_invariants();
2057
2058 let mut on_chain_ids = HashMap::default();
2061 on_chain_ids.insert(seq_id, 2u64);
2062
2063 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2064
2065 assert_eq!(mined.len(), 2, "Should have mined 2 transactions (0, 1)");
2067 let mined_hashes: HashSet<_> = mined.iter().map(|tx| tx.hash()).collect();
2068 assert!(
2069 mined_hashes.contains(&&tx0_hash),
2070 "Transaction 0 should be mined"
2071 );
2072 assert!(
2073 mined_hashes.contains(&&tx1_hash),
2074 "Transaction 1 should be mined"
2075 );
2076
2077 assert_eq!(
2079 promoted.len(),
2080 0,
2081 "No transactions should be promoted (gap at nonce 2)"
2082 );
2083
2084 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2086 assert_eq!(
2087 pending_count, 0,
2088 "Should have 0 pending transactions (gap at nonce 2)"
2089 );
2090 assert_eq!(
2091 queued_count, 3,
2092 "Should have 3 queued transactions (3, 4, 6)"
2093 );
2094
2095 assert!(!pool.contains(&tx0_hash), "Transaction 0 should be removed");
2097 assert!(!pool.contains(&tx1_hash), "Transaction 1 should be removed");
2098
2099 assert!(pool.contains(&tx3_hash), "Transaction 3 should remain");
2101 assert!(pool.contains(&tx4_hash), "Transaction 4 should remain");
2102 assert!(pool.contains(&tx6_hash), "Transaction 6 should remain");
2103
2104 let tx3_id = AA2dTransactionId::new(seq_id, 3);
2106 let tx4_id = AA2dTransactionId::new(seq_id, 4);
2107 let tx6_id = AA2dTransactionId::new(seq_id, 6);
2108
2109 assert!(
2110 !pool.by_id.get(&tx3_id).unwrap().is_pending(),
2111 "Transaction 3 should be queued (gap at nonce 2)"
2112 );
2113 assert!(
2114 !pool.by_id.get(&tx4_id).unwrap().is_pending(),
2115 "Transaction 4 should be queued"
2116 );
2117 assert!(
2118 !pool.by_id.get(&tx6_id).unwrap().is_pending(),
2119 "Transaction 6 should be queued"
2120 );
2121
2122 assert!(
2124 pool.independent_transactions.is_empty(),
2125 "Independent set should be empty (gap at on-chain nonce 2)"
2126 );
2127
2128 pool.assert_invariants();
2129
2130 let mut on_chain_ids = HashMap::default();
2133 on_chain_ids.insert(seq_id, 3u64);
2134
2135 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2136
2137 assert_eq!(
2139 mined.len(),
2140 0,
2141 "No transactions should be mined (nonce 2 was never in pool)"
2142 );
2143
2144 assert_eq!(promoted.len(), 2, "Transactions 3 and 4 should be promoted");
2146 let promoted_hashes: HashSet<_> = promoted.iter().map(|tx| tx.hash()).collect();
2147 assert!(
2148 promoted_hashes.contains(&&tx3_hash),
2149 "Transaction 3 should be promoted"
2150 );
2151 assert!(
2152 promoted_hashes.contains(&&tx4_hash),
2153 "Transaction 4 should be promoted"
2154 );
2155
2156 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2158 assert_eq!(
2159 pending_count, 2,
2160 "Should have 2 pending transactions (3, 4)"
2161 );
2162 assert_eq!(queued_count, 1, "Should have 1 queued transaction (6)");
2163
2164 assert!(
2166 pool.by_id.get(&tx3_id).unwrap().is_pending(),
2167 "Transaction 3 should be pending"
2168 );
2169 assert!(
2170 pool.by_id.get(&tx4_id).unwrap().is_pending(),
2171 "Transaction 4 should be pending"
2172 );
2173
2174 assert!(
2176 !pool.by_id.get(&tx6_id).unwrap().is_pending(),
2177 "Transaction 6 should still be queued (gap at nonce 5)"
2178 );
2179
2180 assert!(
2182 pool.independent_transactions.contains_key(&tx3_id.seq_id),
2183 "Transaction 3 should be in independent set (at on-chain nonce 3)"
2184 );
2185
2186 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
2188 assert_eq!(
2189 independent_tx.transaction.hash(),
2190 &tx3_hash,
2191 "Independent transaction should be tx3 (nonce 3), not tx4 or tx6"
2192 );
2193
2194 pool.assert_invariants();
2195 }
2196
2197 #[test_case::test_case(U256::ZERO)]
2198 #[test_case::test_case(U256::random())]
2199 fn reject_outdated_transaction(nonce_key: U256) {
2200 let mut pool = AA2dPool::default();
2201
2202 let sender = Address::random();
2204
2205 let tx = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2207 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
2208
2209 let result = pool.add_transaction(Arc::new(valid_tx), 5, TempoHardfork::T1);
2211
2212 assert!(result.is_err(), "Should reject outdated transaction");
2214
2215 let err = result.unwrap_err();
2216 assert!(
2217 matches!(
2218 err.kind,
2219 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
2220 InvalidTransactionError::NonceNotConsistent { tx: 3, state: 5 }
2221 ))
2222 ),
2223 "Should fail with NonceNotConsistent error, got: {:?}",
2224 err.kind
2225 );
2226
2227 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2229 assert_eq!(pending_count, 0, "Pool should be empty");
2230 assert_eq!(queued_count, 0, "Pool should be empty");
2231
2232 pool.assert_invariants();
2233 }
2234
2235 #[test_case::test_case(U256::ZERO)]
2236 #[test_case::test_case(U256::random())]
2237 fn replace_with_insufficient_price_bump(nonce_key: U256) {
2238 let mut pool = AA2dPool::default();
2239
2240 let sender = Address::random();
2242
2243 let tx_low = TxBuilder::aa(sender)
2245 .nonce_key(nonce_key)
2246 .max_priority_fee(1_000_000_000)
2247 .max_fee(2_000_000_000)
2248 .build();
2249 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
2250
2251 pool.add_transaction(Arc::new(valid_tx_low), 0, TempoHardfork::T1)
2252 .unwrap();
2253
2254 let tx_insufficient = TxBuilder::aa(sender)
2256 .nonce_key(nonce_key)
2257 .max_priority_fee(1_050_000_000)
2258 .max_fee(2_100_000_000)
2259 .build();
2260 let valid_tx_insufficient = wrap_valid_tx(tx_insufficient, TransactionOrigin::Local);
2261
2262 let result = pool.add_transaction(Arc::new(valid_tx_insufficient), 0, TempoHardfork::T1);
2263
2264 assert!(
2266 result.is_err(),
2267 "Should reject replacement with insufficient price bump"
2268 );
2269 let err = result.unwrap_err();
2270 assert!(
2271 matches!(err.kind, PoolErrorKind::ReplacementUnderpriced),
2272 "Should fail with ReplacementUnderpriced, got: {:?}",
2273 err.kind
2274 );
2275
2276 pool.assert_invariants();
2277 }
2278
2279 #[test_case::test_case(U256::ZERO)]
2280 #[test_case::test_case(U256::random())]
2281 fn fill_gap_in_middle(nonce_key: U256) {
2282 let mut pool = AA2dPool::default();
2283
2284 let sender = Address::random();
2285
2286 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2288 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2289 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2290 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
2291
2292 pool.add_transaction(
2293 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2294 0,
2295 TempoHardfork::T1,
2296 )
2297 .unwrap();
2298 pool.add_transaction(
2299 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
2300 0,
2301 TempoHardfork::T1,
2302 )
2303 .unwrap();
2304 pool.add_transaction(
2305 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
2306 0,
2307 TempoHardfork::T1,
2308 )
2309 .unwrap();
2310 pool.add_transaction(
2311 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
2312 0,
2313 TempoHardfork::T1,
2314 )
2315 .unwrap();
2316
2317 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2319 assert_eq!(pending_count, 2, "Should have 2 pending (0, 1)");
2320 assert_eq!(queued_count, 2, "Should have 2 queued (3, 4)");
2321
2322 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2324 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
2325
2326 let result = pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1);
2327 assert!(result.is_ok(), "Should successfully add tx2");
2328
2329 match result.unwrap() {
2331 AddedTransaction::Pending(ref pending) => {
2332 assert_eq!(pending.promoted.len(), 2, "Should promote tx3 and tx4");
2333 }
2334 _ => panic!("tx2 should be added as pending"),
2335 }
2336
2337 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2339 assert_eq!(pending_count, 5, "All 5 transactions should be pending");
2340 assert_eq!(queued_count, 0, "No transactions should be queued");
2341
2342 let seq_id = AASequenceId::new(sender, nonce_key);
2344 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2345 assert!(
2346 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2347 "tx0 should be in independent set"
2348 );
2349
2350 pool.assert_invariants();
2351 }
2352
2353 #[test_case::test_case(U256::ZERO)]
2354 #[test_case::test_case(U256::random())]
2355 fn remove_pending_transaction(nonce_key: U256) {
2356 let mut pool = AA2dPool::default();
2357
2358 let sender = Address::random();
2359
2360 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2362 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2363 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2364
2365 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2366 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
2367 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
2368
2369 let tx1_hash = *valid_tx1.hash();
2370
2371 pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1)
2372 .unwrap();
2373 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
2374 .unwrap();
2375 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
2376 .unwrap();
2377
2378 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2380 assert_eq!(pending_count, 3, "All 3 should be pending");
2381 assert_eq!(queued_count, 0, "None should be queued");
2382
2383 let seq_id = AASequenceId::new(sender, nonce_key);
2384 let tx1_id = AA2dTransactionId::new(seq_id, 1);
2385 let tx2_id = AA2dTransactionId::new(seq_id, 2);
2386
2387 assert!(
2389 pool.by_id.get(&tx2_id).unwrap().is_pending(),
2390 "tx2 should be pending before removal"
2391 );
2392
2393 let removed = pool.remove_transactions([&tx1_hash].into_iter());
2395 assert_eq!(removed.len(), 1, "Should remove tx1");
2396
2397 assert!(!pool.by_id.contains_key(&tx1_id), "tx1 should be removed");
2399 assert!(!pool.contains(&tx1_hash), "tx1 should be removed");
2400
2401 assert_eq!(pool.by_id.len(), 2, "Should have 2 transactions left");
2403
2404 assert!(
2406 !pool.by_id.get(&tx2_id).unwrap().is_pending(),
2407 "tx2 should be demoted to queued after tx1 removal creates a gap"
2408 );
2409
2410 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2412 assert_eq!(pending_count, 1, "Only tx0 should be pending");
2413 assert_eq!(queued_count, 1, "tx2 should be queued");
2414
2415 pool.assert_invariants();
2416 }
2417
2418 #[test_case::test_case(U256::ZERO, U256::random())]
2419 #[test_case::test_case(U256::random(), U256::ZERO)]
2420 #[test_case::test_case(U256::random(), U256::random())]
2421 fn multiple_senders_independent_set(nonce_key_a: U256, nonce_key_b: U256) {
2422 let mut pool = AA2dPool::default();
2423
2424 let sender_a = Address::random();
2426 let sender_b = Address::random();
2427
2428 let tx_a0 = TxBuilder::aa(sender_a).nonce_key(nonce_key_a).build();
2431 let tx_a1 = TxBuilder::aa(sender_a)
2432 .nonce_key(nonce_key_a)
2433 .nonce(1)
2434 .build();
2435
2436 let tx_b0 = TxBuilder::aa(sender_b).nonce_key(nonce_key_b).build();
2438 let tx_b1 = TxBuilder::aa(sender_b)
2439 .nonce_key(nonce_key_b)
2440 .nonce(1)
2441 .build();
2442
2443 let valid_tx_a0 = wrap_valid_tx(tx_a0, TransactionOrigin::Local);
2444 let valid_tx_a1 = wrap_valid_tx(tx_a1, TransactionOrigin::Local);
2445 let valid_tx_b0 = wrap_valid_tx(tx_b0, TransactionOrigin::Local);
2446 let valid_tx_b1 = wrap_valid_tx(tx_b1, TransactionOrigin::Local);
2447
2448 let tx_a0_hash = *valid_tx_a0.hash();
2449
2450 pool.add_transaction(Arc::new(valid_tx_a0), 0, TempoHardfork::T1)
2451 .unwrap();
2452 pool.add_transaction(Arc::new(valid_tx_a1), 0, TempoHardfork::T1)
2453 .unwrap();
2454 pool.add_transaction(Arc::new(valid_tx_b0), 0, TempoHardfork::T1)
2455 .unwrap();
2456 pool.add_transaction(Arc::new(valid_tx_b1), 0, TempoHardfork::T1)
2457 .unwrap();
2458
2459 let sender_a_id = AASequenceId::new(sender_a, nonce_key_a);
2461 let sender_b_id = AASequenceId::new(sender_b, nonce_key_b);
2462 let tx_a0_id = AA2dTransactionId::new(sender_a_id, 0);
2463 let tx_b0_id = AA2dTransactionId::new(sender_b_id, 0);
2464
2465 assert_eq!(
2466 pool.independent_transactions.len(),
2467 2,
2468 "Should have 2 independent transactions"
2469 );
2470 assert!(
2471 pool.independent_transactions.contains_key(&tx_a0_id.seq_id),
2472 "Sender A's tx0 should be independent"
2473 );
2474 assert!(
2475 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
2476 "Sender B's tx0 should be independent"
2477 );
2478
2479 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2481 assert_eq!(pending_count, 4, "All 4 transactions should be pending");
2482 assert_eq!(queued_count, 0, "No transactions should be queued");
2483
2484 let mut on_chain_ids = HashMap::default();
2486 on_chain_ids.insert(sender_a_id, 1u64);
2487
2488 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2489
2490 assert_eq!(mined.len(), 1, "Only sender A's tx0 should be mined");
2492 assert_eq!(mined[0].hash(), &tx_a0_hash, "Should mine tx_a0");
2493
2494 assert_eq!(
2496 promoted.len(),
2497 0,
2498 "No transactions should be promoted (tx_a1 was already pending)"
2499 );
2500
2501 let tx_a1_id = AA2dTransactionId::new(sender_a_id, 1);
2503 assert_eq!(
2504 pool.independent_transactions.len(),
2505 2,
2506 "Should still have 2 independent transactions"
2507 );
2508 assert!(
2509 pool.independent_transactions.contains_key(&tx_a1_id.seq_id),
2510 "Sender A's tx1 should now be independent"
2511 );
2512 assert!(
2513 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
2514 "Sender B's tx0 should still be independent"
2515 );
2516
2517 pool.assert_invariants();
2518 }
2519
2520 #[test_case::test_case(U256::ZERO)]
2521 #[test_case::test_case(U256::random())]
2522 fn concurrent_replacements_same_nonce(nonce_key: U256) {
2523 let mut pool = AA2dPool::default();
2524 let sender = Address::random();
2525 let seq_id = AASequenceId {
2526 address: sender,
2527 nonce_key,
2528 };
2529
2530 let tx0 = TxBuilder::aa(sender)
2532 .nonce_key(nonce_key)
2533 .max_priority_fee(1_000_000_000)
2534 .max_fee(2_000_000_000)
2535 .build();
2536 let tx0_hash = *tx0.hash();
2537 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2538 let result = pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1);
2539 assert!(result.is_ok());
2540 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2541 assert_eq!(pending_count + queued_count, 1);
2542
2543 let tx0_replacement1 = TxBuilder::aa(sender)
2545 .nonce_key(nonce_key)
2546 .max_priority_fee(1_050_000_000)
2547 .max_fee(2_100_000_000)
2548 .build();
2549 let valid_tx1 = wrap_valid_tx(tx0_replacement1, TransactionOrigin::Local);
2550 let result = pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1);
2551 assert!(result.is_err(), "Should reject insufficient price bump");
2552 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2553 assert_eq!(pending_count + queued_count, 1);
2554 assert!(
2555 pool.contains(&tx0_hash),
2556 "Original tx should still be present"
2557 );
2558
2559 let tx0_replacement2 = TxBuilder::aa(sender)
2561 .nonce_key(nonce_key)
2562 .max_priority_fee(1_100_000_000)
2563 .max_fee(2_200_000_000)
2564 .build();
2565 let tx0_replacement2_hash = *tx0_replacement2.hash();
2566 let valid_tx2 = wrap_valid_tx(tx0_replacement2, TransactionOrigin::Local);
2567 let result = pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1);
2568 assert!(result.is_ok(), "Should accept 10% price bump");
2569 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2570 assert_eq!(pending_count + queued_count, 1, "Pool size should remain 1");
2571 assert!(!pool.contains(&tx0_hash), "Old tx should be removed");
2572 assert!(
2573 pool.contains(&tx0_replacement2_hash),
2574 "New tx should be present"
2575 );
2576
2577 let tx0_replacement3 = TxBuilder::aa(sender)
2579 .nonce_key(nonce_key)
2580 .max_priority_fee(1_500_000_000)
2581 .max_fee(3_000_000_000)
2582 .build();
2583 let tx0_replacement3_hash = *tx0_replacement3.hash();
2584 let valid_tx3 = wrap_valid_tx(tx0_replacement3, TransactionOrigin::Local);
2585 let result = pool.add_transaction(Arc::new(valid_tx3), 0, TempoHardfork::T1);
2586 assert!(result.is_ok(), "Should accept higher price bump");
2587 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2588 assert_eq!(pending_count + queued_count, 1);
2589 assert!(
2590 !pool.contains(&tx0_replacement2_hash),
2591 "Previous tx should be removed"
2592 );
2593 assert!(
2594 pool.contains(&tx0_replacement3_hash),
2595 "Highest priority tx should win"
2596 );
2597
2598 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2600 assert!(pool.independent_transactions.contains_key(&tx0_id.seq_id));
2601
2602 pool.assert_invariants();
2603 }
2604
2605 #[test_case::test_case(U256::ZERO)]
2606 #[test_case::test_case(U256::random())]
2607 fn long_gap_chain(nonce_key: U256) {
2608 let mut pool = AA2dPool::default();
2609 let sender = Address::random();
2610 let seq_id = AASequenceId {
2611 address: sender,
2612 nonce_key,
2613 };
2614
2615 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2617 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
2618 let tx10 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(10).build();
2619 let tx15 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(15).build();
2620
2621 pool.add_transaction(
2622 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2623 0,
2624 TempoHardfork::T1,
2625 )
2626 .unwrap();
2627 pool.add_transaction(
2628 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
2629 0,
2630 TempoHardfork::T1,
2631 )
2632 .unwrap();
2633 pool.add_transaction(
2634 Arc::new(wrap_valid_tx(tx10, TransactionOrigin::Local)),
2635 0,
2636 TempoHardfork::T1,
2637 )
2638 .unwrap();
2639 pool.add_transaction(
2640 Arc::new(wrap_valid_tx(tx15, TransactionOrigin::Local)),
2641 0,
2642 TempoHardfork::T1,
2643 )
2644 .unwrap();
2645
2646 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2647 assert_eq!(pending_count + queued_count, 4);
2648
2649 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2651 assert!(pool.by_id.get(&tx0_id).unwrap().is_pending());
2652 assert!(
2653 !pool
2654 .by_id
2655 .get(&AA2dTransactionId::new(seq_id, 5))
2656 .unwrap()
2657 .is_pending()
2658 );
2659 assert!(
2660 !pool
2661 .by_id
2662 .get(&AA2dTransactionId::new(seq_id, 10))
2663 .unwrap()
2664 .is_pending()
2665 );
2666 assert!(
2667 !pool
2668 .by_id
2669 .get(&AA2dTransactionId::new(seq_id, 15))
2670 .unwrap()
2671 .is_pending()
2672 );
2673 assert_eq!(pool.independent_transactions.len(), 1);
2674
2675 for nonce in 1..=4 {
2677 let tx = TxBuilder::aa(sender)
2678 .nonce_key(nonce_key)
2679 .nonce(nonce)
2680 .build();
2681 pool.add_transaction(
2682 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2683 0,
2684 TempoHardfork::T1,
2685 )
2686 .unwrap();
2687 }
2688
2689 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2690 assert_eq!(pending_count + queued_count, 8);
2691
2692 for nonce in 0..=5 {
2694 let id = AA2dTransactionId::new(seq_id, nonce);
2695 assert!(
2696 pool.by_id.get(&id).unwrap().is_pending(),
2697 "Nonce {nonce} should be pending"
2698 );
2699 }
2700 assert!(
2702 !pool
2703 .by_id
2704 .get(&AA2dTransactionId::new(seq_id, 10))
2705 .unwrap()
2706 .is_pending()
2707 );
2708 assert!(
2709 !pool
2710 .by_id
2711 .get(&AA2dTransactionId::new(seq_id, 15))
2712 .unwrap()
2713 .is_pending()
2714 );
2715
2716 for nonce in 6..=9 {
2718 let tx = TxBuilder::aa(sender)
2719 .nonce_key(nonce_key)
2720 .nonce(nonce)
2721 .build();
2722 pool.add_transaction(
2723 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2724 0,
2725 TempoHardfork::T1,
2726 )
2727 .unwrap();
2728 }
2729
2730 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2731 assert_eq!(pending_count + queued_count, 12);
2732
2733 for nonce in 0..=10 {
2735 let id = AA2dTransactionId::new(seq_id, nonce);
2736 assert!(
2737 pool.by_id.get(&id).unwrap().is_pending(),
2738 "Nonce {nonce} should be pending"
2739 );
2740 }
2741 assert!(
2743 !pool
2744 .by_id
2745 .get(&AA2dTransactionId::new(seq_id, 15))
2746 .unwrap()
2747 .is_pending()
2748 );
2749
2750 for nonce in 11..=14 {
2752 let tx = TxBuilder::aa(sender)
2753 .nonce_key(nonce_key)
2754 .nonce(nonce)
2755 .build();
2756 pool.add_transaction(
2757 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2758 0,
2759 TempoHardfork::T1,
2760 )
2761 .unwrap();
2762 }
2763
2764 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2765 assert_eq!(pending_count + queued_count, 16);
2766
2767 for nonce in 0..=15 {
2769 let id = AA2dTransactionId::new(seq_id, nonce);
2770 assert!(
2771 pool.by_id.get(&id).unwrap().is_pending(),
2772 "Nonce {nonce} should be pending"
2773 );
2774 }
2775
2776 pool.assert_invariants();
2777 }
2778
2779 #[test_case::test_case(U256::ZERO)]
2780 #[test_case::test_case(U256::random())]
2781 fn remove_from_middle_of_chain(nonce_key: U256) {
2782 let mut pool = AA2dPool::default();
2783 let sender = Address::random();
2784 let seq_id = AASequenceId {
2785 address: sender,
2786 nonce_key,
2787 };
2788
2789 for nonce in 0..=4 {
2791 let tx = TxBuilder::aa(sender)
2792 .nonce_key(nonce_key)
2793 .nonce(nonce)
2794 .build();
2795 pool.add_transaction(
2796 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2797 0,
2798 TempoHardfork::T1,
2799 )
2800 .unwrap();
2801 }
2802
2803 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2804 assert_eq!(pending_count + queued_count, 5);
2805
2806 for nonce in 0..=4 {
2808 assert!(
2809 pool.by_id
2810 .get(&AA2dTransactionId::new(seq_id, nonce))
2811 .unwrap()
2812 .is_pending()
2813 );
2814 }
2815
2816 let tx2_id = AA2dTransactionId::new(seq_id, 2);
2818 let tx2_hash = *pool.by_id.get(&tx2_id).unwrap().inner.transaction.hash();
2819 let removed = pool.remove_transactions([&tx2_hash].into_iter());
2820 assert_eq!(removed.len(), 1, "Should remove transaction");
2821
2822 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2823 assert_eq!(pending_count + queued_count, 4);
2824
2825 assert!(!pool.by_id.contains_key(&tx2_id));
2827
2828 pool.assert_invariants();
2833 }
2834
2835 #[test_case::test_case(U256::ZERO)]
2836 #[test_case::test_case(U256::random())]
2837 fn independent_set_after_multiple_promotions(nonce_key: U256) {
2838 let mut pool = AA2dPool::default();
2839 let sender = Address::random();
2840 let seq_id = AASequenceId {
2841 address: sender,
2842 nonce_key,
2843 };
2844
2845 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2847 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2848 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
2849
2850 pool.add_transaction(
2851 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2852 0,
2853 TempoHardfork::T1,
2854 )
2855 .unwrap();
2856 pool.add_transaction(
2857 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
2858 0,
2859 TempoHardfork::T1,
2860 )
2861 .unwrap();
2862 pool.add_transaction(
2863 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
2864 0,
2865 TempoHardfork::T1,
2866 )
2867 .unwrap();
2868
2869 assert_eq!(pool.independent_transactions.len(), 1);
2871 assert!(pool.independent_transactions.contains_key(&seq_id));
2872
2873 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2875 assert_eq!(pending_count, 1);
2876 assert_eq!(queued_count, 2);
2877
2878 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2880 pool.add_transaction(
2881 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
2882 0,
2883 TempoHardfork::T1,
2884 )
2885 .unwrap();
2886
2887 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2889 assert_eq!(pending_count, 3);
2890 assert_eq!(queued_count, 1);
2891
2892 assert_eq!(pool.independent_transactions.len(), 1);
2894 assert!(pool.independent_transactions.contains_key(&seq_id));
2895
2896 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2898 pool.add_transaction(
2899 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
2900 0,
2901 TempoHardfork::T1,
2902 )
2903 .unwrap();
2904
2905 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2907 assert_eq!(pending_count, 5);
2908 assert_eq!(queued_count, 0);
2909
2910 let mut on_chain_ids = HashMap::default();
2912 on_chain_ids.insert(seq_id, 2u64);
2913 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2914
2915 assert_eq!(mined.len(), 2);
2917 assert_eq!(promoted.len(), 0);
2918
2919 assert_eq!(pool.independent_transactions.len(), 1);
2921 assert!(pool.independent_transactions.contains_key(&seq_id));
2922
2923 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2925 assert_eq!(pending_count + queued_count, 3);
2926
2927 pool.assert_invariants();
2928 }
2929
2930 #[test]
2931 fn stress_test_many_senders() {
2932 let mut pool = AA2dPool::default();
2933 const NUM_SENDERS: usize = 100;
2934 const TXS_PER_SENDER: u64 = 5;
2935
2936 let mut senders = Vec::new();
2938 for i in 0..NUM_SENDERS {
2939 let sender = Address::from_word(B256::from(U256::from(i)));
2940 let nonce_key = U256::from(i);
2941 senders.push((sender, nonce_key));
2942
2943 for nonce in 0..TXS_PER_SENDER {
2945 let tx = TxBuilder::aa(sender)
2946 .nonce_key(nonce_key)
2947 .nonce(nonce)
2948 .build();
2949 pool.add_transaction(
2950 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2951 0,
2952 TempoHardfork::T1,
2953 )
2954 .unwrap();
2955 }
2956 }
2957
2958 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2960 assert_eq!(
2961 pending_count + queued_count,
2962 NUM_SENDERS * TXS_PER_SENDER as usize
2963 );
2964
2965 for (sender, nonce_key) in &senders {
2967 let seq_id = AASequenceId {
2968 address: *sender,
2969 nonce_key: *nonce_key,
2970 };
2971 for nonce in 0..TXS_PER_SENDER {
2972 let id = AA2dTransactionId::new(seq_id, nonce);
2973 assert!(pool.by_id.get(&id).unwrap().is_pending());
2974 }
2975 }
2976
2977 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
2979 for (sender, nonce_key) in &senders {
2980 let seq_id = AASequenceId {
2981 address: *sender,
2982 nonce_key: *nonce_key,
2983 };
2984 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2985 assert!(
2986 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2987 "Sender {sender:?} should have tx0 in independent set"
2988 );
2989 }
2990
2991 let mut on_chain_ids = HashMap::default();
2993 for (sender, nonce_key) in &senders {
2994 let seq_id = AASequenceId {
2995 address: *sender,
2996 nonce_key: *nonce_key,
2997 };
2998 on_chain_ids.insert(seq_id, 1u64);
2999 }
3000
3001 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
3002
3003 assert_eq!(mined.len(), NUM_SENDERS);
3005 assert_eq!(promoted.len(), 0);
3007
3008 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3010 assert_eq!(
3011 pending_count + queued_count,
3012 NUM_SENDERS * (TXS_PER_SENDER - 1) as usize
3013 );
3014
3015 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
3017 for (sender, nonce_key) in &senders {
3018 let seq_id = AASequenceId {
3019 address: *sender,
3020 nonce_key: *nonce_key,
3021 };
3022 let tx1_id = AA2dTransactionId::new(seq_id, 1);
3023 assert!(
3024 pool.independent_transactions.contains_key(&tx1_id.seq_id),
3025 "Sender {sender:?} should have tx1 in independent set"
3026 );
3027 }
3028
3029 pool.assert_invariants();
3030 }
3031
3032 #[test_case::test_case(U256::ZERO)]
3033 #[test_case::test_case(U256::random())]
3034 fn on_chain_nonce_update_to_queued_tx_with_gaps(nonce_key: U256) {
3035 let mut pool = AA2dPool::default();
3036 let sender = Address::random();
3037 let seq_id = AASequenceId {
3038 address: sender,
3039 nonce_key,
3040 };
3041
3042 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
3045 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
3046 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
3047
3048 pool.add_transaction(
3049 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3050 0,
3051 TempoHardfork::T1,
3052 )
3053 .unwrap();
3054 pool.add_transaction(
3055 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
3056 0,
3057 TempoHardfork::T1,
3058 )
3059 .unwrap();
3060 pool.add_transaction(
3061 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
3062 0,
3063 TempoHardfork::T1,
3064 )
3065 .unwrap();
3066
3067 assert_eq!(pool.independent_transactions.len(), 1);
3069 assert!(pool.independent_transactions.contains_key(&seq_id));
3070
3071 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3073 assert_eq!(pending_count, 1, "Only tx0 should be pending");
3074 assert_eq!(queued_count, 2, "tx3 and tx5 should be queued");
3075
3076 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
3078 pool.add_transaction(
3079 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3080 0,
3081 TempoHardfork::T1,
3082 )
3083 .unwrap();
3084
3085 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
3086 pool.add_transaction(
3087 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3088 0,
3089 TempoHardfork::T1,
3090 )
3091 .unwrap();
3092
3093 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3095 assert_eq!(pending_count, 4, "Transactions [0,1,2,3] should be pending");
3096 assert_eq!(queued_count, 1, "tx5 should still be queued");
3097
3098 assert_eq!(pool.independent_transactions.len(), 1);
3100 assert!(pool.independent_transactions.contains_key(&seq_id));
3101
3102 let mut on_chain_ids = HashMap::default();
3103 on_chain_ids.insert(seq_id, 3u64);
3104 let (_promoted, mined) = pool.on_nonce_changes(on_chain_ids);
3105
3106 assert_eq!(mined.len(), 3, "Should mine transactions [0,1,2]");
3108
3109 assert_eq!(
3112 pool.independent_transactions.len(),
3113 1,
3114 "Should have one independent transaction"
3115 );
3116 let key = AA2dTransactionId::new(seq_id, 3);
3117 assert!(
3118 pool.independent_transactions.contains_key(&key.seq_id),
3119 "tx3 should be in independent set"
3120 );
3121
3122 let (_pending_count, _queued_count) = pool.pending_and_queued_txn_count();
3124 pool.assert_invariants();
3127
3128 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
3131 pool.add_transaction(
3132 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
3133 3,
3134 TempoHardfork::T1,
3135 )
3136 .unwrap();
3137
3138 let (_pending_count_after, _queued_count_after) = pool.pending_and_queued_txn_count();
3140 pool.assert_invariants();
3141 }
3142
3143 #[test]
3144 fn append_pooled_transaction_elements_respects_limit() {
3145 let mut pool = AA2dPool::default();
3146 let sender = Address::random();
3147 let nonce_key = U256::from(1);
3148
3149 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
3151 let tx0_hash = *tx0.hash();
3152 let tx0_len = tx0.encoded_length();
3153 pool.add_transaction(
3154 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3155 0,
3156 TempoHardfork::T1,
3157 )
3158 .unwrap();
3159
3160 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
3161 let tx1_hash = *tx1.hash();
3162 let tx1_len = tx1.encoded_length();
3163 pool.add_transaction(
3164 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3165 0,
3166 TempoHardfork::T1,
3167 )
3168 .unwrap();
3169
3170 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
3171 let tx2_hash = *tx2.hash();
3172 let tx2_len = tx2.encoded_length();
3173 pool.add_transaction(
3174 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3175 0,
3176 TempoHardfork::T1,
3177 )
3178 .unwrap();
3179
3180 let mut accumulated = 0;
3182 let mut elements = Vec::new();
3183 pool.append_pooled_transaction_elements(
3184 &[tx0_hash, tx1_hash, tx2_hash],
3185 GetPooledTransactionLimit::None,
3186 &mut accumulated,
3187 &mut elements,
3188 );
3189 assert_eq!(elements.len(), 3, "Should return all 3 transactions");
3190 assert_eq!(
3191 accumulated,
3192 tx0_len + tx1_len + tx2_len,
3193 "Should accumulate all sizes"
3194 );
3195
3196 let mut accumulated = 0;
3199 let mut elements = Vec::new();
3200 pool.append_pooled_transaction_elements(
3201 &[tx0_hash, tx1_hash, tx2_hash],
3202 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len - 1),
3203 &mut accumulated,
3204 &mut elements,
3205 );
3206 assert_eq!(
3207 elements.len(),
3208 1,
3209 "Should stop after first tx exceeds limit"
3210 );
3211 assert_eq!(accumulated, tx0_len, "Should accumulate first tx size");
3212
3213 let mut accumulated = 0;
3216 let mut elements = Vec::new();
3217 pool.append_pooled_transaction_elements(
3218 &[tx0_hash, tx1_hash, tx2_hash],
3219 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len + tx1_len - 1),
3220 &mut accumulated,
3221 &mut elements,
3222 );
3223 assert_eq!(
3224 elements.len(),
3225 2,
3226 "Should stop after second tx exceeds limit"
3227 );
3228 assert_eq!(
3229 accumulated,
3230 tx0_len + tx1_len,
3231 "Should accumulate first two tx sizes"
3232 );
3233
3234 let mut accumulated = tx0_len;
3236 let mut elements = Vec::new();
3237 pool.append_pooled_transaction_elements(
3238 &[tx1_hash, tx2_hash],
3239 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len + tx1_len - 1),
3240 &mut accumulated,
3241 &mut elements,
3242 );
3243 assert_eq!(
3244 elements.len(),
3245 1,
3246 "Should return 1 transaction when pre-accumulated size causes early stop"
3247 );
3248 assert_eq!(
3249 accumulated,
3250 tx0_len + tx1_len,
3251 "Should add to pre-accumulated size"
3252 );
3253 }
3254 #[test]
3259 fn test_2d_pool_helpers() {
3260 let mut pool = AA2dPool::default();
3261 let sender = Address::random();
3262 let tx = TxBuilder::aa(sender).build();
3263 let tx_hash = *tx.hash();
3264
3265 assert!(!pool.contains(&tx_hash));
3266 assert!(pool.get(&tx_hash).is_none());
3267
3268 pool.add_transaction(
3269 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3270 0,
3271 TempoHardfork::T1,
3272 )
3273 .unwrap();
3274
3275 assert!(pool.contains(&tx_hash));
3276 let retrieved = pool.get(&tx_hash);
3277 assert!(retrieved.is_some());
3278 assert_eq!(retrieved.unwrap().hash(), &tx_hash);
3279 }
3280
3281 #[test]
3282 fn test_pool_get_all() {
3283 let mut pool = AA2dPool::default();
3284 let sender = Address::random();
3285
3286 let tx0 = TxBuilder::aa(sender).build();
3287 let tx1 = TxBuilder::aa(sender).nonce(1).build();
3288 let tx0_hash = *tx0.hash();
3289 let tx1_hash = *tx1.hash();
3290 let fake_hash = alloy_primitives::B256::random();
3291
3292 pool.add_transaction(
3293 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3294 0,
3295 TempoHardfork::T1,
3296 )
3297 .unwrap();
3298 pool.add_transaction(
3299 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3300 0,
3301 TempoHardfork::T1,
3302 )
3303 .unwrap();
3304
3305 let hashes = [tx0_hash, tx1_hash, fake_hash];
3306 let results = pool.get_all(hashes.iter());
3307
3308 assert_eq!(results.len(), 2); }
3310
3311 #[test]
3312 fn test_pool_senders_iter() {
3313 let mut pool = AA2dPool::default();
3314 let sender1 = Address::random();
3315 let sender2 = Address::random();
3316
3317 let tx1 = TxBuilder::aa(sender1).build();
3318 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3319
3320 pool.add_transaction(
3321 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3322 0,
3323 TempoHardfork::T1,
3324 )
3325 .unwrap();
3326 pool.add_transaction(
3327 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3328 0,
3329 TempoHardfork::T1,
3330 )
3331 .unwrap();
3332
3333 let senders: Vec<_> = pool.senders_iter().collect();
3334 assert_eq!(senders.len(), 2);
3335 assert!(senders.contains(&&sender1));
3336 assert!(senders.contains(&&sender2));
3337 }
3338
3339 #[test]
3340 fn test_pool_pending_and_queued_transactions() {
3341 let mut pool = AA2dPool::default();
3342 let sender = Address::random();
3343
3344 let tx0 = TxBuilder::aa(sender).build();
3346 let tx1 = TxBuilder::aa(sender).nonce(1).build();
3347 let tx2 = TxBuilder::aa(sender).nonce(2).build();
3348 let tx0_hash = *tx0.hash();
3349 let tx1_hash = *tx1.hash();
3350 let tx2_hash = *tx2.hash();
3351
3352 let tx5 = TxBuilder::aa(sender).nonce(5).build();
3354 let tx6 = TxBuilder::aa(sender).nonce(6).build();
3355 let tx7 = TxBuilder::aa(sender).nonce(7).build();
3356 let tx5_hash = *tx5.hash();
3357 let tx6_hash = *tx6.hash();
3358 let tx7_hash = *tx7.hash();
3359
3360 for tx in [tx0, tx1, tx2, tx5, tx6, tx7] {
3361 pool.add_transaction(
3362 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3363 0,
3364 TempoHardfork::T1,
3365 )
3366 .unwrap();
3367 }
3368
3369 let pending: Vec<_> = pool.pending_transactions().collect();
3370 assert_eq!(pending.len(), 3);
3371 let pending_hashes: HashSet<_> = pending.iter().map(|tx| *tx.hash()).collect();
3372 assert!(pending_hashes.contains(&tx0_hash));
3373 assert!(pending_hashes.contains(&tx1_hash));
3374 assert!(pending_hashes.contains(&tx2_hash));
3375
3376 let queued: Vec<_> = pool.queued_transactions().collect();
3377 assert_eq!(queued.len(), 3);
3378 let queued_hashes: HashSet<_> = queued.iter().map(|tx| *tx.hash()).collect();
3379 assert!(queued_hashes.contains(&tx5_hash));
3380 assert!(queued_hashes.contains(&tx6_hash));
3381 assert!(queued_hashes.contains(&tx7_hash));
3382 }
3383
3384 #[test]
3385 fn test_pool_get_transactions_by_sender_iter() {
3386 let mut pool = AA2dPool::default();
3387 let sender1 = Address::random();
3388 let sender2 = Address::random();
3389
3390 let tx1 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
3391 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3392
3393 pool.add_transaction(
3394 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3395 0,
3396 TempoHardfork::T1,
3397 )
3398 .unwrap();
3399 pool.add_transaction(
3400 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3401 0,
3402 TempoHardfork::T1,
3403 )
3404 .unwrap();
3405
3406 let sender1_txs: Vec<_> = pool.get_transactions_by_sender_iter(sender1).collect();
3407 assert_eq!(sender1_txs.len(), 1);
3408 assert_eq!(sender1_txs[0].sender(), sender1);
3409
3410 let sender2_txs: Vec<_> = pool.get_transactions_by_sender_iter(sender2).collect();
3411 assert_eq!(sender2_txs.len(), 1);
3412 assert_eq!(sender2_txs[0].sender(), sender2);
3413 }
3414
3415 #[test]
3416 fn test_pool_get_transactions_by_origin_iter() {
3417 let mut pool = AA2dPool::default();
3418 let sender = Address::random();
3419
3420 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3421 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3422
3423 pool.add_transaction(
3424 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3425 0,
3426 TempoHardfork::T1,
3427 )
3428 .unwrap();
3429 pool.add_transaction(
3430 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::External)),
3431 0,
3432 TempoHardfork::T1,
3433 )
3434 .unwrap();
3435
3436 let local_txs: Vec<_> = pool
3437 .get_transactions_by_origin_iter(TransactionOrigin::Local)
3438 .collect();
3439 assert_eq!(local_txs.len(), 1);
3440
3441 let external_txs: Vec<_> = pool
3442 .get_transactions_by_origin_iter(TransactionOrigin::External)
3443 .collect();
3444 assert_eq!(external_txs.len(), 1);
3445 }
3446
3447 #[test]
3448 fn test_pool_get_pending_transactions_by_origin_iter() {
3449 let mut pool = AA2dPool::default();
3450 let sender = Address::random();
3451
3452 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3453 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build(); pool.add_transaction(
3456 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3457 0,
3458 TempoHardfork::T1,
3459 )
3460 .unwrap();
3461 pool.add_transaction(
3462 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3463 0,
3464 TempoHardfork::T1,
3465 )
3466 .unwrap();
3467
3468 let pending_local: Vec<_> = pool
3469 .get_pending_transactions_by_origin_iter(TransactionOrigin::Local)
3470 .collect();
3471 assert_eq!(pending_local.len(), 1); }
3473
3474 #[test]
3475 fn test_pool_all_transaction_hashes_iter() {
3476 let mut pool = AA2dPool::default();
3477 let sender = Address::random();
3478
3479 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3480 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3481 let tx0_hash = *tx0.hash();
3482 let tx1_hash = *tx1.hash();
3483
3484 pool.add_transaction(
3485 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3486 0,
3487 TempoHardfork::T1,
3488 )
3489 .unwrap();
3490 pool.add_transaction(
3491 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3492 0,
3493 TempoHardfork::T1,
3494 )
3495 .unwrap();
3496
3497 let hashes: Vec<_> = pool.all_transaction_hashes_iter().collect();
3498 assert_eq!(hashes.len(), 2);
3499 assert!(hashes.contains(&tx0_hash));
3500 assert!(hashes.contains(&tx1_hash));
3501 }
3502
3503 #[test]
3504 fn test_pool_pooled_transactions_hashes_iter() {
3505 let mut pool = AA2dPool::default();
3506 let sender = Address::random();
3507
3508 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3509 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3510
3511 pool.add_transaction(
3512 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3513 0,
3514 TempoHardfork::T1,
3515 )
3516 .unwrap();
3517 pool.add_transaction(
3518 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3519 0,
3520 TempoHardfork::T1,
3521 )
3522 .unwrap();
3523
3524 let hashes: Vec<_> = pool.pooled_transactions_hashes_iter().collect();
3525 assert_eq!(hashes.len(), 2);
3526 }
3527
3528 #[test]
3529 fn test_pool_pooled_transactions_iter() {
3530 let mut pool = AA2dPool::default();
3531 let sender = Address::random();
3532
3533 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3534 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3535
3536 pool.add_transaction(
3537 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3538 0,
3539 TempoHardfork::T1,
3540 )
3541 .unwrap();
3542 pool.add_transaction(
3543 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3544 0,
3545 TempoHardfork::T1,
3546 )
3547 .unwrap();
3548
3549 let txs: Vec<_> = pool.pooled_transactions_iter().collect();
3550 assert_eq!(txs.len(), 2);
3551 }
3552
3553 #[test]
3558 fn test_best_transactions_iterator() {
3559 let mut pool = AA2dPool::default();
3560 let sender = Address::random();
3561
3562 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3563 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3564
3565 pool.add_transaction(
3566 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3567 0,
3568 TempoHardfork::T1,
3569 )
3570 .unwrap();
3571 pool.add_transaction(
3572 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3573 0,
3574 TempoHardfork::T1,
3575 )
3576 .unwrap();
3577
3578 let mut best = pool.best_transactions();
3579
3580 let first = best.next();
3582 assert!(first.is_some());
3583
3584 let second = best.next();
3585 assert!(second.is_some());
3586
3587 let third = best.next();
3588 assert!(third.is_none());
3589 }
3590
3591 #[test]
3592 fn test_best_transactions_mark_invalid() {
3593 use reth_primitives_traits::transaction::error::InvalidTransactionError;
3594
3595 let mut pool = AA2dPool::default();
3596 let sender = Address::random();
3597
3598 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3599 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3600
3601 pool.add_transaction(
3602 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3603 0,
3604 TempoHardfork::T1,
3605 )
3606 .unwrap();
3607 pool.add_transaction(
3608 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3609 0,
3610 TempoHardfork::T1,
3611 )
3612 .unwrap();
3613
3614 let mut best = pool.best_transactions();
3615
3616 let first = best.next().unwrap();
3617
3618 let error = reth_transaction_pool::error::InvalidPoolTransactionError::Consensus(
3620 InvalidTransactionError::TxTypeNotSupported,
3621 );
3622 best.mark_invalid(&first, &error);
3623
3624 }
3627
3628 #[test]
3629 fn test_best_transactions_expiring_nonce_independent() {
3630 let mut pool = AA2dPool::default();
3633 let sender = Address::random();
3634
3635 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
3637 pool.add_transaction(
3638 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3639 0,
3640 TempoHardfork::T1,
3641 )
3642 .unwrap();
3643
3644 let mut best = pool.best_transactions();
3645
3646 let first = best.next();
3648 assert!(first.is_some());
3649
3650 assert!(best.next().is_none());
3652 }
3653
3654 #[test]
3659 fn test_remove_transactions_by_sender() {
3660 let mut pool = AA2dPool::default();
3661 let sender1 = Address::random();
3662 let sender2 = Address::random();
3663
3664 let tx1 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
3665 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3666
3667 pool.add_transaction(
3668 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3669 0,
3670 TempoHardfork::T1,
3671 )
3672 .unwrap();
3673 pool.add_transaction(
3674 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3675 0,
3676 TempoHardfork::T1,
3677 )
3678 .unwrap();
3679
3680 let removed = pool.remove_transactions_by_sender(sender1);
3681 assert_eq!(removed.len(), 1);
3682 assert_eq!(removed[0].sender(), sender1);
3683
3684 let (pending, queued) = pool.pending_and_queued_txn_count();
3686 assert_eq!(pending + queued, 1);
3687
3688 pool.assert_invariants();
3689 }
3690
3691 #[test]
3692 fn test_remove_transactions_and_descendants() {
3693 let mut pool = AA2dPool::default();
3694 let sender = Address::random();
3695
3696 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3697 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3698 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
3699 let tx0_hash = *tx0.hash();
3700
3701 pool.add_transaction(
3702 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3703 0,
3704 TempoHardfork::T1,
3705 )
3706 .unwrap();
3707 pool.add_transaction(
3708 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3709 0,
3710 TempoHardfork::T1,
3711 )
3712 .unwrap();
3713 pool.add_transaction(
3714 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3715 0,
3716 TempoHardfork::T1,
3717 )
3718 .unwrap();
3719
3720 let removed = pool.remove_transactions_and_descendants([&tx0_hash].into_iter());
3722 assert_eq!(removed.len(), 3);
3723
3724 let (pending, queued) = pool.pending_and_queued_txn_count();
3725 assert_eq!(pending + queued, 0);
3726
3727 pool.assert_invariants();
3728 }
3729
3730 #[test]
3735 fn test_aa_sequence_id_equality() {
3736 let addr = Address::random();
3737 let nonce_key = U256::from(42);
3738
3739 let id1 = AASequenceId::new(addr, nonce_key);
3740 let id2 = AASequenceId::new(addr, nonce_key);
3741 let id3 = AASequenceId::new(Address::random(), nonce_key);
3742
3743 assert_eq!(id1, id2);
3744 assert_ne!(id1, id3);
3745 }
3746
3747 #[test]
3748 fn test_aa2d_transaction_id_unlocks() {
3749 let addr = Address::random();
3750 let seq_id = AASequenceId::new(addr, U256::ZERO);
3751 let tx_id = AA2dTransactionId::new(seq_id, 5);
3752
3753 let next_id = tx_id.unlocks();
3754 assert_eq!(next_id.seq_id, seq_id);
3755 assert_eq!(next_id.nonce, 6);
3756 }
3757
3758 #[test]
3759 fn test_aa2d_transaction_id_ordering() {
3760 let addr = Address::random();
3761 let seq_id = AASequenceId::new(addr, U256::ZERO);
3762
3763 let id1 = AA2dTransactionId::new(seq_id, 1);
3764 let id2 = AA2dTransactionId::new(seq_id, 2);
3765
3766 assert!(id1 < id2);
3767 }
3768
3769 #[test]
3774 fn test_nonce_overflow_at_u64_max() {
3775 let mut pool = AA2dPool::default();
3776 let sender = Address::random();
3777 let nonce_key = U256::ZERO;
3778
3779 let tx = TxBuilder::aa(sender)
3780 .nonce_key(nonce_key)
3781 .nonce(u64::MAX)
3782 .build();
3783 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
3784
3785 let result = pool.add_transaction(Arc::new(valid_tx), u64::MAX, TempoHardfork::T1);
3786 assert!(result.is_ok());
3787
3788 let (pending, queued) = pool.pending_and_queued_txn_count();
3789 assert_eq!(pending, 1);
3790 assert_eq!(queued, 0);
3791
3792 let seq_id = AASequenceId::new(sender, nonce_key);
3793 let tx_id = AA2dTransactionId::new(seq_id, u64::MAX);
3794 let unlocked = tx_id.unlocks();
3795 assert_eq!(
3796 unlocked.nonce,
3797 u64::MAX,
3798 "saturating_add should not overflow"
3799 );
3800
3801 pool.assert_invariants();
3802 }
3803
3804 #[test]
3805 fn test_nonce_near_max_with_gap() {
3806 let mut pool = AA2dPool::default();
3807 let sender = Address::random();
3808 let nonce_key = U256::ZERO;
3809
3810 let tx_max = TxBuilder::aa(sender)
3811 .nonce_key(nonce_key)
3812 .nonce(u64::MAX)
3813 .build();
3814 let tx_max_minus_1 = TxBuilder::aa(sender)
3815 .nonce_key(nonce_key)
3816 .nonce(u64::MAX - 1)
3817 .build();
3818
3819 pool.add_transaction(
3820 Arc::new(wrap_valid_tx(tx_max, TransactionOrigin::Local)),
3821 u64::MAX - 1,
3822 TempoHardfork::T1,
3823 )
3824 .unwrap();
3825
3826 let (pending, queued) = pool.pending_and_queued_txn_count();
3827 assert_eq!(pending, 0, "tx at u64::MAX should be queued (gap exists)");
3828 assert_eq!(queued, 1);
3829
3830 pool.add_transaction(
3831 Arc::new(wrap_valid_tx(tx_max_minus_1, TransactionOrigin::Local)),
3832 u64::MAX - 1,
3833 TempoHardfork::T1,
3834 )
3835 .unwrap();
3836
3837 let (pending, queued) = pool.pending_and_queued_txn_count();
3838 assert_eq!(pending, 2, "both should now be pending");
3839 assert_eq!(queued, 0);
3840
3841 pool.assert_invariants();
3842 }
3843
3844 #[test]
3845 fn test_empty_pool_operations() {
3846 let pool = AA2dPool::default();
3847
3848 assert_eq!(pool.pending_and_queued_txn_count(), (0, 0));
3849 assert!(pool.get(&B256::random()).is_none());
3850 assert!(!pool.contains(&B256::random()));
3851 assert_eq!(pool.senders_iter().count(), 0);
3852 assert_eq!(pool.pending_transactions().count(), 0);
3853 assert_eq!(pool.queued_transactions().count(), 0);
3854 assert_eq!(pool.all_transaction_hashes_iter().count(), 0);
3855 assert_eq!(pool.pooled_transactions_hashes_iter().count(), 0);
3856 assert_eq!(pool.pooled_transactions_iter().count(), 0);
3857
3858 let mut best = pool.best_transactions();
3859 assert!(best.next().is_none());
3860 }
3861
3862 #[test]
3863 fn test_empty_pool_remove_operations() {
3864 let mut pool = AA2dPool::default();
3865 let random_hash = B256::random();
3866 let random_sender = Address::random();
3867
3868 let removed = pool.remove_transactions([&random_hash].into_iter());
3869 assert!(removed.is_empty());
3870
3871 let removed = pool.remove_transactions_by_sender(random_sender);
3872 assert!(removed.is_empty());
3873
3874 let removed = pool.remove_transactions_and_descendants([&random_hash].into_iter());
3875 assert!(removed.is_empty());
3876
3877 pool.assert_invariants();
3878 }
3879
3880 #[test]
3881 fn test_empty_pool_on_nonce_changes() {
3882 let mut pool = AA2dPool::default();
3883
3884 let mut changes = HashMap::default();
3885 changes.insert(AASequenceId::new(Address::random(), U256::ZERO), 5u64);
3886
3887 let (promoted, mined) = pool.on_nonce_changes(changes);
3888 assert!(promoted.is_empty());
3889 assert!(mined.is_empty());
3890
3891 pool.assert_invariants();
3892 }
3893
3894 #[test]
3899 fn test_add_already_imported_transaction() {
3900 let mut pool = AA2dPool::default();
3901 let sender = Address::random();
3902
3903 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3904 let tx_hash = *tx.hash();
3905 let valid_tx = Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local));
3906
3907 pool.add_transaction(valid_tx.clone(), 0, TempoHardfork::T1)
3908 .unwrap();
3909
3910 let result = pool.add_transaction(valid_tx, 0, TempoHardfork::T1);
3911 assert!(result.is_err());
3912 let err = result.unwrap_err();
3913 assert_eq!(err.hash, tx_hash);
3914 assert!(
3915 matches!(err.kind, PoolErrorKind::AlreadyImported),
3916 "Expected AlreadyImported, got {:?}",
3917 err.kind
3918 );
3919
3920 pool.assert_invariants();
3921 }
3922
3923 #[test]
3924 fn test_add_outdated_nonce_transaction() {
3925 let mut pool = AA2dPool::default();
3926 let sender = Address::random();
3927
3928 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(5).build();
3929 let tx_hash = *tx.hash();
3930 let valid_tx = Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local));
3931
3932 let result = pool.add_transaction(valid_tx, 10, TempoHardfork::T1);
3933 assert!(result.is_err());
3934 let err = result.unwrap_err();
3935 assert_eq!(err.hash, tx_hash);
3936 assert!(
3937 matches!(
3938 err.kind,
3939 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
3940 InvalidTransactionError::NonceNotConsistent { tx: 5, state: 10 }
3941 ))
3942 ),
3943 "Expected NonceNotConsistent, got {:?}",
3944 err.kind
3945 );
3946
3947 let (pending, queued) = pool.pending_and_queued_txn_count();
3948 assert_eq!(pending + queued, 0);
3949 }
3950
3951 #[test]
3952 fn test_replacement_underpriced() {
3953 let mut pool = AA2dPool::default();
3954 let sender = Address::random();
3955
3956 let tx1 = TxBuilder::aa(sender)
3957 .nonce_key(U256::ZERO)
3958 .max_priority_fee(1_000_000_000)
3959 .max_fee(2_000_000_000)
3960 .build();
3961 pool.add_transaction(
3962 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3963 0,
3964 TempoHardfork::T1,
3965 )
3966 .unwrap();
3967
3968 let tx2 = TxBuilder::aa(sender)
3969 .nonce_key(U256::ZERO)
3970 .max_priority_fee(1_000_000_001)
3971 .max_fee(2_000_000_001)
3972 .build();
3973 let tx2_hash = *tx2.hash();
3974 let result = pool.add_transaction(
3975 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3976 0,
3977 TempoHardfork::T1,
3978 );
3979
3980 assert!(result.is_err());
3981 let err = result.unwrap_err();
3982 assert_eq!(err.hash, tx2_hash);
3983 assert!(
3984 matches!(err.kind, PoolErrorKind::ReplacementUnderpriced),
3985 "Expected ReplacementUnderpriced, got {:?}",
3986 err.kind
3987 );
3988
3989 let (pending, queued) = pool.pending_and_queued_txn_count();
3990 assert_eq!(pending + queued, 1);
3991
3992 pool.assert_invariants();
3993 }
3994
3995 #[test]
4000 fn test_discard_at_max_txs_limit() {
4001 let config = AA2dPoolConfig {
4002 price_bump_config: PriceBumpConfig::default(),
4003 pending_limit: SubPoolLimit {
4004 max_txs: 3,
4005 max_size: usize::MAX,
4006 },
4007 queued_limit: SubPoolLimit {
4008 max_txs: 10000,
4009 max_size: usize::MAX,
4010 },
4011 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4012 };
4013 let mut pool = AA2dPool::new(config);
4014
4015 for i in 0..5usize {
4016 let sender = Address::from_word(B256::from(U256::from(i)));
4017 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4018 let result = pool.add_transaction(
4019 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4020 0,
4021 TempoHardfork::T1,
4022 );
4023 assert!(result.is_ok());
4024 }
4025
4026 let (pending, queued) = pool.pending_and_queued_txn_count();
4027 assert_eq!(pending + queued, 3, "Pool should be capped at max_txs=3");
4028 assert_eq!(pending, 3, "All remaining transactions should be pending");
4029
4030 pool.assert_invariants();
4031 }
4032
4033 #[test]
4034 fn test_discard_removes_lowest_priority_same_priority_uses_submission_order() {
4035 let config = AA2dPoolConfig {
4036 price_bump_config: PriceBumpConfig::default(),
4037 pending_limit: SubPoolLimit {
4038 max_txs: 2,
4039 max_size: usize::MAX,
4040 },
4041 queued_limit: SubPoolLimit {
4042 max_txs: 10000,
4043 max_size: usize::MAX,
4044 },
4045 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4046 };
4047 let mut pool = AA2dPool::new(config);
4048 let sender = Address::random();
4049
4050 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4053 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4054 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
4055 let tx0_hash = *tx0.hash();
4056 let tx1_hash = *tx1.hash();
4057 let tx2_hash = *tx2.hash();
4058
4059 pool.add_transaction(
4060 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4061 0,
4062 TempoHardfork::T1,
4063 )
4064 .unwrap();
4065 pool.add_transaction(
4066 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4067 0,
4068 TempoHardfork::T1,
4069 )
4070 .unwrap();
4071 let result = pool.add_transaction(
4072 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4073 0,
4074 TempoHardfork::T1,
4075 );
4076 assert!(result.is_ok());
4077
4078 let added = result.unwrap();
4079 if let AddedTransaction::Pending(pending) = added {
4080 assert!(
4081 !pending.discarded.is_empty(),
4082 "Should have discarded transactions"
4083 );
4084 assert_eq!(
4085 pending.discarded[0].hash(),
4086 &tx2_hash,
4087 "tx2 (last submitted, lowest priority tiebreaker) should be discarded"
4088 );
4089 } else {
4090 panic!("Expected Pending result");
4091 }
4092
4093 assert!(pool.contains(&tx0_hash));
4094 assert!(pool.contains(&tx1_hash));
4095 assert!(!pool.contains(&tx2_hash));
4096
4097 pool.assert_invariants();
4098 }
4099
4100 #[test]
4102 fn test_discard_enforced_for_queued_transactions() {
4103 let config = AA2dPoolConfig {
4104 price_bump_config: PriceBumpConfig::default(),
4105 pending_limit: SubPoolLimit {
4106 max_txs: 2,
4107 max_size: usize::MAX,
4108 },
4109 queued_limit: SubPoolLimit {
4110 max_txs: 2,
4111 max_size: usize::MAX,
4112 },
4113 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4114 };
4115 let mut pool = AA2dPool::new(config);
4116
4117 for i in 0..5usize {
4119 let sender = Address::from_word(B256::from(U256::from(i)));
4120 let tx = TxBuilder::aa(sender)
4121 .nonce_key(U256::from(i))
4122 .nonce(1000)
4123 .build();
4124 let result = pool.add_transaction(
4125 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4126 0,
4127 TempoHardfork::T1,
4128 );
4129 assert!(result.is_ok(), "Transaction {i} should be added");
4130 }
4131
4132 let (pending, queued) = pool.pending_and_queued_txn_count();
4133 assert_eq!(
4134 pending + queued,
4135 2,
4136 "Pool should be capped at max_txs=2, but has {pending} pending + {queued} queued",
4137 );
4138
4139 pool.assert_invariants();
4140 }
4141
4142 #[test]
4144 fn test_queued_limit_enforced_separately() {
4145 let config = AA2dPoolConfig {
4146 price_bump_config: PriceBumpConfig::default(),
4147 pending_limit: SubPoolLimit {
4148 max_txs: 10,
4149 max_size: usize::MAX,
4150 },
4151 queued_limit: SubPoolLimit {
4152 max_txs: 3,
4153 max_size: usize::MAX,
4154 },
4155 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4156 };
4157 let mut pool = AA2dPool::new(config);
4158
4159 for i in 0..5usize {
4161 let sender = Address::from_word(B256::from(U256::from(i)));
4162 let tx = TxBuilder::aa(sender)
4163 .nonce_key(U256::from(i))
4164 .nonce(1000)
4165 .build();
4166 let _ = pool.add_transaction(
4167 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4168 0,
4169 TempoHardfork::T1,
4170 );
4171 }
4172
4173 let (pending, queued) = pool.pending_and_queued_txn_count();
4174 assert_eq!(queued, 3, "Queued should be capped at 3");
4175 assert_eq!(pending, 0, "No pending transactions");
4176 pool.assert_invariants();
4177 }
4178
4179 #[test]
4181 fn test_pending_limit_enforced_separately() {
4182 let config = AA2dPoolConfig {
4183 price_bump_config: PriceBumpConfig::default(),
4184 pending_limit: SubPoolLimit {
4185 max_txs: 3,
4186 max_size: usize::MAX,
4187 },
4188 queued_limit: SubPoolLimit {
4189 max_txs: 10,
4190 max_size: usize::MAX,
4191 },
4192 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4193 };
4194 let mut pool = AA2dPool::new(config);
4195
4196 for i in 0..5usize {
4198 let sender = Address::from_word(B256::from(U256::from(i)));
4199 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4200 let _ = pool.add_transaction(
4201 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4202 0,
4203 TempoHardfork::T1,
4204 );
4205 }
4206
4207 let (pending, queued) = pool.pending_and_queued_txn_count();
4208 assert_eq!(pending, 3, "Pending should be capped at 3");
4209 assert_eq!(queued, 0, "No queued transactions");
4210 pool.assert_invariants();
4211 }
4212
4213 #[test]
4215 fn test_queued_eviction_does_not_affect_pending() {
4216 let config = AA2dPoolConfig {
4217 price_bump_config: PriceBumpConfig::default(),
4218 pending_limit: SubPoolLimit {
4219 max_txs: 5,
4220 max_size: usize::MAX,
4221 },
4222 queued_limit: SubPoolLimit {
4223 max_txs: 2,
4224 max_size: usize::MAX,
4225 },
4226 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4227 };
4228 let mut pool = AA2dPool::new(config);
4229
4230 let mut pending_hashes = Vec::new();
4232 for i in 0..3usize {
4233 let sender = Address::from_word(B256::from(U256::from(i)));
4234 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4235 let hash = *tx.hash();
4236 pending_hashes.push(hash);
4237 let _ = pool.add_transaction(
4238 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4239 0,
4240 TempoHardfork::T1,
4241 );
4242 }
4243
4244 for i in 100..110usize {
4246 let sender = Address::from_word(B256::from(U256::from(i)));
4247 let tx = TxBuilder::aa(sender)
4248 .nonce_key(U256::from(i))
4249 .nonce(1000)
4250 .build();
4251 let _ = pool.add_transaction(
4252 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4253 0,
4254 TempoHardfork::T1,
4255 );
4256 }
4257
4258 for hash in &pending_hashes {
4260 assert!(
4261 pool.contains(hash),
4262 "Pending tx should not be evicted by queued spam"
4263 );
4264 }
4265
4266 let (pending, queued) = pool.pending_and_queued_txn_count();
4267 assert_eq!(pending, 3, "All 3 pending should remain");
4268 assert_eq!(queued, 2, "Queued capped at 2");
4269 pool.assert_invariants();
4270 }
4271
4272 #[test]
4275 fn test_discard_evicts_low_priority_over_vanity_address() {
4276 let config = AA2dPoolConfig {
4277 price_bump_config: PriceBumpConfig::default(),
4278 pending_limit: SubPoolLimit {
4279 max_txs: 2,
4280 max_size: usize::MAX,
4281 },
4282 queued_limit: SubPoolLimit {
4283 max_txs: 10,
4284 max_size: usize::MAX,
4285 },
4286 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4287 };
4288 let mut pool = AA2dPool::new(config);
4289
4290 let vanity_sender = Address::from_word(B256::from_slice(&[0u8; 32])); let normal_sender = Address::from_word(B256::from_slice(&[0xff; 32])); let high_max_fee = 30_000_000_000u128; let high_priority_tx = TxBuilder::aa(vanity_sender)
4302 .nonce_key(U256::ZERO)
4303 .max_fee(high_max_fee)
4304 .max_priority_fee(5_000_000_000) .build();
4306 let high_priority_hash = *high_priority_tx.hash();
4307
4308 let low_priority_tx = TxBuilder::aa(normal_sender)
4311 .nonce_key(U256::ZERO)
4312 .max_fee(high_max_fee)
4313 .max_priority_fee(1) .build();
4315 let low_priority_hash = *low_priority_tx.hash();
4316
4317 pool.add_transaction(
4318 Arc::new(wrap_valid_tx(high_priority_tx, TransactionOrigin::Local)),
4319 0,
4320 TempoHardfork::T1,
4321 )
4322 .unwrap();
4323 pool.add_transaction(
4324 Arc::new(wrap_valid_tx(low_priority_tx, TransactionOrigin::Local)),
4325 0,
4326 TempoHardfork::T1,
4327 )
4328 .unwrap();
4329
4330 let trigger_tx = TxBuilder::aa(Address::random())
4333 .nonce_key(U256::from(1))
4334 .max_fee(high_max_fee)
4335 .max_priority_fee(3_000_000_000) .build();
4337 let trigger_hash = *trigger_tx.hash();
4338
4339 let result = pool.add_transaction(
4340 Arc::new(wrap_valid_tx(trigger_tx, TransactionOrigin::Local)),
4341 0,
4342 TempoHardfork::T1,
4343 );
4344 assert!(result.is_ok());
4345
4346 let added = result.unwrap();
4347 if let AddedTransaction::Pending(pending) = added {
4348 assert!(
4349 !pending.discarded.is_empty(),
4350 "Should have discarded transactions"
4351 );
4352 assert_eq!(
4354 pending.discarded[0].hash(),
4355 &low_priority_hash,
4356 "Low priority tx should be evicted, not the high-priority vanity address tx"
4357 );
4358 } else {
4359 panic!("Expected Pending result");
4360 }
4361
4362 assert!(
4364 pool.contains(&high_priority_hash),
4365 "High priority vanity address tx should be kept"
4366 );
4367 assert!(
4368 !pool.contains(&low_priority_hash),
4369 "Low priority tx should be evicted"
4370 );
4371 assert!(pool.contains(&trigger_hash), "Trigger tx should be kept");
4372
4373 pool.assert_invariants();
4374 }
4375
4376 #[test]
4378 fn test_per_sender_limit_rejects_excess_transactions() {
4379 let config = AA2dPoolConfig {
4380 price_bump_config: PriceBumpConfig::default(),
4381 pending_limit: SubPoolLimit {
4382 max_txs: 1000,
4383 max_size: usize::MAX,
4384 },
4385 queued_limit: SubPoolLimit {
4386 max_txs: 1000,
4387 max_size: usize::MAX,
4388 },
4389 max_txs_per_sender: 3,
4390 };
4391 let mut pool = AA2dPool::new(config);
4392 let sender = Address::random();
4393
4394 for nonce in 0..3u64 {
4396 let tx = TxBuilder::aa(sender)
4397 .nonce_key(U256::ZERO)
4398 .nonce(nonce)
4399 .build();
4400 let result = pool.add_transaction(
4401 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4402 0,
4403 TempoHardfork::T1,
4404 );
4405 assert!(result.is_ok(), "Transaction {nonce} should be accepted");
4406 }
4407
4408 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(3).build();
4410 let result = pool.add_transaction(
4411 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4412 0,
4413 TempoHardfork::T1,
4414 );
4415 assert!(result.is_err(), "4th transaction should be rejected");
4416 let err = result.unwrap_err();
4417 assert!(
4418 matches!(err.kind, PoolErrorKind::SpammerExceededCapacity(_)),
4419 "Error should be SpammerExceededCapacity, got {:?}",
4420 err.kind
4421 );
4422
4423 let other_sender = Address::random();
4425 let tx = TxBuilder::aa(other_sender).nonce_key(U256::ZERO).build();
4426 let result = pool.add_transaction(
4427 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4428 0,
4429 TempoHardfork::T1,
4430 );
4431 assert!(result.is_ok(), "Different sender should be accepted");
4432
4433 pool.assert_invariants();
4434 }
4435
4436 #[test]
4438 fn test_per_sender_limit_allows_replacement() {
4439 let config = AA2dPoolConfig {
4440 price_bump_config: PriceBumpConfig::default(),
4441 pending_limit: SubPoolLimit {
4442 max_txs: 1000,
4443 max_size: usize::MAX,
4444 },
4445 queued_limit: SubPoolLimit {
4446 max_txs: 1000,
4447 max_size: usize::MAX,
4448 },
4449 max_txs_per_sender: 2,
4450 };
4451 let mut pool = AA2dPool::new(config);
4452 let sender = Address::random();
4453
4454 for nonce in 0..2u64 {
4456 let tx = TxBuilder::aa(sender)
4457 .nonce_key(U256::ZERO)
4458 .nonce(nonce)
4459 .build();
4460 pool.add_transaction(
4461 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4462 0,
4463 TempoHardfork::T1,
4464 )
4465 .unwrap();
4466 }
4467
4468 let replacement_tx = TxBuilder::aa(sender)
4470 .nonce_key(U256::ZERO)
4471 .nonce(0)
4472 .max_fee(100_000_000_000) .max_priority_fee(50_000_000_000)
4474 .build();
4475 let result = pool.add_transaction(
4476 Arc::new(wrap_valid_tx(replacement_tx, TransactionOrigin::Local)),
4477 0,
4478 TempoHardfork::T1,
4479 );
4480 assert!(
4481 result.is_ok(),
4482 "Replacement should be allowed even at limit"
4483 );
4484
4485 pool.assert_invariants();
4486 }
4487
4488 #[test]
4490 fn test_per_sender_limit_freed_after_removal() {
4491 let config = AA2dPoolConfig {
4492 price_bump_config: PriceBumpConfig::default(),
4493 pending_limit: SubPoolLimit {
4494 max_txs: 1000,
4495 max_size: usize::MAX,
4496 },
4497 queued_limit: SubPoolLimit {
4498 max_txs: 1000,
4499 max_size: usize::MAX,
4500 },
4501 max_txs_per_sender: 2,
4502 };
4503 let mut pool = AA2dPool::new(config);
4504 let sender = Address::random();
4505
4506 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
4508 let tx1_hash = *tx1.hash();
4509 pool.add_transaction(
4510 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4511 0,
4512 TempoHardfork::T1,
4513 )
4514 .unwrap();
4515
4516 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4517 pool.add_transaction(
4518 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4519 0,
4520 TempoHardfork::T1,
4521 )
4522 .unwrap();
4523
4524 let tx3 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
4526 let result = pool.add_transaction(
4527 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
4528 0,
4529 TempoHardfork::T1,
4530 );
4531 assert!(result.is_err(), "3rd should be rejected at limit");
4532
4533 pool.remove_transactions(std::iter::once(&tx1_hash));
4535
4536 let result = pool.add_transaction(
4538 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4539 0,
4540 TempoHardfork::T1,
4541 );
4542 assert!(result.is_ok(), "3rd should succeed after removal");
4543
4544 pool.assert_invariants();
4545 }
4546
4547 #[test]
4549 fn test_per_sender_limit_includes_expiring_nonce_txs() {
4550 let config = AA2dPoolConfig {
4551 price_bump_config: PriceBumpConfig::default(),
4552 pending_limit: SubPoolLimit {
4553 max_txs: 1000,
4554 max_size: usize::MAX,
4555 },
4556 queued_limit: SubPoolLimit {
4557 max_txs: 1000,
4558 max_size: usize::MAX,
4559 },
4560 max_txs_per_sender: 2,
4561 };
4562 let mut pool = AA2dPool::new(config);
4563 let sender = Address::random();
4564
4565 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
4567 pool.add_transaction(
4568 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4569 0,
4570 TempoHardfork::T1,
4571 )
4572 .unwrap();
4573
4574 let tx2 = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
4576 pool.add_transaction(
4577 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4578 0,
4579 TempoHardfork::T1,
4580 )
4581 .unwrap();
4582
4583 let tx3 = TxBuilder::aa(sender)
4585 .nonce_key(U256::from(1))
4586 .nonce(0)
4587 .build();
4588 let result = pool.add_transaction(
4589 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4590 0,
4591 TempoHardfork::T1,
4592 );
4593 assert!(
4594 result.is_err(),
4595 "3rd tx should be rejected due to per-sender limit"
4596 );
4597
4598 pool.assert_invariants();
4599 }
4600
4601 #[test]
4606 fn test_best_transactions_mark_invalid_skips_sequence() {
4607 use reth_primitives_traits::transaction::error::InvalidTransactionError;
4608
4609 let mut pool = AA2dPool::default();
4610 let sender1 = Address::random();
4611 let sender2 = Address::random();
4612
4613 let tx1_0 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
4614 let tx1_1 = TxBuilder::aa(sender1)
4615 .nonce_key(U256::ZERO)
4616 .nonce(1)
4617 .build();
4618 let tx2_0 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
4619
4620 let tx1_0_hash = *tx1_0.hash();
4621 let tx2_0_hash = *tx2_0.hash();
4622
4623 pool.add_transaction(
4624 Arc::new(wrap_valid_tx(tx1_0, TransactionOrigin::Local)),
4625 0,
4626 TempoHardfork::T1,
4627 )
4628 .unwrap();
4629 pool.add_transaction(
4630 Arc::new(wrap_valid_tx(tx1_1, TransactionOrigin::Local)),
4631 0,
4632 TempoHardfork::T1,
4633 )
4634 .unwrap();
4635 pool.add_transaction(
4636 Arc::new(wrap_valid_tx(tx2_0, TransactionOrigin::Local)),
4637 0,
4638 TempoHardfork::T1,
4639 )
4640 .unwrap();
4641
4642 let mut best = pool.best_transactions();
4643
4644 let first = best.next().unwrap();
4645 let first_hash = *first.hash();
4646
4647 let error =
4648 InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported);
4649 best.mark_invalid(&first, &error);
4650
4651 let mut remaining_hashes = HashSet::new();
4652 for tx in best {
4653 remaining_hashes.insert(*tx.hash());
4654 }
4655
4656 if first_hash == tx1_0_hash {
4657 assert!(
4658 !remaining_hashes.contains(&tx1_0_hash),
4659 "tx1_0 was consumed"
4660 );
4661 assert!(
4662 remaining_hashes.contains(&tx2_0_hash),
4663 "tx2_0 should still be yielded"
4664 );
4665 } else {
4666 assert!(
4667 remaining_hashes.contains(&tx1_0_hash) || remaining_hashes.contains(&tx2_0_hash),
4668 "At least one other independent tx should be yielded"
4669 );
4670 }
4671 }
4672
4673 #[test]
4674 fn test_best_transactions_order_by_priority() {
4675 let mut pool = AA2dPool::default();
4676
4677 let sender1 = Address::random();
4678 let sender2 = Address::random();
4679
4680 let low_priority = TxBuilder::aa(sender1)
4681 .nonce_key(U256::ZERO)
4682 .max_priority_fee(1_000_000)
4683 .max_fee(2_000_000)
4684 .build();
4685 let high_priority = TxBuilder::aa(sender2)
4686 .nonce_key(U256::from(1))
4687 .max_priority_fee(10_000_000_000)
4688 .max_fee(20_000_000_000)
4689 .build();
4690 let high_priority_hash = *high_priority.hash();
4691
4692 pool.add_transaction(
4693 Arc::new(wrap_valid_tx(low_priority, TransactionOrigin::Local)),
4694 0,
4695 TempoHardfork::T1,
4696 )
4697 .unwrap();
4698 pool.add_transaction(
4699 Arc::new(wrap_valid_tx(high_priority, TransactionOrigin::Local)),
4700 0,
4701 TempoHardfork::T1,
4702 )
4703 .unwrap();
4704
4705 let mut best = pool.best_transactions();
4706 let first = best.next().unwrap();
4707
4708 assert_eq!(
4709 first.hash(),
4710 &high_priority_hash,
4711 "Higher priority transaction should come first"
4712 );
4713 }
4714
4715 #[test]
4720 fn test_on_state_updates_with_bundle_account() {
4721 use revm::{
4722 database::{AccountStatus, BundleAccount},
4723 state::AccountInfo,
4724 };
4725
4726 let mut pool = AA2dPool::default();
4727 let sender = Address::random();
4728 let nonce_key = U256::ZERO;
4729
4730 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
4731 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
4732 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
4733
4734 pool.add_transaction(
4735 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4736 0,
4737 TempoHardfork::T1,
4738 )
4739 .unwrap();
4740 pool.add_transaction(
4741 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4742 0,
4743 TempoHardfork::T1,
4744 )
4745 .unwrap();
4746 pool.add_transaction(
4747 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4748 0,
4749 TempoHardfork::T1,
4750 )
4751 .unwrap();
4752
4753 let (pending, queued) = pool.pending_and_queued_txn_count();
4754 assert_eq!(pending, 3);
4755 assert_eq!(queued, 0);
4756
4757 let mut state = HashMap::default();
4758 let sender_account = BundleAccount::new(
4759 None,
4760 Some(AccountInfo {
4761 nonce: 2,
4762 ..Default::default()
4763 }),
4764 Default::default(),
4765 AccountStatus::Changed,
4766 );
4767 state.insert(sender, sender_account);
4768
4769 let (promoted, mined) = pool.on_state_updates(&state);
4770
4771 assert!(promoted.is_empty(), "tx2 was already pending");
4772 assert_eq!(mined.len(), 2, "tx0 and tx1 should be mined");
4773
4774 let (pending, queued) = pool.pending_and_queued_txn_count();
4775 assert_eq!(pending, 1, "Only tx2 should remain pending");
4776 assert_eq!(queued, 0);
4777
4778 pool.assert_invariants();
4779 }
4780
4781 #[test]
4782 fn test_on_state_updates_creates_gap_demotion() {
4783 use revm::{
4784 database::{AccountStatus, BundleAccount},
4785 state::AccountInfo,
4786 };
4787
4788 let mut pool = AA2dPool::default();
4789 let sender = Address::random();
4790 let nonce_key = U256::ZERO;
4791
4792 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
4793 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
4794 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
4795
4796 pool.add_transaction(
4797 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4798 0,
4799 TempoHardfork::T1,
4800 )
4801 .unwrap();
4802 pool.add_transaction(
4803 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4804 0,
4805 TempoHardfork::T1,
4806 )
4807 .unwrap();
4808 pool.add_transaction(
4809 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4810 0,
4811 TempoHardfork::T1,
4812 )
4813 .unwrap();
4814
4815 let (pending, queued) = pool.pending_and_queued_txn_count();
4816 assert_eq!(pending, 2);
4817 assert_eq!(queued, 1);
4818
4819 let mut state = HashMap::default();
4820 let sender_account = BundleAccount::new(
4821 None,
4822 Some(AccountInfo {
4823 nonce: 2,
4824 ..Default::default()
4825 }),
4826 Default::default(),
4827 AccountStatus::Changed,
4828 );
4829 state.insert(sender, sender_account);
4830
4831 let (promoted, mined) = pool.on_state_updates(&state);
4832
4833 assert_eq!(mined.len(), 2, "tx0 and tx1 should be mined");
4834 assert!(promoted.is_empty());
4835
4836 let (pending, queued) = pool.pending_and_queued_txn_count();
4837 assert_eq!(pending, 0, "tx3 should still be queued (gap at nonce 2)");
4838 assert_eq!(queued, 1);
4839
4840 pool.assert_invariants();
4841 }
4842
4843 #[test]
4844 fn test_on_nonce_changes_promotes_queued_transactions() {
4845 let mut pool = AA2dPool::default();
4846 let sender = Address::random();
4847 let nonce_key = U256::ZERO;
4848 let seq_id = AASequenceId::new(sender, nonce_key);
4849
4850 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
4851 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
4852
4853 pool.add_transaction(
4854 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
4855 0,
4856 TempoHardfork::T1,
4857 )
4858 .unwrap();
4859 pool.add_transaction(
4860 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4861 0,
4862 TempoHardfork::T1,
4863 )
4864 .unwrap();
4865
4866 let (pending, queued) = pool.pending_and_queued_txn_count();
4867 assert_eq!(pending, 0);
4868 assert_eq!(queued, 2);
4869
4870 let mut changes = HashMap::default();
4871 changes.insert(seq_id, 2u64);
4872
4873 let (promoted, mined) = pool.on_nonce_changes(changes);
4874
4875 assert!(
4876 mined.is_empty(),
4877 "No transactions to mine (on-chain nonce jumped)"
4878 );
4879 assert_eq!(promoted.len(), 2, "tx2 and tx3 should be promoted");
4880 assert!(promoted.iter().any(|t| t.hash() == tx2.hash()));
4881
4882 let (pending, queued) = pool.pending_and_queued_txn_count();
4883 assert_eq!(pending, 2);
4884 assert_eq!(queued, 0);
4885
4886 pool.assert_invariants();
4887 }
4888
4889 #[test]
4894 fn test_interleaved_inserts_multiple_nonce_keys() {
4895 let mut pool = AA2dPool::default();
4896 let sender = Address::random();
4897
4898 let key_a = U256::ZERO;
4899 let key_b = U256::from(1);
4900
4901 let tx_a0 = TxBuilder::aa(sender).nonce_key(key_a).build();
4902 let tx_b0 = TxBuilder::aa(sender).nonce_key(key_b).build();
4903 let tx_a1 = TxBuilder::aa(sender).nonce_key(key_a).nonce(1).build();
4904 let tx_b2 = TxBuilder::aa(sender).nonce_key(key_b).nonce(2).build();
4905 let tx_b1 = TxBuilder::aa(sender).nonce_key(key_b).nonce(1).build();
4906
4907 pool.add_transaction(
4908 Arc::new(wrap_valid_tx(tx_a0, TransactionOrigin::Local)),
4909 0,
4910 TempoHardfork::T1,
4911 )
4912 .unwrap();
4913 pool.add_transaction(
4914 Arc::new(wrap_valid_tx(tx_b0, TransactionOrigin::Local)),
4915 0,
4916 TempoHardfork::T1,
4917 )
4918 .unwrap();
4919 pool.add_transaction(
4920 Arc::new(wrap_valid_tx(tx_a1, TransactionOrigin::Local)),
4921 0,
4922 TempoHardfork::T1,
4923 )
4924 .unwrap();
4925 pool.add_transaction(
4926 Arc::new(wrap_valid_tx(tx_b2, TransactionOrigin::Local)),
4927 0,
4928 TempoHardfork::T1,
4929 )
4930 .unwrap();
4931 pool.add_transaction(
4932 Arc::new(wrap_valid_tx(tx_b1, TransactionOrigin::Local)),
4933 0,
4934 TempoHardfork::T1,
4935 )
4936 .unwrap();
4937
4938 let (pending, queued) = pool.pending_and_queued_txn_count();
4939 assert_eq!(pending, 5, "All transactions should be pending");
4940 assert_eq!(queued, 0);
4941
4942 assert_eq!(
4943 pool.independent_transactions.len(),
4944 2,
4945 "Two nonce keys = two independent txs"
4946 );
4947
4948 pool.assert_invariants();
4949 }
4950
4951 #[test]
4952 fn test_same_sender_different_nonce_keys_independent() {
4953 let mut pool = AA2dPool::default();
4954 let sender = Address::random();
4955
4956 let key_a = U256::from(100);
4957 let key_b = U256::from(200);
4958
4959 let tx_a5 = TxBuilder::aa(sender).nonce_key(key_a).nonce(5).build();
4960 let tx_b0 = TxBuilder::aa(sender).nonce_key(key_b).build();
4961
4962 pool.add_transaction(
4963 Arc::new(wrap_valid_tx(tx_a5, TransactionOrigin::Local)),
4964 5,
4965 TempoHardfork::T1,
4966 )
4967 .unwrap();
4968 pool.add_transaction(
4969 Arc::new(wrap_valid_tx(tx_b0, TransactionOrigin::Local)),
4970 0,
4971 TempoHardfork::T1,
4972 )
4973 .unwrap();
4974
4975 let (pending, queued) = pool.pending_and_queued_txn_count();
4976 assert_eq!(pending, 2);
4977 assert_eq!(queued, 0);
4978
4979 assert_eq!(pool.independent_transactions.len(), 2);
4980
4981 pool.assert_invariants();
4982 }
4983
4984 #[test_case::test_case(U256::ZERO)]
4989 #[test_case::test_case(U256::random())]
4990 fn reorg_nonce_decrease_clears_stale_independent_transaction(nonce_key: U256) {
4991 let mut pool = AA2dPool::default();
4992 let sender = Address::random();
4993 let seq_id = AASequenceId::new(sender, nonce_key);
4994
4995 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
4997 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
4998 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
4999 let tx5_hash = *tx5.hash();
5000
5001 pool.add_transaction(
5002 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5003 3,
5004 TempoHardfork::T1,
5005 )
5006 .unwrap();
5007 pool.add_transaction(
5008 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
5009 3,
5010 TempoHardfork::T1,
5011 )
5012 .unwrap();
5013 pool.add_transaction(
5014 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
5015 3,
5016 TempoHardfork::T1,
5017 )
5018 .unwrap();
5019
5020 let (pending, queued) = pool.pending_and_queued_txn_count();
5022 assert_eq!(pending, 3, "All transactions should be pending");
5023 assert_eq!(queued, 0);
5024 assert_eq!(pool.independent_transactions.len(), 1);
5025 assert_eq!(
5026 pool.independent_transactions
5027 .get(&seq_id)
5028 .unwrap()
5029 .transaction
5030 .nonce(),
5031 3,
5032 "tx3 should be independent initially"
5033 );
5034 pool.assert_invariants();
5035
5036 let mut on_chain_ids = HashMap::default();
5038 on_chain_ids.insert(seq_id, 5u64);
5039 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
5040
5041 assert_eq!(mined.len(), 2, "tx3 and tx4 should be mined");
5042 assert!(promoted.is_empty(), "No promotions expected");
5043
5044 let (pending, queued) = pool.pending_and_queued_txn_count();
5046 assert_eq!(pending, 1, "Only tx5 should remain pending");
5047 assert_eq!(queued, 0);
5048 assert_eq!(pool.independent_transactions.len(), 1);
5049 assert_eq!(
5050 pool.independent_transactions
5051 .get(&seq_id)
5052 .unwrap()
5053 .transaction
5054 .hash(),
5055 &tx5_hash,
5056 "tx5 should be independent after mining"
5057 );
5058 pool.assert_invariants();
5059
5060 let mut on_chain_ids = HashMap::default();
5062 on_chain_ids.insert(seq_id, 3u64);
5063 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
5064
5065 assert!(mined.is_empty(), "No transactions should be mined");
5067 assert!(promoted.is_empty(), "No promotions expected");
5069
5070 let (pending, queued) = pool.pending_and_queued_txn_count();
5072 assert_eq!(pending, 0, "tx5 should not be pending (nonce gap)");
5073 assert_eq!(queued, 1, "tx5 should be queued");
5074
5075 assert!(
5077 !pool.independent_transactions.contains_key(&seq_id),
5078 "independent_transactions should not contain stale entry after reorg"
5079 );
5080
5081 pool.assert_invariants();
5082 }
5083
5084 #[test_case::test_case(U256::ZERO)]
5094 #[test_case::test_case(U256::random())]
5095 fn reorg_reinjection_via_add_transaction_restores_pending_state(nonce_key: U256) {
5096 let mut pool = AA2dPool::default();
5097 let sender = Address::random();
5098 let seq_id = AASequenceId::new(sender, nonce_key);
5099
5100 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
5102 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
5103 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
5104 let tx3_hash = *tx3.hash();
5105 let tx4_hash = *tx4.hash();
5106 let tx5_hash = *tx5.hash();
5107
5108 pool.add_transaction(
5109 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
5110 3,
5111 TempoHardfork::T1,
5112 )
5113 .unwrap();
5114 pool.add_transaction(
5115 Arc::new(wrap_valid_tx(tx4.clone(), TransactionOrigin::Local)),
5116 3,
5117 TempoHardfork::T1,
5118 )
5119 .unwrap();
5120 pool.add_transaction(
5121 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
5122 3,
5123 TempoHardfork::T1,
5124 )
5125 .unwrap();
5126
5127 let (pending, queued) = pool.pending_and_queued_txn_count();
5128 assert_eq!(pending, 3);
5129 assert_eq!(queued, 0);
5130 pool.assert_invariants();
5131
5132 let mut nonce_changes = HashMap::default();
5134 nonce_changes.insert(seq_id, 5u64);
5135 let (_promoted, mined) = pool.on_nonce_changes(nonce_changes);
5136 assert_eq!(mined.len(), 2);
5137
5138 let (pending, queued) = pool.pending_and_queued_txn_count();
5139 assert_eq!(pending, 1, "only tx5 should remain pending");
5140 assert_eq!(queued, 0);
5141 pool.assert_invariants();
5142
5143 pool.add_transaction(
5147 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::External)),
5148 3,
5149 TempoHardfork::T1,
5150 )
5151 .unwrap();
5152 pool.add_transaction(
5153 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::External)),
5154 3,
5155 TempoHardfork::T1,
5156 )
5157 .unwrap();
5158
5159 let (pending, queued) = pool.pending_and_queued_txn_count();
5161 assert_eq!(pending, 3, "all txs should be pending after re-injection");
5162 assert_eq!(queued, 0);
5163
5164 assert_eq!(
5166 pool.independent_transactions
5167 .get(&seq_id)
5168 .unwrap()
5169 .transaction
5170 .nonce(),
5171 3,
5172 );
5173
5174 assert!(pool.contains(&tx3_hash));
5176 assert!(pool.contains(&tx4_hash));
5177 assert!(pool.contains(&tx5_hash));
5178
5179 pool.assert_invariants();
5180 }
5181
5182 #[test_case::test_case(U256::ZERO)]
5187 #[test_case::test_case(U256::random())]
5188 fn gap_demotion_marks_all_subsequent_transactions_as_queued(nonce_key: U256) {
5189 let mut pool = AA2dPool::default();
5190 let sender = Address::random();
5191 let seq_id = AASequenceId::new(sender, nonce_key);
5192
5193 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
5195 let tx6 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(6).build();
5196 let tx7 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(7).build();
5197 let tx8 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(8).build();
5198 let tx6_hash = *tx6.hash();
5199
5200 pool.add_transaction(
5201 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
5202 5,
5203 TempoHardfork::T1,
5204 )
5205 .unwrap();
5206 pool.add_transaction(
5207 Arc::new(wrap_valid_tx(tx6, TransactionOrigin::Local)),
5208 5,
5209 TempoHardfork::T1,
5210 )
5211 .unwrap();
5212 pool.add_transaction(
5213 Arc::new(wrap_valid_tx(tx7, TransactionOrigin::Local)),
5214 5,
5215 TempoHardfork::T1,
5216 )
5217 .unwrap();
5218 pool.add_transaction(
5219 Arc::new(wrap_valid_tx(tx8, TransactionOrigin::Local)),
5220 5,
5221 TempoHardfork::T1,
5222 )
5223 .unwrap();
5224
5225 let (pending, queued) = pool.pending_and_queued_txn_count();
5227 assert_eq!(pending, 4, "All transactions should be pending initially");
5228 assert_eq!(queued, 0);
5229 assert_eq!(pool.independent_transactions.len(), 1);
5230 pool.assert_invariants();
5231
5232 let removed = pool.remove_transactions(std::iter::once(&tx6_hash));
5235 assert_eq!(removed.len(), 1, "Should remove exactly tx6");
5236
5237 let mut on_chain_ids = HashMap::default();
5240 on_chain_ids.insert(seq_id, 5u64);
5241 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
5242
5243 assert!(mined.is_empty(), "No transactions should be mined");
5244 assert!(promoted.is_empty(), "No promotions expected");
5245
5246 let (pending, queued) = pool.pending_and_queued_txn_count();
5249 assert_eq!(
5250 pending, 1,
5251 "Only tx5 should be pending (tx7 and tx8 are after the gap)"
5252 );
5253 assert_eq!(
5254 queued, 2,
5255 "tx7 and tx8 should both be queued due to gap at nonce 6"
5256 );
5257
5258 pool.assert_invariants();
5259 }
5260
5261 #[test]
5262 fn expiring_nonce_tx_increments_pending_count() {
5263 let mut pool = AA2dPool::default();
5264 let sender = Address::random();
5265
5266 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5268 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
5269
5270 let result = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
5272 assert!(result.is_ok(), "Transaction should be added successfully");
5273 assert!(
5274 matches!(result.unwrap(), AddedTransaction::Pending(_)),
5275 "Expiring nonce transaction should be pending"
5276 );
5277
5278 let (pending, queued) = pool.pending_and_queued_txn_count();
5280 assert_eq!(pending, 1, "Should have 1 pending transaction");
5281 assert_eq!(queued, 0, "Should have 0 queued transactions");
5282
5283 pool.assert_invariants();
5285 }
5286
5287 #[test]
5288 fn expiring_nonce_tx_dedup_uses_expiring_nonce_hash() {
5289 let mut pool = AA2dPool::default();
5290 let sender = Address::random();
5291 let call_to = Address::random();
5292 let fee_token = Address::random();
5293 let calls = vec![Call {
5294 to: TxKind::Call(call_to),
5295 value: U256::ZERO,
5296 input: Bytes::new(),
5297 }];
5298
5299 let build_tx = |fee_payer_signature: Signature| {
5300 let tx = TempoTransaction {
5301 chain_id: 1,
5302 max_priority_fee_per_gas: 1_000_000_000,
5303 max_fee_per_gas: 2_000_000_000,
5304 gas_limit: 1_000_000,
5305 calls: calls.clone(),
5306 nonce_key: U256::MAX,
5307 nonce: 0,
5308 fee_token: Some(fee_token),
5309 fee_payer_signature: Some(fee_payer_signature),
5310 valid_after: None,
5311 valid_before: Some(core::num::NonZeroU64::new(123).unwrap()),
5312 access_list: AccessList::default(),
5313 tempo_authorization_list: Vec::new(),
5314 key_authorization: None,
5315 };
5316
5317 let signature = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
5318 Signature::test_signature(),
5319 ));
5320 let aa_signed = AASigned::new_unhashed(tx, signature);
5321 let envelope: TempoTxEnvelope = aa_signed.into();
5322 let recovered = Recovered::new_unchecked(envelope, sender);
5323 TempoPooledTransaction::new(recovered)
5324 };
5325
5326 let tx1 = build_tx(Signature::new(U256::from(1), U256::from(2), false));
5327 let tx2 = build_tx(Signature::new(U256::from(3), U256::from(4), false));
5328
5329 assert_ne!(tx1.hash(), tx2.hash(), "tx hashes must differ");
5330 let expiring_hash_1 = tx1
5331 .expiring_nonce_hash()
5332 .expect("expiring nonce tx must be AA");
5333 let expiring_hash_2 = tx2
5334 .expiring_nonce_hash()
5335 .expect("expiring nonce tx must be AA");
5336 assert_eq!(
5337 expiring_hash_1, expiring_hash_2,
5338 "expiring nonce hashes must match"
5339 );
5340
5341 let tx1_hash = *tx1.hash();
5342 pool.add_transaction(
5343 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5344 0,
5345 TempoHardfork::T1,
5346 )
5347 .unwrap();
5348
5349 let tx2_hash = *tx2.hash();
5350 let result = pool.add_transaction(
5351 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
5352 0,
5353 TempoHardfork::T1,
5354 );
5355 assert!(result.is_err(), "Expected AlreadyImported error");
5356 let err = result.unwrap_err();
5357 assert_eq!(err.hash, tx2_hash);
5358 assert!(
5359 matches!(err.kind, PoolErrorKind::AlreadyImported),
5360 "Expected AlreadyImported, got {:?}",
5361 err.kind
5362 );
5363
5364 let (pending, queued) = pool.pending_and_queued_txn_count();
5365 assert_eq!(pending, 1, "Expected 1 pending transaction");
5366 assert_eq!(queued, 0, "Expected 0 queued transactions");
5367 assert!(pool.by_hash.contains_key(&tx1_hash));
5368 assert_eq!(pool.expiring_nonce_txs.len(), 1);
5369 pool.assert_invariants();
5370 }
5371
5372 #[test]
5375 fn remove_included_expiring_nonce_tx_uses_correct_key() {
5376 let mut pool = AA2dPool::default();
5377 let sender = Address::random();
5378 let fee_token = Address::random();
5379 let calls = vec![Call {
5380 to: TxKind::Call(Address::random()),
5381 value: U256::ZERO,
5382 input: Bytes::new(),
5383 }];
5384
5385 let tx = TempoTransaction {
5386 chain_id: 1,
5387 max_priority_fee_per_gas: 1_000_000_000,
5388 max_fee_per_gas: 2_000_000_000,
5389 gas_limit: 1_000_000,
5390 calls,
5391 nonce_key: U256::MAX,
5392 nonce: 0,
5393 fee_token: Some(fee_token),
5394 fee_payer_signature: Some(Signature::new(U256::from(1), U256::from(2), false)),
5395 valid_before: Some(core::num::NonZeroU64::new(123).unwrap()),
5396 access_list: AccessList::default(),
5397 tempo_authorization_list: Vec::new(),
5398 key_authorization: None,
5399 valid_after: None,
5400 };
5401
5402 let signature =
5403 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
5404 let aa_signed = AASigned::new_unhashed(tx, signature);
5405 let envelope: TempoTxEnvelope = aa_signed.into();
5406 let recovered = Recovered::new_unchecked(envelope, sender);
5407 let pooled = TempoPooledTransaction::new(recovered);
5408
5409 let tx_hash = *pooled.hash();
5410 pool.add_transaction(
5411 Arc::new(wrap_valid_tx(pooled, TransactionOrigin::Local)),
5412 0,
5413 TempoHardfork::T1,
5414 )
5415 .unwrap();
5416
5417 assert_eq!(pool.expiring_nonce_txs.len(), 1);
5418 assert!(pool.by_hash.contains_key(&tx_hash));
5419 pool.assert_invariants();
5420
5421 let removed = pool.remove_transactions(std::iter::once(&tx_hash));
5423 assert_eq!(removed.len(), 1, "should remove the tx by its tx_hash");
5424 assert_eq!(*removed[0].hash(), tx_hash);
5425
5426 assert!(
5428 pool.expiring_nonce_txs.is_empty(),
5429 "expiring_nonce_txs not cleaned up"
5430 );
5431 assert!(
5432 !pool.by_hash.contains_key(&tx_hash),
5433 "by_hash not cleaned up"
5434 );
5435
5436 let (pending, queued) = pool.pending_and_queued_txn_count();
5437 assert_eq!(pending, 0);
5438 assert_eq!(queued, 0);
5439 pool.assert_invariants();
5440 }
5441
5442 fn eviction_test_pool() -> AA2dPool {
5444 AA2dPool::new(AA2dPoolConfig {
5445 pending_limit: SubPoolLimit {
5446 max_txs: 2,
5447 max_size: usize::MAX,
5448 },
5449 queued_limit: SubPoolLimit {
5450 max_txs: 10,
5451 max_size: usize::MAX,
5452 },
5453 ..Default::default()
5454 })
5455 }
5456
5457 #[test]
5458 fn eviction_same_priority_evicts_newer() {
5459 let mut pool = eviction_test_pool();
5461 let sender = Address::random();
5462
5463 let tx1 = TxBuilder::aa(sender)
5464 .nonce_key(U256::from(1))
5465 .nonce(0)
5466 .build();
5467 let tx2 = TxBuilder::aa(sender)
5468 .nonce_key(U256::from(2))
5469 .nonce(0)
5470 .build();
5471 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5472
5473 pool.add_transaction(
5474 Arc::new(wrap_valid_tx(tx1.clone(), TransactionOrigin::Local)),
5475 0,
5476 TempoHardfork::T1,
5477 )
5478 .unwrap();
5479 pool.add_transaction(
5480 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
5481 0,
5482 TempoHardfork::T1,
5483 )
5484 .unwrap();
5485 let result = pool
5486 .add_transaction(
5487 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5488 0,
5489 TempoHardfork::T1,
5490 )
5491 .unwrap();
5492
5493 let AddedTransaction::Pending(pending) = result else {
5494 panic!("expected pending")
5495 };
5496 assert_eq!(pending.discarded[0].hash(), tx_exp.hash());
5497 assert!(pool.contains(tx1.hash()));
5498 assert!(pool.contains(tx2.hash()));
5499 assert!(!pool.contains(tx_exp.hash()));
5500 pool.assert_invariants();
5501
5502 let mut pool = eviction_test_pool();
5504 let sender = Address::random();
5505
5506 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5507 let tx2 = TxBuilder::aa(sender)
5508 .nonce_key(U256::from(1))
5509 .nonce(0)
5510 .build();
5511 let tx3 = TxBuilder::aa(sender)
5512 .nonce_key(U256::from(2))
5513 .nonce(0)
5514 .build();
5515
5516 pool.add_transaction(
5517 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5518 0,
5519 TempoHardfork::T1,
5520 )
5521 .unwrap();
5522 pool.add_transaction(
5523 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
5524 0,
5525 TempoHardfork::T1,
5526 )
5527 .unwrap();
5528 let result = pool
5529 .add_transaction(
5530 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
5531 0,
5532 TempoHardfork::T1,
5533 )
5534 .unwrap();
5535
5536 let AddedTransaction::Pending(pending) = result else {
5537 panic!("expected pending")
5538 };
5539 assert_eq!(pending.discarded[0].hash(), tx3.hash());
5540 assert!(pool.contains(tx_exp.hash()));
5541 assert!(pool.contains(tx2.hash()));
5542 assert!(!pool.contains(tx3.hash()));
5543 pool.assert_invariants();
5544 }
5545
5546 #[test]
5547 fn eviction_lower_priority_expiring_evicted() {
5548 let mut pool = eviction_test_pool();
5549 let sender = Address::random();
5550
5551 let tx_exp = TxBuilder::aa(sender)
5553 .nonce_key(U256::MAX)
5554 .max_priority_fee(100)
5555 .max_fee(200)
5556 .build();
5557 let tx2 = TxBuilder::aa(sender)
5558 .nonce_key(U256::from(1))
5559 .nonce(0)
5560 .build();
5561 let tx3 = TxBuilder::aa(sender)
5562 .nonce_key(U256::from(2))
5563 .nonce(0)
5564 .build();
5565
5566 pool.add_transaction(
5567 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5568 0,
5569 TempoHardfork::T1,
5570 )
5571 .unwrap();
5572 pool.add_transaction(
5573 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
5574 0,
5575 TempoHardfork::T1,
5576 )
5577 .unwrap();
5578 let result = pool
5579 .add_transaction(
5580 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
5581 0,
5582 TempoHardfork::T1,
5583 )
5584 .unwrap();
5585
5586 let AddedTransaction::Pending(pending) = result else {
5588 panic!("expected pending")
5589 };
5590 assert_eq!(pending.discarded[0].hash(), tx_exp.hash());
5591 assert!(!pool.contains(tx_exp.hash()));
5592 assert!(pool.contains(tx3.hash()));
5593 pool.assert_invariants();
5594 }
5595
5596 #[test]
5597 fn eviction_lower_priority_2d_evicted() {
5598 let mut pool = eviction_test_pool();
5599 let sender = Address::random();
5600
5601 let tx_low = TxBuilder::aa(sender)
5603 .nonce_key(U256::from(1))
5604 .nonce(0)
5605 .max_priority_fee(100)
5606 .max_fee(200)
5607 .build();
5608 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5609 let tx3 = TxBuilder::aa(sender)
5610 .nonce_key(U256::from(2))
5611 .nonce(0)
5612 .build();
5613
5614 pool.add_transaction(
5615 Arc::new(wrap_valid_tx(tx_low.clone(), TransactionOrigin::Local)),
5616 0,
5617 TempoHardfork::T1,
5618 )
5619 .unwrap();
5620 pool.add_transaction(
5621 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5622 0,
5623 TempoHardfork::T1,
5624 )
5625 .unwrap();
5626 let result = pool
5627 .add_transaction(
5628 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5629 0,
5630 TempoHardfork::T1,
5631 )
5632 .unwrap();
5633
5634 let AddedTransaction::Pending(pending) = result else {
5636 panic!("expected pending")
5637 };
5638 assert_eq!(pending.discarded[0].hash(), tx_low.hash());
5639 assert!(!pool.contains(tx_low.hash()));
5640 assert!(pool.contains(tx_exp.hash()));
5641 pool.assert_invariants();
5642 }
5643
5644 #[test]
5645 fn expiring_nonce_tx_subject_to_eviction() {
5646 let config = AA2dPoolConfig {
5648 pending_limit: SubPoolLimit {
5649 max_txs: 2,
5650 max_size: usize::MAX,
5651 },
5652 queued_limit: SubPoolLimit {
5653 max_txs: 10,
5654 max_size: usize::MAX,
5655 },
5656 ..Default::default()
5657 };
5658 let mut pool = AA2dPool::new(config);
5659 let sender = Address::random();
5660
5661 for i in 0..3 {
5663 let tx = TxBuilder::aa(sender)
5664 .nonce_key(U256::MAX)
5665 .max_priority_fee(1_000_000_000 + i as u128 * 100_000_000)
5666 .max_fee(2_000_000_000 + i as u128 * 100_000_000)
5667 .build();
5668 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
5669 let _ = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
5670 }
5671
5672 let (pending, queued) = pool.pending_and_queued_txn_count();
5674 assert!(
5675 pending <= 2,
5676 "Should have at most 2 pending transactions due to limit, got {pending}"
5677 );
5678 assert_eq!(queued, 0, "Should have 0 queued transactions");
5679
5680 pool.assert_invariants();
5681 }
5682
5683 #[test]
5684 fn remove_expiring_nonce_tx_decrements_pending_count() {
5685 let mut pool = AA2dPool::default();
5686 let sender = Address::random();
5687
5688 let tx1 = TxBuilder::aa(sender)
5690 .nonce_key(U256::MAX)
5691 .max_priority_fee(1_000_000_000)
5692 .max_fee(2_000_000_000)
5693 .build();
5694 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
5695 let tx1_hash = *valid_tx1.hash();
5696
5697 let tx2 = TxBuilder::aa(sender)
5698 .nonce_key(U256::MAX)
5699 .max_priority_fee(1_100_000_000)
5700 .max_fee(2_200_000_000)
5701 .build();
5702 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
5703
5704 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
5705 .unwrap();
5706 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
5707 .unwrap();
5708
5709 let (pending, _) = pool.pending_and_queued_txn_count();
5711 assert_eq!(pending, 2, "Should have 2 pending transactions");
5712 pool.assert_invariants();
5713
5714 let removed = pool.remove_transactions(std::iter::once(&tx1_hash));
5716 assert_eq!(removed.len(), 1, "Should remove exactly 1 transaction");
5717
5718 let (pending, _) = pool.pending_and_queued_txn_count();
5720 assert_eq!(
5721 pending, 1,
5722 "Should have 1 pending transaction after removal"
5723 );
5724
5725 pool.assert_invariants();
5727 }
5728
5729 #[test]
5730 fn remove_expiring_nonce_tx_by_hash_updates_pending_count() {
5731 let mut pool = AA2dPool::default();
5732 let sender = Address::random();
5733
5734 let tx = TxBuilder::aa(sender)
5735 .nonce_key(U256::MAX)
5736 .max_priority_fee(1_000_000_000)
5737 .max_fee(2_000_000_000)
5738 .build();
5739 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
5740 let tx_hash = *valid_tx.hash();
5741
5742 pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1)
5743 .unwrap();
5744
5745 let (pending, _) = pool.pending_and_queued_txn_count();
5746 assert_eq!(pending, 1);
5747 pool.assert_invariants();
5748
5749 let removed = pool.remove_transactions(std::iter::once(&tx_hash));
5751 assert_eq!(removed.len(), 1);
5752
5753 let (pending, _) = pool.pending_and_queued_txn_count();
5754 assert_eq!(pending, 0);
5755 pool.assert_invariants();
5756 }
5757
5758 #[test]
5759 fn remove_expiring_nonce_tx_by_sender_updates_pending_count() {
5760 let mut pool = AA2dPool::default();
5761 let sender = Address::random();
5762
5763 let tx1 = TxBuilder::aa(sender)
5764 .nonce_key(U256::MAX)
5765 .max_priority_fee(1_000_000_000)
5766 .max_fee(2_000_000_000)
5767 .build();
5768 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
5769
5770 let tx2 = TxBuilder::aa(sender)
5771 .nonce_key(U256::MAX)
5772 .max_priority_fee(1_100_000_000)
5773 .max_fee(2_200_000_000)
5774 .build();
5775 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
5776
5777 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
5778 .unwrap();
5779 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
5780 .unwrap();
5781
5782 let (pending, _) = pool.pending_and_queued_txn_count();
5783 assert_eq!(pending, 2);
5784 pool.assert_invariants();
5785
5786 let removed = pool.remove_transactions_by_sender(sender);
5788 assert_eq!(removed.len(), 2);
5789
5790 let (pending, _) = pool.pending_and_queued_txn_count();
5791 assert_eq!(pending, 0);
5792 pool.assert_invariants();
5793 }
5794
5795 #[test]
5796 fn test_rejected_2d_tx_does_not_leak_slot_entries() {
5797 let config = AA2dPoolConfig {
5798 price_bump_config: PriceBumpConfig::default(),
5799 pending_limit: SubPoolLimit {
5800 max_txs: 1000,
5801 max_size: usize::MAX,
5802 },
5803 queued_limit: SubPoolLimit {
5804 max_txs: 1000,
5805 max_size: usize::MAX,
5806 },
5807 max_txs_per_sender: 1,
5808 };
5809 let mut pool = AA2dPool::new(config);
5810 let sender = Address::random();
5811
5812 let tx0 = TxBuilder::aa(sender)
5813 .nonce_key(U256::from(1))
5814 .nonce(0)
5815 .build();
5816 pool.add_transaction(
5817 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
5818 0,
5819 TempoHardfork::T1,
5820 )
5821 .unwrap();
5822
5823 assert_eq!(pool.slot_to_seq_id.len(), 1);
5824
5825 for i in 2..12u64 {
5826 let tx = TxBuilder::aa(sender)
5827 .nonce_key(U256::from(i))
5828 .nonce(0)
5829 .build();
5830 let result = pool.add_transaction(
5831 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5832 0,
5833 TempoHardfork::T1,
5834 );
5835 assert!(
5836 result.is_err(),
5837 "tx with nonce_key {i} should be rejected by sender limit"
5838 );
5839 }
5840
5841 assert_eq!(
5842 pool.slot_to_seq_id.len(),
5843 1,
5844 "rejected txs with new nonce keys should not grow slot_to_seq_id"
5845 );
5846 pool.assert_invariants();
5847 }
5848
5849 #[test_case::test_case(false ; "live updates")]
5850 #[test_case::test_case(true ; "no updates")]
5851 fn best_transactions_live_new_tx(no_updates: bool) {
5852 let mut pool = AA2dPool::default();
5853 let sender = Address::random();
5854
5855 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
5857 let tx0_hash = *tx0.hash();
5858 pool.add_transaction(
5859 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
5860 0,
5861 TempoHardfork::T1,
5862 )
5863 .unwrap();
5864
5865 let mut best = pool.best_transactions();
5866 if no_updates {
5867 best.no_updates();
5868 }
5869
5870 let sender2 = Address::random();
5872 let tx1 = TxBuilder::aa(sender2).nonce_key(U256::ZERO).build();
5873 let tx1_hash = *tx1.hash();
5874 pool.add_transaction(
5875 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5876 0,
5877 TempoHardfork::T1,
5878 )
5879 .unwrap();
5880
5881 let mut yielded = HashSet::new();
5882 for tx in best {
5883 yielded.insert(*tx.hash());
5884 }
5885
5886 assert!(
5887 yielded.contains(&tx0_hash),
5888 "should always yield pre-existing tx"
5889 );
5890 assert_eq!(
5891 yielded.contains(&tx1_hash),
5892 !no_updates,
5893 "new tx should only be yielded when live updates are enabled"
5894 );
5895 }
5896
5897 #[test]
5898 fn best_transactions_live_promoted() {
5899 let mut pool = AA2dPool::default();
5900 let sender = Address::random();
5901
5902 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
5904 let tx1_hash = *tx1.hash();
5905 pool.add_transaction(
5906 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5907 0,
5908 TempoHardfork::T1,
5909 )
5910 .unwrap();
5911
5912 let mut best = pool.best_transactions();
5914 assert!(best.next().is_none(), "no pending txs yet");
5915
5916 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
5918 let tx0_hash = *tx0.hash();
5919 pool.add_transaction(
5920 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
5921 0,
5922 TempoHardfork::T1,
5923 )
5924 .unwrap();
5925
5926 let mut yielded = HashSet::new();
5927 for tx in best {
5928 yielded.insert(*tx.hash());
5929 }
5930
5931 assert_eq!(yielded.len(), 2, "should yield both tx0 and promoted tx1");
5932 assert!(yielded.contains(&tx0_hash));
5933 assert!(yielded.contains(&tx1_hash));
5934 }
5935
5936 #[test]
5937 fn best_transactions_live_gapped_unblock_higher_fee_not_promoted() {
5938 let mut pool = AA2dPool::default();
5942
5943 let sender_low = Address::random();
5944 let sender_gapped = Address::random();
5945
5946 let tx_low = TxBuilder::aa(sender_low)
5949 .nonce_key(U256::ZERO)
5950 .max_priority_fee(1_000_000_000)
5951 .max_fee(30_000_000_000)
5952 .build();
5953 pool.add_transaction(
5954 Arc::new(wrap_valid_tx(tx_low, TransactionOrigin::Local)),
5955 0,
5956 TempoHardfork::T1,
5957 )
5958 .unwrap();
5959
5960 let tx_n1 = TxBuilder::aa(sender_gapped)
5962 .nonce_key(U256::ZERO)
5963 .nonce(1)
5964 .max_priority_fee(2_000_000_000)
5965 .max_fee(30_000_000_000)
5966 .build();
5967 let tx_n1_hash = *tx_n1.hash();
5968 pool.add_transaction(
5969 Arc::new(wrap_valid_tx(tx_n1, TransactionOrigin::Local)),
5970 0,
5971 TempoHardfork::T1,
5972 )
5973 .unwrap();
5974
5975 let mut best = pool.best_transactions();
5977 let first = best.next();
5978 assert!(first.is_some(), "should yield the low-priority tx");
5979
5980 let tx_n0 = TxBuilder::aa(sender_gapped)
5982 .nonce_key(U256::ZERO)
5983 .nonce(0)
5984 .max_priority_fee(2_000_000_000)
5985 .max_fee(30_000_000_000)
5986 .build();
5987 let tx_n0_hash = *tx_n0.hash();
5988 pool.add_transaction(
5989 Arc::new(wrap_valid_tx(tx_n0, TransactionOrigin::Local)),
5990 0,
5991 TempoHardfork::T1,
5992 )
5993 .unwrap();
5994
5995 let remaining: Vec<_> = best.map(|tx| *tx.hash()).collect();
5998 assert!(
5999 !remaining.contains(&tx_n0_hash),
6000 "gap-filler with higher fee must not be yielded"
6001 );
6002 assert!(
6003 !remaining.contains(&tx_n1_hash),
6004 "gapped tx must not be promoted when gap-filler is stashed"
6005 );
6006 }
6007
6008 #[test]
6009 fn best_transactions_live_expiring_nonce() {
6010 let mut pool = AA2dPool::default();
6011
6012 let mut best = pool.best_transactions();
6013
6014 let sender = Address::random();
6016 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
6017 let tx_hash = *tx.hash();
6018 pool.add_transaction(
6019 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
6020 0,
6021 TempoHardfork::T1,
6022 )
6023 .unwrap();
6024
6025 let first = best.next();
6026 assert!(first.is_some(), "should yield the expiring nonce tx");
6027 assert_eq!(*first.unwrap().hash(), tx_hash);
6028 assert!(best.next().is_none());
6029 }
6030}