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;
31
32type TxOrdering = CoinbaseTipOrdering<TempoPooledTransaction>;
33#[derive(Debug)]
45pub struct AA2dPool {
46 submission_id: u64,
50 independent_transactions: HashMap<AASequenceId, PendingTransaction<TxOrdering>>,
52 by_id: BTreeMap<AA2dTransactionId, Arc<AA2dInternalTransaction>>,
54 by_hash: HashMap<TxHash, Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
56 expiring_nonce_txs: HashMap<B256, PendingTransaction<TxOrdering>>,
59 slot_to_seq_id: U256Map<AASequenceId>,
67 seq_id_to_slot: HashMap<AASequenceId, U256>,
69 config: AA2dPoolConfig,
71 metrics: AA2dPoolMetrics,
73 by_eviction_order: BTreeSet<EvictionKey>,
79 txs_by_sender: AddressMap<usize>,
84}
85
86impl Default for AA2dPool {
87 fn default() -> Self {
88 Self::new(AA2dPoolConfig::default())
89 }
90}
91
92impl AA2dPool {
93 fn expiring_nonce_hash(
94 transaction: &Arc<ValidPoolTransaction<TempoPooledTransaction>>,
95 ) -> B256 {
96 transaction
97 .transaction
98 .expiring_nonce_hash()
99 .expect("expiring nonce tx must be AA")
100 }
101
102 pub fn new(config: AA2dPoolConfig) -> Self {
104 Self {
105 submission_id: 0,
106 independent_transactions: Default::default(),
107 by_id: Default::default(),
108 by_hash: Default::default(),
109 expiring_nonce_txs: Default::default(),
110 slot_to_seq_id: Default::default(),
111 seq_id_to_slot: Default::default(),
112 config,
113 metrics: AA2dPoolMetrics::default(),
114 by_eviction_order: Default::default(),
115 txs_by_sender: Default::default(),
116 }
117 }
118
119 fn update_metrics(&self) {
121 let (pending, queued) = self.pending_and_queued_txn_count();
122 let total = self.by_id.len() + self.expiring_nonce_txs.len();
123 self.metrics.set_transaction_counts(total, pending, queued);
124 }
125
126 pub(crate) fn add_transaction(
136 &mut self,
137 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
138 on_chain_nonce: u64,
139 hardfork: tempo_chainspec::hardfork::TempoHardfork,
140 ) -> PoolResult<AddedTransaction<TempoPooledTransaction>> {
141 debug_assert!(
142 transaction.transaction.is_aa(),
143 "only AA transactions are supported"
144 );
145 if self.contains(transaction.hash()) {
146 return Err(PoolError::new(
147 *transaction.hash(),
148 PoolErrorKind::AlreadyImported,
149 ));
150 }
151
152 if hardfork.is_t1() && transaction.transaction.is_expiring_nonce() {
155 return self.add_expiring_nonce_transaction(transaction, hardfork);
156 }
157
158 let tx_id = transaction
159 .transaction
160 .aa_transaction_id()
161 .expect("Transaction added to AA2D pool must be an AA transaction");
162
163 if transaction.nonce() < on_chain_nonce {
164 return Err(PoolError::new(
166 *transaction.hash(),
167 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
168 InvalidTransactionError::NonceNotConsistent {
169 tx: transaction.nonce(),
170 state: on_chain_nonce,
171 },
172 )),
173 ));
174 }
175
176 let tx = Arc::new(AA2dInternalTransaction {
178 inner: PendingTransaction {
179 submission_id: self.next_id(),
180 priority: CoinbaseTipOrdering::default()
181 .priority(&transaction.transaction, hardfork.base_fee()),
182 transaction: transaction.clone(),
183 },
184 is_pending: AtomicBool::new(false),
185 });
186
187 let sender = transaction.sender();
190 let replaced = match self.by_id.entry(tx_id) {
191 Entry::Occupied(mut entry) => {
192 if entry
194 .get()
195 .inner
196 .transaction
197 .is_underpriced(&tx.inner.transaction, &self.config.price_bump_config)
198 {
199 return Err(PoolError::new(
200 *transaction.hash(),
201 PoolErrorKind::ReplacementUnderpriced,
202 ));
203 }
204
205 Some(entry.insert(Arc::clone(&tx)))
206 }
207 Entry::Vacant(entry) => {
208 let sender_count = self.txs_by_sender.get(&sender).copied().unwrap_or(0);
210 if sender_count >= self.config.max_txs_per_sender {
211 return Err(PoolError::new(
212 *transaction.hash(),
213 PoolErrorKind::SpammerExceededCapacity(sender),
214 ));
215 }
216
217 entry.insert(Arc::clone(&tx));
218 *self.txs_by_sender.entry(sender).or_insert(0) += 1;
220 None
221 }
222 };
223
224 if transaction.transaction.is_aa_2d() {
228 self.record_2d_slot(&transaction.transaction);
229 }
230
231 if let Some(replaced) = &replaced {
233 self.by_hash.remove(replaced.inner.transaction.hash());
236 let replaced_key = EvictionKey::new(Arc::clone(replaced), tx_id);
238 self.by_eviction_order.remove(&replaced_key);
239 }
240
241 self.by_hash
243 .insert(*tx.inner.transaction.hash(), tx.inner.transaction.clone());
244
245 let mut promoted = Vec::new();
247 let mut inserted_as_pending = false;
249 let on_chain_id = AA2dTransactionId::new(tx_id.seq_id, on_chain_nonce);
251 let mut next_nonce = on_chain_id.nonce;
253
254 for (existing_id, existing_tx) in self.descendant_txs(&on_chain_id) {
257 if existing_id.nonce == next_nonce {
258 match existing_id.nonce.cmp(&tx_id.nonce) {
259 std::cmp::Ordering::Less => {
260 }
262 std::cmp::Ordering::Equal => {
263 existing_tx.set_pending(true);
264 inserted_as_pending = true;
265 }
266 std::cmp::Ordering::Greater => {
267 let was_pending = existing_tx.set_pending(true);
269 if !was_pending {
270 promoted.push(existing_tx.inner.transaction.clone());
271 }
272 }
273 }
274 next_nonce = existing_id.nonce.saturating_add(1);
276 } else {
277 break;
279 }
280 }
281
282 self.metrics.inc_inserted();
284
285 let new_tx_eviction_key = EvictionKey::new(Arc::clone(&tx), tx_id);
287 self.by_eviction_order.insert(new_tx_eviction_key);
288
289 if inserted_as_pending {
290 if !promoted.is_empty() {
291 self.metrics.inc_promoted(promoted.len());
292 }
293 if tx_id.nonce == on_chain_nonce {
295 self.independent_transactions
296 .insert(tx_id.seq_id, tx.inner.clone());
297 }
298
299 return Ok(AddedTransaction::Pending(AddedPendingTransaction {
300 transaction,
301 replaced: replaced.map(|tx| tx.inner.transaction.clone()),
302 promoted,
303 discarded: self.discard(),
304 }));
305 }
306
307 let _ = self.discard();
309
310 Ok(AddedTransaction::Parked {
311 transaction,
312 replaced: replaced.map(|tx| tx.inner.transaction.clone()),
313 subpool: SubPool::Queued,
314 queued_reason: Some(QueuedReason::NonceGap),
315 })
316 }
317
318 fn add_expiring_nonce_transaction(
324 &mut self,
325 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
326 hardfork: TempoHardfork,
327 ) -> PoolResult<AddedTransaction<TempoPooledTransaction>> {
328 let tx_hash = *transaction.hash();
329 let expiring_nonce_hash = Self::expiring_nonce_hash(&transaction);
330
331 if self.expiring_nonce_txs.contains_key(&expiring_nonce_hash) {
333 return Err(PoolError::new(tx_hash, PoolErrorKind::AlreadyImported));
334 }
335
336 let sender = transaction.sender();
338 let sender_count = self.txs_by_sender.get(&sender).copied().unwrap_or(0);
339 if sender_count >= self.config.max_txs_per_sender {
340 return Err(PoolError::new(
341 tx_hash,
342 PoolErrorKind::SpammerExceededCapacity(sender),
343 ));
344 }
345
346 let pending_tx = PendingTransaction {
348 submission_id: self.next_id(),
349 priority: CoinbaseTipOrdering::default()
350 .priority(&transaction.transaction, hardfork.base_fee()),
351 transaction: transaction.clone(),
352 };
353
354 self.expiring_nonce_txs
356 .insert(expiring_nonce_hash, pending_tx);
357 self.by_hash.insert(tx_hash, transaction.clone());
358
359 *self.txs_by_sender.entry(sender).or_insert(0) += 1;
361
362 trace!(target: "txpool", hash = %tx_hash, "Added expiring nonce transaction");
363
364 self.update_metrics();
365
366 Ok(AddedTransaction::Pending(AddedPendingTransaction {
368 transaction,
369 replaced: None,
370 promoted: vec![],
371 discarded: self.discard(),
372 }))
373 }
374
375 pub(crate) fn pending_and_queued_txn_count(&self) -> (usize, usize) {
377 let (pending_2d, queued_2d) = self.by_id.values().fold((0, 0), |mut acc, tx| {
378 if tx.is_pending() {
379 acc.0 += 1;
380 } else {
381 acc.1 += 1;
382 }
383 acc
384 });
385 let expiring_pending = self.expiring_nonce_txs.len();
387 (pending_2d + expiring_pending, queued_2d)
388 }
389
390 pub(crate) fn get_transactions_by_origin_iter(
392 &self,
393 origin: TransactionOrigin,
394 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
395 let regular = self
396 .by_id
397 .values()
398 .filter(move |tx| tx.inner.transaction.origin == origin)
399 .map(|tx| tx.inner.transaction.clone());
400 let expiring = self
401 .expiring_nonce_txs
402 .values()
403 .filter(move |tx| tx.transaction.origin == origin)
404 .map(|tx| tx.transaction.clone());
405 regular.chain(expiring)
406 }
407
408 pub(crate) fn get_pending_transactions_by_origin_iter(
410 &self,
411 origin: TransactionOrigin,
412 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
413 let regular = self
414 .by_id
415 .values()
416 .filter(move |tx| tx.is_pending() && tx.inner.transaction.origin == origin)
417 .map(|tx| tx.inner.transaction.clone());
418 let expiring = self
420 .expiring_nonce_txs
421 .values()
422 .filter(move |tx| tx.transaction.origin == origin)
423 .map(|tx| tx.transaction.clone());
424 regular.chain(expiring)
425 }
426
427 pub(crate) fn get_transactions_by_sender_iter(
429 &self,
430 sender: Address,
431 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
432 let regular = self
433 .by_id
434 .values()
435 .filter(move |tx| tx.inner.transaction.sender() == sender)
436 .map(|tx| tx.inner.transaction.clone());
437 let expiring = self
438 .expiring_nonce_txs
439 .values()
440 .filter(move |tx| tx.transaction.sender() == sender)
441 .map(|tx| tx.transaction.clone());
442 regular.chain(expiring)
443 }
444
445 pub(crate) fn all_transaction_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
447 self.by_hash.keys().copied()
448 }
449
450 pub(crate) fn queued_transactions(
452 &self,
453 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
454 self.by_id
455 .values()
456 .filter(|tx| !tx.is_pending())
457 .map(|tx| tx.inner.transaction.clone())
458 }
459
460 pub(crate) fn pending_transactions(
462 &self,
463 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
464 let regular_pending = self
466 .by_id
467 .values()
468 .filter(|tx| tx.is_pending())
469 .map(|tx| tx.inner.transaction.clone());
470 let expiring_pending = self
471 .expiring_nonce_txs
472 .values()
473 .map(|tx| tx.transaction.clone());
474 regular_pending.chain(expiring_pending)
475 }
476
477 #[allow(clippy::mutable_key_type)]
479 pub(crate) fn best_transactions(&self) -> BestAA2dTransactions {
480 let mut independent: BTreeSet<_> =
482 self.independent_transactions.values().cloned().collect();
483 independent.extend(self.expiring_nonce_txs.values().cloned());
485
486 BestAA2dTransactions {
487 independent,
488 by_id: self
489 .by_id
490 .iter()
491 .filter(|(_, tx)| tx.is_pending())
492 .map(|(id, tx)| (*id, tx.inner.clone()))
493 .collect(),
494 invalid: Default::default(),
495 }
496 }
497
498 pub(crate) fn get(
500 &self,
501 tx_hash: &TxHash,
502 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
503 self.by_hash.get(tx_hash).cloned()
504 }
505
506 pub(crate) fn get_all<'a, I>(
508 &self,
509 tx_hashes: I,
510 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
511 where
512 I: Iterator<Item = &'a TxHash> + 'a,
513 {
514 let mut ret = Vec::new();
515 for tx_hash in tx_hashes {
516 if let Some(tx) = self.get(tx_hash) {
517 ret.push(tx);
518 }
519 }
520 ret
521 }
522
523 pub(crate) fn append_pooled_transaction_elements<'a>(
530 &self,
531 tx_hashes: impl IntoIterator<Item = &'a TxHash>,
532 limit: GetPooledTransactionLimit,
533 accumulated_size: &mut usize,
534 out: &mut Vec<<TempoPooledTransaction as PoolTransaction>::Pooled>,
535 ) {
536 for tx_hash in tx_hashes {
537 let Some(tx) = self.by_hash.get(tx_hash) else {
538 continue;
539 };
540
541 let encoded_len = tx.transaction.encoded_length();
542 let Some(pooled) = tx.transaction.clone_into_pooled().ok() else {
543 continue;
544 };
545
546 *accumulated_size += encoded_len;
547 out.push(pooled.into_inner());
548
549 if limit.exceeds(*accumulated_size) {
550 break;
551 }
552 }
553 }
554
555 pub(crate) fn senders_iter(&self) -> impl Iterator<Item = &Address> {
557 let regular = self
558 .by_id
559 .values()
560 .map(|tx| tx.inner.transaction.sender_ref());
561 let expiring = self
562 .expiring_nonce_txs
563 .values()
564 .map(|tx| tx.transaction.sender_ref());
565 regular.chain(expiring)
566 }
567
568 fn descendant_txs<'a, 'b: 'a>(
573 &'a self,
574 id: &'b AA2dTransactionId,
575 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a Arc<AA2dInternalTransaction>)> + 'a {
576 self.by_id
577 .range(id..)
578 .take_while(|(other, _)| id.seq_id == other.seq_id)
579 }
580
581 fn descendant_txs_exclusive<'a, 'b: 'a>(
585 &'a self,
586 id: &'b AA2dTransactionId,
587 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a Arc<AA2dInternalTransaction>)> + 'a {
588 self.by_id
589 .range((Excluded(id), Unbounded))
590 .take_while(|(other, _)| id.seq_id == other.seq_id)
591 }
592
593 fn remove_transaction_by_id(
597 &mut self,
598 id: &AA2dTransactionId,
599 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
600 let tx = self.by_id.remove(id)?;
601
602 let eviction_key = EvictionKey::new(Arc::clone(&tx), *id);
604 self.by_eviction_order.remove(&eviction_key);
605
606 if self.by_id.range(id.seq_id.range()).next().is_none()
608 && let Some(slot) = self.seq_id_to_slot.remove(&id.seq_id)
609 {
610 self.slot_to_seq_id.remove(&slot);
611 }
612
613 self.remove_independent(id);
614 let removed_tx = tx.inner.transaction.clone();
615 self.by_hash.remove(removed_tx.hash());
616
617 self.decrement_sender_count(removed_tx.sender());
619
620 Some(removed_tx)
621 }
622
623 fn decrement_sender_count(&mut self, sender: Address) {
625 if let hash_map::Entry::Occupied(mut entry) = self.txs_by_sender.entry(sender) {
626 let count = entry.get_mut();
627 *count -= 1;
628 if *count == 0 {
629 entry.remove();
630 }
631 }
632 }
633
634 fn remove_independent(
636 &mut self,
637 id: &AA2dTransactionId,
638 ) -> Option<PendingTransaction<TxOrdering>> {
639 match self.independent_transactions.entry(id.seq_id) {
641 hash_map::Entry::Occupied(entry) => {
642 if entry.get().transaction.nonce() == id.nonce {
644 return Some(entry.remove());
645 }
646 }
647 hash_map::Entry::Vacant(_) => {}
648 };
649 None
650 }
651
652 pub(crate) fn remove_transactions<'a, I>(
657 &mut self,
658 tx_hashes: I,
659 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
660 where
661 I: Iterator<Item = &'a TxHash> + 'a,
662 {
663 let mut txs = Vec::new();
664 let mut seq_ids_to_demote: HashMap<AASequenceId, u64> = HashMap::default();
665
666 for tx_hash in tx_hashes {
667 if let Some((tx, seq_id)) = self.remove_transaction_by_hash_no_demote(tx_hash) {
668 if let Some(id) = seq_id {
669 seq_ids_to_demote
670 .entry(id.seq_id)
671 .and_modify(|min_nonce| {
672 if id.nonce < *min_nonce {
673 *min_nonce = id.nonce;
674 }
675 })
676 .or_insert(id.nonce);
677 }
678 txs.push(tx);
679 }
680 }
681
682 for (seq_id, min_nonce) in seq_ids_to_demote {
684 self.demote_from_nonce(&seq_id, min_nonce);
685 }
686
687 txs
688 }
689
690 fn remove_transaction_by_hash(
695 &mut self,
696 tx_hash: &B256,
697 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
698 let (tx, id) = self.remove_transaction_by_hash_no_demote(tx_hash)?;
699
700 if let Some(id) = id {
702 self.demote_descendants(&id);
703 }
704
705 Some(tx)
706 }
707
708 fn remove_transaction_by_hash_no_demote(
712 &mut self,
713 tx_hash: &B256,
714 ) -> Option<(
715 Arc<ValidPoolTransaction<TempoPooledTransaction>>,
716 Option<AA2dTransactionId>,
717 )> {
718 let tx = self.by_hash.remove(tx_hash)?;
719
720 if tx.transaction.is_expiring_nonce() {
722 self.expiring_nonce_txs
723 .remove(&Self::expiring_nonce_hash(&tx));
724 self.decrement_sender_count(tx.sender());
726 return Some((tx, None));
727 }
728
729 let id = tx
731 .transaction
732 .aa_transaction_id()
733 .expect("is AA transaction");
734 self.remove_transaction_by_id(&id)?;
735
736 Some((tx, Some(id)))
737 }
738
739 fn demote_descendants(&mut self, id: &AA2dTransactionId) {
744 self.demote_from_nonce(&id.seq_id, id.nonce);
745 }
746
747 fn demote_from_nonce(&self, seq_id: &AASequenceId, min_nonce: u64) {
752 let start_id = AA2dTransactionId::new(*seq_id, min_nonce);
753 for (_, tx) in self
754 .by_id
755 .range((Excluded(&start_id), Unbounded))
756 .take_while(|(other, _)| *seq_id == other.seq_id)
757 {
758 tx.set_pending(false);
759 }
760 }
761
762 pub(crate) fn remove_transactions_and_descendants<'a, I>(
765 &mut self,
766 hashes: I,
767 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
768 where
769 I: Iterator<Item = &'a TxHash> + 'a,
770 {
771 let mut removed = Vec::new();
772 for hash in hashes {
773 if let Some(tx) = self.remove_transaction_by_hash(hash) {
774 let id = tx.transaction.aa_transaction_id();
775 removed.push(tx);
776 if let Some(id) = id {
777 self.remove_descendants(&id, &mut removed);
778 }
779 }
780 }
781 removed
782 }
783
784 pub(crate) fn remove_transactions_by_sender(
786 &mut self,
787 sender_id: Address,
788 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
789 let mut removed = Vec::new();
790 let txs = self
791 .get_transactions_by_sender_iter(sender_id)
792 .collect::<Vec<_>>();
793 for tx in txs {
794 if tx.transaction.is_expiring_nonce() {
796 if self
797 .expiring_nonce_txs
798 .remove(&Self::expiring_nonce_hash(&tx))
799 .is_some()
800 {
801 let hash = *tx.hash();
802 self.by_hash.remove(&hash);
803 self.decrement_sender_count(tx.sender());
804 removed.push(tx);
805 }
806 } else if let Some(tx) = tx
807 .transaction
808 .aa_transaction_id()
809 .and_then(|id| self.remove_transaction_by_id(&id))
810 {
811 removed.push(tx);
812 }
813 }
814 removed
815 }
816
817 fn remove_descendants(
821 &mut self,
822 tx: &AA2dTransactionId,
823 removed: &mut Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
824 ) {
825 let mut id = *tx;
826
827 loop {
829 let descendant = self.descendant_txs_exclusive(&id).map(|(id, _)| *id).next();
830 if let Some(descendant) = descendant {
831 if let Some(tx) = self.remove_transaction_by_id(&descendant) {
832 removed.push(tx)
833 }
834 id = descendant;
835 } else {
836 return;
837 }
838 }
839 }
840
841 #[allow(clippy::type_complexity)]
847 pub(crate) fn on_nonce_changes(
848 &mut self,
849 on_chain_ids: HashMap<AASequenceId, u64>,
850 ) -> (
851 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
852 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
853 ) {
854 trace!(target: "txpool::2d", ?on_chain_ids, "processing nonce changes");
855
856 let mut promoted = Vec::new();
857 let mut mined_ids = Vec::new();
858
859 'changes: for (sender_id, on_chain_nonce) in on_chain_ids {
861 let mut iter = self
862 .by_id
863 .range_mut((sender_id.start_bound(), Unbounded))
864 .take_while(move |(other, _)| sender_id == other.seq_id)
865 .peekable();
866
867 let Some(mut current) = iter.next() else {
868 continue;
869 };
870
871 'mined: loop {
873 if current.0.nonce < on_chain_nonce {
874 mined_ids.push(*current.0);
875 let Some(next) = iter.next() else {
876 continue 'changes;
877 };
878 current = next;
879 } else {
880 break 'mined;
881 }
882 }
883
884 let mut next_nonce = on_chain_nonce;
886 for (existing_id, existing_tx) in std::iter::once(current).chain(iter) {
887 if existing_id.nonce == next_nonce {
888 let was_pending = existing_tx.set_pending(true);
890 if !was_pending {
891 promoted.push(existing_tx.inner.transaction.clone());
892 }
893
894 if existing_id.nonce == on_chain_nonce {
895 self.independent_transactions
897 .insert(existing_id.seq_id, existing_tx.inner.clone());
898 }
899
900 next_nonce = next_nonce.saturating_add(1);
901 } else {
902 existing_tx.set_pending(false);
904 }
905 }
906
907 if next_nonce == on_chain_nonce {
911 self.independent_transactions.remove(&sender_id);
912 }
913 }
914
915 let mut mined = Vec::with_capacity(mined_ids.len());
917 for id in mined_ids {
918 if let Some(removed) = self.remove_transaction_by_id(&id) {
919 mined.push(removed);
920 }
921 }
922
923 if !promoted.is_empty() {
925 self.metrics.inc_promoted(promoted.len());
926 }
927 if !mined.is_empty() {
928 self.metrics.inc_removed(mined.len());
929 }
930 self.update_metrics();
931
932 (promoted, mined)
933 }
934
935 fn discard(&mut self) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
947 let mut removed = Vec::new();
948
949 let (pending_count, queued_count) = self.pending_and_queued_txn_count();
951
952 if queued_count > self.config.queued_limit.max_txs {
954 let queued_excess = queued_count - self.config.queued_limit.max_txs;
955 removed.extend(self.evict_lowest_priority(queued_excess, false));
956 }
957
958 if pending_count > self.config.pending_limit.max_txs {
960 let pending_excess = pending_count - self.config.pending_limit.max_txs;
961 removed.extend(self.evict_lowest_priority(pending_excess, true));
962 }
963
964 if !removed.is_empty() {
965 self.metrics.inc_removed(removed.len());
966 }
967
968 removed
969 }
970
971 fn evict_lowest_priority(
977 &mut self,
978 count: usize,
979 evict_pending: bool,
980 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
981 if count == 0 {
982 return vec![];
983 }
984
985 let mut removed = Vec::with_capacity(count);
986
987 if evict_pending {
988 for _ in 0..count {
990 if let Some(tx) = self.evict_one_pending() {
991 removed.push(tx);
992 } else {
993 break;
994 }
995 }
996 } else {
997 let to_remove: Vec<_> = self
999 .by_eviction_order
1000 .iter()
1001 .filter(|key| !key.is_pending())
1002 .map(|key| key.tx_id)
1003 .take(count)
1004 .collect();
1005
1006 for id in to_remove {
1007 if let Some(tx) = self.remove_transaction_by_id(&id) {
1008 removed.push(tx);
1009 }
1010 }
1011 }
1012
1013 removed
1014 }
1015
1016 fn evict_one_pending(&mut self) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1019 let worst_2d = self
1020 .by_eviction_order
1021 .iter()
1022 .find(|key| key.is_pending())
1023 .map(|key| (key.tx_id, key.priority().clone(), key.submission_id()));
1024
1025 let worst_expiring = self
1026 .expiring_nonce_txs
1027 .iter()
1028 .min_by(|a, b| {
1029 a.1.priority
1030 .cmp(&b.1.priority)
1031 .then_with(|| b.1.submission_id.cmp(&a.1.submission_id))
1032 })
1033 .map(|(hash, tx)| (*hash, tx.priority.clone(), tx.submission_id));
1034
1035 match (worst_2d, worst_expiring) {
1036 (Some((id, pri_2d, sid_2d)), Some((hash, pri_exp, sid_exp))) => {
1037 let evict_expiring = pri_exp
1039 .cmp(&pri_2d)
1040 .then_with(|| sid_2d.cmp(&sid_exp))
1041 .is_le();
1042 if evict_expiring {
1043 self.evict_expiring_nonce_tx(&hash)
1044 } else {
1045 self.evict_2d_pending_tx(&id)
1046 }
1047 }
1048 (Some((id, ..)), None) => self.evict_2d_pending_tx(&id),
1049 (None, Some((hash, ..))) => self.evict_expiring_nonce_tx(&hash),
1050 (None, None) => None,
1051 }
1052 }
1053
1054 fn evict_2d_pending_tx(
1056 &mut self,
1057 id: &AA2dTransactionId,
1058 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1059 let tx = self.remove_transaction_by_id(id)?;
1060 self.demote_descendants(id);
1061 Some(tx)
1062 }
1063
1064 fn evict_expiring_nonce_tx(
1066 &mut self,
1067 expiring_hash: &B256,
1068 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1069 let pending_tx = self.expiring_nonce_txs.remove(expiring_hash)?;
1070 let tx_hash = *pending_tx.transaction.hash();
1071 self.by_hash.remove(&tx_hash);
1072 self.decrement_sender_count(pending_tx.transaction.sender());
1073 Some(pending_tx.transaction)
1074 }
1075
1076 pub fn metrics(&self) -> &AA2dPoolMetrics {
1078 &self.metrics
1079 }
1080
1081 pub(crate) fn remove_included_expiring_nonce_txs<'a>(
1086 &mut self,
1087 tx_hashes: impl Iterator<Item = &'a TxHash>,
1088 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1089 let mut removed = Vec::new();
1090 for tx_hash in tx_hashes {
1091 let Some(tx) = self.by_hash.get(tx_hash).cloned() else {
1092 continue;
1093 };
1094 if !tx.transaction.is_expiring_nonce() {
1095 continue;
1096 }
1097 if let Some(pending_tx) = self
1098 .expiring_nonce_txs
1099 .remove(&Self::expiring_nonce_hash(&tx))
1100 {
1101 self.by_hash.remove(tx_hash);
1102 self.decrement_sender_count(pending_tx.transaction.sender());
1103 removed.push(pending_tx.transaction);
1104 }
1105 }
1106 if !removed.is_empty() {
1107 self.metrics.inc_removed(removed.len());
1108 self.update_metrics();
1109 }
1110 removed
1111 }
1112
1113 pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
1115 self.by_hash.contains_key(tx_hash)
1116 }
1117
1118 pub(crate) fn pooled_transactions_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
1120 self.by_hash
1121 .values()
1122 .filter(|tx| tx.propagate)
1123 .map(|tx| *tx.hash())
1124 }
1125
1126 pub(crate) fn pooled_transactions_iter(
1128 &self,
1129 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1130 self.by_hash.values().filter(|tx| tx.propagate).cloned()
1131 }
1132
1133 const fn next_id(&mut self) -> u64 {
1134 let id = self.submission_id;
1135 self.submission_id = self.submission_id.wrapping_add(1);
1136 id
1137 }
1138
1139 fn record_2d_slot(&mut self, transaction: &TempoPooledTransaction) {
1141 let address = transaction.sender();
1142 let nonce_key = transaction.nonce_key().unwrap_or_default();
1143 let Some(slot) = transaction.nonce_key_slot() else {
1144 return;
1145 };
1146
1147 trace!(target: "txpool::2d", ?address, ?nonce_key, "recording 2d nonce slot");
1148 let seq_id = AASequenceId::new(address, nonce_key);
1149
1150 if self.slot_to_seq_id.insert(slot, seq_id).is_none() {
1151 self.metrics.inc_nonce_key_count(1);
1152 self.seq_id_to_slot.insert(seq_id, slot);
1153 }
1154 }
1155
1156 #[expect(clippy::type_complexity)]
1158 pub(crate) fn on_state_updates(
1159 &mut self,
1160 state: &AddressMap<BundleAccount>,
1161 ) -> (
1162 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
1163 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
1164 ) {
1165 let mut changes = HashMap::default();
1166
1167 for (account, state) in state {
1168 if account == &NONCE_PRECOMPILE_ADDRESS {
1169 for (slot, value) in state.storage.iter() {
1171 if let Some(seq_id) = self.slot_to_seq_id.get(slot) {
1172 changes.insert(*seq_id, value.present_value.saturating_to());
1173 }
1174 }
1175 }
1176 let nonce = state
1177 .account_info()
1178 .map(|info| info.nonce)
1179 .unwrap_or_default();
1180 changes.insert(AASequenceId::new(*account, U256::ZERO), nonce);
1181 }
1182
1183 self.on_nonce_changes(changes)
1184 }
1185
1186 #[cfg(test)]
1188 pub(crate) fn assert_invariants(&self) {
1189 assert!(
1191 self.independent_transactions.len() <= self.by_id.len(),
1192 "independent_transactions.len() ({}) > by_id.len() ({})",
1193 self.independent_transactions.len(),
1194 self.by_id.len()
1195 );
1196 assert_eq!(
1198 self.by_id.len() + self.expiring_nonce_txs.len(),
1199 self.by_hash.len(),
1200 "by_id.len() ({}) + expiring_nonce_txs.len() ({}) != by_hash.len() ({})",
1201 self.by_id.len(),
1202 self.expiring_nonce_txs.len(),
1203 self.by_hash.len()
1204 );
1205
1206 for (seq_id, independent_tx) in &self.independent_transactions {
1208 let tx_id = independent_tx
1209 .transaction
1210 .transaction
1211 .aa_transaction_id()
1212 .expect("Independent transaction must have AA transaction ID");
1213 assert!(
1214 self.by_id.contains_key(&tx_id),
1215 "Independent transaction {tx_id:?} not in by_id"
1216 );
1217 assert_eq!(
1218 seq_id, &tx_id.seq_id,
1219 "Independent transactions sequence ID {seq_id:?} does not match transaction sequence ID {tx_id:?}"
1220 );
1221
1222 let tx_in_pool = self.by_id.get(&tx_id).unwrap();
1224 assert!(
1225 tx_in_pool.is_pending(),
1226 "Independent transaction {tx_id:?} is not pending"
1227 );
1228
1229 assert_eq!(
1231 independent_tx.transaction.hash(),
1232 tx_in_pool.inner.transaction.hash(),
1233 "Independent transaction hash mismatch for {tx_id:?}"
1234 );
1235 }
1236
1237 let mut seen_senders = std::collections::HashSet::new();
1239 for id in self.independent_transactions.keys() {
1240 assert!(
1241 seen_senders.insert(*id),
1242 "Duplicate sender {id:?} in independent transactions"
1243 );
1244 }
1245
1246 for (hash, tx) in &self.by_hash {
1248 assert_eq!(
1250 tx.hash(),
1251 hash,
1252 "Hash mismatch in by_hash: expected {:?}, got {:?}",
1253 hash,
1254 tx.hash()
1255 );
1256
1257 if tx.transaction.is_expiring_nonce() {
1259 assert!(
1260 self.expiring_nonce_txs
1261 .contains_key(&Self::expiring_nonce_hash(tx)),
1262 "Expiring nonce transaction with hash {hash:?} in by_hash but not in expiring_nonce_txs"
1263 );
1264 continue;
1265 }
1266
1267 let id = tx
1269 .transaction
1270 .aa_transaction_id()
1271 .expect("Transaction in pool should be AA transaction");
1272 assert!(
1273 self.by_id.contains_key(&id),
1274 "Transaction with hash {hash:?} in by_hash but not in by_id"
1275 );
1276
1277 let tx_in_by_id = &self.by_id.get(&id).unwrap().inner.transaction;
1279 assert_eq!(
1280 tx.hash(),
1281 tx_in_by_id.hash(),
1282 "Transaction hash mismatch between by_hash and by_id for id {id:?}"
1283 );
1284 }
1285
1286 for (id, tx) in &self.by_id {
1288 let hash = tx.inner.transaction.hash();
1290 assert!(
1291 self.by_hash.contains_key(hash),
1292 "Transaction with id {id:?} in by_id but not in by_hash"
1293 );
1294
1295 let tx_id = tx
1297 .inner
1298 .transaction
1299 .transaction
1300 .aa_transaction_id()
1301 .expect("Transaction in pool should be AA transaction");
1302 assert_eq!(
1303 &tx_id, id,
1304 "Transaction ID mismatch: expected {id:?}, got {tx_id:?}"
1305 );
1306
1307 if let Some(independent_tx) = self.independent_transactions.get(&id.seq_id)
1309 && independent_tx.transaction.hash() == tx.inner.transaction.hash()
1310 {
1311 assert!(
1312 tx.is_pending(),
1313 "Transaction {id:?} is in independent set but not pending"
1314 );
1315 }
1316 }
1317
1318 let (pending_count, queued_count) = self.pending_and_queued_txn_count();
1321 assert_eq!(
1322 pending_count + queued_count,
1323 self.by_id.len() + self.expiring_nonce_txs.len(),
1324 "Pending ({}) + queued ({}) != total transactions (by_id: {} + expiring: {})",
1325 pending_count,
1326 queued_count,
1327 self.by_id.len(),
1328 self.expiring_nonce_txs.len()
1329 );
1330
1331 assert!(
1333 pending_count <= self.config.pending_limit.max_txs,
1334 "pending_count {} exceeds limit {}",
1335 pending_count,
1336 self.config.pending_limit.max_txs
1337 );
1338 assert!(
1339 queued_count <= self.config.queued_limit.max_txs,
1340 "queued_count {} exceeds limit {}",
1341 queued_count,
1342 self.config.queued_limit.max_txs
1343 );
1344
1345 for (hash, pending_tx) in &self.expiring_nonce_txs {
1347 let tx_hash = *pending_tx.transaction.hash();
1348 assert!(
1349 self.by_hash.contains_key(&tx_hash),
1350 "Expiring nonce tx {tx_hash:?} not in by_hash (expiring hash {hash:?})"
1351 );
1352 assert!(
1353 pending_tx.transaction.transaction.is_expiring_nonce(),
1354 "Transaction in expiring_nonce_txs is not an expiring nonce tx"
1355 );
1356 }
1357 }
1358}
1359
1360pub const DEFAULT_MAX_TXS_PER_SENDER: usize = 16;
1364
1365#[derive(Debug, Clone)]
1367pub struct AA2dPoolConfig {
1368 pub price_bump_config: PriceBumpConfig,
1370 pub pending_limit: SubPoolLimit,
1372 pub queued_limit: SubPoolLimit,
1374 pub max_txs_per_sender: usize,
1378}
1379
1380impl Default for AA2dPoolConfig {
1381 fn default() -> Self {
1382 Self {
1383 price_bump_config: PriceBumpConfig::default(),
1384 pending_limit: SubPoolLimit::default(),
1385 queued_limit: SubPoolLimit::default(),
1386 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
1387 }
1388 }
1389}
1390
1391#[derive(Debug)]
1392struct AA2dInternalTransaction {
1393 inner: PendingTransaction<CoinbaseTipOrdering<TempoPooledTransaction>>,
1397 is_pending: AtomicBool,
1405}
1406
1407impl AA2dInternalTransaction {
1408 fn is_pending(&self) -> bool {
1410 self.is_pending.load(Ordering::Relaxed)
1411 }
1412
1413 fn set_pending(&self, pending: bool) -> bool {
1415 self.is_pending.swap(pending, Ordering::Relaxed)
1416 }
1417}
1418
1419#[derive(Debug, Clone)]
1428struct EvictionKey {
1429 tx: Arc<AA2dInternalTransaction>,
1431 tx_id: AA2dTransactionId,
1435}
1436
1437impl EvictionKey {
1438 fn new(tx: Arc<AA2dInternalTransaction>, tx_id: AA2dTransactionId) -> Self {
1440 Self { tx, tx_id }
1441 }
1442
1443 fn priority(&self) -> &Priority<u128> {
1445 &self.tx.inner.priority
1446 }
1447
1448 fn submission_id(&self) -> u64 {
1450 self.tx.inner.submission_id
1451 }
1452
1453 fn is_pending(&self) -> bool {
1455 self.tx.is_pending()
1456 }
1457}
1458
1459impl PartialEq for EvictionKey {
1460 fn eq(&self, other: &Self) -> bool {
1461 self.submission_id() == other.submission_id()
1462 }
1463}
1464
1465impl Eq for EvictionKey {}
1466
1467impl Ord for EvictionKey {
1468 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1469 self.priority()
1471 .cmp(other.priority())
1472 .then_with(|| other.submission_id().cmp(&self.submission_id()))
1475 }
1476}
1477
1478impl PartialOrd for EvictionKey {
1479 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1480 Some(self.cmp(other))
1481 }
1482}
1483
1484#[derive(Debug)]
1486pub(crate) struct BestAA2dTransactions {
1487 independent: BTreeSet<PendingTransaction<TxOrdering>>,
1489 by_id: BTreeMap<AA2dTransactionId, PendingTransaction<TxOrdering>>,
1491
1492 invalid: HashSet<AASequenceId>,
1494}
1495
1496impl BestAA2dTransactions {
1497 fn pop_best(&mut self) -> Option<(AA2dTransactionId, PendingTransaction<TxOrdering>)> {
1499 let tx = self.independent.pop_last()?;
1500 let id = tx
1501 .transaction
1502 .transaction
1503 .aa_transaction_id()
1504 .expect("Transaction in AA2D pool must be an AA transaction with valid nonce key");
1505 self.by_id.remove(&id);
1506 Some((id, tx))
1507 }
1508
1509 pub(crate) fn next_tx_and_priority(
1511 &mut self,
1512 ) -> Option<(
1513 Arc<ValidPoolTransaction<TempoPooledTransaction>>,
1514 Priority<u128>,
1515 )> {
1516 loop {
1517 let (id, best) = self.pop_best()?;
1518 if self.invalid.contains(&id.seq_id) {
1519 continue;
1520 }
1521 if !best.transaction.transaction.is_expiring_nonce()
1524 && let Some(unlocked) = self.by_id.get(&id.unlocks())
1525 {
1526 self.independent.insert(unlocked.clone());
1527 }
1528 return Some((best.transaction, best.priority));
1529 }
1530 }
1531}
1532
1533impl Iterator for BestAA2dTransactions {
1534 type Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>;
1535
1536 fn next(&mut self) -> Option<Self::Item> {
1537 self.next_tx_and_priority().map(|(tx, _)| tx)
1538 }
1539}
1540
1541impl BestTransactions for BestAA2dTransactions {
1542 fn mark_invalid(&mut self, transaction: &Self::Item, _kind: &InvalidPoolTransactionError) {
1543 if transaction.transaction.is_expiring_nonce() {
1546 return;
1547 }
1548
1549 if let Some(id) = transaction.transaction.aa_transaction_id() {
1550 self.invalid.insert(id.seq_id);
1551 }
1552 }
1553
1554 fn no_updates(&mut self) {}
1555
1556 fn set_skip_blobs(&mut self, _skip_blobs: bool) {}
1557}
1558
1559#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
1563pub struct AASequenceId {
1564 pub address: Address,
1566 pub nonce_key: U256,
1568}
1569
1570impl AASequenceId {
1571 pub const fn new(address: Address, nonce_key: U256) -> Self {
1573 Self { address, nonce_key }
1574 }
1575
1576 const fn start_bound(self) -> std::ops::Bound<AA2dTransactionId> {
1577 std::ops::Bound::Included(AA2dTransactionId::new(self, 0))
1578 }
1579
1580 const fn range(self) -> std::ops::RangeInclusive<AA2dTransactionId> {
1582 AA2dTransactionId::new(self, 0)..=AA2dTransactionId::new(self, u64::MAX)
1583 }
1584}
1585
1586#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
1590pub(crate) struct AA2dTransactionId {
1591 pub(crate) seq_id: AASequenceId,
1593 pub(crate) nonce: u64,
1595}
1596
1597impl AA2dTransactionId {
1598 pub(crate) const fn new(seq_id: AASequenceId, nonce: u64) -> Self {
1600 Self { seq_id, nonce }
1601 }
1602
1603 pub(crate) fn unlocks(&self) -> Self {
1605 Self::new(self.seq_id, self.nonce.saturating_add(1))
1606 }
1607}
1608
1609#[cfg(test)]
1610mod tests {
1611 use super::*;
1612 use crate::test_utils::{TxBuilder, wrap_valid_tx};
1613 use alloy_eips::eip2930::AccessList;
1614 use alloy_primitives::{Address, Bytes, Signature, TxKind, U256};
1615 use reth_primitives_traits::Recovered;
1616 use reth_transaction_pool::PoolTransaction;
1617 use std::collections::HashSet;
1618 use tempo_chainspec::hardfork::TempoHardfork;
1619 use tempo_primitives::{
1620 TempoTxEnvelope,
1621 transaction::{
1622 TempoTransaction,
1623 tempo_transaction::Call,
1624 tt_signature::{PrimitiveSignature, TempoSignature},
1625 tt_signed::AASigned,
1626 },
1627 };
1628
1629 #[test_case::test_case(U256::ZERO)]
1630 #[test_case::test_case(U256::random())]
1631 fn insert_pending(nonce_key: U256) {
1632 let mut pool = AA2dPool::default();
1633
1634 let sender = Address::random();
1636
1637 let tx = TxBuilder::aa(sender).nonce_key(nonce_key).build();
1639 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
1640
1641 let result = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
1643
1644 assert!(result.is_ok(), "Transaction should be added successfully");
1646 let added = result.unwrap();
1647 assert!(
1648 matches!(added, AddedTransaction::Pending(_)),
1649 "Transaction should be pending, got: {added:?}"
1650 );
1651
1652 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1654 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
1655 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1656
1657 pool.assert_invariants();
1658 }
1659
1660 #[test_case::test_case(U256::ZERO)]
1661 #[test_case::test_case(U256::random())]
1662 fn insert_with_nonce_gap_then_fill(nonce_key: U256) {
1663 let mut pool = AA2dPool::default();
1664
1665 let sender = Address::random();
1667
1668 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
1670 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
1671 let tx1_hash = *valid_tx1.hash();
1672
1673 let result1 = pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1);
1674
1675 assert!(
1677 result1.is_ok(),
1678 "Transaction 1 should be added successfully"
1679 );
1680 let added1 = result1.unwrap();
1681 assert!(
1682 matches!(
1683 added1,
1684 AddedTransaction::Parked {
1685 subpool: SubPool::Queued,
1686 ..
1687 }
1688 ),
1689 "Transaction 1 should be queued due to nonce gap, got: {added1:?}"
1690 );
1691
1692 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1694 assert_eq!(pending_count, 0, "Should have 0 pending transactions");
1695 assert_eq!(queued_count, 1, "Should have 1 queued transaction");
1696
1697 let seq_id = AASequenceId::new(sender, nonce_key);
1699 let tx1_id = AA2dTransactionId::new(seq_id, 1);
1700 assert!(
1701 !pool.independent_transactions.contains_key(&tx1_id.seq_id),
1702 "Transaction 1 should not be in independent set yet"
1703 );
1704
1705 pool.assert_invariants();
1706
1707 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
1709 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
1710 let tx0_hash = *valid_tx0.hash();
1711
1712 let result0 = pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1);
1713
1714 assert!(
1716 result0.is_ok(),
1717 "Transaction 0 should be added successfully"
1718 );
1719 let added0 = result0.unwrap();
1720
1721 match added0 {
1723 AddedTransaction::Pending(ref pending) => {
1724 assert_eq!(pending.transaction.hash(), &tx0_hash, "Should be tx0");
1725 assert_eq!(
1726 pending.promoted.len(),
1727 1,
1728 "Should have promoted 1 transaction"
1729 );
1730 assert_eq!(
1731 pending.promoted[0].hash(),
1732 &tx1_hash,
1733 "Should have promoted tx1"
1734 );
1735 }
1736 _ => panic!("Transaction 0 should be pending, got: {added0:?}"),
1737 }
1738
1739 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1741 assert_eq!(pending_count, 2, "Should have 2 pending transactions");
1742 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1743
1744 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1746 assert!(
1747 pool.by_id.get(&tx0_id).unwrap().is_pending(),
1748 "Transaction 0 should be pending"
1749 );
1750 assert!(
1751 pool.by_id.get(&tx1_id).unwrap().is_pending(),
1752 "Transaction 1 should be pending after promotion"
1753 );
1754
1755 assert!(
1757 pool.independent_transactions.contains_key(&tx0_id.seq_id),
1758 "Transaction 0 should be in independent set (at on-chain nonce)"
1759 );
1760
1761 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
1763 assert_eq!(
1764 independent_tx.transaction.hash(),
1765 &tx0_hash,
1766 "Independent transaction should be tx0, not tx1"
1767 );
1768
1769 pool.assert_invariants();
1770 }
1771
1772 #[test_case::test_case(U256::ZERO)]
1773 #[test_case::test_case(U256::random())]
1774 fn replace_pending_transaction(nonce_key: U256) {
1775 let mut pool = AA2dPool::default();
1776
1777 let sender = Address::random();
1779
1780 let tx_low = TxBuilder::aa(sender)
1782 .nonce_key(nonce_key)
1783 .max_priority_fee(1_000_000_000)
1784 .max_fee(2_000_000_000)
1785 .build();
1786 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
1787 let tx_low_hash = *valid_tx_low.hash();
1788
1789 let result_low = pool.add_transaction(Arc::new(valid_tx_low), 0, TempoHardfork::T1);
1790
1791 assert!(
1793 result_low.is_ok(),
1794 "Initial transaction should be added successfully"
1795 );
1796 let added_low = result_low.unwrap();
1797 assert!(
1798 matches!(added_low, AddedTransaction::Pending(_)),
1799 "Initial transaction should be pending"
1800 );
1801
1802 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1804 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
1805 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1806
1807 let seq_id = AASequenceId::new(sender, nonce_key);
1809 let tx_id = AA2dTransactionId::new(seq_id, 0);
1810 assert!(
1811 pool.independent_transactions.contains_key(&tx_id.seq_id),
1812 "Initial transaction should be in independent set"
1813 );
1814
1815 let independent_tx = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
1817 assert_eq!(
1818 independent_tx.transaction.hash(),
1819 &tx_low_hash,
1820 "Independent set should contain tx_low"
1821 );
1822
1823 pool.assert_invariants();
1824
1825 let tx_high = TxBuilder::aa(sender)
1828 .nonce_key(nonce_key)
1829 .max_priority_fee(1_200_000_000)
1830 .max_fee(2_400_000_000)
1831 .build();
1832 let valid_tx_high = wrap_valid_tx(tx_high, TransactionOrigin::Local);
1833 let tx_high_hash = *valid_tx_high.hash();
1834
1835 let result_high = pool.add_transaction(Arc::new(valid_tx_high), 0, TempoHardfork::T1);
1836
1837 assert!(
1839 result_high.is_ok(),
1840 "Replacement transaction should be added successfully"
1841 );
1842 let added_high = result_high.unwrap();
1843
1844 match added_high {
1846 AddedTransaction::Pending(ref pending) => {
1847 assert_eq!(
1848 pending.transaction.hash(),
1849 &tx_high_hash,
1850 "Should be tx_high"
1851 );
1852 assert!(
1853 pending.replaced.is_some(),
1854 "Should have replaced a transaction"
1855 );
1856 assert_eq!(
1857 pending.replaced.as_ref().unwrap().hash(),
1858 &tx_low_hash,
1859 "Should have replaced tx_low"
1860 );
1861 }
1862 _ => panic!("Replacement transaction should be pending, got: {added_high:?}"),
1863 }
1864
1865 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1867 assert_eq!(
1868 pending_count, 1,
1869 "Should still have 1 pending transaction after replacement"
1870 );
1871 assert_eq!(queued_count, 0, "Should still have 0 queued transactions");
1872
1873 assert!(
1875 !pool.contains(&tx_low_hash),
1876 "Old transaction should be removed from pool"
1877 );
1878
1879 assert!(
1881 pool.contains(&tx_high_hash),
1882 "New transaction should be in pool"
1883 );
1884
1885 assert!(
1887 pool.independent_transactions.contains_key(&tx_id.seq_id),
1888 "Transaction ID should still be in independent set"
1889 );
1890
1891 let independent_tx_after = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
1892 assert_eq!(
1893 independent_tx_after.transaction.hash(),
1894 &tx_high_hash,
1895 "Independent set should now contain tx_high (not tx_low)"
1896 );
1897
1898 let tx_in_pool = pool.by_id.get(&tx_id).unwrap();
1900 assert_eq!(
1901 tx_in_pool.inner.transaction.hash(),
1902 &tx_high_hash,
1903 "Transaction in by_id should be tx_high"
1904 );
1905 assert!(tx_in_pool.is_pending(), "Transaction should be pending");
1906
1907 pool.assert_invariants();
1908 }
1909
1910 #[test_case::test_case(U256::ZERO)]
1911 #[test_case::test_case(U256::random())]
1912 fn on_chain_nonce_update_with_gaps(nonce_key: U256) {
1913 let mut pool = AA2dPool::default();
1914
1915 let sender = Address::random();
1917
1918 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
1923 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
1924 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
1925 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
1926 let tx6 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(6).build();
1927
1928 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
1929 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
1930 let valid_tx3 = wrap_valid_tx(tx3, TransactionOrigin::Local);
1931 let valid_tx4 = wrap_valid_tx(tx4, TransactionOrigin::Local);
1932 let valid_tx6 = wrap_valid_tx(tx6, TransactionOrigin::Local);
1933
1934 let tx0_hash = *valid_tx0.hash();
1935 let tx1_hash = *valid_tx1.hash();
1936 let tx3_hash = *valid_tx3.hash();
1937 let tx4_hash = *valid_tx4.hash();
1938 let tx6_hash = *valid_tx6.hash();
1939
1940 pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1)
1942 .unwrap();
1943 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
1944 .unwrap();
1945 pool.add_transaction(Arc::new(valid_tx3), 0, TempoHardfork::T1)
1946 .unwrap();
1947 pool.add_transaction(Arc::new(valid_tx4), 0, TempoHardfork::T1)
1948 .unwrap();
1949 pool.add_transaction(Arc::new(valid_tx6), 0, TempoHardfork::T1)
1950 .unwrap();
1951
1952 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1954 assert_eq!(
1955 pending_count, 2,
1956 "Should have 2 pending transactions (0, 1)"
1957 );
1958 assert_eq!(
1959 queued_count, 3,
1960 "Should have 3 queued transactions (3, 4, 6)"
1961 );
1962
1963 let seq_id = AASequenceId::new(sender, nonce_key);
1965 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1966 assert!(
1967 pool.independent_transactions.contains_key(&tx0_id.seq_id),
1968 "Transaction 0 should be in independent set"
1969 );
1970
1971 pool.assert_invariants();
1972
1973 let mut on_chain_ids = HashMap::default();
1976 on_chain_ids.insert(seq_id, 2u64);
1977
1978 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
1979
1980 assert_eq!(mined.len(), 2, "Should have mined 2 transactions (0, 1)");
1982 let mined_hashes: HashSet<_> = mined.iter().map(|tx| tx.hash()).collect();
1983 assert!(
1984 mined_hashes.contains(&&tx0_hash),
1985 "Transaction 0 should be mined"
1986 );
1987 assert!(
1988 mined_hashes.contains(&&tx1_hash),
1989 "Transaction 1 should be mined"
1990 );
1991
1992 assert_eq!(
1994 promoted.len(),
1995 0,
1996 "No transactions should be promoted (gap at nonce 2)"
1997 );
1998
1999 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2001 assert_eq!(
2002 pending_count, 0,
2003 "Should have 0 pending transactions (gap at nonce 2)"
2004 );
2005 assert_eq!(
2006 queued_count, 3,
2007 "Should have 3 queued transactions (3, 4, 6)"
2008 );
2009
2010 assert!(!pool.contains(&tx0_hash), "Transaction 0 should be removed");
2012 assert!(!pool.contains(&tx1_hash), "Transaction 1 should be removed");
2013
2014 assert!(pool.contains(&tx3_hash), "Transaction 3 should remain");
2016 assert!(pool.contains(&tx4_hash), "Transaction 4 should remain");
2017 assert!(pool.contains(&tx6_hash), "Transaction 6 should remain");
2018
2019 let tx3_id = AA2dTransactionId::new(seq_id, 3);
2021 let tx4_id = AA2dTransactionId::new(seq_id, 4);
2022 let tx6_id = AA2dTransactionId::new(seq_id, 6);
2023
2024 assert!(
2025 !pool.by_id.get(&tx3_id).unwrap().is_pending(),
2026 "Transaction 3 should be queued (gap at nonce 2)"
2027 );
2028 assert!(
2029 !pool.by_id.get(&tx4_id).unwrap().is_pending(),
2030 "Transaction 4 should be queued"
2031 );
2032 assert!(
2033 !pool.by_id.get(&tx6_id).unwrap().is_pending(),
2034 "Transaction 6 should be queued"
2035 );
2036
2037 assert!(
2039 pool.independent_transactions.is_empty(),
2040 "Independent set should be empty (gap at on-chain nonce 2)"
2041 );
2042
2043 pool.assert_invariants();
2044
2045 let mut on_chain_ids = HashMap::default();
2048 on_chain_ids.insert(seq_id, 3u64);
2049
2050 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2051
2052 assert_eq!(
2054 mined.len(),
2055 0,
2056 "No transactions should be mined (nonce 2 was never in pool)"
2057 );
2058
2059 assert_eq!(promoted.len(), 2, "Transactions 3 and 4 should be promoted");
2061 let promoted_hashes: HashSet<_> = promoted.iter().map(|tx| tx.hash()).collect();
2062 assert!(
2063 promoted_hashes.contains(&&tx3_hash),
2064 "Transaction 3 should be promoted"
2065 );
2066 assert!(
2067 promoted_hashes.contains(&&tx4_hash),
2068 "Transaction 4 should be promoted"
2069 );
2070
2071 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2073 assert_eq!(
2074 pending_count, 2,
2075 "Should have 2 pending transactions (3, 4)"
2076 );
2077 assert_eq!(queued_count, 1, "Should have 1 queued transaction (6)");
2078
2079 assert!(
2081 pool.by_id.get(&tx3_id).unwrap().is_pending(),
2082 "Transaction 3 should be pending"
2083 );
2084 assert!(
2085 pool.by_id.get(&tx4_id).unwrap().is_pending(),
2086 "Transaction 4 should be pending"
2087 );
2088
2089 assert!(
2091 !pool.by_id.get(&tx6_id).unwrap().is_pending(),
2092 "Transaction 6 should still be queued (gap at nonce 5)"
2093 );
2094
2095 assert!(
2097 pool.independent_transactions.contains_key(&tx3_id.seq_id),
2098 "Transaction 3 should be in independent set (at on-chain nonce 3)"
2099 );
2100
2101 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
2103 assert_eq!(
2104 independent_tx.transaction.hash(),
2105 &tx3_hash,
2106 "Independent transaction should be tx3 (nonce 3), not tx4 or tx6"
2107 );
2108
2109 pool.assert_invariants();
2110 }
2111
2112 #[test_case::test_case(U256::ZERO)]
2113 #[test_case::test_case(U256::random())]
2114 fn reject_outdated_transaction(nonce_key: U256) {
2115 let mut pool = AA2dPool::default();
2116
2117 let sender = Address::random();
2119
2120 let tx = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2122 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
2123
2124 let result = pool.add_transaction(Arc::new(valid_tx), 5, TempoHardfork::T1);
2126
2127 assert!(result.is_err(), "Should reject outdated transaction");
2129
2130 let err = result.unwrap_err();
2131 assert!(
2132 matches!(
2133 err.kind,
2134 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
2135 InvalidTransactionError::NonceNotConsistent { tx: 3, state: 5 }
2136 ))
2137 ),
2138 "Should fail with NonceNotConsistent error, got: {:?}",
2139 err.kind
2140 );
2141
2142 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2144 assert_eq!(pending_count, 0, "Pool should be empty");
2145 assert_eq!(queued_count, 0, "Pool should be empty");
2146
2147 pool.assert_invariants();
2148 }
2149
2150 #[test_case::test_case(U256::ZERO)]
2151 #[test_case::test_case(U256::random())]
2152 fn replace_with_insufficient_price_bump(nonce_key: U256) {
2153 let mut pool = AA2dPool::default();
2154
2155 let sender = Address::random();
2157
2158 let tx_low = TxBuilder::aa(sender)
2160 .nonce_key(nonce_key)
2161 .max_priority_fee(1_000_000_000)
2162 .max_fee(2_000_000_000)
2163 .build();
2164 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
2165
2166 pool.add_transaction(Arc::new(valid_tx_low), 0, TempoHardfork::T1)
2167 .unwrap();
2168
2169 let tx_insufficient = TxBuilder::aa(sender)
2171 .nonce_key(nonce_key)
2172 .max_priority_fee(1_050_000_000)
2173 .max_fee(2_100_000_000)
2174 .build();
2175 let valid_tx_insufficient = wrap_valid_tx(tx_insufficient, TransactionOrigin::Local);
2176
2177 let result = pool.add_transaction(Arc::new(valid_tx_insufficient), 0, TempoHardfork::T1);
2178
2179 assert!(
2181 result.is_err(),
2182 "Should reject replacement with insufficient price bump"
2183 );
2184 let err = result.unwrap_err();
2185 assert!(
2186 matches!(err.kind, PoolErrorKind::ReplacementUnderpriced),
2187 "Should fail with ReplacementUnderpriced, got: {:?}",
2188 err.kind
2189 );
2190
2191 pool.assert_invariants();
2192 }
2193
2194 #[test_case::test_case(U256::ZERO)]
2195 #[test_case::test_case(U256::random())]
2196 fn fill_gap_in_middle(nonce_key: U256) {
2197 let mut pool = AA2dPool::default();
2198
2199 let sender = Address::random();
2200
2201 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2203 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2204 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2205 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
2206
2207 pool.add_transaction(
2208 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2209 0,
2210 TempoHardfork::T1,
2211 )
2212 .unwrap();
2213 pool.add_transaction(
2214 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
2215 0,
2216 TempoHardfork::T1,
2217 )
2218 .unwrap();
2219 pool.add_transaction(
2220 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
2221 0,
2222 TempoHardfork::T1,
2223 )
2224 .unwrap();
2225 pool.add_transaction(
2226 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
2227 0,
2228 TempoHardfork::T1,
2229 )
2230 .unwrap();
2231
2232 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2234 assert_eq!(pending_count, 2, "Should have 2 pending (0, 1)");
2235 assert_eq!(queued_count, 2, "Should have 2 queued (3, 4)");
2236
2237 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2239 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
2240
2241 let result = pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1);
2242 assert!(result.is_ok(), "Should successfully add tx2");
2243
2244 match result.unwrap() {
2246 AddedTransaction::Pending(ref pending) => {
2247 assert_eq!(pending.promoted.len(), 2, "Should promote tx3 and tx4");
2248 }
2249 _ => panic!("tx2 should be added as pending"),
2250 }
2251
2252 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2254 assert_eq!(pending_count, 5, "All 5 transactions should be pending");
2255 assert_eq!(queued_count, 0, "No transactions should be queued");
2256
2257 let seq_id = AASequenceId::new(sender, nonce_key);
2259 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2260 assert!(
2261 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2262 "tx0 should be in independent set"
2263 );
2264
2265 pool.assert_invariants();
2266 }
2267
2268 #[test_case::test_case(U256::ZERO)]
2269 #[test_case::test_case(U256::random())]
2270 fn remove_pending_transaction(nonce_key: U256) {
2271 let mut pool = AA2dPool::default();
2272
2273 let sender = Address::random();
2274
2275 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2277 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2278 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2279
2280 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2281 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
2282 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
2283
2284 let tx1_hash = *valid_tx1.hash();
2285
2286 pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1)
2287 .unwrap();
2288 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
2289 .unwrap();
2290 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
2291 .unwrap();
2292
2293 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2295 assert_eq!(pending_count, 3, "All 3 should be pending");
2296 assert_eq!(queued_count, 0, "None should be queued");
2297
2298 let seq_id = AASequenceId::new(sender, nonce_key);
2299 let tx1_id = AA2dTransactionId::new(seq_id, 1);
2300 let tx2_id = AA2dTransactionId::new(seq_id, 2);
2301
2302 assert!(
2304 pool.by_id.get(&tx2_id).unwrap().is_pending(),
2305 "tx2 should be pending before removal"
2306 );
2307
2308 let removed = pool.remove_transactions([&tx1_hash].into_iter());
2310 assert_eq!(removed.len(), 1, "Should remove tx1");
2311
2312 assert!(!pool.by_id.contains_key(&tx1_id), "tx1 should be removed");
2314 assert!(!pool.contains(&tx1_hash), "tx1 should be removed");
2315
2316 assert_eq!(pool.by_id.len(), 2, "Should have 2 transactions left");
2318
2319 assert!(
2321 !pool.by_id.get(&tx2_id).unwrap().is_pending(),
2322 "tx2 should be demoted to queued after tx1 removal creates a gap"
2323 );
2324
2325 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2327 assert_eq!(pending_count, 1, "Only tx0 should be pending");
2328 assert_eq!(queued_count, 1, "tx2 should be queued");
2329
2330 pool.assert_invariants();
2331 }
2332
2333 #[test_case::test_case(U256::ZERO, U256::random())]
2334 #[test_case::test_case(U256::random(), U256::ZERO)]
2335 #[test_case::test_case(U256::random(), U256::random())]
2336 fn multiple_senders_independent_set(nonce_key_a: U256, nonce_key_b: U256) {
2337 let mut pool = AA2dPool::default();
2338
2339 let sender_a = Address::random();
2341 let sender_b = Address::random();
2342
2343 let tx_a0 = TxBuilder::aa(sender_a).nonce_key(nonce_key_a).build();
2346 let tx_a1 = TxBuilder::aa(sender_a)
2347 .nonce_key(nonce_key_a)
2348 .nonce(1)
2349 .build();
2350
2351 let tx_b0 = TxBuilder::aa(sender_b).nonce_key(nonce_key_b).build();
2353 let tx_b1 = TxBuilder::aa(sender_b)
2354 .nonce_key(nonce_key_b)
2355 .nonce(1)
2356 .build();
2357
2358 let valid_tx_a0 = wrap_valid_tx(tx_a0, TransactionOrigin::Local);
2359 let valid_tx_a1 = wrap_valid_tx(tx_a1, TransactionOrigin::Local);
2360 let valid_tx_b0 = wrap_valid_tx(tx_b0, TransactionOrigin::Local);
2361 let valid_tx_b1 = wrap_valid_tx(tx_b1, TransactionOrigin::Local);
2362
2363 let tx_a0_hash = *valid_tx_a0.hash();
2364
2365 pool.add_transaction(Arc::new(valid_tx_a0), 0, TempoHardfork::T1)
2366 .unwrap();
2367 pool.add_transaction(Arc::new(valid_tx_a1), 0, TempoHardfork::T1)
2368 .unwrap();
2369 pool.add_transaction(Arc::new(valid_tx_b0), 0, TempoHardfork::T1)
2370 .unwrap();
2371 pool.add_transaction(Arc::new(valid_tx_b1), 0, TempoHardfork::T1)
2372 .unwrap();
2373
2374 let sender_a_id = AASequenceId::new(sender_a, nonce_key_a);
2376 let sender_b_id = AASequenceId::new(sender_b, nonce_key_b);
2377 let tx_a0_id = AA2dTransactionId::new(sender_a_id, 0);
2378 let tx_b0_id = AA2dTransactionId::new(sender_b_id, 0);
2379
2380 assert_eq!(
2381 pool.independent_transactions.len(),
2382 2,
2383 "Should have 2 independent transactions"
2384 );
2385 assert!(
2386 pool.independent_transactions.contains_key(&tx_a0_id.seq_id),
2387 "Sender A's tx0 should be independent"
2388 );
2389 assert!(
2390 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
2391 "Sender B's tx0 should be independent"
2392 );
2393
2394 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2396 assert_eq!(pending_count, 4, "All 4 transactions should be pending");
2397 assert_eq!(queued_count, 0, "No transactions should be queued");
2398
2399 let mut on_chain_ids = HashMap::default();
2401 on_chain_ids.insert(sender_a_id, 1u64);
2402
2403 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2404
2405 assert_eq!(mined.len(), 1, "Only sender A's tx0 should be mined");
2407 assert_eq!(mined[0].hash(), &tx_a0_hash, "Should mine tx_a0");
2408
2409 assert_eq!(
2411 promoted.len(),
2412 0,
2413 "No transactions should be promoted (tx_a1 was already pending)"
2414 );
2415
2416 let tx_a1_id = AA2dTransactionId::new(sender_a_id, 1);
2418 assert_eq!(
2419 pool.independent_transactions.len(),
2420 2,
2421 "Should still have 2 independent transactions"
2422 );
2423 assert!(
2424 pool.independent_transactions.contains_key(&tx_a1_id.seq_id),
2425 "Sender A's tx1 should now be independent"
2426 );
2427 assert!(
2428 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
2429 "Sender B's tx0 should still be independent"
2430 );
2431
2432 pool.assert_invariants();
2433 }
2434
2435 #[test_case::test_case(U256::ZERO)]
2436 #[test_case::test_case(U256::random())]
2437 fn concurrent_replacements_same_nonce(nonce_key: U256) {
2438 let mut pool = AA2dPool::default();
2439 let sender = Address::random();
2440 let seq_id = AASequenceId {
2441 address: sender,
2442 nonce_key,
2443 };
2444
2445 let tx0 = TxBuilder::aa(sender)
2447 .nonce_key(nonce_key)
2448 .max_priority_fee(1_000_000_000)
2449 .max_fee(2_000_000_000)
2450 .build();
2451 let tx0_hash = *tx0.hash();
2452 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2453 let result = pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1);
2454 assert!(result.is_ok());
2455 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2456 assert_eq!(pending_count + queued_count, 1);
2457
2458 let tx0_replacement1 = TxBuilder::aa(sender)
2460 .nonce_key(nonce_key)
2461 .max_priority_fee(1_050_000_000)
2462 .max_fee(2_100_000_000)
2463 .build();
2464 let valid_tx1 = wrap_valid_tx(tx0_replacement1, TransactionOrigin::Local);
2465 let result = pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1);
2466 assert!(result.is_err(), "Should reject insufficient price bump");
2467 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2468 assert_eq!(pending_count + queued_count, 1);
2469 assert!(
2470 pool.contains(&tx0_hash),
2471 "Original tx should still be present"
2472 );
2473
2474 let tx0_replacement2 = TxBuilder::aa(sender)
2476 .nonce_key(nonce_key)
2477 .max_priority_fee(1_100_000_000)
2478 .max_fee(2_200_000_000)
2479 .build();
2480 let tx0_replacement2_hash = *tx0_replacement2.hash();
2481 let valid_tx2 = wrap_valid_tx(tx0_replacement2, TransactionOrigin::Local);
2482 let result = pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1);
2483 assert!(result.is_ok(), "Should accept 10% price bump");
2484 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2485 assert_eq!(pending_count + queued_count, 1, "Pool size should remain 1");
2486 assert!(!pool.contains(&tx0_hash), "Old tx should be removed");
2487 assert!(
2488 pool.contains(&tx0_replacement2_hash),
2489 "New tx should be present"
2490 );
2491
2492 let tx0_replacement3 = TxBuilder::aa(sender)
2494 .nonce_key(nonce_key)
2495 .max_priority_fee(1_500_000_000)
2496 .max_fee(3_000_000_000)
2497 .build();
2498 let tx0_replacement3_hash = *tx0_replacement3.hash();
2499 let valid_tx3 = wrap_valid_tx(tx0_replacement3, TransactionOrigin::Local);
2500 let result = pool.add_transaction(Arc::new(valid_tx3), 0, TempoHardfork::T1);
2501 assert!(result.is_ok(), "Should accept higher price bump");
2502 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2503 assert_eq!(pending_count + queued_count, 1);
2504 assert!(
2505 !pool.contains(&tx0_replacement2_hash),
2506 "Previous tx should be removed"
2507 );
2508 assert!(
2509 pool.contains(&tx0_replacement3_hash),
2510 "Highest priority tx should win"
2511 );
2512
2513 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2515 assert!(pool.independent_transactions.contains_key(&tx0_id.seq_id));
2516
2517 pool.assert_invariants();
2518 }
2519
2520 #[test_case::test_case(U256::ZERO)]
2521 #[test_case::test_case(U256::random())]
2522 fn long_gap_chain(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).nonce_key(nonce_key).build();
2532 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
2533 let tx10 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(10).build();
2534 let tx15 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(15).build();
2535
2536 pool.add_transaction(
2537 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2538 0,
2539 TempoHardfork::T1,
2540 )
2541 .unwrap();
2542 pool.add_transaction(
2543 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
2544 0,
2545 TempoHardfork::T1,
2546 )
2547 .unwrap();
2548 pool.add_transaction(
2549 Arc::new(wrap_valid_tx(tx10, TransactionOrigin::Local)),
2550 0,
2551 TempoHardfork::T1,
2552 )
2553 .unwrap();
2554 pool.add_transaction(
2555 Arc::new(wrap_valid_tx(tx15, TransactionOrigin::Local)),
2556 0,
2557 TempoHardfork::T1,
2558 )
2559 .unwrap();
2560
2561 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2562 assert_eq!(pending_count + queued_count, 4);
2563
2564 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2566 assert!(pool.by_id.get(&tx0_id).unwrap().is_pending());
2567 assert!(
2568 !pool
2569 .by_id
2570 .get(&AA2dTransactionId::new(seq_id, 5))
2571 .unwrap()
2572 .is_pending()
2573 );
2574 assert!(
2575 !pool
2576 .by_id
2577 .get(&AA2dTransactionId::new(seq_id, 10))
2578 .unwrap()
2579 .is_pending()
2580 );
2581 assert!(
2582 !pool
2583 .by_id
2584 .get(&AA2dTransactionId::new(seq_id, 15))
2585 .unwrap()
2586 .is_pending()
2587 );
2588 assert_eq!(pool.independent_transactions.len(), 1);
2589
2590 for nonce in 1..=4 {
2592 let tx = TxBuilder::aa(sender)
2593 .nonce_key(nonce_key)
2594 .nonce(nonce)
2595 .build();
2596 pool.add_transaction(
2597 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2598 0,
2599 TempoHardfork::T1,
2600 )
2601 .unwrap();
2602 }
2603
2604 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2605 assert_eq!(pending_count + queued_count, 8);
2606
2607 for nonce in 0..=5 {
2609 let id = AA2dTransactionId::new(seq_id, nonce);
2610 assert!(
2611 pool.by_id.get(&id).unwrap().is_pending(),
2612 "Nonce {nonce} should be pending"
2613 );
2614 }
2615 assert!(
2617 !pool
2618 .by_id
2619 .get(&AA2dTransactionId::new(seq_id, 10))
2620 .unwrap()
2621 .is_pending()
2622 );
2623 assert!(
2624 !pool
2625 .by_id
2626 .get(&AA2dTransactionId::new(seq_id, 15))
2627 .unwrap()
2628 .is_pending()
2629 );
2630
2631 for nonce in 6..=9 {
2633 let tx = TxBuilder::aa(sender)
2634 .nonce_key(nonce_key)
2635 .nonce(nonce)
2636 .build();
2637 pool.add_transaction(
2638 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2639 0,
2640 TempoHardfork::T1,
2641 )
2642 .unwrap();
2643 }
2644
2645 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2646 assert_eq!(pending_count + queued_count, 12);
2647
2648 for nonce in 0..=10 {
2650 let id = AA2dTransactionId::new(seq_id, nonce);
2651 assert!(
2652 pool.by_id.get(&id).unwrap().is_pending(),
2653 "Nonce {nonce} should be pending"
2654 );
2655 }
2656 assert!(
2658 !pool
2659 .by_id
2660 .get(&AA2dTransactionId::new(seq_id, 15))
2661 .unwrap()
2662 .is_pending()
2663 );
2664
2665 for nonce in 11..=14 {
2667 let tx = TxBuilder::aa(sender)
2668 .nonce_key(nonce_key)
2669 .nonce(nonce)
2670 .build();
2671 pool.add_transaction(
2672 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2673 0,
2674 TempoHardfork::T1,
2675 )
2676 .unwrap();
2677 }
2678
2679 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2680 assert_eq!(pending_count + queued_count, 16);
2681
2682 for nonce in 0..=15 {
2684 let id = AA2dTransactionId::new(seq_id, nonce);
2685 assert!(
2686 pool.by_id.get(&id).unwrap().is_pending(),
2687 "Nonce {nonce} should be pending"
2688 );
2689 }
2690
2691 pool.assert_invariants();
2692 }
2693
2694 #[test_case::test_case(U256::ZERO)]
2695 #[test_case::test_case(U256::random())]
2696 fn remove_from_middle_of_chain(nonce_key: U256) {
2697 let mut pool = AA2dPool::default();
2698 let sender = Address::random();
2699 let seq_id = AASequenceId {
2700 address: sender,
2701 nonce_key,
2702 };
2703
2704 for nonce in 0..=4 {
2706 let tx = TxBuilder::aa(sender)
2707 .nonce_key(nonce_key)
2708 .nonce(nonce)
2709 .build();
2710 pool.add_transaction(
2711 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2712 0,
2713 TempoHardfork::T1,
2714 )
2715 .unwrap();
2716 }
2717
2718 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2719 assert_eq!(pending_count + queued_count, 5);
2720
2721 for nonce in 0..=4 {
2723 assert!(
2724 pool.by_id
2725 .get(&AA2dTransactionId::new(seq_id, nonce))
2726 .unwrap()
2727 .is_pending()
2728 );
2729 }
2730
2731 let tx2_id = AA2dTransactionId::new(seq_id, 2);
2733 let tx2_hash = *pool.by_id.get(&tx2_id).unwrap().inner.transaction.hash();
2734 let removed = pool.remove_transactions([&tx2_hash].into_iter());
2735 assert_eq!(removed.len(), 1, "Should remove transaction");
2736
2737 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2738 assert_eq!(pending_count + queued_count, 4);
2739
2740 assert!(!pool.by_id.contains_key(&tx2_id));
2742
2743 pool.assert_invariants();
2748 }
2749
2750 #[test_case::test_case(U256::ZERO)]
2751 #[test_case::test_case(U256::random())]
2752 fn independent_set_after_multiple_promotions(nonce_key: U256) {
2753 let mut pool = AA2dPool::default();
2754 let sender = Address::random();
2755 let seq_id = AASequenceId {
2756 address: sender,
2757 nonce_key,
2758 };
2759
2760 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2762 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2763 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
2764
2765 pool.add_transaction(
2766 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2767 0,
2768 TempoHardfork::T1,
2769 )
2770 .unwrap();
2771 pool.add_transaction(
2772 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
2773 0,
2774 TempoHardfork::T1,
2775 )
2776 .unwrap();
2777 pool.add_transaction(
2778 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
2779 0,
2780 TempoHardfork::T1,
2781 )
2782 .unwrap();
2783
2784 assert_eq!(pool.independent_transactions.len(), 1);
2786 assert!(pool.independent_transactions.contains_key(&seq_id));
2787
2788 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2790 assert_eq!(pending_count, 1);
2791 assert_eq!(queued_count, 2);
2792
2793 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2795 pool.add_transaction(
2796 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
2797 0,
2798 TempoHardfork::T1,
2799 )
2800 .unwrap();
2801
2802 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2804 assert_eq!(pending_count, 3);
2805 assert_eq!(queued_count, 1);
2806
2807 assert_eq!(pool.independent_transactions.len(), 1);
2809 assert!(pool.independent_transactions.contains_key(&seq_id));
2810
2811 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2813 pool.add_transaction(
2814 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
2815 0,
2816 TempoHardfork::T1,
2817 )
2818 .unwrap();
2819
2820 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2822 assert_eq!(pending_count, 5);
2823 assert_eq!(queued_count, 0);
2824
2825 let mut on_chain_ids = HashMap::default();
2827 on_chain_ids.insert(seq_id, 2u64);
2828 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2829
2830 assert_eq!(mined.len(), 2);
2832 assert_eq!(promoted.len(), 0);
2833
2834 assert_eq!(pool.independent_transactions.len(), 1);
2836 assert!(pool.independent_transactions.contains_key(&seq_id));
2837
2838 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2840 assert_eq!(pending_count + queued_count, 3);
2841
2842 pool.assert_invariants();
2843 }
2844
2845 #[test]
2846 fn stress_test_many_senders() {
2847 let mut pool = AA2dPool::default();
2848 const NUM_SENDERS: usize = 100;
2849 const TXS_PER_SENDER: u64 = 5;
2850
2851 let mut senders = Vec::new();
2853 for i in 0..NUM_SENDERS {
2854 let sender = Address::from_word(B256::from(U256::from(i)));
2855 let nonce_key = U256::from(i);
2856 senders.push((sender, nonce_key));
2857
2858 for nonce in 0..TXS_PER_SENDER {
2860 let tx = TxBuilder::aa(sender)
2861 .nonce_key(nonce_key)
2862 .nonce(nonce)
2863 .build();
2864 pool.add_transaction(
2865 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
2866 0,
2867 TempoHardfork::T1,
2868 )
2869 .unwrap();
2870 }
2871 }
2872
2873 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2875 assert_eq!(
2876 pending_count + queued_count,
2877 NUM_SENDERS * TXS_PER_SENDER as usize
2878 );
2879
2880 for (sender, nonce_key) in &senders {
2882 let seq_id = AASequenceId {
2883 address: *sender,
2884 nonce_key: *nonce_key,
2885 };
2886 for nonce in 0..TXS_PER_SENDER {
2887 let id = AA2dTransactionId::new(seq_id, nonce);
2888 assert!(pool.by_id.get(&id).unwrap().is_pending());
2889 }
2890 }
2891
2892 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
2894 for (sender, nonce_key) in &senders {
2895 let seq_id = AASequenceId {
2896 address: *sender,
2897 nonce_key: *nonce_key,
2898 };
2899 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2900 assert!(
2901 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2902 "Sender {sender:?} should have tx0 in independent set"
2903 );
2904 }
2905
2906 let mut on_chain_ids = HashMap::default();
2908 for (sender, nonce_key) in &senders {
2909 let seq_id = AASequenceId {
2910 address: *sender,
2911 nonce_key: *nonce_key,
2912 };
2913 on_chain_ids.insert(seq_id, 1u64);
2914 }
2915
2916 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2917
2918 assert_eq!(mined.len(), NUM_SENDERS);
2920 assert_eq!(promoted.len(), 0);
2922
2923 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2925 assert_eq!(
2926 pending_count + queued_count,
2927 NUM_SENDERS * (TXS_PER_SENDER - 1) as usize
2928 );
2929
2930 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
2932 for (sender, nonce_key) in &senders {
2933 let seq_id = AASequenceId {
2934 address: *sender,
2935 nonce_key: *nonce_key,
2936 };
2937 let tx1_id = AA2dTransactionId::new(seq_id, 1);
2938 assert!(
2939 pool.independent_transactions.contains_key(&tx1_id.seq_id),
2940 "Sender {sender:?} should have tx1 in independent set"
2941 );
2942 }
2943
2944 pool.assert_invariants();
2945 }
2946
2947 #[test_case::test_case(U256::ZERO)]
2948 #[test_case::test_case(U256::random())]
2949 fn on_chain_nonce_update_to_queued_tx_with_gaps(nonce_key: U256) {
2950 let mut pool = AA2dPool::default();
2951 let sender = Address::random();
2952 let seq_id = AASequenceId {
2953 address: sender,
2954 nonce_key,
2955 };
2956
2957 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2960 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2961 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
2962
2963 pool.add_transaction(
2964 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2965 0,
2966 TempoHardfork::T1,
2967 )
2968 .unwrap();
2969 pool.add_transaction(
2970 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
2971 0,
2972 TempoHardfork::T1,
2973 )
2974 .unwrap();
2975 pool.add_transaction(
2976 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
2977 0,
2978 TempoHardfork::T1,
2979 )
2980 .unwrap();
2981
2982 assert_eq!(pool.independent_transactions.len(), 1);
2984 assert!(pool.independent_transactions.contains_key(&seq_id));
2985
2986 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2988 assert_eq!(pending_count, 1, "Only tx0 should be pending");
2989 assert_eq!(queued_count, 2, "tx3 and tx5 should be queued");
2990
2991 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2993 pool.add_transaction(
2994 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
2995 0,
2996 TempoHardfork::T1,
2997 )
2998 .unwrap();
2999
3000 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
3001 pool.add_transaction(
3002 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3003 0,
3004 TempoHardfork::T1,
3005 )
3006 .unwrap();
3007
3008 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3010 assert_eq!(pending_count, 4, "Transactions [0,1,2,3] should be pending");
3011 assert_eq!(queued_count, 1, "tx5 should still be queued");
3012
3013 assert_eq!(pool.independent_transactions.len(), 1);
3015 assert!(pool.independent_transactions.contains_key(&seq_id));
3016
3017 let mut on_chain_ids = HashMap::default();
3018 on_chain_ids.insert(seq_id, 3u64);
3019 let (_promoted, mined) = pool.on_nonce_changes(on_chain_ids);
3020
3021 assert_eq!(mined.len(), 3, "Should mine transactions [0,1,2]");
3023
3024 assert_eq!(
3027 pool.independent_transactions.len(),
3028 1,
3029 "Should have one independent transaction"
3030 );
3031 let key = AA2dTransactionId::new(seq_id, 3);
3032 assert!(
3033 pool.independent_transactions.contains_key(&key.seq_id),
3034 "tx3 should be in independent set"
3035 );
3036
3037 let (_pending_count, _queued_count) = pool.pending_and_queued_txn_count();
3039 pool.assert_invariants();
3042
3043 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
3046 pool.add_transaction(
3047 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
3048 3,
3049 TempoHardfork::T1,
3050 )
3051 .unwrap();
3052
3053 let (_pending_count_after, _queued_count_after) = pool.pending_and_queued_txn_count();
3055 pool.assert_invariants();
3056 }
3057
3058 #[test]
3059 fn append_pooled_transaction_elements_respects_limit() {
3060 let mut pool = AA2dPool::default();
3061 let sender = Address::random();
3062 let nonce_key = U256::from(1);
3063
3064 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
3066 let tx0_hash = *tx0.hash();
3067 let tx0_len = tx0.encoded_length();
3068 pool.add_transaction(
3069 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3070 0,
3071 TempoHardfork::T1,
3072 )
3073 .unwrap();
3074
3075 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
3076 let tx1_hash = *tx1.hash();
3077 let tx1_len = tx1.encoded_length();
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 let tx2_hash = *tx2.hash();
3087 let tx2_len = tx2.encoded_length();
3088 pool.add_transaction(
3089 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3090 0,
3091 TempoHardfork::T1,
3092 )
3093 .unwrap();
3094
3095 let mut accumulated = 0;
3097 let mut elements = Vec::new();
3098 pool.append_pooled_transaction_elements(
3099 &[tx0_hash, tx1_hash, tx2_hash],
3100 GetPooledTransactionLimit::None,
3101 &mut accumulated,
3102 &mut elements,
3103 );
3104 assert_eq!(elements.len(), 3, "Should return all 3 transactions");
3105 assert_eq!(
3106 accumulated,
3107 tx0_len + tx1_len + tx2_len,
3108 "Should accumulate all sizes"
3109 );
3110
3111 let mut accumulated = 0;
3114 let mut elements = Vec::new();
3115 pool.append_pooled_transaction_elements(
3116 &[tx0_hash, tx1_hash, tx2_hash],
3117 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len - 1),
3118 &mut accumulated,
3119 &mut elements,
3120 );
3121 assert_eq!(
3122 elements.len(),
3123 1,
3124 "Should stop after first tx exceeds limit"
3125 );
3126 assert_eq!(accumulated, tx0_len, "Should accumulate first tx size");
3127
3128 let mut accumulated = 0;
3131 let mut elements = Vec::new();
3132 pool.append_pooled_transaction_elements(
3133 &[tx0_hash, tx1_hash, tx2_hash],
3134 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len + tx1_len - 1),
3135 &mut accumulated,
3136 &mut elements,
3137 );
3138 assert_eq!(
3139 elements.len(),
3140 2,
3141 "Should stop after second tx exceeds limit"
3142 );
3143 assert_eq!(
3144 accumulated,
3145 tx0_len + tx1_len,
3146 "Should accumulate first two tx sizes"
3147 );
3148
3149 let mut accumulated = tx0_len;
3151 let mut elements = Vec::new();
3152 pool.append_pooled_transaction_elements(
3153 &[tx1_hash, tx2_hash],
3154 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len + tx1_len - 1),
3155 &mut accumulated,
3156 &mut elements,
3157 );
3158 assert_eq!(
3159 elements.len(),
3160 1,
3161 "Should return 1 transaction when pre-accumulated size causes early stop"
3162 );
3163 assert_eq!(
3164 accumulated,
3165 tx0_len + tx1_len,
3166 "Should add to pre-accumulated size"
3167 );
3168 }
3169 #[test]
3174 fn test_2d_pool_helpers() {
3175 let mut pool = AA2dPool::default();
3176 let sender = Address::random();
3177 let tx = TxBuilder::aa(sender).build();
3178 let tx_hash = *tx.hash();
3179
3180 assert!(!pool.contains(&tx_hash));
3181 assert!(pool.get(&tx_hash).is_none());
3182
3183 pool.add_transaction(
3184 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3185 0,
3186 TempoHardfork::T1,
3187 )
3188 .unwrap();
3189
3190 assert!(pool.contains(&tx_hash));
3191 let retrieved = pool.get(&tx_hash);
3192 assert!(retrieved.is_some());
3193 assert_eq!(retrieved.unwrap().hash(), &tx_hash);
3194 }
3195
3196 #[test]
3197 fn test_pool_get_all() {
3198 let mut pool = AA2dPool::default();
3199 let sender = Address::random();
3200
3201 let tx0 = TxBuilder::aa(sender).build();
3202 let tx1 = TxBuilder::aa(sender).nonce(1).build();
3203 let tx0_hash = *tx0.hash();
3204 let tx1_hash = *tx1.hash();
3205 let fake_hash = alloy_primitives::B256::random();
3206
3207 pool.add_transaction(
3208 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3209 0,
3210 TempoHardfork::T1,
3211 )
3212 .unwrap();
3213 pool.add_transaction(
3214 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3215 0,
3216 TempoHardfork::T1,
3217 )
3218 .unwrap();
3219
3220 let hashes = [tx0_hash, tx1_hash, fake_hash];
3221 let results = pool.get_all(hashes.iter());
3222
3223 assert_eq!(results.len(), 2); }
3225
3226 #[test]
3227 fn test_pool_senders_iter() {
3228 let mut pool = AA2dPool::default();
3229 let sender1 = Address::random();
3230 let sender2 = Address::random();
3231
3232 let tx1 = TxBuilder::aa(sender1).build();
3233 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3234
3235 pool.add_transaction(
3236 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3237 0,
3238 TempoHardfork::T1,
3239 )
3240 .unwrap();
3241 pool.add_transaction(
3242 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3243 0,
3244 TempoHardfork::T1,
3245 )
3246 .unwrap();
3247
3248 let senders: Vec<_> = pool.senders_iter().collect();
3249 assert_eq!(senders.len(), 2);
3250 assert!(senders.contains(&&sender1));
3251 assert!(senders.contains(&&sender2));
3252 }
3253
3254 #[test]
3255 fn test_pool_pending_and_queued_transactions() {
3256 let mut pool = AA2dPool::default();
3257 let sender = Address::random();
3258
3259 let tx0 = TxBuilder::aa(sender).build();
3261 let tx1 = TxBuilder::aa(sender).nonce(1).build();
3262 let tx2 = TxBuilder::aa(sender).nonce(2).build();
3263 let tx0_hash = *tx0.hash();
3264 let tx1_hash = *tx1.hash();
3265 let tx2_hash = *tx2.hash();
3266
3267 let tx5 = TxBuilder::aa(sender).nonce(5).build();
3269 let tx6 = TxBuilder::aa(sender).nonce(6).build();
3270 let tx7 = TxBuilder::aa(sender).nonce(7).build();
3271 let tx5_hash = *tx5.hash();
3272 let tx6_hash = *tx6.hash();
3273 let tx7_hash = *tx7.hash();
3274
3275 for tx in [tx0, tx1, tx2, tx5, tx6, tx7] {
3276 pool.add_transaction(
3277 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3278 0,
3279 TempoHardfork::T1,
3280 )
3281 .unwrap();
3282 }
3283
3284 let pending: Vec<_> = pool.pending_transactions().collect();
3285 assert_eq!(pending.len(), 3);
3286 let pending_hashes: HashSet<_> = pending.iter().map(|tx| *tx.hash()).collect();
3287 assert!(pending_hashes.contains(&tx0_hash));
3288 assert!(pending_hashes.contains(&tx1_hash));
3289 assert!(pending_hashes.contains(&tx2_hash));
3290
3291 let queued: Vec<_> = pool.queued_transactions().collect();
3292 assert_eq!(queued.len(), 3);
3293 let queued_hashes: HashSet<_> = queued.iter().map(|tx| *tx.hash()).collect();
3294 assert!(queued_hashes.contains(&tx5_hash));
3295 assert!(queued_hashes.contains(&tx6_hash));
3296 assert!(queued_hashes.contains(&tx7_hash));
3297 }
3298
3299 #[test]
3300 fn test_pool_get_transactions_by_sender_iter() {
3301 let mut pool = AA2dPool::default();
3302 let sender1 = Address::random();
3303 let sender2 = Address::random();
3304
3305 let tx1 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
3306 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3307
3308 pool.add_transaction(
3309 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3310 0,
3311 TempoHardfork::T1,
3312 )
3313 .unwrap();
3314 pool.add_transaction(
3315 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3316 0,
3317 TempoHardfork::T1,
3318 )
3319 .unwrap();
3320
3321 let sender1_txs: Vec<_> = pool.get_transactions_by_sender_iter(sender1).collect();
3322 assert_eq!(sender1_txs.len(), 1);
3323 assert_eq!(sender1_txs[0].sender(), sender1);
3324
3325 let sender2_txs: Vec<_> = pool.get_transactions_by_sender_iter(sender2).collect();
3326 assert_eq!(sender2_txs.len(), 1);
3327 assert_eq!(sender2_txs[0].sender(), sender2);
3328 }
3329
3330 #[test]
3331 fn test_pool_get_transactions_by_origin_iter() {
3332 let mut pool = AA2dPool::default();
3333 let sender = Address::random();
3334
3335 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3336 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3337
3338 pool.add_transaction(
3339 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3340 0,
3341 TempoHardfork::T1,
3342 )
3343 .unwrap();
3344 pool.add_transaction(
3345 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::External)),
3346 0,
3347 TempoHardfork::T1,
3348 )
3349 .unwrap();
3350
3351 let local_txs: Vec<_> = pool
3352 .get_transactions_by_origin_iter(TransactionOrigin::Local)
3353 .collect();
3354 assert_eq!(local_txs.len(), 1);
3355
3356 let external_txs: Vec<_> = pool
3357 .get_transactions_by_origin_iter(TransactionOrigin::External)
3358 .collect();
3359 assert_eq!(external_txs.len(), 1);
3360 }
3361
3362 #[test]
3363 fn test_pool_get_pending_transactions_by_origin_iter() {
3364 let mut pool = AA2dPool::default();
3365 let sender = Address::random();
3366
3367 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3368 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build(); pool.add_transaction(
3371 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3372 0,
3373 TempoHardfork::T1,
3374 )
3375 .unwrap();
3376 pool.add_transaction(
3377 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3378 0,
3379 TempoHardfork::T1,
3380 )
3381 .unwrap();
3382
3383 let pending_local: Vec<_> = pool
3384 .get_pending_transactions_by_origin_iter(TransactionOrigin::Local)
3385 .collect();
3386 assert_eq!(pending_local.len(), 1); }
3388
3389 #[test]
3390 fn test_pool_all_transaction_hashes_iter() {
3391 let mut pool = AA2dPool::default();
3392 let sender = Address::random();
3393
3394 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3395 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3396 let tx0_hash = *tx0.hash();
3397 let tx1_hash = *tx1.hash();
3398
3399 pool.add_transaction(
3400 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3401 0,
3402 TempoHardfork::T1,
3403 )
3404 .unwrap();
3405 pool.add_transaction(
3406 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3407 0,
3408 TempoHardfork::T1,
3409 )
3410 .unwrap();
3411
3412 let hashes: Vec<_> = pool.all_transaction_hashes_iter().collect();
3413 assert_eq!(hashes.len(), 2);
3414 assert!(hashes.contains(&tx0_hash));
3415 assert!(hashes.contains(&tx1_hash));
3416 }
3417
3418 #[test]
3419 fn test_pool_pooled_transactions_hashes_iter() {
3420 let mut pool = AA2dPool::default();
3421 let sender = Address::random();
3422
3423 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3424 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3425
3426 pool.add_transaction(
3427 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3428 0,
3429 TempoHardfork::T1,
3430 )
3431 .unwrap();
3432 pool.add_transaction(
3433 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3434 0,
3435 TempoHardfork::T1,
3436 )
3437 .unwrap();
3438
3439 let hashes: Vec<_> = pool.pooled_transactions_hashes_iter().collect();
3440 assert_eq!(hashes.len(), 2);
3441 }
3442
3443 #[test]
3444 fn test_pool_pooled_transactions_iter() {
3445 let mut pool = AA2dPool::default();
3446 let sender = Address::random();
3447
3448 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3449 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3450
3451 pool.add_transaction(
3452 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3453 0,
3454 TempoHardfork::T1,
3455 )
3456 .unwrap();
3457 pool.add_transaction(
3458 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3459 0,
3460 TempoHardfork::T1,
3461 )
3462 .unwrap();
3463
3464 let txs: Vec<_> = pool.pooled_transactions_iter().collect();
3465 assert_eq!(txs.len(), 2);
3466 }
3467
3468 #[test]
3473 fn test_best_transactions_iterator() {
3474 let mut pool = AA2dPool::default();
3475 let sender = Address::random();
3476
3477 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3478 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3479
3480 pool.add_transaction(
3481 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3482 0,
3483 TempoHardfork::T1,
3484 )
3485 .unwrap();
3486 pool.add_transaction(
3487 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3488 0,
3489 TempoHardfork::T1,
3490 )
3491 .unwrap();
3492
3493 let mut best = pool.best_transactions();
3494
3495 let first = best.next();
3497 assert!(first.is_some());
3498
3499 let second = best.next();
3500 assert!(second.is_some());
3501
3502 let third = best.next();
3503 assert!(third.is_none());
3504 }
3505
3506 #[test]
3507 fn test_best_transactions_mark_invalid() {
3508 use reth_primitives_traits::transaction::error::InvalidTransactionError;
3509
3510 let mut pool = AA2dPool::default();
3511 let sender = Address::random();
3512
3513 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3514 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3515
3516 pool.add_transaction(
3517 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3518 0,
3519 TempoHardfork::T1,
3520 )
3521 .unwrap();
3522 pool.add_transaction(
3523 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3524 0,
3525 TempoHardfork::T1,
3526 )
3527 .unwrap();
3528
3529 let mut best = pool.best_transactions();
3530
3531 let first = best.next().unwrap();
3532
3533 let error = reth_transaction_pool::error::InvalidPoolTransactionError::Consensus(
3535 InvalidTransactionError::TxTypeNotSupported,
3536 );
3537 best.mark_invalid(&first, &error);
3538
3539 }
3542
3543 #[test]
3544 fn test_best_transactions_expiring_nonce_independent() {
3545 let mut pool = AA2dPool::default();
3548 let sender = Address::random();
3549
3550 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
3552 pool.add_transaction(
3553 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3554 0,
3555 TempoHardfork::T1,
3556 )
3557 .unwrap();
3558
3559 let mut best = pool.best_transactions();
3560
3561 let first = best.next();
3563 assert!(first.is_some());
3564
3565 assert!(best.next().is_none());
3567 }
3568
3569 #[test]
3574 fn test_remove_transactions_by_sender() {
3575 let mut pool = AA2dPool::default();
3576 let sender1 = Address::random();
3577 let sender2 = Address::random();
3578
3579 let tx1 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
3580 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3581
3582 pool.add_transaction(
3583 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3584 0,
3585 TempoHardfork::T1,
3586 )
3587 .unwrap();
3588 pool.add_transaction(
3589 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3590 0,
3591 TempoHardfork::T1,
3592 )
3593 .unwrap();
3594
3595 let removed = pool.remove_transactions_by_sender(sender1);
3596 assert_eq!(removed.len(), 1);
3597 assert_eq!(removed[0].sender(), sender1);
3598
3599 let (pending, queued) = pool.pending_and_queued_txn_count();
3601 assert_eq!(pending + queued, 1);
3602
3603 pool.assert_invariants();
3604 }
3605
3606 #[test]
3607 fn test_remove_transactions_and_descendants() {
3608 let mut pool = AA2dPool::default();
3609 let sender = Address::random();
3610
3611 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3612 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3613 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
3614 let tx0_hash = *tx0.hash();
3615
3616 pool.add_transaction(
3617 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3618 0,
3619 TempoHardfork::T1,
3620 )
3621 .unwrap();
3622 pool.add_transaction(
3623 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3624 0,
3625 TempoHardfork::T1,
3626 )
3627 .unwrap();
3628 pool.add_transaction(
3629 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3630 0,
3631 TempoHardfork::T1,
3632 )
3633 .unwrap();
3634
3635 let removed = pool.remove_transactions_and_descendants([&tx0_hash].into_iter());
3637 assert_eq!(removed.len(), 3);
3638
3639 let (pending, queued) = pool.pending_and_queued_txn_count();
3640 assert_eq!(pending + queued, 0);
3641
3642 pool.assert_invariants();
3643 }
3644
3645 #[test]
3650 fn test_aa_sequence_id_equality() {
3651 let addr = Address::random();
3652 let nonce_key = U256::from(42);
3653
3654 let id1 = AASequenceId::new(addr, nonce_key);
3655 let id2 = AASequenceId::new(addr, nonce_key);
3656 let id3 = AASequenceId::new(Address::random(), nonce_key);
3657
3658 assert_eq!(id1, id2);
3659 assert_ne!(id1, id3);
3660 }
3661
3662 #[test]
3663 fn test_aa2d_transaction_id_unlocks() {
3664 let addr = Address::random();
3665 let seq_id = AASequenceId::new(addr, U256::ZERO);
3666 let tx_id = AA2dTransactionId::new(seq_id, 5);
3667
3668 let next_id = tx_id.unlocks();
3669 assert_eq!(next_id.seq_id, seq_id);
3670 assert_eq!(next_id.nonce, 6);
3671 }
3672
3673 #[test]
3674 fn test_aa2d_transaction_id_ordering() {
3675 let addr = Address::random();
3676 let seq_id = AASequenceId::new(addr, U256::ZERO);
3677
3678 let id1 = AA2dTransactionId::new(seq_id, 1);
3679 let id2 = AA2dTransactionId::new(seq_id, 2);
3680
3681 assert!(id1 < id2);
3682 }
3683
3684 #[test]
3689 fn test_nonce_overflow_at_u64_max() {
3690 let mut pool = AA2dPool::default();
3691 let sender = Address::random();
3692 let nonce_key = U256::ZERO;
3693
3694 let tx = TxBuilder::aa(sender)
3695 .nonce_key(nonce_key)
3696 .nonce(u64::MAX)
3697 .build();
3698 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
3699
3700 let result = pool.add_transaction(Arc::new(valid_tx), u64::MAX, TempoHardfork::T1);
3701 assert!(result.is_ok());
3702
3703 let (pending, queued) = pool.pending_and_queued_txn_count();
3704 assert_eq!(pending, 1);
3705 assert_eq!(queued, 0);
3706
3707 let seq_id = AASequenceId::new(sender, nonce_key);
3708 let tx_id = AA2dTransactionId::new(seq_id, u64::MAX);
3709 let unlocked = tx_id.unlocks();
3710 assert_eq!(
3711 unlocked.nonce,
3712 u64::MAX,
3713 "saturating_add should not overflow"
3714 );
3715
3716 pool.assert_invariants();
3717 }
3718
3719 #[test]
3720 fn test_nonce_near_max_with_gap() {
3721 let mut pool = AA2dPool::default();
3722 let sender = Address::random();
3723 let nonce_key = U256::ZERO;
3724
3725 let tx_max = TxBuilder::aa(sender)
3726 .nonce_key(nonce_key)
3727 .nonce(u64::MAX)
3728 .build();
3729 let tx_max_minus_1 = TxBuilder::aa(sender)
3730 .nonce_key(nonce_key)
3731 .nonce(u64::MAX - 1)
3732 .build();
3733
3734 pool.add_transaction(
3735 Arc::new(wrap_valid_tx(tx_max, TransactionOrigin::Local)),
3736 u64::MAX - 1,
3737 TempoHardfork::T1,
3738 )
3739 .unwrap();
3740
3741 let (pending, queued) = pool.pending_and_queued_txn_count();
3742 assert_eq!(pending, 0, "tx at u64::MAX should be queued (gap exists)");
3743 assert_eq!(queued, 1);
3744
3745 pool.add_transaction(
3746 Arc::new(wrap_valid_tx(tx_max_minus_1, TransactionOrigin::Local)),
3747 u64::MAX - 1,
3748 TempoHardfork::T1,
3749 )
3750 .unwrap();
3751
3752 let (pending, queued) = pool.pending_and_queued_txn_count();
3753 assert_eq!(pending, 2, "both should now be pending");
3754 assert_eq!(queued, 0);
3755
3756 pool.assert_invariants();
3757 }
3758
3759 #[test]
3760 fn test_empty_pool_operations() {
3761 let pool = AA2dPool::default();
3762
3763 assert_eq!(pool.pending_and_queued_txn_count(), (0, 0));
3764 assert!(pool.get(&B256::random()).is_none());
3765 assert!(!pool.contains(&B256::random()));
3766 assert_eq!(pool.senders_iter().count(), 0);
3767 assert_eq!(pool.pending_transactions().count(), 0);
3768 assert_eq!(pool.queued_transactions().count(), 0);
3769 assert_eq!(pool.all_transaction_hashes_iter().count(), 0);
3770 assert_eq!(pool.pooled_transactions_hashes_iter().count(), 0);
3771 assert_eq!(pool.pooled_transactions_iter().count(), 0);
3772
3773 let mut best = pool.best_transactions();
3774 assert!(best.next().is_none());
3775 }
3776
3777 #[test]
3778 fn test_empty_pool_remove_operations() {
3779 let mut pool = AA2dPool::default();
3780 let random_hash = B256::random();
3781 let random_sender = Address::random();
3782
3783 let removed = pool.remove_transactions([&random_hash].into_iter());
3784 assert!(removed.is_empty());
3785
3786 let removed = pool.remove_transactions_by_sender(random_sender);
3787 assert!(removed.is_empty());
3788
3789 let removed = pool.remove_transactions_and_descendants([&random_hash].into_iter());
3790 assert!(removed.is_empty());
3791
3792 pool.assert_invariants();
3793 }
3794
3795 #[test]
3796 fn test_empty_pool_on_nonce_changes() {
3797 let mut pool = AA2dPool::default();
3798
3799 let mut changes = HashMap::default();
3800 changes.insert(AASequenceId::new(Address::random(), U256::ZERO), 5u64);
3801
3802 let (promoted, mined) = pool.on_nonce_changes(changes);
3803 assert!(promoted.is_empty());
3804 assert!(mined.is_empty());
3805
3806 pool.assert_invariants();
3807 }
3808
3809 #[test]
3814 fn test_add_already_imported_transaction() {
3815 let mut pool = AA2dPool::default();
3816 let sender = Address::random();
3817
3818 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3819 let tx_hash = *tx.hash();
3820 let valid_tx = Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local));
3821
3822 pool.add_transaction(valid_tx.clone(), 0, TempoHardfork::T1)
3823 .unwrap();
3824
3825 let result = pool.add_transaction(valid_tx, 0, TempoHardfork::T1);
3826 assert!(result.is_err());
3827 let err = result.unwrap_err();
3828 assert_eq!(err.hash, tx_hash);
3829 assert!(
3830 matches!(err.kind, PoolErrorKind::AlreadyImported),
3831 "Expected AlreadyImported, got {:?}",
3832 err.kind
3833 );
3834
3835 pool.assert_invariants();
3836 }
3837
3838 #[test]
3839 fn test_add_outdated_nonce_transaction() {
3840 let mut pool = AA2dPool::default();
3841 let sender = Address::random();
3842
3843 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(5).build();
3844 let tx_hash = *tx.hash();
3845 let valid_tx = Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local));
3846
3847 let result = pool.add_transaction(valid_tx, 10, TempoHardfork::T1);
3848 assert!(result.is_err());
3849 let err = result.unwrap_err();
3850 assert_eq!(err.hash, tx_hash);
3851 assert!(
3852 matches!(
3853 err.kind,
3854 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
3855 InvalidTransactionError::NonceNotConsistent { tx: 5, state: 10 }
3856 ))
3857 ),
3858 "Expected NonceNotConsistent, got {:?}",
3859 err.kind
3860 );
3861
3862 let (pending, queued) = pool.pending_and_queued_txn_count();
3863 assert_eq!(pending + queued, 0);
3864 }
3865
3866 #[test]
3867 fn test_replacement_underpriced() {
3868 let mut pool = AA2dPool::default();
3869 let sender = Address::random();
3870
3871 let tx1 = TxBuilder::aa(sender)
3872 .nonce_key(U256::ZERO)
3873 .max_priority_fee(1_000_000_000)
3874 .max_fee(2_000_000_000)
3875 .build();
3876 pool.add_transaction(
3877 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3878 0,
3879 TempoHardfork::T1,
3880 )
3881 .unwrap();
3882
3883 let tx2 = TxBuilder::aa(sender)
3884 .nonce_key(U256::ZERO)
3885 .max_priority_fee(1_000_000_001)
3886 .max_fee(2_000_000_001)
3887 .build();
3888 let tx2_hash = *tx2.hash();
3889 let result = pool.add_transaction(
3890 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3891 0,
3892 TempoHardfork::T1,
3893 );
3894
3895 assert!(result.is_err());
3896 let err = result.unwrap_err();
3897 assert_eq!(err.hash, tx2_hash);
3898 assert!(
3899 matches!(err.kind, PoolErrorKind::ReplacementUnderpriced),
3900 "Expected ReplacementUnderpriced, got {:?}",
3901 err.kind
3902 );
3903
3904 let (pending, queued) = pool.pending_and_queued_txn_count();
3905 assert_eq!(pending + queued, 1);
3906
3907 pool.assert_invariants();
3908 }
3909
3910 #[test]
3915 fn test_discard_at_max_txs_limit() {
3916 let config = AA2dPoolConfig {
3917 price_bump_config: PriceBumpConfig::default(),
3918 pending_limit: SubPoolLimit {
3919 max_txs: 3,
3920 max_size: usize::MAX,
3921 },
3922 queued_limit: SubPoolLimit {
3923 max_txs: 10000,
3924 max_size: usize::MAX,
3925 },
3926 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
3927 };
3928 let mut pool = AA2dPool::new(config);
3929
3930 for i in 0..5usize {
3931 let sender = Address::from_word(B256::from(U256::from(i)));
3932 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
3933 let result = pool.add_transaction(
3934 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3935 0,
3936 TempoHardfork::T1,
3937 );
3938 assert!(result.is_ok());
3939 }
3940
3941 let (pending, queued) = pool.pending_and_queued_txn_count();
3942 assert_eq!(pending + queued, 3, "Pool should be capped at max_txs=3");
3943 assert_eq!(pending, 3, "All remaining transactions should be pending");
3944
3945 pool.assert_invariants();
3946 }
3947
3948 #[test]
3949 fn test_discard_removes_lowest_priority_same_priority_uses_submission_order() {
3950 let config = AA2dPoolConfig {
3951 price_bump_config: PriceBumpConfig::default(),
3952 pending_limit: SubPoolLimit {
3953 max_txs: 2,
3954 max_size: usize::MAX,
3955 },
3956 queued_limit: SubPoolLimit {
3957 max_txs: 10000,
3958 max_size: usize::MAX,
3959 },
3960 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
3961 };
3962 let mut pool = AA2dPool::new(config);
3963 let sender = Address::random();
3964
3965 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
3968 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
3969 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
3970 let tx0_hash = *tx0.hash();
3971 let tx1_hash = *tx1.hash();
3972 let tx2_hash = *tx2.hash();
3973
3974 pool.add_transaction(
3975 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3976 0,
3977 TempoHardfork::T1,
3978 )
3979 .unwrap();
3980 pool.add_transaction(
3981 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3982 0,
3983 TempoHardfork::T1,
3984 )
3985 .unwrap();
3986 let result = pool.add_transaction(
3987 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3988 0,
3989 TempoHardfork::T1,
3990 );
3991 assert!(result.is_ok());
3992
3993 let added = result.unwrap();
3994 if let AddedTransaction::Pending(pending) = added {
3995 assert!(
3996 !pending.discarded.is_empty(),
3997 "Should have discarded transactions"
3998 );
3999 assert_eq!(
4000 pending.discarded[0].hash(),
4001 &tx2_hash,
4002 "tx2 (last submitted, lowest priority tiebreaker) should be discarded"
4003 );
4004 } else {
4005 panic!("Expected Pending result");
4006 }
4007
4008 assert!(pool.contains(&tx0_hash));
4009 assert!(pool.contains(&tx1_hash));
4010 assert!(!pool.contains(&tx2_hash));
4011
4012 pool.assert_invariants();
4013 }
4014
4015 #[test]
4017 fn test_discard_enforced_for_queued_transactions() {
4018 let config = AA2dPoolConfig {
4019 price_bump_config: PriceBumpConfig::default(),
4020 pending_limit: SubPoolLimit {
4021 max_txs: 2,
4022 max_size: usize::MAX,
4023 },
4024 queued_limit: SubPoolLimit {
4025 max_txs: 2,
4026 max_size: usize::MAX,
4027 },
4028 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4029 };
4030 let mut pool = AA2dPool::new(config);
4031
4032 for i in 0..5usize {
4034 let sender = Address::from_word(B256::from(U256::from(i)));
4035 let tx = TxBuilder::aa(sender)
4036 .nonce_key(U256::from(i))
4037 .nonce(1000)
4038 .build();
4039 let result = pool.add_transaction(
4040 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4041 0,
4042 TempoHardfork::T1,
4043 );
4044 assert!(result.is_ok(), "Transaction {i} should be added");
4045 }
4046
4047 let (pending, queued) = pool.pending_and_queued_txn_count();
4048 assert_eq!(
4049 pending + queued,
4050 2,
4051 "Pool should be capped at max_txs=2, but has {pending} pending + {queued} queued",
4052 );
4053
4054 pool.assert_invariants();
4055 }
4056
4057 #[test]
4059 fn test_queued_limit_enforced_separately() {
4060 let config = AA2dPoolConfig {
4061 price_bump_config: PriceBumpConfig::default(),
4062 pending_limit: SubPoolLimit {
4063 max_txs: 10,
4064 max_size: usize::MAX,
4065 },
4066 queued_limit: SubPoolLimit {
4067 max_txs: 3,
4068 max_size: usize::MAX,
4069 },
4070 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4071 };
4072 let mut pool = AA2dPool::new(config);
4073
4074 for i in 0..5usize {
4076 let sender = Address::from_word(B256::from(U256::from(i)));
4077 let tx = TxBuilder::aa(sender)
4078 .nonce_key(U256::from(i))
4079 .nonce(1000)
4080 .build();
4081 let _ = pool.add_transaction(
4082 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4083 0,
4084 TempoHardfork::T1,
4085 );
4086 }
4087
4088 let (pending, queued) = pool.pending_and_queued_txn_count();
4089 assert_eq!(queued, 3, "Queued should be capped at 3");
4090 assert_eq!(pending, 0, "No pending transactions");
4091 pool.assert_invariants();
4092 }
4093
4094 #[test]
4096 fn test_pending_limit_enforced_separately() {
4097 let config = AA2dPoolConfig {
4098 price_bump_config: PriceBumpConfig::default(),
4099 pending_limit: SubPoolLimit {
4100 max_txs: 3,
4101 max_size: usize::MAX,
4102 },
4103 queued_limit: SubPoolLimit {
4104 max_txs: 10,
4105 max_size: usize::MAX,
4106 },
4107 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4108 };
4109 let mut pool = AA2dPool::new(config);
4110
4111 for i in 0..5usize {
4113 let sender = Address::from_word(B256::from(U256::from(i)));
4114 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4115 let _ = pool.add_transaction(
4116 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4117 0,
4118 TempoHardfork::T1,
4119 );
4120 }
4121
4122 let (pending, queued) = pool.pending_and_queued_txn_count();
4123 assert_eq!(pending, 3, "Pending should be capped at 3");
4124 assert_eq!(queued, 0, "No queued transactions");
4125 pool.assert_invariants();
4126 }
4127
4128 #[test]
4130 fn test_queued_eviction_does_not_affect_pending() {
4131 let config = AA2dPoolConfig {
4132 price_bump_config: PriceBumpConfig::default(),
4133 pending_limit: SubPoolLimit {
4134 max_txs: 5,
4135 max_size: usize::MAX,
4136 },
4137 queued_limit: SubPoolLimit {
4138 max_txs: 2,
4139 max_size: usize::MAX,
4140 },
4141 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4142 };
4143 let mut pool = AA2dPool::new(config);
4144
4145 let mut pending_hashes = Vec::new();
4147 for i in 0..3usize {
4148 let sender = Address::from_word(B256::from(U256::from(i)));
4149 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4150 let hash = *tx.hash();
4151 pending_hashes.push(hash);
4152 let _ = pool.add_transaction(
4153 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4154 0,
4155 TempoHardfork::T1,
4156 );
4157 }
4158
4159 for i in 100..110usize {
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 for hash in &pending_hashes {
4175 assert!(
4176 pool.contains(hash),
4177 "Pending tx should not be evicted by queued spam"
4178 );
4179 }
4180
4181 let (pending, queued) = pool.pending_and_queued_txn_count();
4182 assert_eq!(pending, 3, "All 3 pending should remain");
4183 assert_eq!(queued, 2, "Queued capped at 2");
4184 pool.assert_invariants();
4185 }
4186
4187 #[test]
4190 fn test_discard_evicts_low_priority_over_vanity_address() {
4191 let config = AA2dPoolConfig {
4192 price_bump_config: PriceBumpConfig::default(),
4193 pending_limit: SubPoolLimit {
4194 max_txs: 2,
4195 max_size: usize::MAX,
4196 },
4197 queued_limit: SubPoolLimit {
4198 max_txs: 10,
4199 max_size: usize::MAX,
4200 },
4201 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4202 };
4203 let mut pool = AA2dPool::new(config);
4204
4205 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)
4217 .nonce_key(U256::ZERO)
4218 .max_fee(high_max_fee)
4219 .max_priority_fee(5_000_000_000) .build();
4221 let high_priority_hash = *high_priority_tx.hash();
4222
4223 let low_priority_tx = TxBuilder::aa(normal_sender)
4226 .nonce_key(U256::ZERO)
4227 .max_fee(high_max_fee)
4228 .max_priority_fee(1) .build();
4230 let low_priority_hash = *low_priority_tx.hash();
4231
4232 pool.add_transaction(
4233 Arc::new(wrap_valid_tx(high_priority_tx, TransactionOrigin::Local)),
4234 0,
4235 TempoHardfork::T1,
4236 )
4237 .unwrap();
4238 pool.add_transaction(
4239 Arc::new(wrap_valid_tx(low_priority_tx, TransactionOrigin::Local)),
4240 0,
4241 TempoHardfork::T1,
4242 )
4243 .unwrap();
4244
4245 let trigger_tx = TxBuilder::aa(Address::random())
4248 .nonce_key(U256::from(1))
4249 .max_fee(high_max_fee)
4250 .max_priority_fee(3_000_000_000) .build();
4252 let trigger_hash = *trigger_tx.hash();
4253
4254 let result = pool.add_transaction(
4255 Arc::new(wrap_valid_tx(trigger_tx, TransactionOrigin::Local)),
4256 0,
4257 TempoHardfork::T1,
4258 );
4259 assert!(result.is_ok());
4260
4261 let added = result.unwrap();
4262 if let AddedTransaction::Pending(pending) = added {
4263 assert!(
4264 !pending.discarded.is_empty(),
4265 "Should have discarded transactions"
4266 );
4267 assert_eq!(
4269 pending.discarded[0].hash(),
4270 &low_priority_hash,
4271 "Low priority tx should be evicted, not the high-priority vanity address tx"
4272 );
4273 } else {
4274 panic!("Expected Pending result");
4275 }
4276
4277 assert!(
4279 pool.contains(&high_priority_hash),
4280 "High priority vanity address tx should be kept"
4281 );
4282 assert!(
4283 !pool.contains(&low_priority_hash),
4284 "Low priority tx should be evicted"
4285 );
4286 assert!(pool.contains(&trigger_hash), "Trigger tx should be kept");
4287
4288 pool.assert_invariants();
4289 }
4290
4291 #[test]
4293 fn test_per_sender_limit_rejects_excess_transactions() {
4294 let config = AA2dPoolConfig {
4295 price_bump_config: PriceBumpConfig::default(),
4296 pending_limit: SubPoolLimit {
4297 max_txs: 1000,
4298 max_size: usize::MAX,
4299 },
4300 queued_limit: SubPoolLimit {
4301 max_txs: 1000,
4302 max_size: usize::MAX,
4303 },
4304 max_txs_per_sender: 3,
4305 };
4306 let mut pool = AA2dPool::new(config);
4307 let sender = Address::random();
4308
4309 for nonce in 0..3u64 {
4311 let tx = TxBuilder::aa(sender)
4312 .nonce_key(U256::ZERO)
4313 .nonce(nonce)
4314 .build();
4315 let result = pool.add_transaction(
4316 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4317 0,
4318 TempoHardfork::T1,
4319 );
4320 assert!(result.is_ok(), "Transaction {nonce} should be accepted");
4321 }
4322
4323 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(3).build();
4325 let result = pool.add_transaction(
4326 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4327 0,
4328 TempoHardfork::T1,
4329 );
4330 assert!(result.is_err(), "4th transaction should be rejected");
4331 let err = result.unwrap_err();
4332 assert!(
4333 matches!(err.kind, PoolErrorKind::SpammerExceededCapacity(_)),
4334 "Error should be SpammerExceededCapacity, got {:?}",
4335 err.kind
4336 );
4337
4338 let other_sender = Address::random();
4340 let tx = TxBuilder::aa(other_sender).nonce_key(U256::ZERO).build();
4341 let result = pool.add_transaction(
4342 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4343 0,
4344 TempoHardfork::T1,
4345 );
4346 assert!(result.is_ok(), "Different sender should be accepted");
4347
4348 pool.assert_invariants();
4349 }
4350
4351 #[test]
4353 fn test_per_sender_limit_allows_replacement() {
4354 let config = AA2dPoolConfig {
4355 price_bump_config: PriceBumpConfig::default(),
4356 pending_limit: SubPoolLimit {
4357 max_txs: 1000,
4358 max_size: usize::MAX,
4359 },
4360 queued_limit: SubPoolLimit {
4361 max_txs: 1000,
4362 max_size: usize::MAX,
4363 },
4364 max_txs_per_sender: 2,
4365 };
4366 let mut pool = AA2dPool::new(config);
4367 let sender = Address::random();
4368
4369 for nonce in 0..2u64 {
4371 let tx = TxBuilder::aa(sender)
4372 .nonce_key(U256::ZERO)
4373 .nonce(nonce)
4374 .build();
4375 pool.add_transaction(
4376 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4377 0,
4378 TempoHardfork::T1,
4379 )
4380 .unwrap();
4381 }
4382
4383 let replacement_tx = TxBuilder::aa(sender)
4385 .nonce_key(U256::ZERO)
4386 .nonce(0)
4387 .max_fee(100_000_000_000) .max_priority_fee(50_000_000_000)
4389 .build();
4390 let result = pool.add_transaction(
4391 Arc::new(wrap_valid_tx(replacement_tx, TransactionOrigin::Local)),
4392 0,
4393 TempoHardfork::T1,
4394 );
4395 assert!(
4396 result.is_ok(),
4397 "Replacement should be allowed even at limit"
4398 );
4399
4400 pool.assert_invariants();
4401 }
4402
4403 #[test]
4405 fn test_per_sender_limit_freed_after_removal() {
4406 let config = AA2dPoolConfig {
4407 price_bump_config: PriceBumpConfig::default(),
4408 pending_limit: SubPoolLimit {
4409 max_txs: 1000,
4410 max_size: usize::MAX,
4411 },
4412 queued_limit: SubPoolLimit {
4413 max_txs: 1000,
4414 max_size: usize::MAX,
4415 },
4416 max_txs_per_sender: 2,
4417 };
4418 let mut pool = AA2dPool::new(config);
4419 let sender = Address::random();
4420
4421 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
4423 let tx1_hash = *tx1.hash();
4424 pool.add_transaction(
4425 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4426 0,
4427 TempoHardfork::T1,
4428 )
4429 .unwrap();
4430
4431 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4432 pool.add_transaction(
4433 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4434 0,
4435 TempoHardfork::T1,
4436 )
4437 .unwrap();
4438
4439 let tx3 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
4441 let result = pool.add_transaction(
4442 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
4443 0,
4444 TempoHardfork::T1,
4445 );
4446 assert!(result.is_err(), "3rd should be rejected at limit");
4447
4448 pool.remove_transactions(std::iter::once(&tx1_hash));
4450
4451 let result = pool.add_transaction(
4453 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4454 0,
4455 TempoHardfork::T1,
4456 );
4457 assert!(result.is_ok(), "3rd should succeed after removal");
4458
4459 pool.assert_invariants();
4460 }
4461
4462 #[test]
4464 fn test_per_sender_limit_includes_expiring_nonce_txs() {
4465 let config = AA2dPoolConfig {
4466 price_bump_config: PriceBumpConfig::default(),
4467 pending_limit: SubPoolLimit {
4468 max_txs: 1000,
4469 max_size: usize::MAX,
4470 },
4471 queued_limit: SubPoolLimit {
4472 max_txs: 1000,
4473 max_size: usize::MAX,
4474 },
4475 max_txs_per_sender: 2,
4476 };
4477 let mut pool = AA2dPool::new(config);
4478 let sender = Address::random();
4479
4480 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
4482 pool.add_transaction(
4483 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4484 0,
4485 TempoHardfork::T1,
4486 )
4487 .unwrap();
4488
4489 let tx2 = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
4491 pool.add_transaction(
4492 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4493 0,
4494 TempoHardfork::T1,
4495 )
4496 .unwrap();
4497
4498 let tx3 = TxBuilder::aa(sender)
4500 .nonce_key(U256::from(1))
4501 .nonce(0)
4502 .build();
4503 let result = pool.add_transaction(
4504 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4505 0,
4506 TempoHardfork::T1,
4507 );
4508 assert!(
4509 result.is_err(),
4510 "3rd tx should be rejected due to per-sender limit"
4511 );
4512
4513 pool.assert_invariants();
4514 }
4515
4516 #[test]
4521 fn test_best_transactions_mark_invalid_skips_sequence() {
4522 use reth_primitives_traits::transaction::error::InvalidTransactionError;
4523
4524 let mut pool = AA2dPool::default();
4525 let sender1 = Address::random();
4526 let sender2 = Address::random();
4527
4528 let tx1_0 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
4529 let tx1_1 = TxBuilder::aa(sender1)
4530 .nonce_key(U256::ZERO)
4531 .nonce(1)
4532 .build();
4533 let tx2_0 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
4534
4535 let tx1_0_hash = *tx1_0.hash();
4536 let tx2_0_hash = *tx2_0.hash();
4537
4538 pool.add_transaction(
4539 Arc::new(wrap_valid_tx(tx1_0, TransactionOrigin::Local)),
4540 0,
4541 TempoHardfork::T1,
4542 )
4543 .unwrap();
4544 pool.add_transaction(
4545 Arc::new(wrap_valid_tx(tx1_1, TransactionOrigin::Local)),
4546 0,
4547 TempoHardfork::T1,
4548 )
4549 .unwrap();
4550 pool.add_transaction(
4551 Arc::new(wrap_valid_tx(tx2_0, TransactionOrigin::Local)),
4552 0,
4553 TempoHardfork::T1,
4554 )
4555 .unwrap();
4556
4557 let mut best = pool.best_transactions();
4558
4559 let first = best.next().unwrap();
4560 let first_hash = *first.hash();
4561
4562 let error =
4563 InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported);
4564 best.mark_invalid(&first, &error);
4565
4566 let mut remaining_hashes = HashSet::new();
4567 for tx in best {
4568 remaining_hashes.insert(*tx.hash());
4569 }
4570
4571 if first_hash == tx1_0_hash {
4572 assert!(
4573 !remaining_hashes.contains(&tx1_0_hash),
4574 "tx1_0 was consumed"
4575 );
4576 assert!(
4577 remaining_hashes.contains(&tx2_0_hash),
4578 "tx2_0 should still be yielded"
4579 );
4580 } else {
4581 assert!(
4582 remaining_hashes.contains(&tx1_0_hash) || remaining_hashes.contains(&tx2_0_hash),
4583 "At least one other independent tx should be yielded"
4584 );
4585 }
4586 }
4587
4588 #[test]
4589 fn test_best_transactions_order_by_priority() {
4590 let mut pool = AA2dPool::default();
4591
4592 let sender1 = Address::random();
4593 let sender2 = Address::random();
4594
4595 let low_priority = TxBuilder::aa(sender1)
4596 .nonce_key(U256::ZERO)
4597 .max_priority_fee(1_000_000)
4598 .max_fee(2_000_000)
4599 .build();
4600 let high_priority = TxBuilder::aa(sender2)
4601 .nonce_key(U256::from(1))
4602 .max_priority_fee(10_000_000_000)
4603 .max_fee(20_000_000_000)
4604 .build();
4605 let high_priority_hash = *high_priority.hash();
4606
4607 pool.add_transaction(
4608 Arc::new(wrap_valid_tx(low_priority, TransactionOrigin::Local)),
4609 0,
4610 TempoHardfork::T1,
4611 )
4612 .unwrap();
4613 pool.add_transaction(
4614 Arc::new(wrap_valid_tx(high_priority, TransactionOrigin::Local)),
4615 0,
4616 TempoHardfork::T1,
4617 )
4618 .unwrap();
4619
4620 let mut best = pool.best_transactions();
4621 let first = best.next().unwrap();
4622
4623 assert_eq!(
4624 first.hash(),
4625 &high_priority_hash,
4626 "Higher priority transaction should come first"
4627 );
4628 }
4629
4630 #[test]
4635 fn test_on_state_updates_with_bundle_account() {
4636 use revm::{
4637 database::{AccountStatus, BundleAccount},
4638 state::AccountInfo,
4639 };
4640
4641 let mut pool = AA2dPool::default();
4642 let sender = Address::random();
4643 let nonce_key = U256::ZERO;
4644
4645 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
4646 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
4647 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
4648
4649 pool.add_transaction(
4650 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4651 0,
4652 TempoHardfork::T1,
4653 )
4654 .unwrap();
4655 pool.add_transaction(
4656 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4657 0,
4658 TempoHardfork::T1,
4659 )
4660 .unwrap();
4661 pool.add_transaction(
4662 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4663 0,
4664 TempoHardfork::T1,
4665 )
4666 .unwrap();
4667
4668 let (pending, queued) = pool.pending_and_queued_txn_count();
4669 assert_eq!(pending, 3);
4670 assert_eq!(queued, 0);
4671
4672 let mut state = HashMap::default();
4673 let sender_account = BundleAccount::new(
4674 None,
4675 Some(AccountInfo {
4676 nonce: 2,
4677 ..Default::default()
4678 }),
4679 Default::default(),
4680 AccountStatus::Changed,
4681 );
4682 state.insert(sender, sender_account);
4683
4684 let (promoted, mined) = pool.on_state_updates(&state);
4685
4686 assert!(promoted.is_empty(), "tx2 was already pending");
4687 assert_eq!(mined.len(), 2, "tx0 and tx1 should be mined");
4688
4689 let (pending, queued) = pool.pending_and_queued_txn_count();
4690 assert_eq!(pending, 1, "Only tx2 should remain pending");
4691 assert_eq!(queued, 0);
4692
4693 pool.assert_invariants();
4694 }
4695
4696 #[test]
4697 fn test_on_state_updates_creates_gap_demotion() {
4698 use revm::{
4699 database::{AccountStatus, BundleAccount},
4700 state::AccountInfo,
4701 };
4702
4703 let mut pool = AA2dPool::default();
4704 let sender = Address::random();
4705 let nonce_key = U256::ZERO;
4706
4707 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
4708 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
4709 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
4710
4711 pool.add_transaction(
4712 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4713 0,
4714 TempoHardfork::T1,
4715 )
4716 .unwrap();
4717 pool.add_transaction(
4718 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4719 0,
4720 TempoHardfork::T1,
4721 )
4722 .unwrap();
4723 pool.add_transaction(
4724 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4725 0,
4726 TempoHardfork::T1,
4727 )
4728 .unwrap();
4729
4730 let (pending, queued) = pool.pending_and_queued_txn_count();
4731 assert_eq!(pending, 2);
4732 assert_eq!(queued, 1);
4733
4734 let mut state = HashMap::default();
4735 let sender_account = BundleAccount::new(
4736 None,
4737 Some(AccountInfo {
4738 nonce: 2,
4739 ..Default::default()
4740 }),
4741 Default::default(),
4742 AccountStatus::Changed,
4743 );
4744 state.insert(sender, sender_account);
4745
4746 let (promoted, mined) = pool.on_state_updates(&state);
4747
4748 assert_eq!(mined.len(), 2, "tx0 and tx1 should be mined");
4749 assert!(promoted.is_empty());
4750
4751 let (pending, queued) = pool.pending_and_queued_txn_count();
4752 assert_eq!(pending, 0, "tx3 should still be queued (gap at nonce 2)");
4753 assert_eq!(queued, 1);
4754
4755 pool.assert_invariants();
4756 }
4757
4758 #[test]
4759 fn test_on_nonce_changes_promotes_queued_transactions() {
4760 let mut pool = AA2dPool::default();
4761 let sender = Address::random();
4762 let nonce_key = U256::ZERO;
4763 let seq_id = AASequenceId::new(sender, nonce_key);
4764
4765 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
4766 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
4767
4768 pool.add_transaction(
4769 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
4770 0,
4771 TempoHardfork::T1,
4772 )
4773 .unwrap();
4774 pool.add_transaction(
4775 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4776 0,
4777 TempoHardfork::T1,
4778 )
4779 .unwrap();
4780
4781 let (pending, queued) = pool.pending_and_queued_txn_count();
4782 assert_eq!(pending, 0);
4783 assert_eq!(queued, 2);
4784
4785 let mut changes = HashMap::default();
4786 changes.insert(seq_id, 2u64);
4787
4788 let (promoted, mined) = pool.on_nonce_changes(changes);
4789
4790 assert!(
4791 mined.is_empty(),
4792 "No transactions to mine (on-chain nonce jumped)"
4793 );
4794 assert_eq!(promoted.len(), 2, "tx2 and tx3 should be promoted");
4795 assert!(promoted.iter().any(|t| t.hash() == tx2.hash()));
4796
4797 let (pending, queued) = pool.pending_and_queued_txn_count();
4798 assert_eq!(pending, 2);
4799 assert_eq!(queued, 0);
4800
4801 pool.assert_invariants();
4802 }
4803
4804 #[test]
4809 fn test_interleaved_inserts_multiple_nonce_keys() {
4810 let mut pool = AA2dPool::default();
4811 let sender = Address::random();
4812
4813 let key_a = U256::ZERO;
4814 let key_b = U256::from(1);
4815
4816 let tx_a0 = TxBuilder::aa(sender).nonce_key(key_a).build();
4817 let tx_b0 = TxBuilder::aa(sender).nonce_key(key_b).build();
4818 let tx_a1 = TxBuilder::aa(sender).nonce_key(key_a).nonce(1).build();
4819 let tx_b2 = TxBuilder::aa(sender).nonce_key(key_b).nonce(2).build();
4820 let tx_b1 = TxBuilder::aa(sender).nonce_key(key_b).nonce(1).build();
4821
4822 pool.add_transaction(
4823 Arc::new(wrap_valid_tx(tx_a0, TransactionOrigin::Local)),
4824 0,
4825 TempoHardfork::T1,
4826 )
4827 .unwrap();
4828 pool.add_transaction(
4829 Arc::new(wrap_valid_tx(tx_b0, TransactionOrigin::Local)),
4830 0,
4831 TempoHardfork::T1,
4832 )
4833 .unwrap();
4834 pool.add_transaction(
4835 Arc::new(wrap_valid_tx(tx_a1, TransactionOrigin::Local)),
4836 0,
4837 TempoHardfork::T1,
4838 )
4839 .unwrap();
4840 pool.add_transaction(
4841 Arc::new(wrap_valid_tx(tx_b2, TransactionOrigin::Local)),
4842 0,
4843 TempoHardfork::T1,
4844 )
4845 .unwrap();
4846 pool.add_transaction(
4847 Arc::new(wrap_valid_tx(tx_b1, TransactionOrigin::Local)),
4848 0,
4849 TempoHardfork::T1,
4850 )
4851 .unwrap();
4852
4853 let (pending, queued) = pool.pending_and_queued_txn_count();
4854 assert_eq!(pending, 5, "All transactions should be pending");
4855 assert_eq!(queued, 0);
4856
4857 assert_eq!(
4858 pool.independent_transactions.len(),
4859 2,
4860 "Two nonce keys = two independent txs"
4861 );
4862
4863 pool.assert_invariants();
4864 }
4865
4866 #[test]
4867 fn test_same_sender_different_nonce_keys_independent() {
4868 let mut pool = AA2dPool::default();
4869 let sender = Address::random();
4870
4871 let key_a = U256::from(100);
4872 let key_b = U256::from(200);
4873
4874 let tx_a5 = TxBuilder::aa(sender).nonce_key(key_a).nonce(5).build();
4875 let tx_b0 = TxBuilder::aa(sender).nonce_key(key_b).build();
4876
4877 pool.add_transaction(
4878 Arc::new(wrap_valid_tx(tx_a5, TransactionOrigin::Local)),
4879 5,
4880 TempoHardfork::T1,
4881 )
4882 .unwrap();
4883 pool.add_transaction(
4884 Arc::new(wrap_valid_tx(tx_b0, TransactionOrigin::Local)),
4885 0,
4886 TempoHardfork::T1,
4887 )
4888 .unwrap();
4889
4890 let (pending, queued) = pool.pending_and_queued_txn_count();
4891 assert_eq!(pending, 2);
4892 assert_eq!(queued, 0);
4893
4894 assert_eq!(pool.independent_transactions.len(), 2);
4895
4896 pool.assert_invariants();
4897 }
4898
4899 #[test_case::test_case(U256::ZERO)]
4904 #[test_case::test_case(U256::random())]
4905 fn reorg_nonce_decrease_clears_stale_independent_transaction(nonce_key: U256) {
4906 let mut pool = AA2dPool::default();
4907 let sender = Address::random();
4908 let seq_id = AASequenceId::new(sender, nonce_key);
4909
4910 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
4912 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
4913 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
4914 let tx5_hash = *tx5.hash();
4915
4916 pool.add_transaction(
4917 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
4918 3,
4919 TempoHardfork::T1,
4920 )
4921 .unwrap();
4922 pool.add_transaction(
4923 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
4924 3,
4925 TempoHardfork::T1,
4926 )
4927 .unwrap();
4928 pool.add_transaction(
4929 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
4930 3,
4931 TempoHardfork::T1,
4932 )
4933 .unwrap();
4934
4935 let (pending, queued) = pool.pending_and_queued_txn_count();
4937 assert_eq!(pending, 3, "All transactions should be pending");
4938 assert_eq!(queued, 0);
4939 assert_eq!(pool.independent_transactions.len(), 1);
4940 assert_eq!(
4941 pool.independent_transactions
4942 .get(&seq_id)
4943 .unwrap()
4944 .transaction
4945 .nonce(),
4946 3,
4947 "tx3 should be independent initially"
4948 );
4949 pool.assert_invariants();
4950
4951 let mut on_chain_ids = HashMap::default();
4953 on_chain_ids.insert(seq_id, 5u64);
4954 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
4955
4956 assert_eq!(mined.len(), 2, "tx3 and tx4 should be mined");
4957 assert!(promoted.is_empty(), "No promotions expected");
4958
4959 let (pending, queued) = pool.pending_and_queued_txn_count();
4961 assert_eq!(pending, 1, "Only tx5 should remain pending");
4962 assert_eq!(queued, 0);
4963 assert_eq!(pool.independent_transactions.len(), 1);
4964 assert_eq!(
4965 pool.independent_transactions
4966 .get(&seq_id)
4967 .unwrap()
4968 .transaction
4969 .hash(),
4970 &tx5_hash,
4971 "tx5 should be independent after mining"
4972 );
4973 pool.assert_invariants();
4974
4975 let mut on_chain_ids = HashMap::default();
4977 on_chain_ids.insert(seq_id, 3u64);
4978 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
4979
4980 assert!(mined.is_empty(), "No transactions should be mined");
4982 assert!(promoted.is_empty(), "No promotions expected");
4984
4985 let (pending, queued) = pool.pending_and_queued_txn_count();
4987 assert_eq!(pending, 0, "tx5 should not be pending (nonce gap)");
4988 assert_eq!(queued, 1, "tx5 should be queued");
4989
4990 assert!(
4992 !pool.independent_transactions.contains_key(&seq_id),
4993 "independent_transactions should not contain stale entry after reorg"
4994 );
4995
4996 pool.assert_invariants();
4997 }
4998
4999 #[test_case::test_case(U256::ZERO)]
5004 #[test_case::test_case(U256::random())]
5005 fn gap_demotion_marks_all_subsequent_transactions_as_queued(nonce_key: U256) {
5006 let mut pool = AA2dPool::default();
5007 let sender = Address::random();
5008 let seq_id = AASequenceId::new(sender, nonce_key);
5009
5010 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
5012 let tx6 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(6).build();
5013 let tx7 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(7).build();
5014 let tx8 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(8).build();
5015 let tx6_hash = *tx6.hash();
5016
5017 pool.add_transaction(
5018 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
5019 5,
5020 TempoHardfork::T1,
5021 )
5022 .unwrap();
5023 pool.add_transaction(
5024 Arc::new(wrap_valid_tx(tx6, TransactionOrigin::Local)),
5025 5,
5026 TempoHardfork::T1,
5027 )
5028 .unwrap();
5029 pool.add_transaction(
5030 Arc::new(wrap_valid_tx(tx7, TransactionOrigin::Local)),
5031 5,
5032 TempoHardfork::T1,
5033 )
5034 .unwrap();
5035 pool.add_transaction(
5036 Arc::new(wrap_valid_tx(tx8, TransactionOrigin::Local)),
5037 5,
5038 TempoHardfork::T1,
5039 )
5040 .unwrap();
5041
5042 let (pending, queued) = pool.pending_and_queued_txn_count();
5044 assert_eq!(pending, 4, "All transactions should be pending initially");
5045 assert_eq!(queued, 0);
5046 assert_eq!(pool.independent_transactions.len(), 1);
5047 pool.assert_invariants();
5048
5049 let removed = pool.remove_transactions(std::iter::once(&tx6_hash));
5052 assert_eq!(removed.len(), 1, "Should remove exactly tx6");
5053
5054 let mut on_chain_ids = HashMap::default();
5057 on_chain_ids.insert(seq_id, 5u64);
5058 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
5059
5060 assert!(mined.is_empty(), "No transactions should be mined");
5061 assert!(promoted.is_empty(), "No promotions expected");
5062
5063 let (pending, queued) = pool.pending_and_queued_txn_count();
5066 assert_eq!(
5067 pending, 1,
5068 "Only tx5 should be pending (tx7 and tx8 are after the gap)"
5069 );
5070 assert_eq!(
5071 queued, 2,
5072 "tx7 and tx8 should both be queued due to gap at nonce 6"
5073 );
5074
5075 pool.assert_invariants();
5076 }
5077
5078 #[test]
5079 fn expiring_nonce_tx_increments_pending_count() {
5080 let mut pool = AA2dPool::default();
5081 let sender = Address::random();
5082
5083 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5085 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
5086
5087 let result = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
5089 assert!(result.is_ok(), "Transaction should be added successfully");
5090 assert!(
5091 matches!(result.unwrap(), AddedTransaction::Pending(_)),
5092 "Expiring nonce transaction should be pending"
5093 );
5094
5095 let (pending, queued) = pool.pending_and_queued_txn_count();
5097 assert_eq!(pending, 1, "Should have 1 pending transaction");
5098 assert_eq!(queued, 0, "Should have 0 queued transactions");
5099
5100 pool.assert_invariants();
5102 }
5103
5104 #[test]
5105 fn expiring_nonce_tx_dedup_uses_expiring_nonce_hash() {
5106 let mut pool = AA2dPool::default();
5107 let sender = Address::random();
5108 let call_to = Address::random();
5109 let fee_token = Address::random();
5110 let calls = vec![Call {
5111 to: TxKind::Call(call_to),
5112 value: U256::ZERO,
5113 input: Bytes::new(),
5114 }];
5115
5116 let build_tx = |fee_payer_signature: Signature| {
5117 let tx = TempoTransaction {
5118 chain_id: 1,
5119 max_priority_fee_per_gas: 1_000_000_000,
5120 max_fee_per_gas: 2_000_000_000,
5121 gas_limit: 1_000_000,
5122 calls: calls.clone(),
5123 nonce_key: U256::MAX,
5124 nonce: 0,
5125 fee_token: Some(fee_token),
5126 fee_payer_signature: Some(fee_payer_signature),
5127 valid_after: None,
5128 valid_before: Some(123),
5129 access_list: AccessList::default(),
5130 tempo_authorization_list: Vec::new(),
5131 key_authorization: None,
5132 };
5133
5134 let signature = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
5135 Signature::test_signature(),
5136 ));
5137 let aa_signed = AASigned::new_unhashed(tx, signature);
5138 let envelope: TempoTxEnvelope = aa_signed.into();
5139 let recovered = Recovered::new_unchecked(envelope, sender);
5140 TempoPooledTransaction::new(recovered)
5141 };
5142
5143 let tx1 = build_tx(Signature::new(U256::from(1), U256::from(2), false));
5144 let tx2 = build_tx(Signature::new(U256::from(3), U256::from(4), false));
5145
5146 assert_ne!(tx1.hash(), tx2.hash(), "tx hashes must differ");
5147 let expiring_hash_1 = tx1
5148 .expiring_nonce_hash()
5149 .expect("expiring nonce tx must be AA");
5150 let expiring_hash_2 = tx2
5151 .expiring_nonce_hash()
5152 .expect("expiring nonce tx must be AA");
5153 assert_eq!(
5154 expiring_hash_1, expiring_hash_2,
5155 "expiring nonce hashes must match"
5156 );
5157
5158 let tx1_hash = *tx1.hash();
5159 pool.add_transaction(
5160 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5161 0,
5162 TempoHardfork::T1,
5163 )
5164 .unwrap();
5165
5166 let tx2_hash = *tx2.hash();
5167 let result = pool.add_transaction(
5168 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
5169 0,
5170 TempoHardfork::T1,
5171 );
5172 assert!(result.is_err(), "Expected AlreadyImported error");
5173 let err = result.unwrap_err();
5174 assert_eq!(err.hash, tx2_hash);
5175 assert!(
5176 matches!(err.kind, PoolErrorKind::AlreadyImported),
5177 "Expected AlreadyImported, got {:?}",
5178 err.kind
5179 );
5180
5181 let (pending, queued) = pool.pending_and_queued_txn_count();
5182 assert_eq!(pending, 1, "Expected 1 pending transaction");
5183 assert_eq!(queued, 0, "Expected 0 queued transactions");
5184 assert!(pool.by_hash.contains_key(&tx1_hash));
5185 assert_eq!(pool.expiring_nonce_txs.len(), 1);
5186 pool.assert_invariants();
5187 }
5188
5189 #[test]
5193 fn remove_included_expiring_nonce_tx_uses_correct_key() {
5194 let mut pool = AA2dPool::default();
5195 let sender = Address::random();
5196 let fee_token = Address::random();
5197 let calls = vec![Call {
5198 to: TxKind::Call(Address::random()),
5199 value: U256::ZERO,
5200 input: Bytes::new(),
5201 }];
5202
5203 let tx = TempoTransaction {
5204 chain_id: 1,
5205 max_priority_fee_per_gas: 1_000_000_000,
5206 max_fee_per_gas: 2_000_000_000,
5207 gas_limit: 1_000_000,
5208 calls,
5209 nonce_key: U256::MAX,
5210 nonce: 0,
5211 fee_token: Some(fee_token),
5212 fee_payer_signature: Some(Signature::new(U256::from(1), U256::from(2), false)),
5213 valid_before: Some(123),
5214 access_list: AccessList::default(),
5215 tempo_authorization_list: Vec::new(),
5216 key_authorization: None,
5217 valid_after: None,
5218 };
5219
5220 let signature =
5221 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
5222 let aa_signed = AASigned::new_unhashed(tx, signature);
5223 let envelope: TempoTxEnvelope = aa_signed.into();
5224 let recovered = Recovered::new_unchecked(envelope, sender);
5225 let pooled = TempoPooledTransaction::new(recovered);
5226
5227 let tx_hash = *pooled.hash();
5228 pool.add_transaction(
5229 Arc::new(wrap_valid_tx(pooled, TransactionOrigin::Local)),
5230 0,
5231 TempoHardfork::T1,
5232 )
5233 .unwrap();
5234
5235 assert_eq!(pool.expiring_nonce_txs.len(), 1);
5236 assert!(pool.by_hash.contains_key(&tx_hash));
5237 pool.assert_invariants();
5238
5239 let removed = pool.remove_included_expiring_nonce_txs(std::iter::once(&tx_hash));
5241 assert_eq!(removed.len(), 1, "should remove the tx by its tx_hash");
5242 assert_eq!(*removed[0].hash(), tx_hash);
5243
5244 assert!(
5246 pool.expiring_nonce_txs.is_empty(),
5247 "expiring_nonce_txs not cleaned up"
5248 );
5249 assert!(
5250 !pool.by_hash.contains_key(&tx_hash),
5251 "by_hash not cleaned up"
5252 );
5253
5254 let (pending, queued) = pool.pending_and_queued_txn_count();
5255 assert_eq!(pending, 0);
5256 assert_eq!(queued, 0);
5257 pool.assert_invariants();
5258 }
5259
5260 fn eviction_test_pool() -> AA2dPool {
5262 AA2dPool::new(AA2dPoolConfig {
5263 pending_limit: SubPoolLimit {
5264 max_txs: 2,
5265 max_size: usize::MAX,
5266 },
5267 queued_limit: SubPoolLimit {
5268 max_txs: 10,
5269 max_size: usize::MAX,
5270 },
5271 ..Default::default()
5272 })
5273 }
5274
5275 #[test]
5276 fn eviction_same_priority_evicts_newer() {
5277 let mut pool = eviction_test_pool();
5279 let sender = Address::random();
5280
5281 let tx1 = TxBuilder::aa(sender)
5282 .nonce_key(U256::from(1))
5283 .nonce(0)
5284 .build();
5285 let tx2 = TxBuilder::aa(sender)
5286 .nonce_key(U256::from(2))
5287 .nonce(0)
5288 .build();
5289 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5290
5291 pool.add_transaction(
5292 Arc::new(wrap_valid_tx(tx1.clone(), TransactionOrigin::Local)),
5293 0,
5294 TempoHardfork::T1,
5295 )
5296 .unwrap();
5297 pool.add_transaction(
5298 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
5299 0,
5300 TempoHardfork::T1,
5301 )
5302 .unwrap();
5303 let result = pool
5304 .add_transaction(
5305 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5306 0,
5307 TempoHardfork::T1,
5308 )
5309 .unwrap();
5310
5311 let AddedTransaction::Pending(pending) = result else {
5312 panic!("expected pending")
5313 };
5314 assert_eq!(pending.discarded[0].hash(), tx_exp.hash());
5315 assert!(pool.contains(tx1.hash()));
5316 assert!(pool.contains(tx2.hash()));
5317 assert!(!pool.contains(tx_exp.hash()));
5318 pool.assert_invariants();
5319
5320 let mut pool = eviction_test_pool();
5322 let sender = Address::random();
5323
5324 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5325 let tx2 = TxBuilder::aa(sender)
5326 .nonce_key(U256::from(1))
5327 .nonce(0)
5328 .build();
5329 let tx3 = TxBuilder::aa(sender)
5330 .nonce_key(U256::from(2))
5331 .nonce(0)
5332 .build();
5333
5334 pool.add_transaction(
5335 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5336 0,
5337 TempoHardfork::T1,
5338 )
5339 .unwrap();
5340 pool.add_transaction(
5341 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
5342 0,
5343 TempoHardfork::T1,
5344 )
5345 .unwrap();
5346 let result = pool
5347 .add_transaction(
5348 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
5349 0,
5350 TempoHardfork::T1,
5351 )
5352 .unwrap();
5353
5354 let AddedTransaction::Pending(pending) = result else {
5355 panic!("expected pending")
5356 };
5357 assert_eq!(pending.discarded[0].hash(), tx3.hash());
5358 assert!(pool.contains(tx_exp.hash()));
5359 assert!(pool.contains(tx2.hash()));
5360 assert!(!pool.contains(tx3.hash()));
5361 pool.assert_invariants();
5362 }
5363
5364 #[test]
5365 fn eviction_lower_priority_expiring_evicted() {
5366 let mut pool = eviction_test_pool();
5367 let sender = Address::random();
5368
5369 let tx_exp = TxBuilder::aa(sender)
5371 .nonce_key(U256::MAX)
5372 .max_priority_fee(100)
5373 .max_fee(200)
5374 .build();
5375 let tx2 = TxBuilder::aa(sender)
5376 .nonce_key(U256::from(1))
5377 .nonce(0)
5378 .build();
5379 let tx3 = TxBuilder::aa(sender)
5380 .nonce_key(U256::from(2))
5381 .nonce(0)
5382 .build();
5383
5384 pool.add_transaction(
5385 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5386 0,
5387 TempoHardfork::T1,
5388 )
5389 .unwrap();
5390 pool.add_transaction(
5391 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
5392 0,
5393 TempoHardfork::T1,
5394 )
5395 .unwrap();
5396 let result = pool
5397 .add_transaction(
5398 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
5399 0,
5400 TempoHardfork::T1,
5401 )
5402 .unwrap();
5403
5404 let AddedTransaction::Pending(pending) = result else {
5406 panic!("expected pending")
5407 };
5408 assert_eq!(pending.discarded[0].hash(), tx_exp.hash());
5409 assert!(!pool.contains(tx_exp.hash()));
5410 assert!(pool.contains(tx3.hash()));
5411 pool.assert_invariants();
5412 }
5413
5414 #[test]
5415 fn eviction_lower_priority_2d_evicted() {
5416 let mut pool = eviction_test_pool();
5417 let sender = Address::random();
5418
5419 let tx_low = TxBuilder::aa(sender)
5421 .nonce_key(U256::from(1))
5422 .nonce(0)
5423 .max_priority_fee(100)
5424 .max_fee(200)
5425 .build();
5426 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
5427 let tx3 = TxBuilder::aa(sender)
5428 .nonce_key(U256::from(2))
5429 .nonce(0)
5430 .build();
5431
5432 pool.add_transaction(
5433 Arc::new(wrap_valid_tx(tx_low.clone(), TransactionOrigin::Local)),
5434 0,
5435 TempoHardfork::T1,
5436 )
5437 .unwrap();
5438 pool.add_transaction(
5439 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
5440 0,
5441 TempoHardfork::T1,
5442 )
5443 .unwrap();
5444 let result = pool
5445 .add_transaction(
5446 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5447 0,
5448 TempoHardfork::T1,
5449 )
5450 .unwrap();
5451
5452 let AddedTransaction::Pending(pending) = result else {
5454 panic!("expected pending")
5455 };
5456 assert_eq!(pending.discarded[0].hash(), tx_low.hash());
5457 assert!(!pool.contains(tx_low.hash()));
5458 assert!(pool.contains(tx_exp.hash()));
5459 pool.assert_invariants();
5460 }
5461
5462 #[test]
5463 fn expiring_nonce_tx_subject_to_eviction() {
5464 let config = AA2dPoolConfig {
5466 pending_limit: SubPoolLimit {
5467 max_txs: 2,
5468 max_size: usize::MAX,
5469 },
5470 queued_limit: SubPoolLimit {
5471 max_txs: 10,
5472 max_size: usize::MAX,
5473 },
5474 ..Default::default()
5475 };
5476 let mut pool = AA2dPool::new(config);
5477 let sender = Address::random();
5478
5479 for i in 0..3 {
5481 let tx = TxBuilder::aa(sender)
5482 .nonce_key(U256::MAX)
5483 .max_priority_fee(1_000_000_000 + i as u128 * 100_000_000)
5484 .max_fee(2_000_000_000 + i as u128 * 100_000_000)
5485 .build();
5486 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
5487 let _ = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
5488 }
5489
5490 let (pending, queued) = pool.pending_and_queued_txn_count();
5492 assert!(
5493 pending <= 2,
5494 "Should have at most 2 pending transactions due to limit, got {pending}"
5495 );
5496 assert_eq!(queued, 0, "Should have 0 queued transactions");
5497
5498 pool.assert_invariants();
5499 }
5500
5501 #[test]
5502 fn remove_expiring_nonce_tx_decrements_pending_count() {
5503 let mut pool = AA2dPool::default();
5504 let sender = Address::random();
5505
5506 let tx1 = TxBuilder::aa(sender)
5508 .nonce_key(U256::MAX)
5509 .max_priority_fee(1_000_000_000)
5510 .max_fee(2_000_000_000)
5511 .build();
5512 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
5513 let tx1_hash = *valid_tx1.hash();
5514
5515 let tx2 = TxBuilder::aa(sender)
5516 .nonce_key(U256::MAX)
5517 .max_priority_fee(1_100_000_000)
5518 .max_fee(2_200_000_000)
5519 .build();
5520 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
5521
5522 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
5523 .unwrap();
5524 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
5525 .unwrap();
5526
5527 let (pending, _) = pool.pending_and_queued_txn_count();
5529 assert_eq!(pending, 2, "Should have 2 pending transactions");
5530 pool.assert_invariants();
5531
5532 let removed = pool.remove_included_expiring_nonce_txs(std::iter::once(&tx1_hash));
5534 assert_eq!(removed.len(), 1, "Should remove exactly 1 transaction");
5535
5536 let (pending, _) = pool.pending_and_queued_txn_count();
5538 assert_eq!(
5539 pending, 1,
5540 "Should have 1 pending transaction after removal"
5541 );
5542
5543 pool.assert_invariants();
5545 }
5546
5547 #[test]
5548 fn remove_expiring_nonce_tx_by_hash_updates_pending_count() {
5549 let mut pool = AA2dPool::default();
5550 let sender = Address::random();
5551
5552 let tx = TxBuilder::aa(sender)
5553 .nonce_key(U256::MAX)
5554 .max_priority_fee(1_000_000_000)
5555 .max_fee(2_000_000_000)
5556 .build();
5557 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
5558 let tx_hash = *valid_tx.hash();
5559
5560 pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1)
5561 .unwrap();
5562
5563 let (pending, _) = pool.pending_and_queued_txn_count();
5564 assert_eq!(pending, 1);
5565 pool.assert_invariants();
5566
5567 let removed = pool.remove_transactions(std::iter::once(&tx_hash));
5569 assert_eq!(removed.len(), 1);
5570
5571 let (pending, _) = pool.pending_and_queued_txn_count();
5572 assert_eq!(pending, 0);
5573 pool.assert_invariants();
5574 }
5575
5576 #[test]
5577 fn remove_expiring_nonce_tx_by_sender_updates_pending_count() {
5578 let mut pool = AA2dPool::default();
5579 let sender = Address::random();
5580
5581 let tx1 = TxBuilder::aa(sender)
5582 .nonce_key(U256::MAX)
5583 .max_priority_fee(1_000_000_000)
5584 .max_fee(2_000_000_000)
5585 .build();
5586 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
5587
5588 let tx2 = TxBuilder::aa(sender)
5589 .nonce_key(U256::MAX)
5590 .max_priority_fee(1_100_000_000)
5591 .max_fee(2_200_000_000)
5592 .build();
5593 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
5594
5595 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
5596 .unwrap();
5597 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
5598 .unwrap();
5599
5600 let (pending, _) = pool.pending_and_queued_txn_count();
5601 assert_eq!(pending, 2);
5602 pool.assert_invariants();
5603
5604 let removed = pool.remove_transactions_by_sender(sender);
5606 assert_eq!(removed.len(), 2);
5607
5608 let (pending, _) = pool.pending_and_queued_txn_count();
5609 assert_eq!(pending, 0);
5610 pool.assert_invariants();
5611 }
5612
5613 #[test]
5614 fn test_rejected_2d_tx_does_not_leak_slot_entries() {
5615 let config = AA2dPoolConfig {
5616 price_bump_config: PriceBumpConfig::default(),
5617 pending_limit: SubPoolLimit {
5618 max_txs: 1000,
5619 max_size: usize::MAX,
5620 },
5621 queued_limit: SubPoolLimit {
5622 max_txs: 1000,
5623 max_size: usize::MAX,
5624 },
5625 max_txs_per_sender: 1,
5626 };
5627 let mut pool = AA2dPool::new(config);
5628 let sender = Address::random();
5629
5630 let tx0 = TxBuilder::aa(sender)
5631 .nonce_key(U256::from(1))
5632 .nonce(0)
5633 .build();
5634 pool.add_transaction(
5635 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
5636 0,
5637 TempoHardfork::T1,
5638 )
5639 .unwrap();
5640
5641 assert_eq!(pool.slot_to_seq_id.len(), 1);
5642 assert_eq!(pool.seq_id_to_slot.len(), 1);
5643
5644 for i in 2..12u64 {
5645 let tx = TxBuilder::aa(sender)
5646 .nonce_key(U256::from(i))
5647 .nonce(0)
5648 .build();
5649 let result = pool.add_transaction(
5650 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5651 0,
5652 TempoHardfork::T1,
5653 );
5654 assert!(
5655 result.is_err(),
5656 "tx with nonce_key {i} should be rejected by sender limit"
5657 );
5658 }
5659
5660 assert_eq!(
5661 pool.slot_to_seq_id.len(),
5662 1,
5663 "rejected txs with new nonce keys should not grow slot_to_seq_id"
5664 );
5665 assert_eq!(
5666 pool.seq_id_to_slot.len(),
5667 1,
5668 "rejected txs with new nonce keys should not grow seq_id_to_slot"
5669 );
5670
5671 pool.assert_invariants();
5672 }
5673}