1use crate::{
3 metrics::AA2dPoolMetrics, ordering::TempoTipOrdering, transaction::TempoPooledTransaction,
4};
5use alloy_consensus::Transaction;
6use alloy_primitives::{
7 Address, B256, TxHash, U256,
8 map::{AddressMap, B256Map, HashMap, HashSet, U256Map, hash_map},
9};
10use reth_primitives_traits::transaction::error::InvalidTransactionError;
11use reth_tracing::tracing::trace;
12use reth_transaction_pool::{
13 AllPoolTransactions, BestTransactions, GetPooledTransactionLimit, PoolResult, PoolTransaction,
14 PriceBumpConfig, Priority, SubPool, SubPoolLimit, TransactionOrdering, TransactionOrigin,
15 ValidPoolTransaction,
16 error::{InvalidPoolTransactionError, PoolError, PoolErrorKind},
17 pool::{AddedPendingTransaction, AddedTransaction, QueuedReason, pending::PendingTransaction},
18};
19use revm::database::BundleAccount;
20use std::{
21 borrow::Borrow,
22 collections::{
23 BTreeMap, BTreeSet,
24 Bound::{Excluded, Unbounded},
25 btree_map::Entry,
26 },
27 sync::{
28 Arc,
29 atomic::{AtomicBool, Ordering},
30 },
31};
32use tempo_precompiles::NONCE_PRECOMPILE_ADDRESS;
33use tokio::sync::broadcast;
34
35type TxOrdering = TempoTipOrdering<TempoPooledTransaction>;
36type PoolUpdateResult = (
37 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
38 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
39);
40#[derive(Debug)]
52pub struct AA2dPool {
53 submission_id: u64,
57 independent_transactions: HashMap<AASequenceId, AA2dStoredTransaction>,
63 by_id: BTreeMap<AA2dTransactionId, Arc<AA2dInternalTransaction>>,
69 by_hash: B256Map<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
71 expiring_nonce_txs: B256Map<AA2dStoredTransaction>,
74 expiring_nonce_eviction_order: BTreeSet<ExpiringNonceEvictionKey>,
83 slot_to_expiring_nonce_hash: U256Map<B256>,
87 state_update_nonce_changes: HashMap<AASequenceId, u64>,
89 state_update_included_expiring_nonce_hashes: Vec<B256>,
91 slot_to_seq_id: U256Map<AASequenceId>,
99 config: AA2dPoolConfig,
101 metrics: AA2dPoolMetrics,
103 by_eviction_order: BTreeSet<EvictionKey>,
110 base_fee: u64,
112 txs_by_sender: AddressMap<usize>,
117 pending_count: usize,
119 queued_count: usize,
121 new_transaction_notifier: broadcast::Sender<AA2dStoredTransaction>,
123}
124
125impl Default for AA2dPool {
126 fn default() -> Self {
127 Self::new(AA2dPoolConfig::default())
128 }
129}
130
131impl AA2dPool {
132 pub fn new(config: AA2dPoolConfig) -> Self {
134 let (new_transaction_notifier, _) = broadcast::channel(200);
135 Self {
136 submission_id: 0,
137 independent_transactions: Default::default(),
138 by_id: Default::default(),
139 by_hash: Default::default(),
140 expiring_nonce_txs: Default::default(),
141 expiring_nonce_eviction_order: Default::default(),
142 slot_to_expiring_nonce_hash: Default::default(),
143 state_update_nonce_changes: Default::default(),
144 state_update_included_expiring_nonce_hashes: Default::default(),
145 slot_to_seq_id: Default::default(),
146 config,
147 metrics: AA2dPoolMetrics::default(),
148 by_eviction_order: Default::default(),
149 base_fee: 0,
150 txs_by_sender: Default::default(),
151 pending_count: 0,
152 queued_count: 0,
153 new_transaction_notifier,
154 }
155 }
156
157 fn notify_new_pending(&self, tx: &AA2dStoredTransaction) {
159 if self.new_transaction_notifier.receiver_count() > 0 {
160 let _ = self.new_transaction_notifier.send(tx.clone());
161 }
162 }
163
164 fn update_metrics(&self) {
166 let (pending, queued) = self.pending_and_queued_txn_count();
167 let total = self.by_id.len() + self.expiring_nonce_txs.len();
168 self.metrics.set_transaction_counts(total, pending, queued);
169 }
170
171 pub(crate) fn set_base_fee(&mut self, base_fee: u64) {
172 if self.base_fee == base_fee {
173 return;
174 }
175
176 self.base_fee = base_fee;
177 self.rebuild_eviction_order();
178 }
179
180 fn rebuild_eviction_order(&mut self) {
181 self.by_eviction_order.clear();
182 for (id, tx) in &self.by_id {
183 self.by_eviction_order.insert(EvictionKey::with_base_fee(
184 Arc::clone(tx),
185 *id,
186 self.base_fee,
187 ));
188 }
189
190 self.expiring_nonce_eviction_order.clear();
191 for tx in self.expiring_nonce_txs.values() {
192 self.expiring_nonce_eviction_order.insert(
193 ExpiringNonceEvictionKey::from_pending_with_base_fee(tx, self.base_fee),
194 );
195 }
196 }
197
198 pub(crate) fn add_transaction(
208 &mut self,
209 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
210 on_chain_nonce: u64,
211 hardfork: tempo_chainspec::hardfork::TempoHardfork,
212 ) -> PoolResult<AddedTransaction<TempoPooledTransaction>> {
213 debug_assert!(
214 transaction.transaction.is_aa(),
215 "only AA transactions are supported"
216 );
217 if hardfork.is_t1() && transaction.transaction.is_expiring_nonce() {
223 return self.add_expiring_nonce_transaction(transaction);
224 }
225
226 if self.contains(transaction.hash()) {
227 return Err(PoolError::new(
228 *transaction.hash(),
229 PoolErrorKind::AlreadyImported,
230 ));
231 }
232
233 let tx_id = transaction
234 .transaction
235 .aa_transaction_id()
236 .expect("Transaction added to AA2D pool must be an AA transaction");
237
238 if transaction.nonce() < on_chain_nonce {
239 return Err(PoolError::new(
241 *transaction.hash(),
242 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
243 InvalidTransactionError::NonceNotConsistent {
244 tx: transaction.nonce(),
245 state: on_chain_nonce,
246 },
247 )),
248 ));
249 }
250
251 let tx = Arc::new(AA2dInternalTransaction {
253 inner: AA2dStoredTransaction::new(self.next_id(), transaction.clone()),
254 is_pending: AtomicBool::new(false),
255 });
256
257 let sender = transaction.sender();
260 let replaced = match self.by_id.entry(tx_id) {
261 Entry::Occupied(mut entry) => {
262 if entry
264 .get()
265 .inner
266 .transaction
267 .is_underpriced(&tx.inner.transaction, &self.config.price_bump_config)
268 {
269 return Err(PoolError::new(
270 *transaction.hash(),
271 PoolErrorKind::ReplacementUnderpriced,
272 ));
273 }
274
275 let replaced = entry.insert(Arc::clone(&tx));
276 self.remove_from_counts(replaced.is_pending());
277 self.queued_count += 1;
278 Some(replaced)
279 }
280 Entry::Vacant(entry) => {
281 match self.txs_by_sender.entry(sender) {
284 hash_map::Entry::Occupied(mut count) => {
285 if *count.get() >= self.config.max_txs_per_sender {
286 return Err(PoolError::new(
287 *transaction.hash(),
288 PoolErrorKind::SpammerExceededCapacity(sender),
289 ));
290 }
291 *count.get_mut() += 1;
292 }
293 hash_map::Entry::Vacant(count) => {
294 if self.config.max_txs_per_sender == 0 {
295 return Err(PoolError::new(
296 *transaction.hash(),
297 PoolErrorKind::SpammerExceededCapacity(sender),
298 ));
299 }
300 count.insert(1);
301 }
302 }
303
304 entry.insert(Arc::clone(&tx));
305 self.queued_count += 1;
306 None
307 }
308 };
309
310 if transaction.transaction.is_aa_2d() {
314 self.record_2d_slot(&transaction.transaction);
315 }
316
317 if let Some(replaced) = &replaced {
319 self.by_hash.remove(replaced.inner.transaction.hash());
322 self.remove_eviction_key(replaced);
324 }
325
326 self.by_hash
328 .insert(*tx.inner.transaction.hash(), tx.inner.transaction.clone());
329
330 let mut promoted = Vec::new();
332 let mut inserted_as_pending = false;
334 let mut newly_pending = 0usize;
335 let on_chain_id = AA2dTransactionId::new(tx_id.seq_id, on_chain_nonce);
337 let mut next_nonce = on_chain_id.nonce;
339
340 for (existing_id, existing_tx) in self.descendant_txs(&on_chain_id) {
343 if existing_id.nonce == next_nonce {
344 match existing_id.nonce.cmp(&tx_id.nonce) {
345 std::cmp::Ordering::Less => {
346 }
348 std::cmp::Ordering::Equal => {
349 if !existing_tx.set_pending(true) {
350 newly_pending += 1;
351 }
352 inserted_as_pending = true;
353 }
354 std::cmp::Ordering::Greater => {
355 let was_pending = existing_tx.set_pending(true);
357 if !was_pending {
358 newly_pending += 1;
359 promoted.push(existing_tx.inner.clone());
360 } else {
361 break;
364 }
365 }
366 }
367 next_nonce = existing_id.nonce.saturating_add(1);
369 } else {
370 break;
372 }
373 }
374 self.pending_count += newly_pending;
375 self.queued_count -= newly_pending;
376
377 self.metrics.inc_inserted();
379
380 let new_tx_eviction_key = EvictionKey::with_base_fee(Arc::clone(&tx), tx_id, self.base_fee);
382 self.by_eviction_order.insert(new_tx_eviction_key);
383
384 if inserted_as_pending {
385 if !promoted.is_empty() {
386 self.metrics.inc_promoted(promoted.len());
387 }
388 if tx_id.nonce == on_chain_nonce {
390 self.independent_transactions
391 .insert(tx_id.seq_id, tx.inner.clone());
392 }
393
394 self.notify_new_pending(&tx.inner);
396 for promoted_tx in &promoted {
397 self.notify_new_pending(promoted_tx);
398 }
399
400 return Ok(AddedTransaction::Pending(AddedPendingTransaction {
401 transaction,
402 replaced: replaced.map(|tx| tx.inner.transaction.clone()),
403 promoted: promoted.into_iter().map(|tx| tx.transaction).collect(),
404 discarded: self.discard(),
405 }));
406 }
407
408 let _ = self.discard();
410
411 Ok(AddedTransaction::Parked {
412 transaction,
413 replaced: replaced.map(|tx| tx.inner.transaction.clone()),
414 subpool: SubPool::Queued,
415 queued_reason: Some(QueuedReason::NonceGap),
416 })
417 }
418
419 fn add_expiring_nonce_transaction(
425 &mut self,
426 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
427 ) -> PoolResult<AddedTransaction<TempoPooledTransaction>> {
428 let tx_hash = *transaction.hash();
429 let expiring_nonce_hash = transaction.transaction.precomputed_expiring_nonce_hash();
430
431 let expiring_nonce_entry = match self.expiring_nonce_txs.entry(expiring_nonce_hash) {
432 hash_map::Entry::Occupied(_) => {
433 return Err(PoolError::new(tx_hash, PoolErrorKind::AlreadyImported));
434 }
435 hash_map::Entry::Vacant(entry) => entry,
436 };
437
438 let sender = transaction.sender();
440 match self.txs_by_sender.entry(sender) {
441 hash_map::Entry::Occupied(mut count) => {
442 if *count.get() >= self.config.max_txs_per_sender {
443 return Err(PoolError::new(
444 tx_hash,
445 PoolErrorKind::SpammerExceededCapacity(sender),
446 ));
447 }
448 *count.get_mut() += 1;
449 }
450 hash_map::Entry::Vacant(count) => {
451 if self.config.max_txs_per_sender == 0 {
452 return Err(PoolError::new(
453 tx_hash,
454 PoolErrorKind::SpammerExceededCapacity(sender),
455 ));
456 }
457 count.insert(1);
458 }
459 }
460
461 let pending_tx = AA2dStoredTransaction {
463 submission_id: {
464 let id = self.submission_id;
465 self.submission_id = self.submission_id.wrapping_add(1);
466 id
467 },
468 transaction: transaction.clone(),
469 };
470 let eviction_key =
471 ExpiringNonceEvictionKey::from_pending_with_base_fee(&pending_tx, self.base_fee);
472 let pending_tx_update = if self.new_transaction_notifier.receiver_count() > 0 {
473 Some(pending_tx.clone())
474 } else {
475 None
476 };
477
478 expiring_nonce_entry.insert(pending_tx);
480 self.expiring_nonce_eviction_order.insert(eviction_key);
481 if let Some(slot) = transaction.transaction.expiring_nonce_slot() {
482 self.slot_to_expiring_nonce_hash
483 .insert(slot, expiring_nonce_hash);
484 }
485 self.by_hash.insert(tx_hash, transaction.clone());
486
487 self.pending_count += 1;
488
489 trace!(target: "txpool", hash = %tx_hash, "Added expiring nonce transaction");
490
491 self.update_metrics();
492
493 if let Some(pending_tx) = pending_tx_update {
495 let _ = self.new_transaction_notifier.send(pending_tx);
496 }
497
498 Ok(AddedTransaction::Pending(AddedPendingTransaction {
500 transaction,
501 replaced: None,
502 promoted: vec![],
503 discarded: self.discard(),
504 }))
505 }
506
507 pub(crate) fn pending_and_queued_txn_count(&self) -> (usize, usize) {
509 (self.pending_count, self.queued_count)
510 }
511
512 #[cfg(test)]
514 fn scan_pending_and_queued_txn_count(&self) -> (usize, usize) {
515 let (pending_2d, queued_2d) = self.by_id.values().fold((0, 0), |mut acc, tx| {
516 if tx.is_pending() {
517 acc.0 += 1;
518 } else {
519 acc.1 += 1;
520 }
521 acc
522 });
523 let expiring_pending = self.expiring_nonce_txs.len();
525 (pending_2d + expiring_pending, queued_2d)
526 }
527
528 pub(crate) fn get_transactions_by_origin_iter(
530 &self,
531 origin: TransactionOrigin,
532 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
533 let regular = self
534 .by_id
535 .values()
536 .filter(move |tx| tx.inner.transaction.origin == origin)
537 .map(|tx| tx.inner.transaction.clone());
538 let expiring = self
539 .expiring_nonce_txs
540 .values()
541 .filter(move |tx| tx.transaction.origin == origin)
542 .map(|tx| tx.transaction.clone());
543 regular.chain(expiring)
544 }
545
546 pub(crate) fn get_pending_transactions_by_origin_iter(
548 &self,
549 origin: TransactionOrigin,
550 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
551 let regular = self
552 .by_id
553 .values()
554 .filter(move |tx| tx.is_pending() && tx.inner.transaction.origin == origin)
555 .map(|tx| tx.inner.transaction.clone());
556 let expiring = self
558 .expiring_nonce_txs
559 .values()
560 .filter(move |tx| tx.transaction.origin == origin)
561 .map(|tx| tx.transaction.clone());
562 regular.chain(expiring)
563 }
564
565 pub(crate) fn get_transactions_by_sender_iter(
567 &self,
568 sender: Address,
569 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
570 let regular = self
571 .by_id
572 .values()
573 .filter(move |tx| tx.inner.transaction.sender() == sender)
574 .map(|tx| tx.inner.transaction.clone());
575 let expiring = self
576 .expiring_nonce_txs
577 .values()
578 .filter(move |tx| tx.transaction.sender() == sender)
579 .map(|tx| tx.transaction.clone());
580 regular.chain(expiring)
581 }
582
583 pub(crate) fn all_transaction_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
585 self.by_hash.keys().copied()
586 }
587
588 pub(crate) fn queued_transactions(
590 &self,
591 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
592 self.by_id
593 .values()
594 .filter(|tx| !tx.is_pending())
595 .map(|tx| tx.inner.transaction.clone())
596 }
597
598 pub(crate) fn pending_transactions(
600 &self,
601 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> + '_ {
602 let regular_pending = self
604 .by_id
605 .values()
606 .filter(|tx| tx.is_pending())
607 .map(|tx| tx.inner.transaction.clone());
608 let expiring_pending = self
609 .expiring_nonce_txs
610 .values()
611 .map(|tx| tx.transaction.clone());
612 regular_pending.chain(expiring_pending)
613 }
614
615 pub(crate) fn append_all_transactions(
617 &self,
618 transactions: &mut AllPoolTransactions<TempoPooledTransaction>,
619 ) {
620 transactions.pending.reserve(self.pending_count);
621 transactions.queued.reserve(self.queued_count);
622
623 for tx in self.by_id.values() {
624 if tx.is_pending() {
625 transactions.pending.push(tx.inner.transaction.clone());
626 } else {
627 transactions.queued.push(tx.inner.transaction.clone());
628 }
629 }
630
631 transactions.pending.extend(
632 self.expiring_nonce_txs
633 .values()
634 .map(|tx| tx.transaction.clone()),
635 );
636 }
637
638 pub(crate) fn best_transactions(&self) -> BestAA2dTransactions {
640 self.best_transactions_with_base_fee(self.base_fee)
641 }
642
643 #[expect(clippy::mutable_key_type)]
645 pub(crate) fn best_transactions_with_base_fee(&self, base_fee: u64) -> BestAA2dTransactions {
646 let expiring_nonce_order = if base_fee == self.base_fee {
647 self.expiring_nonce_eviction_order.clone()
648 } else {
649 self.expiring_nonce_txs
650 .values()
651 .map(|tx| ExpiringNonceEvictionKey::from_pending_with_base_fee(tx, base_fee))
652 .collect()
653 };
654 let independent = self
655 .independent_transactions
656 .values()
657 .filter_map(|tx| {
658 let id = tx
659 .transaction
660 .transaction
661 .aa_transaction_id()
662 .expect("Independent transaction must have AA transaction ID");
663 let tx = self.by_id.get(&id)?;
664 Some(tx.inner.clone_into_pending(base_fee))
665 })
666 .collect();
667
668 BestAA2dTransactions {
669 independent,
670 by_id: self
671 .by_id
672 .iter()
673 .filter(|(_, tx)| tx.is_pending())
674 .map(|(id, tx)| (*id, tx.inner.clone()))
675 .collect(),
676 expiring_nonce_order,
677 invalid: Default::default(),
678 new_transaction_receiver: Some(self.new_transaction_notifier.subscribe()),
679 last_priority: None,
680 base_fee,
681 }
682 }
683
684 pub(crate) fn get(
686 &self,
687 tx_hash: &TxHash,
688 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
689 self.by_hash.get(tx_hash).cloned()
690 }
691
692 pub(crate) fn get_all<'a, I>(
694 &self,
695 tx_hashes: I,
696 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
697 where
698 I: Iterator<Item = &'a TxHash> + 'a,
699 {
700 let mut ret = Vec::new();
701 for tx_hash in tx_hashes {
702 if let Some(tx) = self.get(tx_hash) {
703 ret.push(tx);
704 }
705 }
706 ret
707 }
708
709 pub(crate) fn append_pooled_transaction_elements<'a>(
716 &self,
717 tx_hashes: impl IntoIterator<Item = &'a TxHash>,
718 limit: GetPooledTransactionLimit,
719 accumulated_size: &mut usize,
720 out: &mut Vec<<TempoPooledTransaction as PoolTransaction>::Pooled>,
721 ) {
722 for tx_hash in tx_hashes {
723 let Some(tx) = self.by_hash.get(tx_hash) else {
724 continue;
725 };
726
727 let encoded_len = tx.transaction.encoded_length();
728 let Some(pooled) = tx.transaction.clone_into_pooled().ok() else {
729 continue;
730 };
731
732 *accumulated_size += encoded_len;
733 out.push(pooled.into_inner());
734
735 if limit.exceeds(*accumulated_size) {
736 break;
737 }
738 }
739 }
740
741 pub(crate) fn senders_iter(&self) -> impl Iterator<Item = &Address> {
743 let regular = self
744 .by_id
745 .values()
746 .map(|tx| tx.inner.transaction.sender_ref());
747 let expiring = self
748 .expiring_nonce_txs
749 .values()
750 .map(|tx| tx.transaction.sender_ref());
751 regular.chain(expiring)
752 }
753
754 fn descendant_txs<'a, 'b: 'a>(
759 &'a self,
760 id: &'b AA2dTransactionId,
761 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a Arc<AA2dInternalTransaction>)> + 'a {
762 self.by_id
763 .range(id..)
764 .take_while(|(other, _)| id.seq_id == other.seq_id)
765 }
766
767 fn descendant_txs_exclusive<'a, 'b: 'a>(
771 &'a self,
772 id: &'b AA2dTransactionId,
773 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a Arc<AA2dInternalTransaction>)> + 'a {
774 self.by_id
775 .range((Excluded(id), Unbounded))
776 .take_while(|(other, _)| id.seq_id == other.seq_id)
777 }
778
779 fn remove_transaction_by_id(
783 &mut self,
784 id: &AA2dTransactionId,
785 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
786 let tx = self.by_id.remove(id)?;
787
788 self.remove_eviction_key(&tx);
790
791 if self.by_id.range(id.seq_id.range()).next().is_none()
793 && let Some(slot) = tx.inner.transaction.transaction.nonce_key_slot()
794 {
795 self.slot_to_seq_id.remove(&slot);
796 }
797
798 self.remove_independent(id);
799 let removed_tx = tx.inner.transaction.clone();
800 self.by_hash.remove(removed_tx.hash());
801 self.remove_from_counts(tx.is_pending());
802
803 self.decrement_sender_count(removed_tx.sender());
805
806 Some(removed_tx)
807 }
808
809 fn decrement_sender_count(&mut self, sender: Address) {
811 if let hash_map::Entry::Occupied(mut entry) = self.txs_by_sender.entry(sender) {
812 let count = entry.get_mut();
813 *count -= 1;
814 if *count == 0 {
815 entry.remove();
816 }
817 }
818 }
819
820 fn remove_eviction_key(&mut self, tx: &Arc<AA2dInternalTransaction>) {
821 self.by_eviction_order.remove(&EvictionOrderKey::new(
822 TempoTipOrdering::default().priority(&tx.inner.transaction.transaction, self.base_fee),
823 tx.inner.submission_id,
824 ));
825 }
826
827 fn remove_independent(&mut self, id: &AA2dTransactionId) -> Option<AA2dStoredTransaction> {
829 match self.independent_transactions.entry(id.seq_id) {
831 hash_map::Entry::Occupied(entry) => {
832 if entry.get().transaction.nonce() == id.nonce {
834 return Some(entry.remove());
835 }
836 }
837 hash_map::Entry::Vacant(_) => {}
838 };
839 None
840 }
841
842 pub(crate) fn remove_transactions<'a, I>(
847 &mut self,
848 tx_hashes: I,
849 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
850 where
851 I: Iterator<Item = &'a TxHash> + 'a,
852 {
853 let mut txs = Vec::new();
854 let mut seq_ids_to_demote: HashMap<AASequenceId, u64> = HashMap::default();
855
856 for tx_hash in tx_hashes {
857 if let Some((tx, seq_id)) = self.remove_transaction_by_hash_no_demote(tx_hash) {
858 if let Some(id) = seq_id {
859 seq_ids_to_demote
860 .entry(id.seq_id)
861 .and_modify(|min_nonce| {
862 if id.nonce < *min_nonce {
863 *min_nonce = id.nonce;
864 }
865 })
866 .or_insert(id.nonce);
867 }
868 txs.push(tx);
869 }
870 }
871
872 for (seq_id, min_nonce) in seq_ids_to_demote {
874 self.demote_from_nonce(&seq_id, min_nonce);
875 }
876
877 txs
878 }
879
880 fn remove_transaction_by_hash(
885 &mut self,
886 tx_hash: &B256,
887 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
888 let (tx, id) = self.remove_transaction_by_hash_no_demote(tx_hash)?;
889
890 if let Some(id) = id {
892 self.demote_descendants(&id);
893 }
894
895 Some(tx)
896 }
897
898 fn remove_transaction_by_hash_no_demote(
902 &mut self,
903 tx_hash: &B256,
904 ) -> Option<(
905 Arc<ValidPoolTransaction<TempoPooledTransaction>>,
906 Option<AA2dTransactionId>,
907 )> {
908 let tx = self.by_hash.remove(tx_hash)?;
909
910 if tx.transaction.is_expiring_nonce() {
912 let tx =
913 self.remove_expiring_nonce_tx(&tx.transaction.precomputed_expiring_nonce_hash())?;
914 return Some((tx, None));
915 }
916
917 let id = tx
919 .transaction
920 .aa_transaction_id()
921 .expect("is AA transaction");
922 self.remove_transaction_by_id(&id)?;
923
924 Some((tx, Some(id)))
925 }
926
927 fn demote_descendants(&mut self, id: &AA2dTransactionId) {
932 self.demote_from_nonce(&id.seq_id, id.nonce);
933 }
934
935 fn demote_from_nonce(&mut self, seq_id: &AASequenceId, min_nonce: u64) {
940 let start_id = AA2dTransactionId::new(*seq_id, min_nonce);
941 for (_, tx) in self
942 .by_id
943 .range((Excluded(&start_id), Unbounded))
944 .take_while(|(other, _)| *seq_id == other.seq_id)
945 {
946 if tx.set_pending(false) {
947 self.pending_count -= 1;
948 self.queued_count += 1;
949 }
950 }
951 }
952
953 pub(crate) fn remove_transactions_and_descendants<'a, I>(
956 &mut self,
957 hashes: I,
958 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
959 where
960 I: Iterator<Item = &'a TxHash> + 'a,
961 {
962 let mut removed = Vec::new();
963 for hash in hashes {
964 if let Some(tx) = self.remove_transaction_by_hash(hash) {
965 let id = tx.transaction.aa_transaction_id();
966 removed.push(tx);
967 if let Some(id) = id {
968 self.remove_descendants(&id, &mut removed);
969 }
970 }
971 }
972 removed
973 }
974
975 pub(crate) fn remove_transactions_by_sender(
977 &mut self,
978 sender_id: Address,
979 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
980 let mut removed = Vec::new();
981 let txs = self
982 .get_transactions_by_sender_iter(sender_id)
983 .collect::<Vec<_>>();
984 for tx in txs {
985 if tx.transaction.is_expiring_nonce() {
986 if let Some(tx) =
987 self.remove_expiring_nonce_tx(&tx.transaction.precomputed_expiring_nonce_hash())
988 {
989 removed.push(tx);
990 }
991 } else if let Some(tx) = tx
992 .transaction
993 .aa_transaction_id()
994 .and_then(|id| self.remove_transaction_by_id(&id))
995 {
996 removed.push(tx);
997 }
998 }
999 removed
1000 }
1001
1002 fn remove_descendants(
1006 &mut self,
1007 tx: &AA2dTransactionId,
1008 removed: &mut Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
1009 ) {
1010 let mut id = *tx;
1011
1012 loop {
1014 let descendant = self.descendant_txs_exclusive(&id).map(|(id, _)| *id).next();
1015 if let Some(descendant) = descendant {
1016 if let Some(tx) = self.remove_transaction_by_id(&descendant) {
1017 removed.push(tx)
1018 }
1019 id = descendant;
1020 } else {
1021 return;
1022 }
1023 }
1024 }
1025
1026 #[cfg(test)]
1032 pub(crate) fn on_nonce_changes(
1033 &mut self,
1034 on_chain_ids: HashMap<AASequenceId, u64>,
1035 ) -> PoolUpdateResult {
1036 trace!(target: "txpool::2d", ?on_chain_ids, "processing nonce changes");
1037
1038 self.on_nonce_changes_iter(on_chain_ids)
1039 }
1040
1041 fn on_nonce_changes_iter(
1042 &mut self,
1043 on_chain_ids: impl IntoIterator<Item = (AASequenceId, u64)>,
1044 ) -> PoolUpdateResult {
1045 let mut promoted = Vec::new();
1046 let mut mined_ids = Vec::new();
1047
1048 'changes: for (sender_id, on_chain_nonce) in on_chain_ids {
1050 let mut iter = self
1051 .by_id
1052 .range_mut((sender_id.start_bound(), Unbounded))
1053 .take_while(move |(other, _)| sender_id == other.seq_id)
1054 .peekable();
1055
1056 let Some(mut current) = iter.next() else {
1057 continue;
1058 };
1059
1060 'mined: loop {
1062 if current.0.nonce < on_chain_nonce {
1063 mined_ids.push(*current.0);
1064 let Some(next) = iter.next() else {
1065 continue 'changes;
1066 };
1067 current = next;
1068 } else {
1069 break 'mined;
1070 }
1071 }
1072
1073 let mut next_nonce = on_chain_nonce;
1075 let mut newly_pending = 0usize;
1076 let mut newly_queued = 0usize;
1077 for (existing_id, existing_tx) in std::iter::once(current).chain(iter) {
1078 if existing_id.nonce == next_nonce {
1079 let was_pending = existing_tx.set_pending(true);
1081 if !was_pending {
1082 newly_pending += 1;
1083 promoted.push(existing_tx.inner.transaction.clone());
1084 }
1085
1086 if existing_id.nonce == on_chain_nonce {
1087 self.independent_transactions
1089 .insert(existing_id.seq_id, existing_tx.inner.clone());
1090 }
1091
1092 next_nonce = next_nonce.saturating_add(1);
1093 } else {
1094 if existing_tx.set_pending(false) {
1096 newly_queued += 1;
1097 }
1098 }
1099 }
1100 self.pending_count += newly_pending;
1101 self.queued_count -= newly_pending;
1102 self.pending_count -= newly_queued;
1103 self.queued_count += newly_queued;
1104
1105 if next_nonce == on_chain_nonce {
1109 self.independent_transactions.remove(&sender_id);
1110 }
1111 }
1112
1113 let mut mined = Vec::with_capacity(mined_ids.len());
1115 for id in mined_ids {
1116 if let Some(removed) = self.remove_transaction_by_id(&id) {
1117 mined.push(removed);
1118 }
1119 }
1120
1121 (promoted, mined)
1122 }
1123
1124 fn discard(&mut self) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1137 let mut removed = Vec::new();
1138
1139 let (pending_count, queued_count) = self.pending_and_queued_txn_count();
1141
1142 if queued_count > self.config.queued_limit.max_txs {
1144 let queued_excess = queued_count - self.config.queued_limit.max_txs;
1145 self.evict_lowest_priority(queued_excess, false, &mut removed);
1146 }
1147
1148 if pending_count > self.config.pending_limit.max_txs {
1150 let pending_excess = pending_count - self.config.pending_limit.max_txs;
1151 self.evict_lowest_priority(pending_excess, true, &mut removed);
1152 }
1153
1154 if !removed.is_empty() {
1155 self.metrics.inc_removed(removed.len());
1156 }
1157
1158 removed
1159 }
1160
1161 fn evict_lowest_priority(
1167 &mut self,
1168 count: usize,
1169 evict_pending: bool,
1170 removed: &mut Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
1171 ) {
1172 if count == 0 {
1173 return;
1174 }
1175
1176 removed.reserve(count);
1177
1178 if evict_pending {
1179 for _ in 0..count {
1181 if let Some(tx) = self.evict_one_pending() {
1182 removed.push(tx);
1183 } else {
1184 break;
1185 }
1186 }
1187 } else {
1188 let to_remove: Vec<_> = self
1190 .by_eviction_order
1191 .iter()
1192 .filter(|key| !key.is_pending())
1193 .map(|key| key.tx_id)
1194 .take(count)
1195 .collect();
1196
1197 for id in to_remove {
1198 if let Some(tx) = self.remove_transaction_by_id(&id) {
1199 removed.push(tx);
1200 }
1201 }
1202 }
1203 }
1204
1205 fn evict_one_pending(&mut self) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1208 let worst_2d = self
1209 .by_eviction_order
1210 .iter()
1211 .find(|key| key.is_pending())
1212 .map(|key| (key.tx_id, key.priority().clone(), key.submission_id()));
1213
1214 let worst_expiring = self
1215 .expiring_nonce_eviction_order
1216 .first()
1217 .map(|key| (key.priority().clone(), key.submission_id()));
1218
1219 match (worst_2d, worst_expiring) {
1220 (Some((id, pri_2d, sid_2d)), Some((pri_exp, sid_exp))) => {
1221 let evict_expiring = pri_exp
1223 .cmp(&pri_2d)
1224 .then_with(|| sid_2d.cmp(&sid_exp))
1225 .is_le();
1226 if evict_expiring {
1227 self.evict_worst_expiring_nonce_tx()
1228 } else {
1229 self.evict_2d_pending_tx(&id)
1230 }
1231 }
1232 (Some((id, ..)), None) => self.evict_2d_pending_tx(&id),
1233 (None, Some(_)) => self.evict_worst_expiring_nonce_tx(),
1234 (None, None) => None,
1235 }
1236 }
1237
1238 fn evict_2d_pending_tx(
1240 &mut self,
1241 id: &AA2dTransactionId,
1242 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1243 let tx = self.remove_transaction_by_id(id)?;
1244 self.demote_descendants(id);
1245 Some(tx)
1246 }
1247
1248 fn evict_worst_expiring_nonce_tx(
1254 &mut self,
1255 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1256 let eviction_key = self.expiring_nonce_eviction_order.pop_first()?;
1257 let pending_tx = self
1258 .expiring_nonce_txs
1259 .remove(&eviction_key.expiring_hash())?;
1260
1261 Some(self.remove_expiring_nonce_pending_tx(pending_tx))
1262 }
1263
1264 fn remove_expiring_nonce_tx(
1270 &mut self,
1271 expiring_hash: &B256,
1272 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1273 let pending_tx = self.expiring_nonce_txs.remove(expiring_hash)?;
1274 self.expiring_nonce_eviction_order
1275 .remove(&EvictionOrderKey::new(
1276 TempoTipOrdering::default()
1277 .priority(&pending_tx.transaction.transaction, self.base_fee),
1278 pending_tx.submission_id,
1279 ));
1280 Some(self.remove_expiring_nonce_pending_tx(pending_tx))
1281 }
1282
1283 fn remove_expiring_nonce_pending_tx(
1289 &mut self,
1290 pending_tx: AA2dStoredTransaction,
1291 ) -> Arc<ValidPoolTransaction<TempoPooledTransaction>> {
1292 self.by_hash.remove(pending_tx.transaction.hash());
1293 if let Some(slot) = pending_tx.transaction.transaction.expiring_nonce_slot() {
1294 self.slot_to_expiring_nonce_hash.remove(&slot);
1295 }
1296 self.decrement_sender_count(pending_tx.transaction.sender());
1297 self.pending_count -= 1;
1298 pending_tx.transaction
1299 }
1300
1301 fn remove_from_counts(&mut self, pending: bool) {
1302 if pending {
1303 self.pending_count -= 1;
1304 } else {
1305 self.queued_count -= 1;
1306 }
1307 }
1308
1309 pub fn metrics(&self) -> &AA2dPoolMetrics {
1311 &self.metrics
1312 }
1313
1314 pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
1316 self.by_hash.contains_key(tx_hash)
1317 }
1318
1319 pub(crate) fn pooled_transactions_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
1321 self.by_hash
1322 .values()
1323 .filter(|tx| tx.propagate)
1324 .map(|tx| *tx.hash())
1325 }
1326
1327 pub(crate) fn pooled_transactions_iter(
1329 &self,
1330 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
1331 self.by_hash.values().filter(|tx| tx.propagate).cloned()
1332 }
1333
1334 const fn next_id(&mut self) -> u64 {
1335 let id = self.submission_id;
1336 self.submission_id = self.submission_id.wrapping_add(1);
1337 id
1338 }
1339
1340 fn record_2d_slot(&mut self, transaction: &TempoPooledTransaction) {
1342 let address = transaction.sender();
1343 let nonce_key = transaction.nonce_key().unwrap_or_default();
1344 let Some(slot) = transaction.nonce_key_slot() else {
1345 return;
1346 };
1347
1348 trace!(target: "txpool::2d", ?address, ?nonce_key, "recording 2d nonce slot");
1349 let seq_id = AASequenceId::new(address, nonce_key);
1350
1351 if self.slot_to_seq_id.insert(slot, seq_id).is_none() {
1352 self.metrics.inc_nonce_key_count(1);
1353 }
1354 }
1355
1356 pub(crate) fn on_state_updates(
1358 &mut self,
1359 state: &AddressMap<BundleAccount>,
1360 ) -> PoolUpdateResult {
1361 self.state_update_nonce_changes.clear();
1362 self.state_update_included_expiring_nonce_hashes.clear();
1363
1364 let Some(nonce_state) = state.get(&NONCE_PRECOMPILE_ADDRESS) else {
1365 return (Vec::new(), Vec::new());
1366 };
1367
1368 let mut changes = std::mem::take(&mut self.state_update_nonce_changes);
1369 let mut included_expiring_nonce_hashes =
1370 std::mem::take(&mut self.state_update_included_expiring_nonce_hashes);
1371
1372 for (slot, value) in nonce_state.storage.iter() {
1374 if let Some(seq_id) = self.slot_to_seq_id.get(slot) {
1375 changes.insert(*seq_id, value.present_value.saturating_to());
1376 }
1377 if !value.present_value.is_zero()
1380 && let Some(expiring_nonce_hash) = self.slot_to_expiring_nonce_hash.get(slot)
1381 {
1382 included_expiring_nonce_hashes.push(*expiring_nonce_hash);
1383 }
1384 }
1385
1386 let (promoted, mut mined) = self.on_nonce_changes_iter(changes.drain());
1387
1388 for expiring_nonce_hash in included_expiring_nonce_hashes.drain(..) {
1390 if let Some(tx) = self.remove_expiring_nonce_tx(&expiring_nonce_hash) {
1391 mined.push(tx);
1392 }
1393 }
1394 self.state_update_nonce_changes = changes;
1395 self.state_update_included_expiring_nonce_hashes = included_expiring_nonce_hashes;
1396
1397 if !promoted.is_empty() {
1399 self.metrics.inc_promoted(promoted.len());
1400 }
1401 if !mined.is_empty() {
1402 self.metrics.inc_removed(mined.len());
1403 }
1404 self.update_metrics();
1405
1406 (promoted, mined)
1407 }
1408
1409 #[cfg(test)]
1411 pub(crate) fn assert_invariants(&self) {
1412 assert!(
1414 self.independent_transactions.len() <= self.by_id.len(),
1415 "independent_transactions.len() ({}) > by_id.len() ({})",
1416 self.independent_transactions.len(),
1417 self.by_id.len()
1418 );
1419 assert_eq!(
1421 self.by_id.len() + self.expiring_nonce_txs.len(),
1422 self.by_hash.len(),
1423 "by_id.len() ({}) + expiring_nonce_txs.len() ({}) != by_hash.len() ({})",
1424 self.by_id.len(),
1425 self.expiring_nonce_txs.len(),
1426 self.by_hash.len()
1427 );
1428 assert_eq!(
1429 self.expiring_nonce_txs.len(),
1430 self.expiring_nonce_eviction_order.len(),
1431 "expiring_nonce_txs.len() ({}) != expiring_nonce_eviction_order.len() ({})",
1432 self.expiring_nonce_txs.len(),
1433 self.expiring_nonce_eviction_order.len()
1434 );
1435 assert_eq!(
1436 self.by_id.len(),
1437 self.by_eviction_order.len(),
1438 "by_id.len() ({}) != by_eviction_order.len() ({})",
1439 self.by_id.len(),
1440 self.by_eviction_order.len()
1441 );
1442
1443 for (seq_id, independent_tx) in &self.independent_transactions {
1445 let tx_id = independent_tx
1446 .transaction
1447 .transaction
1448 .aa_transaction_id()
1449 .expect("Independent transaction must have AA transaction ID");
1450 assert!(
1451 self.by_id.contains_key(&tx_id),
1452 "Independent transaction {tx_id:?} not in by_id"
1453 );
1454 assert_eq!(
1455 seq_id, &tx_id.seq_id,
1456 "Independent transactions sequence ID {seq_id:?} does not match transaction sequence ID {tx_id:?}"
1457 );
1458
1459 let tx_in_pool = self.by_id.get(&tx_id).unwrap();
1461 assert!(
1462 tx_in_pool.is_pending(),
1463 "Independent transaction {tx_id:?} is not pending"
1464 );
1465
1466 assert_eq!(
1468 independent_tx.transaction.hash(),
1469 tx_in_pool.inner.transaction.hash(),
1470 "Independent transaction hash mismatch for {tx_id:?}"
1471 );
1472 }
1473
1474 let mut seen_senders = std::collections::HashSet::new();
1476 for id in self.independent_transactions.keys() {
1477 assert!(
1478 seen_senders.insert(*id),
1479 "Duplicate sender {id:?} in independent transactions"
1480 );
1481 }
1482
1483 for (hash, tx) in &self.by_hash {
1485 assert_eq!(
1487 tx.hash(),
1488 hash,
1489 "Hash mismatch in by_hash: expected {:?}, got {:?}",
1490 hash,
1491 tx.hash()
1492 );
1493
1494 if tx.transaction.is_expiring_nonce() {
1496 assert!(
1497 self.expiring_nonce_txs
1498 .contains_key(&tx.transaction.precomputed_expiring_nonce_hash()),
1499 "Expiring nonce transaction with hash {hash:?} in by_hash but not in expiring_nonce_txs"
1500 );
1501 continue;
1502 }
1503
1504 let id = tx
1506 .transaction
1507 .aa_transaction_id()
1508 .expect("Transaction in pool should be AA transaction");
1509 assert!(
1510 self.by_id.contains_key(&id),
1511 "Transaction with hash {hash:?} in by_hash but not in by_id"
1512 );
1513
1514 let tx_in_by_id = &self.by_id.get(&id).unwrap().inner.transaction;
1516 assert_eq!(
1517 tx.hash(),
1518 tx_in_by_id.hash(),
1519 "Transaction hash mismatch between by_hash and by_id for id {id:?}"
1520 );
1521 }
1522
1523 for (id, tx) in &self.by_id {
1525 let hash = tx.inner.transaction.hash();
1527 assert!(
1528 self.by_hash.contains_key(hash),
1529 "Transaction with id {id:?} in by_id but not in by_hash"
1530 );
1531
1532 let tx_id = tx
1534 .inner
1535 .transaction
1536 .transaction
1537 .aa_transaction_id()
1538 .expect("Transaction in pool should be AA transaction");
1539 assert_eq!(
1540 &tx_id, id,
1541 "Transaction ID mismatch: expected {id:?}, got {tx_id:?}"
1542 );
1543
1544 if let Some(independent_tx) = self.independent_transactions.get(&id.seq_id)
1546 && independent_tx.transaction.hash() == tx.inner.transaction.hash()
1547 {
1548 assert!(
1549 tx.is_pending(),
1550 "Transaction {id:?} is in independent set but not pending"
1551 );
1552 }
1553
1554 let expected_priority = TempoTipOrdering::default()
1555 .priority(&tx.inner.transaction.transaction, self.base_fee);
1556 let expected_order =
1557 EvictionOrderKey::new(expected_priority.clone(), tx.inner.submission_id);
1558 let Some(eviction_key) = self.by_eviction_order.get(&expected_order) else {
1559 panic!("Transaction with id {id:?} in by_id but not in by_eviction_order");
1560 };
1561 assert_eq!(
1562 eviction_key.tx_id, *id,
1563 "Eviction key for transaction {id:?} has mismatched transaction id"
1564 );
1565 assert_eq!(
1566 eviction_key.priority(),
1567 &expected_priority,
1568 "Eviction key for transaction {id:?} has stale priority"
1569 );
1570 assert_eq!(
1571 eviction_key.tx.inner.transaction.hash(),
1572 tx.inner.transaction.hash(),
1573 "Eviction key for transaction {id:?} has mismatched transaction hash"
1574 );
1575 }
1576
1577 for key in &self.by_eviction_order {
1578 let Some(tx) = self.by_id.get(&key.tx_id) else {
1579 panic!("Eviction key {:?} not in by_id", key.tx_id);
1580 };
1581 assert_eq!(
1582 key.submission_id(),
1583 tx.inner.submission_id,
1584 "Eviction key {:?} has mismatched submission id",
1585 key.tx_id
1586 );
1587 let expected_priority = TempoTipOrdering::default()
1588 .priority(&tx.inner.transaction.transaction, self.base_fee);
1589 assert_eq!(
1590 key.priority(),
1591 &expected_priority,
1592 "Eviction key {:?} has stale priority",
1593 key.tx_id
1594 );
1595 assert_eq!(
1596 key.tx.inner.transaction.hash(),
1597 tx.inner.transaction.hash(),
1598 "Eviction key {:?} has mismatched transaction hash",
1599 key.tx_id
1600 );
1601 }
1602
1603 let (pending_count, queued_count) = self.pending_and_queued_txn_count();
1606 let (scanned_pending, scanned_queued) = self.scan_pending_and_queued_txn_count();
1607 assert_eq!(
1608 (pending_count, queued_count),
1609 (scanned_pending, scanned_queued),
1610 "cached counts ({pending_count}, {queued_count}) != scanned counts ({scanned_pending}, {scanned_queued})"
1611 );
1612 assert_eq!(
1613 pending_count + queued_count,
1614 self.by_id.len() + self.expiring_nonce_txs.len(),
1615 "Pending ({}) + queued ({}) != total transactions (by_id: {} + expiring: {})",
1616 pending_count,
1617 queued_count,
1618 self.by_id.len(),
1619 self.expiring_nonce_txs.len()
1620 );
1621
1622 assert!(
1624 pending_count <= self.config.pending_limit.max_txs,
1625 "pending_count {} exceeds limit {}",
1626 pending_count,
1627 self.config.pending_limit.max_txs
1628 );
1629 assert!(
1630 queued_count <= self.config.queued_limit.max_txs,
1631 "queued_count {} exceeds limit {}",
1632 queued_count,
1633 self.config.queued_limit.max_txs
1634 );
1635
1636 for (hash, pending_tx) in &self.expiring_nonce_txs {
1638 let tx_hash = *pending_tx.transaction.hash();
1639 assert!(
1640 self.by_hash.contains_key(&tx_hash),
1641 "Expiring nonce tx {tx_hash:?} not in by_hash (expiring hash {hash:?})"
1642 );
1643 assert!(
1644 self.expiring_nonce_eviction_order
1645 .iter()
1646 .any(|key| key.expiring_hash() == *hash
1647 && key.submission_id() == pending_tx.submission_id),
1648 "Expiring nonce tx {tx_hash:?} not in expiring_nonce_eviction_order"
1649 );
1650 assert!(
1651 pending_tx.transaction.transaction.is_expiring_nonce(),
1652 "Transaction in expiring_nonce_txs is not an expiring nonce tx"
1653 );
1654 }
1655
1656 for key in &self.expiring_nonce_eviction_order {
1657 let expiring_hash = key.expiring_hash();
1658 let Some(pending_tx) = self.expiring_nonce_txs.get(&expiring_hash) else {
1659 panic!("Expiring nonce eviction key {expiring_hash:?} not in expiring_nonce_txs");
1660 };
1661 assert_eq!(
1662 key.submission_id(),
1663 pending_tx.submission_id,
1664 "Expiring nonce eviction key {expiring_hash:?} has mismatched submission id"
1665 );
1666 assert_eq!(
1667 key.transaction.hash(),
1668 pending_tx.transaction.hash(),
1669 "Expiring nonce eviction key {expiring_hash:?} has mismatched transaction hash"
1670 );
1671 }
1672 }
1673}
1674
1675pub const DEFAULT_MAX_TXS_PER_SENDER: usize = 16;
1679
1680#[derive(Debug, Clone)]
1682pub struct AA2dPoolConfig {
1683 pub price_bump_config: PriceBumpConfig,
1685 pub pending_limit: SubPoolLimit,
1687 pub queued_limit: SubPoolLimit,
1689 pub max_txs_per_sender: usize,
1693}
1694
1695impl Default for AA2dPoolConfig {
1696 fn default() -> Self {
1697 Self {
1698 price_bump_config: PriceBumpConfig::default(),
1699 pending_limit: SubPoolLimit::default(),
1700 queued_limit: SubPoolLimit::default(),
1701 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
1702 }
1703 }
1704}
1705
1706#[derive(Debug, Clone)]
1707struct AA2dStoredTransaction {
1708 submission_id: u64,
1709 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
1710}
1711
1712impl AA2dStoredTransaction {
1713 fn new(
1714 submission_id: u64,
1715 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
1716 ) -> Self {
1717 Self {
1718 submission_id,
1719 transaction,
1720 }
1721 }
1722
1723 fn clone_into_pending(&self, base_fee: u64) -> PendingTransaction<TxOrdering> {
1724 PendingTransaction {
1725 submission_id: self.submission_id,
1726 priority: TempoTipOrdering::default().priority(&self.transaction.transaction, base_fee),
1727 transaction: self.transaction.clone(),
1728 }
1729 }
1730}
1731
1732#[derive(Debug)]
1733struct AA2dInternalTransaction {
1734 inner: AA2dStoredTransaction,
1736 is_pending: AtomicBool,
1744}
1745
1746impl AA2dInternalTransaction {
1747 fn is_pending(&self) -> bool {
1749 self.is_pending.load(Ordering::Relaxed)
1750 }
1751
1752 fn set_pending(&self, pending: bool) -> bool {
1754 self.is_pending.swap(pending, Ordering::Relaxed)
1755 }
1756}
1757
1758#[derive(Debug, Clone, PartialEq, Eq)]
1764struct EvictionOrderKey {
1765 priority: Priority<u64>,
1766 submission_id: u64,
1767}
1768
1769impl EvictionOrderKey {
1770 fn new(priority: Priority<u64>, submission_id: u64) -> Self {
1771 Self {
1772 priority,
1773 submission_id,
1774 }
1775 }
1776}
1777
1778impl Ord for EvictionOrderKey {
1779 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1780 self.priority
1781 .cmp(&other.priority)
1782 .then_with(|| other.submission_id.cmp(&self.submission_id))
1783 }
1784}
1785
1786impl PartialOrd for EvictionOrderKey {
1787 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1788 Some(self.cmp(other))
1789 }
1790}
1791
1792#[derive(Debug, Clone)]
1805struct ExpiringNonceEvictionKey {
1806 order: EvictionOrderKey,
1807 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
1808}
1809
1810impl ExpiringNonceEvictionKey {
1811 fn from_pending_with_base_fee(tx: &AA2dStoredTransaction, base_fee: u64) -> Self {
1812 Self {
1813 order: EvictionOrderKey::new(
1814 TempoTipOrdering::default().priority(&tx.transaction.transaction, base_fee),
1815 tx.submission_id,
1816 ),
1817 transaction: tx.transaction.clone(),
1818 }
1819 }
1820
1821 fn from_pending_owned(tx: PendingTransaction<TxOrdering>) -> Self {
1822 Self {
1823 order: EvictionOrderKey::new(tx.priority, tx.submission_id),
1824 transaction: tx.transaction,
1825 }
1826 }
1827
1828 fn into_transaction(self) -> PendingTransaction<TxOrdering> {
1829 PendingTransaction {
1830 submission_id: self.order.submission_id,
1831 priority: self.order.priority,
1832 transaction: self.transaction,
1833 }
1834 }
1835
1836 fn priority(&self) -> &Priority<u64> {
1837 &self.order.priority
1838 }
1839
1840 fn submission_id(&self) -> u64 {
1841 self.order.submission_id
1842 }
1843
1844 fn expiring_hash(&self) -> B256 {
1845 self.transaction
1846 .transaction
1847 .precomputed_expiring_nonce_hash()
1848 }
1849}
1850
1851impl Borrow<EvictionOrderKey> for ExpiringNonceEvictionKey {
1852 fn borrow(&self) -> &EvictionOrderKey {
1853 &self.order
1854 }
1855}
1856
1857impl PartialEq for ExpiringNonceEvictionKey {
1858 fn eq(&self, other: &Self) -> bool {
1859 self.order == other.order
1860 }
1861}
1862
1863impl Eq for ExpiringNonceEvictionKey {}
1864
1865impl Ord for ExpiringNonceEvictionKey {
1866 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1867 self.order.cmp(&other.order)
1868 }
1869}
1870
1871impl PartialOrd for ExpiringNonceEvictionKey {
1872 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1873 Some(self.cmp(other))
1874 }
1875}
1876
1877#[derive(Debug, Clone)]
1886struct EvictionKey {
1887 tx: Arc<AA2dInternalTransaction>,
1889 tx_id: AA2dTransactionId,
1893 order: EvictionOrderKey,
1895}
1896
1897impl EvictionKey {
1898 fn with_base_fee(
1899 tx: Arc<AA2dInternalTransaction>,
1900 tx_id: AA2dTransactionId,
1901 base_fee: u64,
1902 ) -> Self {
1903 let priority =
1904 TempoTipOrdering::default().priority(&tx.inner.transaction.transaction, base_fee);
1905 let submission_id = tx.inner.submission_id;
1906 Self {
1907 tx,
1908 tx_id,
1909 order: EvictionOrderKey::new(priority, submission_id),
1910 }
1911 }
1912
1913 fn priority(&self) -> &Priority<u64> {
1915 &self.order.priority
1916 }
1917
1918 fn submission_id(&self) -> u64 {
1920 self.order.submission_id
1921 }
1922
1923 fn is_pending(&self) -> bool {
1925 self.tx.is_pending()
1926 }
1927}
1928
1929impl Borrow<EvictionOrderKey> for EvictionKey {
1930 fn borrow(&self) -> &EvictionOrderKey {
1931 &self.order
1932 }
1933}
1934
1935impl PartialEq for EvictionKey {
1936 fn eq(&self, other: &Self) -> bool {
1937 self.submission_id() == other.submission_id()
1938 }
1939}
1940
1941impl Eq for EvictionKey {}
1942
1943impl Ord for EvictionKey {
1944 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1945 self.priority()
1947 .cmp(other.priority())
1948 .then_with(|| other.submission_id().cmp(&self.submission_id()))
1951 }
1952}
1953
1954impl PartialOrd for EvictionKey {
1955 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1956 Some(self.cmp(other))
1957 }
1958}
1959
1960const MAX_NEW_TRANSACTIONS_PER_BATCH: usize = 16;
1962
1963enum IncomingAA2dTransaction {
1966 Process(PendingTransaction<TxOrdering>),
1968 Stash(PendingTransaction<TxOrdering>),
1970}
1971
1972enum PoppedAA2dTransaction {
1973 Regular(AA2dTransactionId, PendingTransaction<TxOrdering>),
1974 Expiring(PendingTransaction<TxOrdering>),
1975}
1976
1977#[derive(Debug)]
1979pub(crate) struct BestAA2dTransactions {
1980 independent: BTreeSet<PendingTransaction<TxOrdering>>,
1985 by_id: HashMap<AA2dTransactionId, AA2dStoredTransaction>,
1990 expiring_nonce_order: BTreeSet<ExpiringNonceEvictionKey>,
1994
1995 invalid: HashSet<AASequenceId>,
1997 new_transaction_receiver: Option<broadcast::Receiver<AA2dStoredTransaction>>,
1999 last_priority: Option<Priority<u64>>,
2001 base_fee: u64,
2003}
2004
2005impl BestAA2dTransactions {
2006 fn pop_best_regular(&mut self) -> Option<(AA2dTransactionId, PendingTransaction<TxOrdering>)> {
2008 let tx = self.independent.pop_last()?;
2009 let id = tx
2010 .transaction
2011 .transaction
2012 .aa_transaction_id()
2013 .expect("Transaction in AA2D pool must be an AA transaction with valid nonce key");
2014 self.by_id.remove(&id);
2015 Some((id, tx))
2016 }
2017
2018 fn pop_best_expiring_nonce(&mut self) -> Option<PendingTransaction<TxOrdering>> {
2020 let key = self.expiring_nonce_order.pop_last()?;
2021 Some(key.into_transaction())
2022 }
2023
2024 fn pop_best(&mut self) -> Option<PoppedAA2dTransaction> {
2026 match (self.independent.last(), self.expiring_nonce_order.last()) {
2027 (Some(regular), Some(expiring)) => {
2028 if regular
2029 .priority
2030 .cmp(expiring.priority())
2031 .then_with(|| expiring.submission_id().cmp(®ular.submission_id))
2032 .is_ge()
2033 {
2034 let (id, tx) = self.pop_best_regular()?;
2035 Some(PoppedAA2dTransaction::Regular(id, tx))
2036 } else {
2037 self.pop_best_expiring_nonce()
2038 .map(PoppedAA2dTransaction::Expiring)
2039 }
2040 }
2041 (Some(_), None) => {
2042 let (id, tx) = self.pop_best_regular()?;
2043 Some(PoppedAA2dTransaction::Regular(id, tx))
2044 }
2045 (None, Some(_)) => self
2046 .pop_best_expiring_nonce()
2047 .map(PoppedAA2dTransaction::Expiring),
2048 (None, None) => None,
2049 }
2050 }
2051
2052 fn try_recv(&mut self) -> Option<IncomingAA2dTransaction> {
2054 loop {
2055 match self.new_transaction_receiver.as_mut()?.try_recv() {
2056 Ok(tx) => {
2057 let priority = TempoTipOrdering::default()
2058 .priority(&tx.transaction.transaction, self.base_fee);
2059 let tx = PendingTransaction {
2060 submission_id: tx.submission_id,
2061 transaction: tx.transaction,
2062 priority,
2063 };
2064 if let Some(last_priority) = &self.last_priority
2065 && &tx.priority > last_priority
2066 {
2067 return Some(IncomingAA2dTransaction::Stash(tx));
2070 }
2071 return Some(IncomingAA2dTransaction::Process(tx));
2072 }
2073 Err(broadcast::error::TryRecvError::Lagged(_)) => {
2074 }
2076 Err(_) => return None,
2077 }
2078 }
2079 }
2080
2081 fn add_new_transactions(&mut self) {
2083 for _ in 0..MAX_NEW_TRANSACTIONS_PER_BATCH {
2084 if let Some(incoming) = self.try_recv() {
2085 let (tx, process) = match incoming {
2086 IncomingAA2dTransaction::Process(tx) => (tx, true),
2087 IncomingAA2dTransaction::Stash(tx) => (tx, false),
2088 };
2089 if tx.transaction.transaction.is_expiring_nonce() {
2090 if process && can_pay_base_fee(&tx, self.base_fee) {
2091 self.expiring_nonce_order
2092 .insert(ExpiringNonceEvictionKey::from_pending_owned(tx));
2093 }
2094 } else if let Some(id) = tx.transaction.transaction.aa_transaction_id() {
2095 if process {
2096 if !self.by_id.contains_key(&AA2dTransactionId::new(
2098 id.seq_id,
2099 id.nonce.saturating_sub(1),
2100 )) || id.nonce == 0
2101 {
2102 self.independent.insert(tx.clone());
2103 }
2104 }
2105 self.by_id.insert(
2106 id,
2107 AA2dStoredTransaction {
2108 submission_id: tx.submission_id,
2109 transaction: tx.transaction,
2110 },
2111 );
2112 }
2113 } else {
2114 break;
2115 }
2116 }
2117 }
2118
2119 pub(crate) fn next_tx_and_priority(
2121 &mut self,
2122 ) -> Option<(
2123 Arc<ValidPoolTransaction<TempoPooledTransaction>>,
2124 Priority<u64>,
2125 )> {
2126 loop {
2127 self.add_new_transactions();
2128 let best = match self.pop_best()? {
2129 PoppedAA2dTransaction::Regular(id, best) => {
2130 if self.invalid.contains(&id.seq_id) {
2131 continue;
2132 }
2133 if !can_pay_base_fee(&best, self.base_fee) {
2134 self.invalid.insert(id.seq_id);
2135 continue;
2136 }
2137 if let Some(unlocked) = self.by_id.get(&id.unlocks()) {
2139 self.independent
2140 .insert(unlocked.clone_into_pending(self.base_fee));
2141 }
2142 best
2143 }
2144 PoppedAA2dTransaction::Expiring(best) => {
2145 if !can_pay_base_fee(&best, self.base_fee) {
2146 continue;
2147 }
2148 best
2149 }
2150 };
2151 if self.new_transaction_receiver.is_some() {
2152 self.last_priority = Some(best.priority.clone());
2153 }
2154 return Some((best.transaction, best.priority));
2155 }
2156 }
2157}
2158
2159fn can_pay_base_fee(tx: &PendingTransaction<TxOrdering>, base_fee: u64) -> bool {
2160 tx.transaction.transaction.max_fee_per_gas() >= u128::from(base_fee)
2161}
2162
2163impl Iterator for BestAA2dTransactions {
2164 type Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>;
2165
2166 fn next(&mut self) -> Option<Self::Item> {
2167 self.next_tx_and_priority().map(|(tx, _)| tx)
2168 }
2169
2170 fn size_hint(&self) -> (usize, Option<usize>) {
2171 (
2172 0,
2173 self.by_id
2174 .len()
2175 .checked_add(self.expiring_nonce_order.len()),
2176 )
2177 }
2178}
2179
2180impl BestTransactions for BestAA2dTransactions {
2181 fn mark_invalid(&mut self, transaction: &Self::Item, _kind: InvalidPoolTransactionError) {
2182 if transaction.transaction.is_expiring_nonce() {
2185 return;
2186 }
2187
2188 if let Some(id) = transaction.transaction.aa_transaction_id() {
2189 self.invalid.insert(id.seq_id);
2190 }
2191 }
2192
2193 fn no_updates(&mut self) {
2194 self.new_transaction_receiver.take();
2195 self.last_priority.take();
2196 }
2197
2198 fn set_skip_blobs(&mut self, _skip_blobs: bool) {}
2199}
2200
2201#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
2205pub struct AASequenceId {
2206 pub address: Address,
2208 pub nonce_key: U256,
2210}
2211
2212impl AASequenceId {
2213 pub const fn new(address: Address, nonce_key: U256) -> Self {
2215 Self { address, nonce_key }
2216 }
2217
2218 const fn start_bound(self) -> std::ops::Bound<AA2dTransactionId> {
2219 std::ops::Bound::Included(AA2dTransactionId::new(self, 0))
2220 }
2221
2222 const fn range(self) -> std::ops::RangeInclusive<AA2dTransactionId> {
2224 AA2dTransactionId::new(self, 0)..=AA2dTransactionId::new(self, u64::MAX)
2225 }
2226}
2227
2228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
2232pub struct AA2dTransactionId {
2233 pub(crate) seq_id: AASequenceId,
2235 pub(crate) nonce: u64,
2237}
2238
2239impl AA2dTransactionId {
2240 pub(crate) const fn new(seq_id: AASequenceId, nonce: u64) -> Self {
2242 Self { seq_id, nonce }
2243 }
2244
2245 pub(crate) fn unlocks(&self) -> Self {
2247 Self::new(self.seq_id, self.nonce.saturating_add(1))
2248 }
2249
2250 pub fn seq_id(&self) -> &AASequenceId {
2252 &self.seq_id
2253 }
2254}
2255
2256#[cfg(test)]
2257mod tests {
2258 use super::*;
2259 use crate::test_utils::{TxBuilder, wrap_valid_tx};
2260 use alloy_eips::eip2930::AccessList;
2261 use alloy_primitives::{Address, Bytes, Signature, TxKind, U256};
2262 use reth_primitives_traits::Recovered;
2263 use reth_transaction_pool::PoolTransaction;
2264 use std::collections::HashSet;
2265 use tempo_chainspec::{hardfork::TempoHardfork, spec::TEMPO_T1_BASE_FEE};
2266 use tempo_primitives::{
2267 TempoTxEnvelope,
2268 transaction::{
2269 TempoTransaction,
2270 tempo_transaction::Call,
2271 tt_signature::{PrimitiveSignature, TempoSignature},
2272 tt_signed::AASigned,
2273 },
2274 };
2275
2276 #[test_case::test_case(U256::ZERO)]
2277 #[test_case::test_case(U256::random())]
2278 fn insert_pending(nonce_key: U256) {
2279 let mut pool = AA2dPool::default();
2280
2281 let sender = Address::random();
2283
2284 let tx = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2286 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
2287
2288 let result = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
2290
2291 assert!(result.is_ok(), "Transaction should be added successfully");
2293 let added = result.unwrap();
2294 assert!(
2295 matches!(added, AddedTransaction::Pending(_)),
2296 "Transaction should be pending, got: {added:?}"
2297 );
2298
2299 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2301 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
2302 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
2303
2304 pool.assert_invariants();
2305 }
2306
2307 #[test_case::test_case(U256::ZERO)]
2308 #[test_case::test_case(U256::random())]
2309 fn insert_with_nonce_gap_then_fill(nonce_key: U256) {
2310 let mut pool = AA2dPool::default();
2311
2312 let sender = Address::random();
2314
2315 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2317 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
2318 let tx1_hash = *valid_tx1.hash();
2319
2320 let result1 = pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1);
2321
2322 assert!(
2324 result1.is_ok(),
2325 "Transaction 1 should be added successfully"
2326 );
2327 let added1 = result1.unwrap();
2328 assert!(
2329 matches!(
2330 added1,
2331 AddedTransaction::Parked {
2332 subpool: SubPool::Queued,
2333 ..
2334 }
2335 ),
2336 "Transaction 1 should be queued due to nonce gap, got: {added1:?}"
2337 );
2338
2339 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2341 assert_eq!(pending_count, 0, "Should have 0 pending transactions");
2342 assert_eq!(queued_count, 1, "Should have 1 queued transaction");
2343
2344 let seq_id = AASequenceId::new(sender, nonce_key);
2346 let tx1_id = AA2dTransactionId::new(seq_id, 1);
2347 assert!(
2348 !pool.independent_transactions.contains_key(&tx1_id.seq_id),
2349 "Transaction 1 should not be in independent set yet"
2350 );
2351
2352 pool.assert_invariants();
2353
2354 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2356 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2357 let tx0_hash = *valid_tx0.hash();
2358
2359 let result0 = pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1);
2360
2361 assert!(
2363 result0.is_ok(),
2364 "Transaction 0 should be added successfully"
2365 );
2366 let added0 = result0.unwrap();
2367
2368 match added0 {
2370 AddedTransaction::Pending(ref pending) => {
2371 assert_eq!(pending.transaction.hash(), &tx0_hash, "Should be tx0");
2372 assert_eq!(
2373 pending.promoted.len(),
2374 1,
2375 "Should have promoted 1 transaction"
2376 );
2377 assert_eq!(
2378 pending.promoted[0].hash(),
2379 &tx1_hash,
2380 "Should have promoted tx1"
2381 );
2382 }
2383 _ => panic!("Transaction 0 should be pending, got: {added0:?}"),
2384 }
2385
2386 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2388 assert_eq!(pending_count, 2, "Should have 2 pending transactions");
2389 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
2390
2391 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2393 assert!(
2394 pool.by_id.get(&tx0_id).unwrap().is_pending(),
2395 "Transaction 0 should be pending"
2396 );
2397 assert!(
2398 pool.by_id.get(&tx1_id).unwrap().is_pending(),
2399 "Transaction 1 should be pending after promotion"
2400 );
2401
2402 assert!(
2404 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2405 "Transaction 0 should be in independent set (at on-chain nonce)"
2406 );
2407
2408 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
2410 assert_eq!(
2411 independent_tx.transaction.hash(),
2412 &tx0_hash,
2413 "Independent transaction should be tx0, not tx1"
2414 );
2415
2416 pool.assert_invariants();
2417 }
2418
2419 #[test_case::test_case(U256::ZERO)]
2420 #[test_case::test_case(U256::random())]
2421 fn replace_pending_transaction(nonce_key: U256) {
2422 let mut pool = AA2dPool::default();
2423
2424 let sender = Address::random();
2426
2427 let tx_low = TxBuilder::aa(sender)
2429 .nonce_key(nonce_key)
2430 .max_priority_fee(1_000_000_000)
2431 .max_fee(2_000_000_000)
2432 .build();
2433 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
2434 let tx_low_hash = *valid_tx_low.hash();
2435
2436 let result_low = pool.add_transaction(Arc::new(valid_tx_low), 0, TempoHardfork::T1);
2437
2438 assert!(
2440 result_low.is_ok(),
2441 "Initial transaction should be added successfully"
2442 );
2443 let added_low = result_low.unwrap();
2444 assert!(
2445 matches!(added_low, AddedTransaction::Pending(_)),
2446 "Initial transaction should be pending"
2447 );
2448
2449 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2451 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
2452 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
2453
2454 let seq_id = AASequenceId::new(sender, nonce_key);
2456 let tx_id = AA2dTransactionId::new(seq_id, 0);
2457 assert!(
2458 pool.independent_transactions.contains_key(&tx_id.seq_id),
2459 "Initial transaction should be in independent set"
2460 );
2461
2462 let independent_tx = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
2464 assert_eq!(
2465 independent_tx.transaction.hash(),
2466 &tx_low_hash,
2467 "Independent set should contain tx_low"
2468 );
2469
2470 pool.assert_invariants();
2471
2472 let tx_high = TxBuilder::aa(sender)
2475 .nonce_key(nonce_key)
2476 .max_priority_fee(1_200_000_000)
2477 .max_fee(2_400_000_000)
2478 .build();
2479 let valid_tx_high = wrap_valid_tx(tx_high, TransactionOrigin::Local);
2480 let tx_high_hash = *valid_tx_high.hash();
2481
2482 let result_high = pool.add_transaction(Arc::new(valid_tx_high), 0, TempoHardfork::T1);
2483
2484 assert!(
2486 result_high.is_ok(),
2487 "Replacement transaction should be added successfully"
2488 );
2489 let added_high = result_high.unwrap();
2490
2491 match added_high {
2493 AddedTransaction::Pending(ref pending) => {
2494 assert_eq!(
2495 pending.transaction.hash(),
2496 &tx_high_hash,
2497 "Should be tx_high"
2498 );
2499 assert!(
2500 pending.replaced.is_some(),
2501 "Should have replaced a transaction"
2502 );
2503 assert_eq!(
2504 pending.replaced.as_ref().unwrap().hash(),
2505 &tx_low_hash,
2506 "Should have replaced tx_low"
2507 );
2508 }
2509 _ => panic!("Replacement transaction should be pending, got: {added_high:?}"),
2510 }
2511
2512 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2514 assert_eq!(
2515 pending_count, 1,
2516 "Should still have 1 pending transaction after replacement"
2517 );
2518 assert_eq!(queued_count, 0, "Should still have 0 queued transactions");
2519
2520 assert!(
2522 !pool.contains(&tx_low_hash),
2523 "Old transaction should be removed from pool"
2524 );
2525
2526 assert!(
2528 pool.contains(&tx_high_hash),
2529 "New transaction should be in pool"
2530 );
2531
2532 assert!(
2534 pool.independent_transactions.contains_key(&tx_id.seq_id),
2535 "Transaction ID should still be in independent set"
2536 );
2537
2538 let independent_tx_after = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
2539 assert_eq!(
2540 independent_tx_after.transaction.hash(),
2541 &tx_high_hash,
2542 "Independent set should now contain tx_high (not tx_low)"
2543 );
2544
2545 let tx_in_pool = pool.by_id.get(&tx_id).unwrap();
2547 assert_eq!(
2548 tx_in_pool.inner.transaction.hash(),
2549 &tx_high_hash,
2550 "Transaction in by_id should be tx_high"
2551 );
2552 assert!(tx_in_pool.is_pending(), "Transaction should be pending");
2553
2554 pool.assert_invariants();
2555 }
2556
2557 #[test_case::test_case(U256::ZERO)]
2558 #[test_case::test_case(U256::random())]
2559 fn on_chain_nonce_update_with_gaps(nonce_key: U256) {
2560 let mut pool = AA2dPool::default();
2561
2562 let sender = Address::random();
2564
2565 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2570 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2571 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2572 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
2573 let tx6 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(6).build();
2574
2575 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2576 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
2577 let valid_tx3 = wrap_valid_tx(tx3, TransactionOrigin::Local);
2578 let valid_tx4 = wrap_valid_tx(tx4, TransactionOrigin::Local);
2579 let valid_tx6 = wrap_valid_tx(tx6, TransactionOrigin::Local);
2580
2581 let tx0_hash = *valid_tx0.hash();
2582 let tx1_hash = *valid_tx1.hash();
2583 let tx3_hash = *valid_tx3.hash();
2584 let tx4_hash = *valid_tx4.hash();
2585 let tx6_hash = *valid_tx6.hash();
2586
2587 pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1)
2589 .unwrap();
2590 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
2591 .unwrap();
2592 pool.add_transaction(Arc::new(valid_tx3), 0, TempoHardfork::T1)
2593 .unwrap();
2594 pool.add_transaction(Arc::new(valid_tx4), 0, TempoHardfork::T1)
2595 .unwrap();
2596 pool.add_transaction(Arc::new(valid_tx6), 0, TempoHardfork::T1)
2597 .unwrap();
2598
2599 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2601 assert_eq!(
2602 pending_count, 2,
2603 "Should have 2 pending transactions (0, 1)"
2604 );
2605 assert_eq!(
2606 queued_count, 3,
2607 "Should have 3 queued transactions (3, 4, 6)"
2608 );
2609
2610 let seq_id = AASequenceId::new(sender, nonce_key);
2612 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2613 assert!(
2614 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2615 "Transaction 0 should be in independent set"
2616 );
2617
2618 pool.assert_invariants();
2619
2620 let mut on_chain_ids = HashMap::default();
2623 on_chain_ids.insert(seq_id, 2u64);
2624
2625 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2626
2627 assert_eq!(mined.len(), 2, "Should have mined 2 transactions (0, 1)");
2629 let mined_hashes: HashSet<_> = mined.iter().map(|tx| tx.hash()).collect();
2630 assert!(
2631 mined_hashes.contains(&&tx0_hash),
2632 "Transaction 0 should be mined"
2633 );
2634 assert!(
2635 mined_hashes.contains(&&tx1_hash),
2636 "Transaction 1 should be mined"
2637 );
2638
2639 assert_eq!(
2641 promoted.len(),
2642 0,
2643 "No transactions should be promoted (gap at nonce 2)"
2644 );
2645
2646 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2648 assert_eq!(
2649 pending_count, 0,
2650 "Should have 0 pending transactions (gap at nonce 2)"
2651 );
2652 assert_eq!(
2653 queued_count, 3,
2654 "Should have 3 queued transactions (3, 4, 6)"
2655 );
2656
2657 assert!(!pool.contains(&tx0_hash), "Transaction 0 should be removed");
2659 assert!(!pool.contains(&tx1_hash), "Transaction 1 should be removed");
2660
2661 assert!(pool.contains(&tx3_hash), "Transaction 3 should remain");
2663 assert!(pool.contains(&tx4_hash), "Transaction 4 should remain");
2664 assert!(pool.contains(&tx6_hash), "Transaction 6 should remain");
2665
2666 let tx3_id = AA2dTransactionId::new(seq_id, 3);
2668 let tx4_id = AA2dTransactionId::new(seq_id, 4);
2669 let tx6_id = AA2dTransactionId::new(seq_id, 6);
2670
2671 assert!(
2672 !pool.by_id.get(&tx3_id).unwrap().is_pending(),
2673 "Transaction 3 should be queued (gap at nonce 2)"
2674 );
2675 assert!(
2676 !pool.by_id.get(&tx4_id).unwrap().is_pending(),
2677 "Transaction 4 should be queued"
2678 );
2679 assert!(
2680 !pool.by_id.get(&tx6_id).unwrap().is_pending(),
2681 "Transaction 6 should be queued"
2682 );
2683
2684 assert!(
2686 pool.independent_transactions.is_empty(),
2687 "Independent set should be empty (gap at on-chain nonce 2)"
2688 );
2689
2690 pool.assert_invariants();
2691
2692 let mut on_chain_ids = HashMap::default();
2695 on_chain_ids.insert(seq_id, 3u64);
2696
2697 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2698
2699 assert_eq!(
2701 mined.len(),
2702 0,
2703 "No transactions should be mined (nonce 2 was never in pool)"
2704 );
2705
2706 assert_eq!(promoted.len(), 2, "Transactions 3 and 4 should be promoted");
2708 let promoted_hashes: HashSet<_> = promoted.iter().map(|tx| tx.hash()).collect();
2709 assert!(
2710 promoted_hashes.contains(&&tx3_hash),
2711 "Transaction 3 should be promoted"
2712 );
2713 assert!(
2714 promoted_hashes.contains(&&tx4_hash),
2715 "Transaction 4 should be promoted"
2716 );
2717
2718 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2720 assert_eq!(
2721 pending_count, 2,
2722 "Should have 2 pending transactions (3, 4)"
2723 );
2724 assert_eq!(queued_count, 1, "Should have 1 queued transaction (6)");
2725
2726 assert!(
2728 pool.by_id.get(&tx3_id).unwrap().is_pending(),
2729 "Transaction 3 should be pending"
2730 );
2731 assert!(
2732 pool.by_id.get(&tx4_id).unwrap().is_pending(),
2733 "Transaction 4 should be pending"
2734 );
2735
2736 assert!(
2738 !pool.by_id.get(&tx6_id).unwrap().is_pending(),
2739 "Transaction 6 should still be queued (gap at nonce 5)"
2740 );
2741
2742 assert!(
2744 pool.independent_transactions.contains_key(&tx3_id.seq_id),
2745 "Transaction 3 should be in independent set (at on-chain nonce 3)"
2746 );
2747
2748 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
2750 assert_eq!(
2751 independent_tx.transaction.hash(),
2752 &tx3_hash,
2753 "Independent transaction should be tx3 (nonce 3), not tx4 or tx6"
2754 );
2755
2756 pool.assert_invariants();
2757 }
2758
2759 #[test_case::test_case(U256::ZERO)]
2760 #[test_case::test_case(U256::random())]
2761 fn reject_outdated_transaction(nonce_key: U256) {
2762 let mut pool = AA2dPool::default();
2763
2764 let sender = Address::random();
2766
2767 let tx = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2769 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
2770
2771 let result = pool.add_transaction(Arc::new(valid_tx), 5, TempoHardfork::T1);
2773
2774 assert!(result.is_err(), "Should reject outdated transaction");
2776
2777 let err = result.unwrap_err();
2778 assert!(
2779 matches!(
2780 err.kind,
2781 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
2782 InvalidTransactionError::NonceNotConsistent { tx: 3, state: 5 }
2783 ))
2784 ),
2785 "Should fail with NonceNotConsistent error, got: {:?}",
2786 err.kind
2787 );
2788
2789 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2791 assert_eq!(pending_count, 0, "Pool should be empty");
2792 assert_eq!(queued_count, 0, "Pool should be empty");
2793
2794 pool.assert_invariants();
2795 }
2796
2797 #[test_case::test_case(U256::ZERO)]
2798 #[test_case::test_case(U256::random())]
2799 fn replace_with_insufficient_price_bump(nonce_key: U256) {
2800 let mut pool = AA2dPool::default();
2801
2802 let sender = Address::random();
2804
2805 let tx_low = TxBuilder::aa(sender)
2807 .nonce_key(nonce_key)
2808 .max_priority_fee(1_000_000_000)
2809 .max_fee(2_000_000_000)
2810 .build();
2811 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
2812
2813 pool.add_transaction(Arc::new(valid_tx_low), 0, TempoHardfork::T1)
2814 .unwrap();
2815
2816 let tx_insufficient = TxBuilder::aa(sender)
2818 .nonce_key(nonce_key)
2819 .max_priority_fee(1_050_000_000)
2820 .max_fee(2_100_000_000)
2821 .build();
2822 let valid_tx_insufficient = wrap_valid_tx(tx_insufficient, TransactionOrigin::Local);
2823
2824 let result = pool.add_transaction(Arc::new(valid_tx_insufficient), 0, TempoHardfork::T1);
2825
2826 assert!(
2828 result.is_err(),
2829 "Should reject replacement with insufficient price bump"
2830 );
2831 let err = result.unwrap_err();
2832 assert!(
2833 matches!(err.kind, PoolErrorKind::ReplacementUnderpriced),
2834 "Should fail with ReplacementUnderpriced, got: {:?}",
2835 err.kind
2836 );
2837
2838 pool.assert_invariants();
2839 }
2840
2841 #[test_case::test_case(U256::ZERO)]
2842 #[test_case::test_case(U256::random())]
2843 fn fill_gap_in_middle(nonce_key: U256) {
2844 let mut pool = AA2dPool::default();
2845
2846 let sender = Address::random();
2847
2848 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2850 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2851 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
2852 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
2853
2854 pool.add_transaction(
2855 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
2856 0,
2857 TempoHardfork::T1,
2858 )
2859 .unwrap();
2860 pool.add_transaction(
2861 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
2862 0,
2863 TempoHardfork::T1,
2864 )
2865 .unwrap();
2866 pool.add_transaction(
2867 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
2868 0,
2869 TempoHardfork::T1,
2870 )
2871 .unwrap();
2872 pool.add_transaction(
2873 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
2874 0,
2875 TempoHardfork::T1,
2876 )
2877 .unwrap();
2878
2879 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2881 assert_eq!(pending_count, 2, "Should have 2 pending (0, 1)");
2882 assert_eq!(queued_count, 2, "Should have 2 queued (3, 4)");
2883
2884 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2886 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
2887
2888 let result = pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1);
2889 assert!(result.is_ok(), "Should successfully add tx2");
2890
2891 match result.unwrap() {
2893 AddedTransaction::Pending(ref pending) => {
2894 assert_eq!(pending.promoted.len(), 2, "Should promote tx3 and tx4");
2895 }
2896 _ => panic!("tx2 should be added as pending"),
2897 }
2898
2899 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2901 assert_eq!(pending_count, 5, "All 5 transactions should be pending");
2902 assert_eq!(queued_count, 0, "No transactions should be queued");
2903
2904 let seq_id = AASequenceId::new(sender, nonce_key);
2906 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2907 assert!(
2908 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2909 "tx0 should be in independent set"
2910 );
2911
2912 pool.assert_invariants();
2913 }
2914
2915 #[test_case::test_case(U256::ZERO)]
2916 #[test_case::test_case(U256::random())]
2917 fn remove_pending_transaction(nonce_key: U256) {
2918 let mut pool = AA2dPool::default();
2919
2920 let sender = Address::random();
2921
2922 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
2924 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
2925 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
2926
2927 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
2928 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
2929 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
2930
2931 let tx1_hash = *valid_tx1.hash();
2932
2933 pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1)
2934 .unwrap();
2935 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
2936 .unwrap();
2937 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
2938 .unwrap();
2939
2940 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2942 assert_eq!(pending_count, 3, "All 3 should be pending");
2943 assert_eq!(queued_count, 0, "None should be queued");
2944
2945 let seq_id = AASequenceId::new(sender, nonce_key);
2946 let tx1_id = AA2dTransactionId::new(seq_id, 1);
2947 let tx2_id = AA2dTransactionId::new(seq_id, 2);
2948
2949 assert!(
2951 pool.by_id.get(&tx2_id).unwrap().is_pending(),
2952 "tx2 should be pending before removal"
2953 );
2954
2955 let removed = pool.remove_transactions([&tx1_hash].into_iter());
2957 assert_eq!(removed.len(), 1, "Should remove tx1");
2958
2959 assert!(!pool.by_id.contains_key(&tx1_id), "tx1 should be removed");
2961 assert!(!pool.contains(&tx1_hash), "tx1 should be removed");
2962
2963 assert_eq!(pool.by_id.len(), 2, "Should have 2 transactions left");
2965
2966 assert!(
2968 !pool.by_id.get(&tx2_id).unwrap().is_pending(),
2969 "tx2 should be demoted to queued after tx1 removal creates a gap"
2970 );
2971
2972 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2974 assert_eq!(pending_count, 1, "Only tx0 should be pending");
2975 assert_eq!(queued_count, 1, "tx2 should be queued");
2976
2977 pool.assert_invariants();
2978 }
2979
2980 #[test_case::test_case(U256::ZERO, U256::random())]
2981 #[test_case::test_case(U256::random(), U256::ZERO)]
2982 #[test_case::test_case(U256::random(), U256::random())]
2983 fn multiple_senders_independent_set(nonce_key_a: U256, nonce_key_b: U256) {
2984 let mut pool = AA2dPool::default();
2985
2986 let sender_a = Address::random();
2988 let sender_b = Address::random();
2989
2990 let tx_a0 = TxBuilder::aa(sender_a).nonce_key(nonce_key_a).build();
2993 let tx_a1 = TxBuilder::aa(sender_a)
2994 .nonce_key(nonce_key_a)
2995 .nonce(1)
2996 .build();
2997
2998 let tx_b0 = TxBuilder::aa(sender_b).nonce_key(nonce_key_b).build();
3000 let tx_b1 = TxBuilder::aa(sender_b)
3001 .nonce_key(nonce_key_b)
3002 .nonce(1)
3003 .build();
3004
3005 let valid_tx_a0 = wrap_valid_tx(tx_a0, TransactionOrigin::Local);
3006 let valid_tx_a1 = wrap_valid_tx(tx_a1, TransactionOrigin::Local);
3007 let valid_tx_b0 = wrap_valid_tx(tx_b0, TransactionOrigin::Local);
3008 let valid_tx_b1 = wrap_valid_tx(tx_b1, TransactionOrigin::Local);
3009
3010 let tx_a0_hash = *valid_tx_a0.hash();
3011
3012 pool.add_transaction(Arc::new(valid_tx_a0), 0, TempoHardfork::T1)
3013 .unwrap();
3014 pool.add_transaction(Arc::new(valid_tx_a1), 0, TempoHardfork::T1)
3015 .unwrap();
3016 pool.add_transaction(Arc::new(valid_tx_b0), 0, TempoHardfork::T1)
3017 .unwrap();
3018 pool.add_transaction(Arc::new(valid_tx_b1), 0, TempoHardfork::T1)
3019 .unwrap();
3020
3021 let sender_a_id = AASequenceId::new(sender_a, nonce_key_a);
3023 let sender_b_id = AASequenceId::new(sender_b, nonce_key_b);
3024 let tx_a0_id = AA2dTransactionId::new(sender_a_id, 0);
3025 let tx_b0_id = AA2dTransactionId::new(sender_b_id, 0);
3026
3027 assert_eq!(
3028 pool.independent_transactions.len(),
3029 2,
3030 "Should have 2 independent transactions"
3031 );
3032 assert!(
3033 pool.independent_transactions.contains_key(&tx_a0_id.seq_id),
3034 "Sender A's tx0 should be independent"
3035 );
3036 assert!(
3037 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
3038 "Sender B's tx0 should be independent"
3039 );
3040
3041 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3043 assert_eq!(pending_count, 4, "All 4 transactions should be pending");
3044 assert_eq!(queued_count, 0, "No transactions should be queued");
3045
3046 let mut on_chain_ids = HashMap::default();
3048 on_chain_ids.insert(sender_a_id, 1u64);
3049
3050 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
3051
3052 assert_eq!(mined.len(), 1, "Only sender A's tx0 should be mined");
3054 assert_eq!(mined[0].hash(), &tx_a0_hash, "Should mine tx_a0");
3055
3056 assert_eq!(
3058 promoted.len(),
3059 0,
3060 "No transactions should be promoted (tx_a1 was already pending)"
3061 );
3062
3063 let tx_a1_id = AA2dTransactionId::new(sender_a_id, 1);
3065 assert_eq!(
3066 pool.independent_transactions.len(),
3067 2,
3068 "Should still have 2 independent transactions"
3069 );
3070 assert!(
3071 pool.independent_transactions.contains_key(&tx_a1_id.seq_id),
3072 "Sender A's tx1 should now be independent"
3073 );
3074 assert!(
3075 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
3076 "Sender B's tx0 should still be independent"
3077 );
3078
3079 pool.assert_invariants();
3080 }
3081
3082 #[test_case::test_case(U256::ZERO)]
3083 #[test_case::test_case(U256::random())]
3084 fn concurrent_replacements_same_nonce(nonce_key: U256) {
3085 let mut pool = AA2dPool::default();
3086 let sender = Address::random();
3087 let seq_id = AASequenceId {
3088 address: sender,
3089 nonce_key,
3090 };
3091
3092 let tx0 = TxBuilder::aa(sender)
3094 .nonce_key(nonce_key)
3095 .max_priority_fee(1_000_000_000)
3096 .max_fee(2_000_000_000)
3097 .build();
3098 let tx0_hash = *tx0.hash();
3099 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
3100 let result = pool.add_transaction(Arc::new(valid_tx0), 0, TempoHardfork::T1);
3101 assert!(result.is_ok());
3102 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3103 assert_eq!(pending_count + queued_count, 1);
3104
3105 let tx0_replacement1 = TxBuilder::aa(sender)
3107 .nonce_key(nonce_key)
3108 .max_priority_fee(1_050_000_000)
3109 .max_fee(2_100_000_000)
3110 .build();
3111 let valid_tx1 = wrap_valid_tx(tx0_replacement1, TransactionOrigin::Local);
3112 let result = pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1);
3113 assert!(result.is_err(), "Should reject insufficient price bump");
3114 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3115 assert_eq!(pending_count + queued_count, 1);
3116 assert!(
3117 pool.contains(&tx0_hash),
3118 "Original tx should still be present"
3119 );
3120
3121 let tx0_replacement2 = TxBuilder::aa(sender)
3123 .nonce_key(nonce_key)
3124 .max_priority_fee(1_100_000_000)
3125 .max_fee(2_200_000_000)
3126 .build();
3127 let tx0_replacement2_hash = *tx0_replacement2.hash();
3128 let valid_tx2 = wrap_valid_tx(tx0_replacement2, TransactionOrigin::Local);
3129 let result = pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1);
3130 assert!(result.is_ok(), "Should accept 10% price bump");
3131 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3132 assert_eq!(pending_count + queued_count, 1, "Pool size should remain 1");
3133 assert!(!pool.contains(&tx0_hash), "Old tx should be removed");
3134 assert!(
3135 pool.contains(&tx0_replacement2_hash),
3136 "New tx should be present"
3137 );
3138
3139 let tx0_replacement3 = TxBuilder::aa(sender)
3141 .nonce_key(nonce_key)
3142 .max_priority_fee(1_500_000_000)
3143 .max_fee(3_000_000_000)
3144 .build();
3145 let tx0_replacement3_hash = *tx0_replacement3.hash();
3146 let valid_tx3 = wrap_valid_tx(tx0_replacement3, TransactionOrigin::Local);
3147 let result = pool.add_transaction(Arc::new(valid_tx3), 0, TempoHardfork::T1);
3148 assert!(result.is_ok(), "Should accept higher price bump");
3149 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3150 assert_eq!(pending_count + queued_count, 1);
3151 assert!(
3152 !pool.contains(&tx0_replacement2_hash),
3153 "Previous tx should be removed"
3154 );
3155 assert!(
3156 pool.contains(&tx0_replacement3_hash),
3157 "Highest priority tx should win"
3158 );
3159
3160 let tx0_id = AA2dTransactionId::new(seq_id, 0);
3162 assert!(pool.independent_transactions.contains_key(&tx0_id.seq_id));
3163
3164 pool.assert_invariants();
3165 }
3166
3167 #[test_case::test_case(U256::ZERO)]
3168 #[test_case::test_case(U256::random())]
3169 fn long_gap_chain(nonce_key: U256) {
3170 let mut pool = AA2dPool::default();
3171 let sender = Address::random();
3172 let seq_id = AASequenceId {
3173 address: sender,
3174 nonce_key,
3175 };
3176
3177 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
3179 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
3180 let tx10 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(10).build();
3181 let tx15 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(15).build();
3182
3183 pool.add_transaction(
3184 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3185 0,
3186 TempoHardfork::T1,
3187 )
3188 .unwrap();
3189 pool.add_transaction(
3190 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
3191 0,
3192 TempoHardfork::T1,
3193 )
3194 .unwrap();
3195 pool.add_transaction(
3196 Arc::new(wrap_valid_tx(tx10, TransactionOrigin::Local)),
3197 0,
3198 TempoHardfork::T1,
3199 )
3200 .unwrap();
3201 pool.add_transaction(
3202 Arc::new(wrap_valid_tx(tx15, TransactionOrigin::Local)),
3203 0,
3204 TempoHardfork::T1,
3205 )
3206 .unwrap();
3207
3208 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3209 assert_eq!(pending_count + queued_count, 4);
3210
3211 let tx0_id = AA2dTransactionId::new(seq_id, 0);
3213 assert!(pool.by_id.get(&tx0_id).unwrap().is_pending());
3214 assert!(
3215 !pool
3216 .by_id
3217 .get(&AA2dTransactionId::new(seq_id, 5))
3218 .unwrap()
3219 .is_pending()
3220 );
3221 assert!(
3222 !pool
3223 .by_id
3224 .get(&AA2dTransactionId::new(seq_id, 10))
3225 .unwrap()
3226 .is_pending()
3227 );
3228 assert!(
3229 !pool
3230 .by_id
3231 .get(&AA2dTransactionId::new(seq_id, 15))
3232 .unwrap()
3233 .is_pending()
3234 );
3235 assert_eq!(pool.independent_transactions.len(), 1);
3236
3237 for nonce in 1..=4 {
3239 let tx = TxBuilder::aa(sender)
3240 .nonce_key(nonce_key)
3241 .nonce(nonce)
3242 .build();
3243 pool.add_transaction(
3244 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3245 0,
3246 TempoHardfork::T1,
3247 )
3248 .unwrap();
3249 }
3250
3251 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3252 assert_eq!(pending_count + queued_count, 8);
3253
3254 for nonce in 0..=5 {
3256 let id = AA2dTransactionId::new(seq_id, nonce);
3257 assert!(
3258 pool.by_id.get(&id).unwrap().is_pending(),
3259 "Nonce {nonce} should be pending"
3260 );
3261 }
3262 assert!(
3264 !pool
3265 .by_id
3266 .get(&AA2dTransactionId::new(seq_id, 10))
3267 .unwrap()
3268 .is_pending()
3269 );
3270 assert!(
3271 !pool
3272 .by_id
3273 .get(&AA2dTransactionId::new(seq_id, 15))
3274 .unwrap()
3275 .is_pending()
3276 );
3277
3278 for nonce in 6..=9 {
3280 let tx = TxBuilder::aa(sender)
3281 .nonce_key(nonce_key)
3282 .nonce(nonce)
3283 .build();
3284 pool.add_transaction(
3285 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3286 0,
3287 TempoHardfork::T1,
3288 )
3289 .unwrap();
3290 }
3291
3292 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3293 assert_eq!(pending_count + queued_count, 12);
3294
3295 for nonce in 0..=10 {
3297 let id = AA2dTransactionId::new(seq_id, nonce);
3298 assert!(
3299 pool.by_id.get(&id).unwrap().is_pending(),
3300 "Nonce {nonce} should be pending"
3301 );
3302 }
3303 assert!(
3305 !pool
3306 .by_id
3307 .get(&AA2dTransactionId::new(seq_id, 15))
3308 .unwrap()
3309 .is_pending()
3310 );
3311
3312 for nonce in 11..=14 {
3314 let tx = TxBuilder::aa(sender)
3315 .nonce_key(nonce_key)
3316 .nonce(nonce)
3317 .build();
3318 pool.add_transaction(
3319 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3320 0,
3321 TempoHardfork::T1,
3322 )
3323 .unwrap();
3324 }
3325
3326 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3327 assert_eq!(pending_count + queued_count, 16);
3328
3329 for nonce in 0..=15 {
3331 let id = AA2dTransactionId::new(seq_id, nonce);
3332 assert!(
3333 pool.by_id.get(&id).unwrap().is_pending(),
3334 "Nonce {nonce} should be pending"
3335 );
3336 }
3337
3338 pool.assert_invariants();
3339 }
3340
3341 #[test_case::test_case(U256::ZERO)]
3342 #[test_case::test_case(U256::random())]
3343 fn remove_from_middle_of_chain(nonce_key: U256) {
3344 let mut pool = AA2dPool::default();
3345 let sender = Address::random();
3346 let seq_id = AASequenceId {
3347 address: sender,
3348 nonce_key,
3349 };
3350
3351 for nonce in 0..=4 {
3353 let tx = TxBuilder::aa(sender)
3354 .nonce_key(nonce_key)
3355 .nonce(nonce)
3356 .build();
3357 pool.add_transaction(
3358 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3359 0,
3360 TempoHardfork::T1,
3361 )
3362 .unwrap();
3363 }
3364
3365 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3366 assert_eq!(pending_count + queued_count, 5);
3367
3368 for nonce in 0..=4 {
3370 assert!(
3371 pool.by_id
3372 .get(&AA2dTransactionId::new(seq_id, nonce))
3373 .unwrap()
3374 .is_pending()
3375 );
3376 }
3377
3378 let tx2_id = AA2dTransactionId::new(seq_id, 2);
3380 let tx2_hash = *pool.by_id.get(&tx2_id).unwrap().inner.transaction.hash();
3381 let removed = pool.remove_transactions([&tx2_hash].into_iter());
3382 assert_eq!(removed.len(), 1, "Should remove transaction");
3383
3384 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3385 assert_eq!(pending_count + queued_count, 4);
3386
3387 assert!(!pool.by_id.contains_key(&tx2_id));
3389
3390 pool.assert_invariants();
3395 }
3396
3397 #[test_case::test_case(U256::ZERO)]
3398 #[test_case::test_case(U256::random())]
3399 fn independent_set_after_multiple_promotions(nonce_key: U256) {
3400 let mut pool = AA2dPool::default();
3401 let sender = Address::random();
3402 let seq_id = AASequenceId {
3403 address: sender,
3404 nonce_key,
3405 };
3406
3407 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
3409 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
3410 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
3411
3412 pool.add_transaction(
3413 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3414 0,
3415 TempoHardfork::T1,
3416 )
3417 .unwrap();
3418 pool.add_transaction(
3419 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3420 0,
3421 TempoHardfork::T1,
3422 )
3423 .unwrap();
3424 pool.add_transaction(
3425 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
3426 0,
3427 TempoHardfork::T1,
3428 )
3429 .unwrap();
3430
3431 assert_eq!(pool.independent_transactions.len(), 1);
3433 assert!(pool.independent_transactions.contains_key(&seq_id));
3434
3435 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3437 assert_eq!(pending_count, 1);
3438 assert_eq!(queued_count, 2);
3439
3440 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
3442 pool.add_transaction(
3443 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3444 0,
3445 TempoHardfork::T1,
3446 )
3447 .unwrap();
3448
3449 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3451 assert_eq!(pending_count, 3);
3452 assert_eq!(queued_count, 1);
3453
3454 assert_eq!(pool.independent_transactions.len(), 1);
3456 assert!(pool.independent_transactions.contains_key(&seq_id));
3457
3458 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
3460 pool.add_transaction(
3461 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
3462 0,
3463 TempoHardfork::T1,
3464 )
3465 .unwrap();
3466
3467 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3469 assert_eq!(pending_count, 5);
3470 assert_eq!(queued_count, 0);
3471
3472 let mut on_chain_ids = HashMap::default();
3474 on_chain_ids.insert(seq_id, 2u64);
3475 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
3476
3477 assert_eq!(mined.len(), 2);
3479 assert_eq!(promoted.len(), 0);
3480
3481 assert_eq!(pool.independent_transactions.len(), 1);
3483 assert!(pool.independent_transactions.contains_key(&seq_id));
3484
3485 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3487 assert_eq!(pending_count + queued_count, 3);
3488
3489 pool.assert_invariants();
3490 }
3491
3492 #[test]
3493 fn stress_test_many_senders() {
3494 let mut pool = AA2dPool::default();
3495 const NUM_SENDERS: usize = 100;
3496 const TXS_PER_SENDER: u64 = 5;
3497
3498 let mut senders = Vec::new();
3500 for i in 0..NUM_SENDERS {
3501 let sender = Address::from_word(B256::from(U256::from(i)));
3502 let nonce_key = U256::from(i);
3503 senders.push((sender, nonce_key));
3504
3505 for nonce in 0..TXS_PER_SENDER {
3507 let tx = TxBuilder::aa(sender)
3508 .nonce_key(nonce_key)
3509 .nonce(nonce)
3510 .build();
3511 pool.add_transaction(
3512 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3513 0,
3514 TempoHardfork::T1,
3515 )
3516 .unwrap();
3517 }
3518 }
3519
3520 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3522 assert_eq!(
3523 pending_count + queued_count,
3524 NUM_SENDERS * TXS_PER_SENDER as usize
3525 );
3526
3527 for (sender, nonce_key) in &senders {
3529 let seq_id = AASequenceId {
3530 address: *sender,
3531 nonce_key: *nonce_key,
3532 };
3533 for nonce in 0..TXS_PER_SENDER {
3534 let id = AA2dTransactionId::new(seq_id, nonce);
3535 assert!(pool.by_id.get(&id).unwrap().is_pending());
3536 }
3537 }
3538
3539 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
3541 for (sender, nonce_key) in &senders {
3542 let seq_id = AASequenceId {
3543 address: *sender,
3544 nonce_key: *nonce_key,
3545 };
3546 let tx0_id = AA2dTransactionId::new(seq_id, 0);
3547 assert!(
3548 pool.independent_transactions.contains_key(&tx0_id.seq_id),
3549 "Sender {sender:?} should have tx0 in independent set"
3550 );
3551 }
3552
3553 let mut on_chain_ids = HashMap::default();
3555 for (sender, nonce_key) in &senders {
3556 let seq_id = AASequenceId {
3557 address: *sender,
3558 nonce_key: *nonce_key,
3559 };
3560 on_chain_ids.insert(seq_id, 1u64);
3561 }
3562
3563 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
3564
3565 assert_eq!(mined.len(), NUM_SENDERS);
3567 assert_eq!(promoted.len(), 0);
3569
3570 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3572 assert_eq!(
3573 pending_count + queued_count,
3574 NUM_SENDERS * (TXS_PER_SENDER - 1) as usize
3575 );
3576
3577 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
3579 for (sender, nonce_key) in &senders {
3580 let seq_id = AASequenceId {
3581 address: *sender,
3582 nonce_key: *nonce_key,
3583 };
3584 let tx1_id = AA2dTransactionId::new(seq_id, 1);
3585 assert!(
3586 pool.independent_transactions.contains_key(&tx1_id.seq_id),
3587 "Sender {sender:?} should have tx1 in independent set"
3588 );
3589 }
3590
3591 pool.assert_invariants();
3592 }
3593
3594 #[test_case::test_case(U256::ZERO)]
3595 #[test_case::test_case(U256::random())]
3596 fn on_chain_nonce_update_to_queued_tx_with_gaps(nonce_key: U256) {
3597 let mut pool = AA2dPool::default();
3598 let sender = Address::random();
3599 let seq_id = AASequenceId {
3600 address: sender,
3601 nonce_key,
3602 };
3603
3604 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
3607 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
3608 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
3609
3610 pool.add_transaction(
3611 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3612 0,
3613 TempoHardfork::T1,
3614 )
3615 .unwrap();
3616 pool.add_transaction(
3617 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
3618 0,
3619 TempoHardfork::T1,
3620 )
3621 .unwrap();
3622 pool.add_transaction(
3623 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
3624 0,
3625 TempoHardfork::T1,
3626 )
3627 .unwrap();
3628
3629 assert_eq!(pool.independent_transactions.len(), 1);
3631 assert!(pool.independent_transactions.contains_key(&seq_id));
3632
3633 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3635 assert_eq!(pending_count, 1, "Only tx0 should be pending");
3636 assert_eq!(queued_count, 2, "tx3 and tx5 should be queued");
3637
3638 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
3640 pool.add_transaction(
3641 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3642 0,
3643 TempoHardfork::T1,
3644 )
3645 .unwrap();
3646
3647 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
3648 pool.add_transaction(
3649 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3650 0,
3651 TempoHardfork::T1,
3652 )
3653 .unwrap();
3654
3655 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
3657 assert_eq!(pending_count, 4, "Transactions [0,1,2,3] should be pending");
3658 assert_eq!(queued_count, 1, "tx5 should still be queued");
3659
3660 assert_eq!(pool.independent_transactions.len(), 1);
3662 assert!(pool.independent_transactions.contains_key(&seq_id));
3663
3664 let mut on_chain_ids = HashMap::default();
3665 on_chain_ids.insert(seq_id, 3u64);
3666 let (_promoted, mined) = pool.on_nonce_changes(on_chain_ids);
3667
3668 assert_eq!(mined.len(), 3, "Should mine transactions [0,1,2]");
3670
3671 assert_eq!(
3674 pool.independent_transactions.len(),
3675 1,
3676 "Should have one independent transaction"
3677 );
3678 let key = AA2dTransactionId::new(seq_id, 3);
3679 assert!(
3680 pool.independent_transactions.contains_key(&key.seq_id),
3681 "tx3 should be in independent set"
3682 );
3683
3684 let (_pending_count, _queued_count) = pool.pending_and_queued_txn_count();
3686 pool.assert_invariants();
3689
3690 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
3693 pool.add_transaction(
3694 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
3695 3,
3696 TempoHardfork::T1,
3697 )
3698 .unwrap();
3699
3700 let (_pending_count_after, _queued_count_after) = pool.pending_and_queued_txn_count();
3702 pool.assert_invariants();
3703 }
3704
3705 #[test]
3706 fn append_pooled_transaction_elements_respects_limit() {
3707 let mut pool = AA2dPool::default();
3708 let sender = Address::random();
3709 let nonce_key = U256::from(1);
3710
3711 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
3713 let tx0_hash = *tx0.hash();
3714 let tx0_len = tx0.encoded_length();
3715 pool.add_transaction(
3716 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3717 0,
3718 TempoHardfork::T1,
3719 )
3720 .unwrap();
3721
3722 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
3723 let tx1_hash = *tx1.hash();
3724 let tx1_len = tx1.encoded_length();
3725 pool.add_transaction(
3726 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3727 0,
3728 TempoHardfork::T1,
3729 )
3730 .unwrap();
3731
3732 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
3733 let tx2_hash = *tx2.hash();
3734 let tx2_len = tx2.encoded_length();
3735 pool.add_transaction(
3736 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3737 0,
3738 TempoHardfork::T1,
3739 )
3740 .unwrap();
3741
3742 let mut accumulated = 0;
3744 let mut elements = Vec::new();
3745 pool.append_pooled_transaction_elements(
3746 &[tx0_hash, tx1_hash, tx2_hash],
3747 GetPooledTransactionLimit::None,
3748 &mut accumulated,
3749 &mut elements,
3750 );
3751 assert_eq!(elements.len(), 3, "Should return all 3 transactions");
3752 assert_eq!(
3753 accumulated,
3754 tx0_len + tx1_len + tx2_len,
3755 "Should accumulate all sizes"
3756 );
3757
3758 let mut accumulated = 0;
3761 let mut elements = Vec::new();
3762 pool.append_pooled_transaction_elements(
3763 &[tx0_hash, tx1_hash, tx2_hash],
3764 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len - 1),
3765 &mut accumulated,
3766 &mut elements,
3767 );
3768 assert_eq!(
3769 elements.len(),
3770 1,
3771 "Should stop after first tx exceeds limit"
3772 );
3773 assert_eq!(accumulated, tx0_len, "Should accumulate first tx size");
3774
3775 let mut accumulated = 0;
3778 let mut elements = Vec::new();
3779 pool.append_pooled_transaction_elements(
3780 &[tx0_hash, tx1_hash, tx2_hash],
3781 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len + tx1_len - 1),
3782 &mut accumulated,
3783 &mut elements,
3784 );
3785 assert_eq!(
3786 elements.len(),
3787 2,
3788 "Should stop after second tx exceeds limit"
3789 );
3790 assert_eq!(
3791 accumulated,
3792 tx0_len + tx1_len,
3793 "Should accumulate first two tx sizes"
3794 );
3795
3796 let mut accumulated = tx0_len;
3798 let mut elements = Vec::new();
3799 pool.append_pooled_transaction_elements(
3800 &[tx1_hash, tx2_hash],
3801 GetPooledTransactionLimit::ResponseSizeSoftLimit(tx0_len + tx1_len - 1),
3802 &mut accumulated,
3803 &mut elements,
3804 );
3805 assert_eq!(
3806 elements.len(),
3807 1,
3808 "Should return 1 transaction when pre-accumulated size causes early stop"
3809 );
3810 assert_eq!(
3811 accumulated,
3812 tx0_len + tx1_len,
3813 "Should add to pre-accumulated size"
3814 );
3815 }
3816 #[test]
3821 fn test_2d_pool_helpers() {
3822 let mut pool = AA2dPool::default();
3823 let sender = Address::random();
3824 let tx = TxBuilder::aa(sender).build();
3825 let tx_hash = *tx.hash();
3826
3827 assert!(!pool.contains(&tx_hash));
3828 assert!(pool.get(&tx_hash).is_none());
3829
3830 pool.add_transaction(
3831 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3832 0,
3833 TempoHardfork::T1,
3834 )
3835 .unwrap();
3836
3837 assert!(pool.contains(&tx_hash));
3838 let retrieved = pool.get(&tx_hash);
3839 assert!(retrieved.is_some());
3840 assert_eq!(retrieved.unwrap().hash(), &tx_hash);
3841 }
3842
3843 #[test]
3844 fn test_pool_get_all() {
3845 let mut pool = AA2dPool::default();
3846 let sender = Address::random();
3847
3848 let tx0 = TxBuilder::aa(sender).build();
3849 let tx1 = TxBuilder::aa(sender).nonce(1).build();
3850 let tx0_hash = *tx0.hash();
3851 let tx1_hash = *tx1.hash();
3852 let fake_hash = alloy_primitives::B256::random();
3853
3854 pool.add_transaction(
3855 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
3856 0,
3857 TempoHardfork::T1,
3858 )
3859 .unwrap();
3860 pool.add_transaction(
3861 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3862 0,
3863 TempoHardfork::T1,
3864 )
3865 .unwrap();
3866
3867 let hashes = [tx0_hash, tx1_hash, fake_hash];
3868 let results = pool.get_all(hashes.iter());
3869
3870 assert_eq!(results.len(), 2); }
3872
3873 #[test]
3874 fn test_pool_senders_iter() {
3875 let mut pool = AA2dPool::default();
3876 let sender1 = Address::random();
3877 let sender2 = Address::random();
3878
3879 let tx1 = TxBuilder::aa(sender1).build();
3880 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3881
3882 pool.add_transaction(
3883 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3884 0,
3885 TempoHardfork::T1,
3886 )
3887 .unwrap();
3888 pool.add_transaction(
3889 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3890 0,
3891 TempoHardfork::T1,
3892 )
3893 .unwrap();
3894
3895 let senders: Vec<_> = pool.senders_iter().collect();
3896 assert_eq!(senders.len(), 2);
3897 assert!(senders.contains(&&sender1));
3898 assert!(senders.contains(&&sender2));
3899 }
3900
3901 #[test]
3902 fn test_pool_pending_and_queued_transactions() {
3903 let mut pool = AA2dPool::default();
3904 let sender = Address::random();
3905
3906 let tx0 = TxBuilder::aa(sender).build();
3908 let tx1 = TxBuilder::aa(sender).nonce(1).build();
3909 let tx2 = TxBuilder::aa(sender).nonce(2).build();
3910 let tx0_hash = *tx0.hash();
3911 let tx1_hash = *tx1.hash();
3912 let tx2_hash = *tx2.hash();
3913
3914 let tx5 = TxBuilder::aa(sender).nonce(5).build();
3916 let tx6 = TxBuilder::aa(sender).nonce(6).build();
3917 let tx7 = TxBuilder::aa(sender).nonce(7).build();
3918 let tx5_hash = *tx5.hash();
3919 let tx6_hash = *tx6.hash();
3920 let tx7_hash = *tx7.hash();
3921
3922 for tx in [tx0, tx1, tx2, tx5, tx6, tx7] {
3923 pool.add_transaction(
3924 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3925 0,
3926 TempoHardfork::T1,
3927 )
3928 .unwrap();
3929 }
3930
3931 let pending: Vec<_> = pool.pending_transactions().collect();
3932 assert_eq!(pending.len(), 3);
3933 let pending_hashes: HashSet<_> = pending.iter().map(|tx| *tx.hash()).collect();
3934 assert!(pending_hashes.contains(&tx0_hash));
3935 assert!(pending_hashes.contains(&tx1_hash));
3936 assert!(pending_hashes.contains(&tx2_hash));
3937
3938 let queued: Vec<_> = pool.queued_transactions().collect();
3939 assert_eq!(queued.len(), 3);
3940 let queued_hashes: HashSet<_> = queued.iter().map(|tx| *tx.hash()).collect();
3941 assert!(queued_hashes.contains(&tx5_hash));
3942 assert!(queued_hashes.contains(&tx6_hash));
3943 assert!(queued_hashes.contains(&tx7_hash));
3944 }
3945
3946 #[test]
3947 fn test_append_all_transactions() {
3948 let mut pool = AA2dPool::default();
3949 let sender = Address::random();
3950 let expiring_sender = Address::random();
3951
3952 let tx0 = TxBuilder::aa(sender).build();
3953 let tx2 = TxBuilder::aa(sender).nonce(2).build();
3954 let expiring_tx = TxBuilder::aa(expiring_sender).nonce_key(U256::MAX).build();
3955
3956 for tx in [tx0, tx2, expiring_tx] {
3957 pool.add_transaction(
3958 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
3959 0,
3960 TempoHardfork::T1,
3961 )
3962 .unwrap();
3963 }
3964
3965 let expected_pending: HashSet<_> =
3966 pool.pending_transactions().map(|tx| *tx.hash()).collect();
3967 let expected_queued: HashSet<_> = pool.queued_transactions().map(|tx| *tx.hash()).collect();
3968
3969 let mut transactions = AllPoolTransactions::default();
3970 pool.append_all_transactions(&mut transactions);
3971
3972 let pending_hashes: HashSet<_> = transactions.pending.iter().map(|tx| *tx.hash()).collect();
3973 let queued_hashes: HashSet<_> = transactions.queued.iter().map(|tx| *tx.hash()).collect();
3974
3975 assert_eq!(pending_hashes, expected_pending);
3976 assert_eq!(queued_hashes, expected_queued);
3977 }
3978
3979 #[test]
3980 fn test_pool_get_transactions_by_sender_iter() {
3981 let mut pool = AA2dPool::default();
3982 let sender1 = Address::random();
3983 let sender2 = Address::random();
3984
3985 let tx1 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
3986 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
3987
3988 pool.add_transaction(
3989 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
3990 0,
3991 TempoHardfork::T1,
3992 )
3993 .unwrap();
3994 pool.add_transaction(
3995 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
3996 0,
3997 TempoHardfork::T1,
3998 )
3999 .unwrap();
4000
4001 let sender1_txs: Vec<_> = pool.get_transactions_by_sender_iter(sender1).collect();
4002 assert_eq!(sender1_txs.len(), 1);
4003 assert_eq!(sender1_txs[0].sender(), sender1);
4004
4005 let sender2_txs: Vec<_> = pool.get_transactions_by_sender_iter(sender2).collect();
4006 assert_eq!(sender2_txs.len(), 1);
4007 assert_eq!(sender2_txs[0].sender(), sender2);
4008 }
4009
4010 #[test]
4011 fn test_pool_get_transactions_by_origin_iter() {
4012 let mut pool = AA2dPool::default();
4013 let sender = Address::random();
4014
4015 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4016 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4017
4018 pool.add_transaction(
4019 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4020 0,
4021 TempoHardfork::T1,
4022 )
4023 .unwrap();
4024 pool.add_transaction(
4025 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::External)),
4026 0,
4027 TempoHardfork::T1,
4028 )
4029 .unwrap();
4030
4031 let local_txs: Vec<_> = pool
4032 .get_transactions_by_origin_iter(TransactionOrigin::Local)
4033 .collect();
4034 assert_eq!(local_txs.len(), 1);
4035
4036 let external_txs: Vec<_> = pool
4037 .get_transactions_by_origin_iter(TransactionOrigin::External)
4038 .collect();
4039 assert_eq!(external_txs.len(), 1);
4040 }
4041
4042 #[test]
4043 fn test_pool_get_pending_transactions_by_origin_iter() {
4044 let mut pool = AA2dPool::default();
4045 let sender = Address::random();
4046
4047 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4048 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build(); pool.add_transaction(
4051 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4052 0,
4053 TempoHardfork::T1,
4054 )
4055 .unwrap();
4056 pool.add_transaction(
4057 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4058 0,
4059 TempoHardfork::T1,
4060 )
4061 .unwrap();
4062
4063 let pending_local: Vec<_> = pool
4064 .get_pending_transactions_by_origin_iter(TransactionOrigin::Local)
4065 .collect();
4066 assert_eq!(pending_local.len(), 1); }
4068
4069 #[test]
4070 fn test_pool_all_transaction_hashes_iter() {
4071 let mut pool = AA2dPool::default();
4072 let sender = Address::random();
4073
4074 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4075 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4076 let tx0_hash = *tx0.hash();
4077 let tx1_hash = *tx1.hash();
4078
4079 pool.add_transaction(
4080 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4081 0,
4082 TempoHardfork::T1,
4083 )
4084 .unwrap();
4085 pool.add_transaction(
4086 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4087 0,
4088 TempoHardfork::T1,
4089 )
4090 .unwrap();
4091
4092 let hashes: Vec<_> = pool.all_transaction_hashes_iter().collect();
4093 assert_eq!(hashes.len(), 2);
4094 assert!(hashes.contains(&tx0_hash));
4095 assert!(hashes.contains(&tx1_hash));
4096 }
4097
4098 #[test]
4099 fn test_pool_pooled_transactions_hashes_iter() {
4100 let mut pool = AA2dPool::default();
4101 let sender = Address::random();
4102
4103 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4104 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4105
4106 pool.add_transaction(
4107 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4108 0,
4109 TempoHardfork::T1,
4110 )
4111 .unwrap();
4112 pool.add_transaction(
4113 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4114 0,
4115 TempoHardfork::T1,
4116 )
4117 .unwrap();
4118
4119 let hashes: Vec<_> = pool.pooled_transactions_hashes_iter().collect();
4120 assert_eq!(hashes.len(), 2);
4121 }
4122
4123 #[test]
4124 fn test_pool_pooled_transactions_iter() {
4125 let mut pool = AA2dPool::default();
4126 let sender = Address::random();
4127
4128 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4129 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4130
4131 pool.add_transaction(
4132 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4133 0,
4134 TempoHardfork::T1,
4135 )
4136 .unwrap();
4137 pool.add_transaction(
4138 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4139 0,
4140 TempoHardfork::T1,
4141 )
4142 .unwrap();
4143
4144 let txs: Vec<_> = pool.pooled_transactions_iter().collect();
4145 assert_eq!(txs.len(), 2);
4146 }
4147
4148 #[test]
4153 fn test_best_transactions_iterator() {
4154 let mut pool = AA2dPool::default();
4155 let sender = Address::random();
4156
4157 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4158 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4159
4160 pool.add_transaction(
4161 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4162 0,
4163 TempoHardfork::T1,
4164 )
4165 .unwrap();
4166 pool.add_transaction(
4167 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4168 0,
4169 TempoHardfork::T1,
4170 )
4171 .unwrap();
4172
4173 let mut best = pool.best_transactions();
4174
4175 let first = best.next();
4177 assert!(first.is_some());
4178
4179 let second = best.next();
4180 assert!(second.is_some());
4181
4182 let third = best.next();
4183 assert!(third.is_none());
4184 }
4185
4186 #[test]
4187 fn test_best_transactions_size_hint_counts_snapshot() {
4188 let mut pool = AA2dPool::default();
4189 let sender = Address::random();
4190 let expiring_sender = Address::random();
4191
4192 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4193 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4194 let expiring_tx = TxBuilder::aa(expiring_sender).nonce_key(U256::MAX).build();
4195
4196 pool.add_transaction(
4197 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4198 0,
4199 TempoHardfork::T1,
4200 )
4201 .unwrap();
4202 pool.add_transaction(
4203 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4204 0,
4205 TempoHardfork::T1,
4206 )
4207 .unwrap();
4208 pool.add_transaction(
4209 Arc::new(wrap_valid_tx(expiring_tx, TransactionOrigin::Local)),
4210 0,
4211 TempoHardfork::T1,
4212 )
4213 .unwrap();
4214
4215 let mut best = pool.best_transactions();
4216
4217 assert_eq!(best.size_hint(), (0, Some(3)));
4218 assert!(best.next().is_some());
4219 assert_eq!(best.size_hint(), (0, Some(2)));
4220 assert!(best.next().is_some());
4221 assert_eq!(best.size_hint(), (0, Some(1)));
4222 assert!(best.next().is_some());
4223 assert_eq!(best.size_hint(), (0, Some(0)));
4224 }
4225
4226 #[test]
4227 fn test_best_transactions_size_hint_ignores_unread_new_transactions() {
4228 let mut pool = AA2dPool::default();
4229 let snapshot_sender = Address::random();
4230 let incoming_sender = Address::random();
4231
4232 let snapshot_tx = TxBuilder::aa(snapshot_sender).nonce_key(U256::ZERO).build();
4233 let incoming_tx = TxBuilder::aa(incoming_sender)
4234 .nonce_key(U256::from(1))
4235 .build();
4236
4237 pool.add_transaction(
4238 Arc::new(wrap_valid_tx(snapshot_tx, TransactionOrigin::Local)),
4239 0,
4240 TempoHardfork::T1,
4241 )
4242 .unwrap();
4243
4244 let best = pool.best_transactions();
4245
4246 pool.add_transaction(
4247 Arc::new(wrap_valid_tx(incoming_tx, TransactionOrigin::Local)),
4248 0,
4249 TempoHardfork::T1,
4250 )
4251 .unwrap();
4252
4253 assert_eq!(best.size_hint(), (0, Some(1)));
4254 }
4255
4256 #[test]
4257 fn test_best_transactions_mark_invalid() {
4258 use reth_primitives_traits::transaction::error::InvalidTransactionError;
4259
4260 let mut pool = AA2dPool::default();
4261 let sender = Address::random();
4262
4263 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4264 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4265
4266 pool.add_transaction(
4267 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4268 0,
4269 TempoHardfork::T1,
4270 )
4271 .unwrap();
4272 pool.add_transaction(
4273 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4274 0,
4275 TempoHardfork::T1,
4276 )
4277 .unwrap();
4278
4279 let mut best = pool.best_transactions();
4280
4281 let first = best.next().unwrap();
4282
4283 let error = reth_transaction_pool::error::InvalidPoolTransactionError::Consensus(
4285 InvalidTransactionError::TxTypeNotSupported,
4286 );
4287 best.mark_invalid(&first, error);
4288
4289 }
4292
4293 #[test]
4294 fn test_best_transactions_expiring_nonce_independent() {
4295 let mut pool = AA2dPool::default();
4298 let sender = Address::random();
4299
4300 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
4302 pool.add_transaction(
4303 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4304 0,
4305 TempoHardfork::T1,
4306 )
4307 .unwrap();
4308
4309 let mut best = pool.best_transactions();
4310
4311 let first = best.next();
4313 assert!(first.is_some());
4314
4315 assert!(best.next().is_none());
4317 }
4318
4319 #[test]
4324 fn test_remove_transactions_by_sender() {
4325 let mut pool = AA2dPool::default();
4326 let sender1 = Address::random();
4327 let sender2 = Address::random();
4328
4329 let tx1 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
4330 let tx2 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
4331
4332 pool.add_transaction(
4333 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4334 0,
4335 TempoHardfork::T1,
4336 )
4337 .unwrap();
4338 pool.add_transaction(
4339 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4340 0,
4341 TempoHardfork::T1,
4342 )
4343 .unwrap();
4344
4345 let removed = pool.remove_transactions_by_sender(sender1);
4346 assert_eq!(removed.len(), 1);
4347 assert_eq!(removed[0].sender(), sender1);
4348
4349 let (pending, queued) = pool.pending_and_queued_txn_count();
4351 assert_eq!(pending + queued, 1);
4352
4353 pool.assert_invariants();
4354 }
4355
4356 #[test]
4357 fn test_remove_transactions_and_descendants() {
4358 let mut pool = AA2dPool::default();
4359 let sender = Address::random();
4360
4361 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4362 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4363 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
4364 let tx0_hash = *tx0.hash();
4365
4366 pool.add_transaction(
4367 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4368 0,
4369 TempoHardfork::T1,
4370 )
4371 .unwrap();
4372 pool.add_transaction(
4373 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4374 0,
4375 TempoHardfork::T1,
4376 )
4377 .unwrap();
4378 pool.add_transaction(
4379 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4380 0,
4381 TempoHardfork::T1,
4382 )
4383 .unwrap();
4384
4385 let removed = pool.remove_transactions_and_descendants([&tx0_hash].into_iter());
4387 assert_eq!(removed.len(), 3);
4388
4389 let (pending, queued) = pool.pending_and_queued_txn_count();
4390 assert_eq!(pending + queued, 0);
4391
4392 pool.assert_invariants();
4393 }
4394
4395 #[test]
4400 fn test_aa_sequence_id_equality() {
4401 let addr = Address::random();
4402 let nonce_key = U256::from(42);
4403
4404 let id1 = AASequenceId::new(addr, nonce_key);
4405 let id2 = AASequenceId::new(addr, nonce_key);
4406 let id3 = AASequenceId::new(Address::random(), nonce_key);
4407
4408 assert_eq!(id1, id2);
4409 assert_ne!(id1, id3);
4410 }
4411
4412 #[test]
4413 fn test_aa2d_transaction_id_unlocks() {
4414 let addr = Address::random();
4415 let seq_id = AASequenceId::new(addr, U256::ZERO);
4416 let tx_id = AA2dTransactionId::new(seq_id, 5);
4417
4418 let next_id = tx_id.unlocks();
4419 assert_eq!(next_id.seq_id, seq_id);
4420 assert_eq!(next_id.nonce, 6);
4421 }
4422
4423 #[test]
4424 fn test_aa2d_transaction_id_ordering() {
4425 let addr = Address::random();
4426 let seq_id = AASequenceId::new(addr, U256::ZERO);
4427
4428 let id1 = AA2dTransactionId::new(seq_id, 1);
4429 let id2 = AA2dTransactionId::new(seq_id, 2);
4430
4431 assert!(id1 < id2);
4432 }
4433
4434 #[test]
4439 fn test_nonce_overflow_at_u64_max() {
4440 let mut pool = AA2dPool::default();
4441 let sender = Address::random();
4442 let nonce_key = U256::ZERO;
4443
4444 let tx = TxBuilder::aa(sender)
4445 .nonce_key(nonce_key)
4446 .nonce(u64::MAX)
4447 .build();
4448 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
4449
4450 let result = pool.add_transaction(Arc::new(valid_tx), u64::MAX, TempoHardfork::T1);
4451 assert!(result.is_ok());
4452
4453 let (pending, queued) = pool.pending_and_queued_txn_count();
4454 assert_eq!(pending, 1);
4455 assert_eq!(queued, 0);
4456
4457 let seq_id = AASequenceId::new(sender, nonce_key);
4458 let tx_id = AA2dTransactionId::new(seq_id, u64::MAX);
4459 let unlocked = tx_id.unlocks();
4460 assert_eq!(
4461 unlocked.nonce,
4462 u64::MAX,
4463 "saturating_add should not overflow"
4464 );
4465
4466 pool.assert_invariants();
4467 }
4468
4469 #[test]
4470 fn test_nonce_near_max_with_gap() {
4471 let mut pool = AA2dPool::default();
4472 let sender = Address::random();
4473 let nonce_key = U256::ZERO;
4474
4475 let tx_max = TxBuilder::aa(sender)
4476 .nonce_key(nonce_key)
4477 .nonce(u64::MAX)
4478 .build();
4479 let tx_max_minus_1 = TxBuilder::aa(sender)
4480 .nonce_key(nonce_key)
4481 .nonce(u64::MAX - 1)
4482 .build();
4483
4484 pool.add_transaction(
4485 Arc::new(wrap_valid_tx(tx_max, TransactionOrigin::Local)),
4486 u64::MAX - 1,
4487 TempoHardfork::T1,
4488 )
4489 .unwrap();
4490
4491 let (pending, queued) = pool.pending_and_queued_txn_count();
4492 assert_eq!(pending, 0, "tx at u64::MAX should be queued (gap exists)");
4493 assert_eq!(queued, 1);
4494
4495 pool.add_transaction(
4496 Arc::new(wrap_valid_tx(tx_max_minus_1, TransactionOrigin::Local)),
4497 u64::MAX - 1,
4498 TempoHardfork::T1,
4499 )
4500 .unwrap();
4501
4502 let (pending, queued) = pool.pending_and_queued_txn_count();
4503 assert_eq!(pending, 2, "both should now be pending");
4504 assert_eq!(queued, 0);
4505
4506 pool.assert_invariants();
4507 }
4508
4509 #[test]
4510 fn test_empty_pool_operations() {
4511 let pool = AA2dPool::default();
4512
4513 assert_eq!(pool.pending_and_queued_txn_count(), (0, 0));
4514 assert!(pool.get(&B256::random()).is_none());
4515 assert!(!pool.contains(&B256::random()));
4516 assert_eq!(pool.senders_iter().count(), 0);
4517 assert_eq!(pool.pending_transactions().count(), 0);
4518 assert_eq!(pool.queued_transactions().count(), 0);
4519 assert_eq!(pool.all_transaction_hashes_iter().count(), 0);
4520 assert_eq!(pool.pooled_transactions_hashes_iter().count(), 0);
4521 assert_eq!(pool.pooled_transactions_iter().count(), 0);
4522
4523 let mut best = pool.best_transactions();
4524 assert!(best.next().is_none());
4525 }
4526
4527 #[test]
4528 fn test_empty_pool_remove_operations() {
4529 let mut pool = AA2dPool::default();
4530 let random_hash = B256::random();
4531 let random_sender = Address::random();
4532
4533 let removed = pool.remove_transactions([&random_hash].into_iter());
4534 assert!(removed.is_empty());
4535
4536 let removed = pool.remove_transactions_by_sender(random_sender);
4537 assert!(removed.is_empty());
4538
4539 let removed = pool.remove_transactions_and_descendants([&random_hash].into_iter());
4540 assert!(removed.is_empty());
4541
4542 pool.assert_invariants();
4543 }
4544
4545 #[test]
4546 fn test_empty_pool_on_nonce_changes() {
4547 let mut pool = AA2dPool::default();
4548
4549 let mut changes = HashMap::default();
4550 changes.insert(AASequenceId::new(Address::random(), U256::ZERO), 5u64);
4551
4552 let (promoted, mined) = pool.on_nonce_changes(changes);
4553 assert!(promoted.is_empty());
4554 assert!(mined.is_empty());
4555
4556 pool.assert_invariants();
4557 }
4558
4559 #[test]
4564 fn test_add_already_imported_transaction() {
4565 let mut pool = AA2dPool::default();
4566 let sender = Address::random();
4567
4568 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4569 let tx_hash = *tx.hash();
4570 let valid_tx = Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local));
4571
4572 pool.add_transaction(valid_tx.clone(), 0, TempoHardfork::T1)
4573 .unwrap();
4574
4575 let result = pool.add_transaction(valid_tx, 0, TempoHardfork::T1);
4576 assert!(result.is_err());
4577 let err = result.unwrap_err();
4578 assert_eq!(err.hash, tx_hash);
4579 assert!(
4580 matches!(err.kind, PoolErrorKind::AlreadyImported),
4581 "Expected AlreadyImported, got {:?}",
4582 err.kind
4583 );
4584
4585 pool.assert_invariants();
4586 }
4587
4588 #[test]
4589 fn test_add_outdated_nonce_transaction() {
4590 let mut pool = AA2dPool::default();
4591 let sender = Address::random();
4592
4593 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(5).build();
4594 let tx_hash = *tx.hash();
4595 let valid_tx = Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local));
4596
4597 let result = pool.add_transaction(valid_tx, 10, TempoHardfork::T1);
4598 assert!(result.is_err());
4599 let err = result.unwrap_err();
4600 assert_eq!(err.hash, tx_hash);
4601 assert!(
4602 matches!(
4603 err.kind,
4604 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
4605 InvalidTransactionError::NonceNotConsistent { tx: 5, state: 10 }
4606 ))
4607 ),
4608 "Expected NonceNotConsistent, got {:?}",
4609 err.kind
4610 );
4611
4612 let (pending, queued) = pool.pending_and_queued_txn_count();
4613 assert_eq!(pending + queued, 0);
4614 }
4615
4616 #[test]
4617 fn test_replacement_underpriced() {
4618 let mut pool = AA2dPool::default();
4619 let sender = Address::random();
4620
4621 let tx1 = TxBuilder::aa(sender)
4622 .nonce_key(U256::ZERO)
4623 .max_priority_fee(1_000_000_000)
4624 .max_fee(2_000_000_000)
4625 .build();
4626 pool.add_transaction(
4627 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4628 0,
4629 TempoHardfork::T1,
4630 )
4631 .unwrap();
4632
4633 let tx2 = TxBuilder::aa(sender)
4634 .nonce_key(U256::ZERO)
4635 .max_priority_fee(1_000_000_001)
4636 .max_fee(2_000_000_001)
4637 .build();
4638 let tx2_hash = *tx2.hash();
4639 let result = pool.add_transaction(
4640 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4641 0,
4642 TempoHardfork::T1,
4643 );
4644
4645 assert!(result.is_err());
4646 let err = result.unwrap_err();
4647 assert_eq!(err.hash, tx2_hash);
4648 assert!(
4649 matches!(err.kind, PoolErrorKind::ReplacementUnderpriced),
4650 "Expected ReplacementUnderpriced, got {:?}",
4651 err.kind
4652 );
4653
4654 let (pending, queued) = pool.pending_and_queued_txn_count();
4655 assert_eq!(pending + queued, 1);
4656
4657 pool.assert_invariants();
4658 }
4659
4660 #[test]
4665 fn test_discard_at_max_txs_limit() {
4666 let config = AA2dPoolConfig {
4667 price_bump_config: PriceBumpConfig::default(),
4668 pending_limit: SubPoolLimit {
4669 max_txs: 3,
4670 max_size: usize::MAX,
4671 },
4672 queued_limit: SubPoolLimit {
4673 max_txs: 10000,
4674 max_size: usize::MAX,
4675 },
4676 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4677 };
4678 let mut pool = AA2dPool::new(config);
4679
4680 for i in 0..5usize {
4681 let sender = Address::from_word(B256::from(U256::from(i)));
4682 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4683 let result = pool.add_transaction(
4684 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4685 0,
4686 TempoHardfork::T1,
4687 );
4688 assert!(result.is_ok());
4689 }
4690
4691 let (pending, queued) = pool.pending_and_queued_txn_count();
4692 assert_eq!(pending + queued, 3, "Pool should be capped at max_txs=3");
4693 assert_eq!(pending, 3, "All remaining transactions should be pending");
4694
4695 pool.assert_invariants();
4696 }
4697
4698 #[test]
4699 fn test_discard_removes_lowest_priority_same_priority_uses_submission_order() {
4700 let config = AA2dPoolConfig {
4701 price_bump_config: PriceBumpConfig::default(),
4702 pending_limit: SubPoolLimit {
4703 max_txs: 2,
4704 max_size: usize::MAX,
4705 },
4706 queued_limit: SubPoolLimit {
4707 max_txs: 10000,
4708 max_size: usize::MAX,
4709 },
4710 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4711 };
4712 let mut pool = AA2dPool::new(config);
4713 let sender = Address::random();
4714
4715 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
4718 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
4719 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
4720 let tx0_hash = *tx0.hash();
4721 let tx1_hash = *tx1.hash();
4722 let tx2_hash = *tx2.hash();
4723
4724 pool.add_transaction(
4725 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
4726 0,
4727 TempoHardfork::T1,
4728 )
4729 .unwrap();
4730 pool.add_transaction(
4731 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
4732 0,
4733 TempoHardfork::T1,
4734 )
4735 .unwrap();
4736 let result = pool.add_transaction(
4737 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
4738 0,
4739 TempoHardfork::T1,
4740 );
4741 assert!(result.is_ok());
4742
4743 let added = result.unwrap();
4744 if let AddedTransaction::Pending(pending) = added {
4745 assert!(
4746 !pending.discarded.is_empty(),
4747 "Should have discarded transactions"
4748 );
4749 assert_eq!(
4750 pending.discarded[0].hash(),
4751 &tx2_hash,
4752 "tx2 (last submitted, lowest priority tiebreaker) should be discarded"
4753 );
4754 } else {
4755 panic!("Expected Pending result");
4756 }
4757
4758 assert!(pool.contains(&tx0_hash));
4759 assert!(pool.contains(&tx1_hash));
4760 assert!(!pool.contains(&tx2_hash));
4761
4762 pool.assert_invariants();
4763 }
4764
4765 #[test]
4767 fn test_discard_enforced_for_queued_transactions() {
4768 let config = AA2dPoolConfig {
4769 price_bump_config: PriceBumpConfig::default(),
4770 pending_limit: SubPoolLimit {
4771 max_txs: 2,
4772 max_size: usize::MAX,
4773 },
4774 queued_limit: SubPoolLimit {
4775 max_txs: 2,
4776 max_size: usize::MAX,
4777 },
4778 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4779 };
4780 let mut pool = AA2dPool::new(config);
4781
4782 for i in 0..5usize {
4784 let sender = Address::from_word(B256::from(U256::from(i)));
4785 let tx = TxBuilder::aa(sender)
4786 .nonce_key(U256::from(i))
4787 .nonce(1000)
4788 .build();
4789 let result = pool.add_transaction(
4790 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4791 0,
4792 TempoHardfork::T1,
4793 );
4794 assert!(result.is_ok(), "Transaction {i} should be added");
4795 }
4796
4797 let (pending, queued) = pool.pending_and_queued_txn_count();
4798 assert_eq!(
4799 pending + queued,
4800 2,
4801 "Pool should be capped at max_txs=2, but has {pending} pending + {queued} queued",
4802 );
4803
4804 pool.assert_invariants();
4805 }
4806
4807 #[test]
4809 fn test_queued_limit_enforced_separately() {
4810 let config = AA2dPoolConfig {
4811 price_bump_config: PriceBumpConfig::default(),
4812 pending_limit: SubPoolLimit {
4813 max_txs: 10,
4814 max_size: usize::MAX,
4815 },
4816 queued_limit: SubPoolLimit {
4817 max_txs: 3,
4818 max_size: usize::MAX,
4819 },
4820 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4821 };
4822 let mut pool = AA2dPool::new(config);
4823
4824 for i in 0..5usize {
4826 let sender = Address::from_word(B256::from(U256::from(i)));
4827 let tx = TxBuilder::aa(sender)
4828 .nonce_key(U256::from(i))
4829 .nonce(1000)
4830 .build();
4831 let _ = pool.add_transaction(
4832 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4833 0,
4834 TempoHardfork::T1,
4835 );
4836 }
4837
4838 let (pending, queued) = pool.pending_and_queued_txn_count();
4839 assert_eq!(queued, 3, "Queued should be capped at 3");
4840 assert_eq!(pending, 0, "No pending transactions");
4841 pool.assert_invariants();
4842 }
4843
4844 #[test]
4846 fn test_pending_limit_enforced_separately() {
4847 let config = AA2dPoolConfig {
4848 price_bump_config: PriceBumpConfig::default(),
4849 pending_limit: SubPoolLimit {
4850 max_txs: 3,
4851 max_size: usize::MAX,
4852 },
4853 queued_limit: SubPoolLimit {
4854 max_txs: 10,
4855 max_size: usize::MAX,
4856 },
4857 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4858 };
4859 let mut pool = AA2dPool::new(config);
4860
4861 for i in 0..5usize {
4863 let sender = Address::from_word(B256::from(U256::from(i)));
4864 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4865 let _ = pool.add_transaction(
4866 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4867 0,
4868 TempoHardfork::T1,
4869 );
4870 }
4871
4872 let (pending, queued) = pool.pending_and_queued_txn_count();
4873 assert_eq!(pending, 3, "Pending should be capped at 3");
4874 assert_eq!(queued, 0, "No queued transactions");
4875 pool.assert_invariants();
4876 }
4877
4878 #[test]
4880 fn test_queued_eviction_does_not_affect_pending() {
4881 let config = AA2dPoolConfig {
4882 price_bump_config: PriceBumpConfig::default(),
4883 pending_limit: SubPoolLimit {
4884 max_txs: 5,
4885 max_size: usize::MAX,
4886 },
4887 queued_limit: SubPoolLimit {
4888 max_txs: 2,
4889 max_size: usize::MAX,
4890 },
4891 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4892 };
4893 let mut pool = AA2dPool::new(config);
4894
4895 let mut pending_hashes = Vec::new();
4897 for i in 0..3usize {
4898 let sender = Address::from_word(B256::from(U256::from(i)));
4899 let tx = TxBuilder::aa(sender).nonce_key(U256::from(i)).build();
4900 let hash = *tx.hash();
4901 pending_hashes.push(hash);
4902 let _ = pool.add_transaction(
4903 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4904 0,
4905 TempoHardfork::T1,
4906 );
4907 }
4908
4909 for i in 100..110usize {
4911 let sender = Address::from_word(B256::from(U256::from(i)));
4912 let tx = TxBuilder::aa(sender)
4913 .nonce_key(U256::from(i))
4914 .nonce(1000)
4915 .build();
4916 let _ = pool.add_transaction(
4917 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
4918 0,
4919 TempoHardfork::T1,
4920 );
4921 }
4922
4923 for hash in &pending_hashes {
4925 assert!(
4926 pool.contains(hash),
4927 "Pending tx should not be evicted by queued spam"
4928 );
4929 }
4930
4931 let (pending, queued) = pool.pending_and_queued_txn_count();
4932 assert_eq!(pending, 3, "All 3 pending should remain");
4933 assert_eq!(queued, 2, "Queued capped at 2");
4934 pool.assert_invariants();
4935 }
4936
4937 #[test]
4940 fn test_discard_evicts_low_priority_over_vanity_address() {
4941 let config = AA2dPoolConfig {
4942 price_bump_config: PriceBumpConfig::default(),
4943 pending_limit: SubPoolLimit {
4944 max_txs: 2,
4945 max_size: usize::MAX,
4946 },
4947 queued_limit: SubPoolLimit {
4948 max_txs: 10,
4949 max_size: usize::MAX,
4950 },
4951 max_txs_per_sender: DEFAULT_MAX_TXS_PER_SENDER,
4952 };
4953 let mut pool = AA2dPool::new(config);
4954
4955 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)
4967 .nonce_key(U256::ZERO)
4968 .max_fee(high_max_fee)
4969 .max_priority_fee(5_000_000_000) .build();
4971 let high_priority_hash = *high_priority_tx.hash();
4972
4973 let low_priority_tx = TxBuilder::aa(normal_sender)
4976 .nonce_key(U256::ZERO)
4977 .max_fee(high_max_fee)
4978 .max_priority_fee(1) .build();
4980 let low_priority_hash = *low_priority_tx.hash();
4981
4982 pool.add_transaction(
4983 Arc::new(wrap_valid_tx(high_priority_tx, TransactionOrigin::Local)),
4984 0,
4985 TempoHardfork::T1,
4986 )
4987 .unwrap();
4988 pool.add_transaction(
4989 Arc::new(wrap_valid_tx(low_priority_tx, TransactionOrigin::Local)),
4990 0,
4991 TempoHardfork::T1,
4992 )
4993 .unwrap();
4994
4995 let trigger_tx = TxBuilder::aa(Address::random())
4998 .nonce_key(U256::from(1))
4999 .max_fee(high_max_fee)
5000 .max_priority_fee(3_000_000_000) .build();
5002 let trigger_hash = *trigger_tx.hash();
5003
5004 let result = pool.add_transaction(
5005 Arc::new(wrap_valid_tx(trigger_tx, TransactionOrigin::Local)),
5006 0,
5007 TempoHardfork::T1,
5008 );
5009 assert!(result.is_ok());
5010
5011 let added = result.unwrap();
5012 if let AddedTransaction::Pending(pending) = added {
5013 assert!(
5014 !pending.discarded.is_empty(),
5015 "Should have discarded transactions"
5016 );
5017 assert_eq!(
5019 pending.discarded[0].hash(),
5020 &low_priority_hash,
5021 "Low priority tx should be evicted, not the high-priority vanity address tx"
5022 );
5023 } else {
5024 panic!("Expected Pending result");
5025 }
5026
5027 assert!(
5029 pool.contains(&high_priority_hash),
5030 "High priority vanity address tx should be kept"
5031 );
5032 assert!(
5033 !pool.contains(&low_priority_hash),
5034 "Low priority tx should be evicted"
5035 );
5036 assert!(pool.contains(&trigger_hash), "Trigger tx should be kept");
5037
5038 pool.assert_invariants();
5039 }
5040
5041 #[test]
5043 fn test_per_sender_limit_rejects_excess_transactions() {
5044 let config = AA2dPoolConfig {
5045 price_bump_config: PriceBumpConfig::default(),
5046 pending_limit: SubPoolLimit {
5047 max_txs: 1000,
5048 max_size: usize::MAX,
5049 },
5050 queued_limit: SubPoolLimit {
5051 max_txs: 1000,
5052 max_size: usize::MAX,
5053 },
5054 max_txs_per_sender: 3,
5055 };
5056 let mut pool = AA2dPool::new(config);
5057 let sender = Address::random();
5058
5059 for nonce in 0..3u64 {
5061 let tx = TxBuilder::aa(sender)
5062 .nonce_key(U256::ZERO)
5063 .nonce(nonce)
5064 .build();
5065 let result = pool.add_transaction(
5066 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5067 0,
5068 TempoHardfork::T1,
5069 );
5070 assert!(result.is_ok(), "Transaction {nonce} should be accepted");
5071 }
5072
5073 let tx = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(3).build();
5075 let result = pool.add_transaction(
5076 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5077 0,
5078 TempoHardfork::T1,
5079 );
5080 assert!(result.is_err(), "4th transaction should be rejected");
5081 let err = result.unwrap_err();
5082 assert!(
5083 matches!(err.kind, PoolErrorKind::SpammerExceededCapacity(_)),
5084 "Error should be SpammerExceededCapacity, got {:?}",
5085 err.kind
5086 );
5087
5088 let other_sender = Address::random();
5090 let tx = TxBuilder::aa(other_sender).nonce_key(U256::ZERO).build();
5091 let result = pool.add_transaction(
5092 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5093 0,
5094 TempoHardfork::T1,
5095 );
5096 assert!(result.is_ok(), "Different sender should be accepted");
5097
5098 pool.assert_invariants();
5099 }
5100
5101 #[test]
5103 fn test_per_sender_limit_allows_replacement() {
5104 let config = AA2dPoolConfig {
5105 price_bump_config: PriceBumpConfig::default(),
5106 pending_limit: SubPoolLimit {
5107 max_txs: 1000,
5108 max_size: usize::MAX,
5109 },
5110 queued_limit: SubPoolLimit {
5111 max_txs: 1000,
5112 max_size: usize::MAX,
5113 },
5114 max_txs_per_sender: 2,
5115 };
5116 let mut pool = AA2dPool::new(config);
5117 let sender = Address::random();
5118
5119 for nonce in 0..2u64 {
5121 let tx = TxBuilder::aa(sender)
5122 .nonce_key(U256::ZERO)
5123 .nonce(nonce)
5124 .build();
5125 pool.add_transaction(
5126 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5127 0,
5128 TempoHardfork::T1,
5129 )
5130 .unwrap();
5131 }
5132
5133 let replacement_tx = TxBuilder::aa(sender)
5135 .nonce_key(U256::ZERO)
5136 .nonce(0)
5137 .max_fee(100_000_000_000) .max_priority_fee(50_000_000_000)
5139 .build();
5140 let result = pool.add_transaction(
5141 Arc::new(wrap_valid_tx(replacement_tx, TransactionOrigin::Local)),
5142 0,
5143 TempoHardfork::T1,
5144 );
5145 assert!(
5146 result.is_ok(),
5147 "Replacement should be allowed even at limit"
5148 );
5149
5150 pool.assert_invariants();
5151 }
5152
5153 #[test]
5155 fn test_per_sender_limit_freed_after_removal() {
5156 let config = AA2dPoolConfig {
5157 price_bump_config: PriceBumpConfig::default(),
5158 pending_limit: SubPoolLimit {
5159 max_txs: 1000,
5160 max_size: usize::MAX,
5161 },
5162 queued_limit: SubPoolLimit {
5163 max_txs: 1000,
5164 max_size: usize::MAX,
5165 },
5166 max_txs_per_sender: 2,
5167 };
5168 let mut pool = AA2dPool::new(config);
5169 let sender = Address::random();
5170
5171 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
5173 let tx1_hash = *tx1.hash();
5174 pool.add_transaction(
5175 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5176 0,
5177 TempoHardfork::T1,
5178 )
5179 .unwrap();
5180
5181 let tx2 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
5182 pool.add_transaction(
5183 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
5184 0,
5185 TempoHardfork::T1,
5186 )
5187 .unwrap();
5188
5189 let tx3 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(2).build();
5191 let result = pool.add_transaction(
5192 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
5193 0,
5194 TempoHardfork::T1,
5195 );
5196 assert!(result.is_err(), "3rd should be rejected at limit");
5197
5198 pool.remove_transactions(std::iter::once(&tx1_hash));
5200
5201 let result = pool.add_transaction(
5203 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5204 0,
5205 TempoHardfork::T1,
5206 );
5207 assert!(result.is_ok(), "3rd should succeed after removal");
5208
5209 pool.assert_invariants();
5210 }
5211
5212 #[test]
5214 fn test_per_sender_limit_includes_expiring_nonce_txs() {
5215 let config = AA2dPoolConfig {
5216 price_bump_config: PriceBumpConfig::default(),
5217 pending_limit: SubPoolLimit {
5218 max_txs: 1000,
5219 max_size: usize::MAX,
5220 },
5221 queued_limit: SubPoolLimit {
5222 max_txs: 1000,
5223 max_size: usize::MAX,
5224 },
5225 max_txs_per_sender: 2,
5226 };
5227 let mut pool = AA2dPool::new(config);
5228 let sender = Address::random();
5229
5230 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
5232 pool.add_transaction(
5233 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5234 0,
5235 TempoHardfork::T1,
5236 )
5237 .unwrap();
5238
5239 let tx2 = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
5241 pool.add_transaction(
5242 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
5243 0,
5244 TempoHardfork::T1,
5245 )
5246 .unwrap();
5247
5248 let tx3 = TxBuilder::aa(sender)
5250 .nonce_key(U256::from(1))
5251 .nonce(0)
5252 .build();
5253 let result = pool.add_transaction(
5254 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5255 0,
5256 TempoHardfork::T1,
5257 );
5258 assert!(
5259 result.is_err(),
5260 "3rd tx should be rejected due to per-sender limit"
5261 );
5262
5263 pool.assert_invariants();
5264 }
5265
5266 #[test]
5271 fn test_best_transactions_mark_invalid_skips_sequence() {
5272 use reth_primitives_traits::transaction::error::InvalidTransactionError;
5273
5274 let mut pool = AA2dPool::default();
5275 let sender1 = Address::random();
5276 let sender2 = Address::random();
5277
5278 let tx1_0 = TxBuilder::aa(sender1).nonce_key(U256::ZERO).build();
5279 let tx1_1 = TxBuilder::aa(sender1)
5280 .nonce_key(U256::ZERO)
5281 .nonce(1)
5282 .build();
5283 let tx2_0 = TxBuilder::aa(sender2).nonce_key(U256::from(1)).build();
5284
5285 let tx1_0_hash = *tx1_0.hash();
5286 let tx2_0_hash = *tx2_0.hash();
5287
5288 pool.add_transaction(
5289 Arc::new(wrap_valid_tx(tx1_0, TransactionOrigin::Local)),
5290 0,
5291 TempoHardfork::T1,
5292 )
5293 .unwrap();
5294 pool.add_transaction(
5295 Arc::new(wrap_valid_tx(tx1_1, TransactionOrigin::Local)),
5296 0,
5297 TempoHardfork::T1,
5298 )
5299 .unwrap();
5300 pool.add_transaction(
5301 Arc::new(wrap_valid_tx(tx2_0, TransactionOrigin::Local)),
5302 0,
5303 TempoHardfork::T1,
5304 )
5305 .unwrap();
5306
5307 let mut best = pool.best_transactions();
5308
5309 let first = best.next().unwrap();
5310 let first_hash = *first.hash();
5311
5312 let error =
5313 InvalidPoolTransactionError::Consensus(InvalidTransactionError::TxTypeNotSupported);
5314 best.mark_invalid(&first, error);
5315
5316 let mut remaining_hashes = HashSet::new();
5317 for tx in best {
5318 remaining_hashes.insert(*tx.hash());
5319 }
5320
5321 if first_hash == tx1_0_hash {
5322 assert!(
5323 !remaining_hashes.contains(&tx1_0_hash),
5324 "tx1_0 was consumed"
5325 );
5326 assert!(
5327 remaining_hashes.contains(&tx2_0_hash),
5328 "tx2_0 should still be yielded"
5329 );
5330 } else {
5331 assert!(
5332 remaining_hashes.contains(&tx1_0_hash) || remaining_hashes.contains(&tx2_0_hash),
5333 "At least one other independent tx should be yielded"
5334 );
5335 }
5336 }
5337
5338 #[test]
5339 fn test_best_transactions_order_by_priority() {
5340 let mut pool = AA2dPool::default();
5341
5342 let sender1 = Address::random();
5343 let sender2 = Address::random();
5344
5345 let low_priority = TxBuilder::aa(sender1)
5346 .nonce_key(U256::ZERO)
5347 .max_priority_fee(1_000_000)
5348 .max_fee(2_000_000)
5349 .build();
5350 let high_priority = TxBuilder::aa(sender2)
5351 .nonce_key(U256::from(1))
5352 .max_priority_fee(10_000_000_000)
5353 .max_fee(20_000_000_000)
5354 .build();
5355 let high_priority_hash = *high_priority.hash();
5356
5357 pool.add_transaction(
5358 Arc::new(wrap_valid_tx(low_priority, TransactionOrigin::Local)),
5359 0,
5360 TempoHardfork::T1,
5361 )
5362 .unwrap();
5363 pool.add_transaction(
5364 Arc::new(wrap_valid_tx(high_priority, TransactionOrigin::Local)),
5365 0,
5366 TempoHardfork::T1,
5367 )
5368 .unwrap();
5369
5370 let mut best = pool.best_transactions();
5371 let first = best.next().unwrap();
5372
5373 assert_eq!(
5374 first.hash(),
5375 &high_priority_hash,
5376 "Higher priority transaction should come first"
5377 );
5378 }
5379
5380 fn priority_flip_pool(block_base_fee: u64) -> (AA2dPool, B256, B256) {
5381 priority_flip_pool_with_config(block_base_fee, AA2dPoolConfig::default())
5382 }
5383
5384 fn priority_flip_pool_with_config(
5385 block_base_fee: u64,
5386 config: AA2dPoolConfig,
5387 ) -> (AA2dPool, B256, B256) {
5388 let mut pool = AA2dPool::new(config);
5389 pool.set_base_fee(TEMPO_T1_BASE_FEE);
5390
5391 let high_at_insert_low_at_block = TxBuilder::aa(Address::random())
5392 .nonce_key(U256::from(1))
5393 .max_priority_fee(10_000_000_000)
5394 .max_fee(u128::from(block_base_fee) + 1)
5395 .build();
5396 let high_at_insert_low_at_block_hash = *high_at_insert_low_at_block.hash();
5397
5398 let low_at_insert_high_at_block = TxBuilder::aa(Address::random())
5399 .nonce_key(U256::from(2))
5400 .max_priority_fee(5_000_000_000)
5401 .max_fee(u128::from(block_base_fee) + 5_000_000_000)
5402 .build();
5403 let low_at_insert_high_at_block_hash = *low_at_insert_high_at_block.hash();
5404
5405 for tx in [high_at_insert_low_at_block, low_at_insert_high_at_block] {
5406 pool.add_transaction(
5407 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5408 0,
5409 TempoHardfork::T1,
5410 )
5411 .unwrap();
5412 }
5413
5414 (
5415 pool,
5416 low_at_insert_high_at_block_hash,
5417 high_at_insert_low_at_block_hash,
5418 )
5419 }
5420
5421 #[test]
5422 fn test_best_transactions_with_base_fee_reprioritizes_regular_transactions() {
5423 let block_base_fee = TEMPO_T1_BASE_FEE + 10_000_000_000;
5424 let (pool, expected_first, expected_second) = priority_flip_pool(block_base_fee);
5425
5426 let hashes = pool
5427 .best_transactions_with_base_fee(block_base_fee)
5428 .map(|tx| *tx.hash())
5429 .collect::<Vec<_>>();
5430
5431 assert_eq!(hashes, vec![expected_first, expected_second]);
5432 }
5433
5434 #[test]
5435 fn test_discard_reprices_eviction_priorities() {
5436 let block_base_fee = TEMPO_T1_BASE_FEE + 10_000_000_000;
5437 let (mut pool, expected_kept, expected_evicted) = priority_flip_pool_with_config(
5438 block_base_fee,
5439 AA2dPoolConfig {
5440 pending_limit: SubPoolLimit {
5441 max_txs: 2,
5442 max_size: usize::MAX,
5443 },
5444 queued_limit: SubPoolLimit {
5445 max_txs: 10,
5446 max_size: usize::MAX,
5447 },
5448 ..Default::default()
5449 },
5450 );
5451 pool.set_base_fee(block_base_fee);
5452 let trigger = TxBuilder::aa(Address::random())
5453 .nonce_key(U256::from(3))
5454 .max_priority_fee(10_000_000_000)
5455 .max_fee(u128::from(block_base_fee) + 10_000_000_000)
5456 .build();
5457
5458 let result = pool
5459 .add_transaction(
5460 Arc::new(wrap_valid_tx(trigger, TransactionOrigin::Local)),
5461 0,
5462 TempoHardfork::T1,
5463 )
5464 .unwrap();
5465
5466 let AddedTransaction::Pending(pending) = result else {
5467 panic!("expected pending transaction")
5468 };
5469 assert_eq!(pending.discarded[0].hash(), &expected_evicted);
5470 assert!(!pool.contains(&expected_evicted));
5471 assert!(pool.contains(&expected_kept));
5472 }
5473
5474 #[test]
5475 fn test_best_transactions_with_base_fee_filters_underpriced_regular_sequence() {
5476 let mut pool = AA2dPool::default();
5477 let block_base_fee = TEMPO_T1_BASE_FEE + 10_000_000_000;
5478 let sequence_sender = Address::random();
5479
5480 let underpriced_parent = TxBuilder::aa(sequence_sender)
5481 .nonce_key(U256::from(1))
5482 .max_fee(u128::from(block_base_fee - 1))
5483 .max_priority_fee(1_000_000_000)
5484 .build();
5485 let valid_child = TxBuilder::aa(sequence_sender)
5486 .nonce_key(U256::from(1))
5487 .nonce(1)
5488 .max_fee(u128::from(block_base_fee) + 10_000_000_000)
5489 .max_priority_fee(10_000_000_000)
5490 .build();
5491 let valid_independent = TxBuilder::aa(Address::random())
5492 .nonce_key(U256::from(2))
5493 .max_fee(u128::from(block_base_fee) + 1_000_000_000)
5494 .max_priority_fee(1_000_000_000)
5495 .build();
5496 let valid_independent_hash = *valid_independent.hash();
5497
5498 for tx in [underpriced_parent, valid_child, valid_independent] {
5499 pool.add_transaction(
5500 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5501 0,
5502 TempoHardfork::T1,
5503 )
5504 .unwrap();
5505 }
5506
5507 let hashes = pool
5508 .best_transactions_with_base_fee(block_base_fee)
5509 .map(|tx| *tx.hash())
5510 .collect::<Vec<_>>();
5511
5512 assert_eq!(hashes, vec![valid_independent_hash]);
5513 }
5514
5515 #[test]
5516 fn test_best_transactions_with_base_fee_filters_underpriced_expiring_nonce() {
5517 let mut pool = AA2dPool::default();
5518 let block_base_fee = TEMPO_T1_BASE_FEE + 10_000_000_000;
5519
5520 let underpriced = TxBuilder::aa(Address::random())
5521 .nonce_key(U256::MAX)
5522 .max_fee(u128::from(block_base_fee - 1))
5523 .max_priority_fee(1_000_000_000)
5524 .build();
5525 let valid = TxBuilder::aa(Address::random())
5526 .nonce_key(U256::MAX)
5527 .max_fee(u128::from(block_base_fee) + 1_000_000_000)
5528 .max_priority_fee(1_000_000_000)
5529 .build();
5530 let valid_hash = *valid.hash();
5531
5532 for tx in [underpriced, valid] {
5533 pool.add_transaction(
5534 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5535 0,
5536 TempoHardfork::T1,
5537 )
5538 .unwrap();
5539 }
5540
5541 let hashes = pool
5542 .best_transactions_with_base_fee(block_base_fee)
5543 .map(|tx| *tx.hash())
5544 .collect::<Vec<_>>();
5545
5546 assert_eq!(hashes, vec![valid_hash]);
5547 }
5548
5549 #[test]
5550 fn test_best_transactions_merges_regular_and_expiring_by_priority() {
5551 let mut pool = AA2dPool::default();
5552 let max_fee = 30_000_000_000u128;
5553
5554 let regular_low = TxBuilder::aa(Address::random())
5555 .nonce_key(U256::from(1))
5556 .max_fee(max_fee)
5557 .max_priority_fee(1_000_000_000)
5558 .build();
5559 let regular_low_hash = *regular_low.hash();
5560
5561 let expiring_high = TxBuilder::aa(Address::random())
5562 .nonce_key(U256::MAX)
5563 .max_fee(max_fee)
5564 .max_priority_fee(5_000_000_000)
5565 .build();
5566 let expiring_high_hash = *expiring_high.hash();
5567
5568 let regular_mid = TxBuilder::aa(Address::random())
5569 .nonce_key(U256::from(2))
5570 .max_fee(max_fee)
5571 .max_priority_fee(3_000_000_000)
5572 .build();
5573 let regular_mid_hash = *regular_mid.hash();
5574
5575 let expiring_low = TxBuilder::aa(Address::random())
5576 .nonce_key(U256::MAX)
5577 .max_fee(max_fee)
5578 .max_priority_fee(2_000_000_000)
5579 .build();
5580 let expiring_low_hash = *expiring_low.hash();
5581
5582 for tx in [regular_low, expiring_high, regular_mid, expiring_low] {
5583 pool.add_transaction(
5584 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5585 0,
5586 TempoHardfork::T1,
5587 )
5588 .unwrap();
5589 }
5590 pool.assert_invariants();
5591
5592 let hashes = pool
5593 .best_transactions()
5594 .map(|tx| *tx.hash())
5595 .collect::<Vec<_>>();
5596
5597 assert_eq!(
5598 hashes,
5599 vec![
5600 expiring_high_hash,
5601 regular_mid_hash,
5602 expiring_low_hash,
5603 regular_low_hash,
5604 ]
5605 );
5606 }
5607
5608 #[test]
5609 fn test_best_transactions_merges_regular_and_expiring_by_submission_id() {
5610 let max_fee = 30_000_000_000u128;
5611
5612 let mut expiring_older_pool = AA2dPool::default();
5613 let expiring_older = TxBuilder::aa(Address::random())
5614 .nonce_key(U256::MAX)
5615 .max_fee(max_fee)
5616 .max_priority_fee(1_000_000_000)
5617 .build();
5618 let expiring_older_hash = *expiring_older.hash();
5619 let regular_newer = TxBuilder::aa(Address::random())
5620 .nonce_key(U256::from(1))
5621 .max_fee(max_fee)
5622 .max_priority_fee(1_000_000_000)
5623 .build();
5624
5625 for tx in [expiring_older, regular_newer] {
5626 expiring_older_pool
5627 .add_transaction(
5628 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5629 0,
5630 TempoHardfork::T1,
5631 )
5632 .unwrap();
5633 }
5634
5635 assert_eq!(
5636 expiring_older_pool
5637 .best_transactions()
5638 .next()
5639 .unwrap()
5640 .hash(),
5641 &expiring_older_hash
5642 );
5643
5644 let mut regular_older_pool = AA2dPool::default();
5645 let regular_older = TxBuilder::aa(Address::random())
5646 .nonce_key(U256::from(1))
5647 .max_fee(max_fee)
5648 .max_priority_fee(1_000_000_000)
5649 .build();
5650 let regular_older_hash = *regular_older.hash();
5651 let expiring_newer = TxBuilder::aa(Address::random())
5652 .nonce_key(U256::MAX)
5653 .max_fee(max_fee)
5654 .max_priority_fee(1_000_000_000)
5655 .build();
5656
5657 for tx in [regular_older, expiring_newer] {
5658 regular_older_pool
5659 .add_transaction(
5660 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
5661 0,
5662 TempoHardfork::T1,
5663 )
5664 .unwrap();
5665 }
5666
5667 assert_eq!(
5668 regular_older_pool
5669 .best_transactions()
5670 .next()
5671 .unwrap()
5672 .hash(),
5673 ®ular_older_hash
5674 );
5675 }
5676
5677 #[test]
5682 fn on_state_updates_clears_scratch_buffers_without_nonce_state() {
5683 let mut pool = AA2dPool::default();
5684 pool.state_update_nonce_changes
5685 .insert(AASequenceId::new(Address::random(), U256::from(1)), 1);
5686 pool.state_update_included_expiring_nonce_hashes
5687 .push(B256::random());
5688
5689 let state = AddressMap::default();
5690 let (promoted, mined) = pool.on_state_updates(&state);
5691
5692 assert!(promoted.is_empty());
5693 assert!(mined.is_empty());
5694 assert!(pool.state_update_nonce_changes.is_empty());
5695 assert!(pool.state_update_included_expiring_nonce_hashes.is_empty());
5696 }
5697
5698 #[test]
5699 fn test_on_state_updates_with_nonce_precompile_slot() {
5700 use revm::database::{AccountStatus, BundleAccount, states::StorageSlot};
5701
5702 let mut pool = AA2dPool::default();
5703 let sender = Address::random();
5704 let nonce_key = U256::from(1);
5705
5706 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
5707 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
5708 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
5709 let nonce_slot = tx0
5710 .nonce_key_slot()
5711 .expect("2D nonce tx should have nonce key slot");
5712
5713 pool.add_transaction(
5714 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
5715 0,
5716 TempoHardfork::T1,
5717 )
5718 .unwrap();
5719 pool.add_transaction(
5720 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5721 0,
5722 TempoHardfork::T1,
5723 )
5724 .unwrap();
5725 pool.add_transaction(
5726 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
5727 0,
5728 TempoHardfork::T1,
5729 )
5730 .unwrap();
5731
5732 let (pending, queued) = pool.pending_and_queued_txn_count();
5733 assert_eq!(pending, 3);
5734 assert_eq!(queued, 0);
5735
5736 let mut storage = HashMap::default();
5737 storage.insert(
5738 nonce_slot,
5739 StorageSlot::new_changed(U256::ZERO, U256::from(2u64)),
5740 );
5741 let mut state = AddressMap::default();
5742 state.insert(
5743 NONCE_PRECOMPILE_ADDRESS,
5744 BundleAccount::new(None, None, storage, AccountStatus::Changed),
5745 );
5746
5747 let (promoted, mined) = pool.on_state_updates(&state);
5748
5749 assert!(promoted.is_empty(), "tx2 was already pending");
5750 assert_eq!(mined.len(), 2, "tx0 and tx1 should be mined");
5751
5752 let (pending, queued) = pool.pending_and_queued_txn_count();
5753 assert_eq!(pending, 1, "Only tx2 should remain pending");
5754 assert_eq!(queued, 0);
5755
5756 pool.assert_invariants();
5757 assert!(pool.state_update_nonce_changes.is_empty());
5758 assert!(pool.state_update_included_expiring_nonce_hashes.is_empty());
5759 }
5760
5761 #[test]
5762 fn test_on_state_updates_creates_gap_demotion() {
5763 use revm::database::{AccountStatus, BundleAccount, states::StorageSlot};
5764
5765 let mut pool = AA2dPool::default();
5766 let sender = Address::random();
5767 let nonce_key = U256::from(1);
5768
5769 let tx0 = TxBuilder::aa(sender).nonce_key(nonce_key).build();
5770 let tx1 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(1).build();
5771 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
5772 let nonce_slot = tx0
5773 .nonce_key_slot()
5774 .expect("2D nonce tx should have nonce key slot");
5775
5776 pool.add_transaction(
5777 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
5778 0,
5779 TempoHardfork::T1,
5780 )
5781 .unwrap();
5782 pool.add_transaction(
5783 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
5784 0,
5785 TempoHardfork::T1,
5786 )
5787 .unwrap();
5788 pool.add_transaction(
5789 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5790 0,
5791 TempoHardfork::T1,
5792 )
5793 .unwrap();
5794
5795 let (pending, queued) = pool.pending_and_queued_txn_count();
5796 assert_eq!(pending, 2);
5797 assert_eq!(queued, 1);
5798
5799 let mut storage = HashMap::default();
5800 storage.insert(
5801 nonce_slot,
5802 StorageSlot::new_changed(U256::ZERO, U256::from(2u64)),
5803 );
5804 let mut state = AddressMap::default();
5805 state.insert(
5806 NONCE_PRECOMPILE_ADDRESS,
5807 BundleAccount::new(None, None, storage, AccountStatus::Changed),
5808 );
5809
5810 let (promoted, mined) = pool.on_state_updates(&state);
5811
5812 assert_eq!(mined.len(), 2, "tx0 and tx1 should be mined");
5813 assert!(promoted.is_empty());
5814
5815 let (pending, queued) = pool.pending_and_queued_txn_count();
5816 assert_eq!(pending, 0, "tx3 should still be queued (gap at nonce 2)");
5817 assert_eq!(queued, 1);
5818
5819 pool.assert_invariants();
5820 }
5821
5822 #[test]
5823 fn test_on_nonce_changes_promotes_queued_transactions() {
5824 let mut pool = AA2dPool::default();
5825 let sender = Address::random();
5826 let nonce_key = U256::ZERO;
5827 let seq_id = AASequenceId::new(sender, nonce_key);
5828
5829 let tx2 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(2).build();
5830 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
5831
5832 pool.add_transaction(
5833 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
5834 0,
5835 TempoHardfork::T1,
5836 )
5837 .unwrap();
5838 pool.add_transaction(
5839 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5840 0,
5841 TempoHardfork::T1,
5842 )
5843 .unwrap();
5844
5845 let (pending, queued) = pool.pending_and_queued_txn_count();
5846 assert_eq!(pending, 0);
5847 assert_eq!(queued, 2);
5848
5849 let mut changes = HashMap::default();
5850 changes.insert(seq_id, 2u64);
5851
5852 let (promoted, mined) = pool.on_nonce_changes(changes);
5853
5854 assert!(
5855 mined.is_empty(),
5856 "No transactions to mine (on-chain nonce jumped)"
5857 );
5858 assert_eq!(promoted.len(), 2, "tx2 and tx3 should be promoted");
5859 assert!(promoted.iter().any(|t| t.hash() == tx2.hash()));
5860
5861 let (pending, queued) = pool.pending_and_queued_txn_count();
5862 assert_eq!(pending, 2);
5863 assert_eq!(queued, 0);
5864
5865 pool.assert_invariants();
5866 }
5867
5868 #[test]
5873 fn test_interleaved_inserts_multiple_nonce_keys() {
5874 let mut pool = AA2dPool::default();
5875 let sender = Address::random();
5876
5877 let key_a = U256::ZERO;
5878 let key_b = U256::from(1);
5879
5880 let tx_a0 = TxBuilder::aa(sender).nonce_key(key_a).build();
5881 let tx_b0 = TxBuilder::aa(sender).nonce_key(key_b).build();
5882 let tx_a1 = TxBuilder::aa(sender).nonce_key(key_a).nonce(1).build();
5883 let tx_b2 = TxBuilder::aa(sender).nonce_key(key_b).nonce(2).build();
5884 let tx_b1 = TxBuilder::aa(sender).nonce_key(key_b).nonce(1).build();
5885
5886 pool.add_transaction(
5887 Arc::new(wrap_valid_tx(tx_a0, TransactionOrigin::Local)),
5888 0,
5889 TempoHardfork::T1,
5890 )
5891 .unwrap();
5892 pool.add_transaction(
5893 Arc::new(wrap_valid_tx(tx_b0, TransactionOrigin::Local)),
5894 0,
5895 TempoHardfork::T1,
5896 )
5897 .unwrap();
5898 pool.add_transaction(
5899 Arc::new(wrap_valid_tx(tx_a1, TransactionOrigin::Local)),
5900 0,
5901 TempoHardfork::T1,
5902 )
5903 .unwrap();
5904 pool.add_transaction(
5905 Arc::new(wrap_valid_tx(tx_b2, TransactionOrigin::Local)),
5906 0,
5907 TempoHardfork::T1,
5908 )
5909 .unwrap();
5910 pool.add_transaction(
5911 Arc::new(wrap_valid_tx(tx_b1, TransactionOrigin::Local)),
5912 0,
5913 TempoHardfork::T1,
5914 )
5915 .unwrap();
5916
5917 let (pending, queued) = pool.pending_and_queued_txn_count();
5918 assert_eq!(pending, 5, "All transactions should be pending");
5919 assert_eq!(queued, 0);
5920
5921 assert_eq!(
5922 pool.independent_transactions.len(),
5923 2,
5924 "Two nonce keys = two independent txs"
5925 );
5926
5927 pool.assert_invariants();
5928 }
5929
5930 #[test]
5931 fn test_same_sender_different_nonce_keys_independent() {
5932 let mut pool = AA2dPool::default();
5933 let sender = Address::random();
5934
5935 let key_a = U256::from(100);
5936 let key_b = U256::from(200);
5937
5938 let tx_a5 = TxBuilder::aa(sender).nonce_key(key_a).nonce(5).build();
5939 let tx_b0 = TxBuilder::aa(sender).nonce_key(key_b).build();
5940
5941 pool.add_transaction(
5942 Arc::new(wrap_valid_tx(tx_a5, TransactionOrigin::Local)),
5943 5,
5944 TempoHardfork::T1,
5945 )
5946 .unwrap();
5947 pool.add_transaction(
5948 Arc::new(wrap_valid_tx(tx_b0, TransactionOrigin::Local)),
5949 0,
5950 TempoHardfork::T1,
5951 )
5952 .unwrap();
5953
5954 let (pending, queued) = pool.pending_and_queued_txn_count();
5955 assert_eq!(pending, 2);
5956 assert_eq!(queued, 0);
5957
5958 assert_eq!(pool.independent_transactions.len(), 2);
5959
5960 pool.assert_invariants();
5961 }
5962
5963 #[test_case::test_case(U256::ZERO)]
5968 #[test_case::test_case(U256::random())]
5969 fn reorg_nonce_decrease_clears_stale_independent_transaction(nonce_key: U256) {
5970 let mut pool = AA2dPool::default();
5971 let sender = Address::random();
5972 let seq_id = AASequenceId::new(sender, nonce_key);
5973
5974 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
5976 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
5977 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
5978 let tx5_hash = *tx5.hash();
5979
5980 pool.add_transaction(
5981 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
5982 3,
5983 TempoHardfork::T1,
5984 )
5985 .unwrap();
5986 pool.add_transaction(
5987 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)),
5988 3,
5989 TempoHardfork::T1,
5990 )
5991 .unwrap();
5992 pool.add_transaction(
5993 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
5994 3,
5995 TempoHardfork::T1,
5996 )
5997 .unwrap();
5998
5999 let (pending, queued) = pool.pending_and_queued_txn_count();
6001 assert_eq!(pending, 3, "All transactions should be pending");
6002 assert_eq!(queued, 0);
6003 assert_eq!(pool.independent_transactions.len(), 1);
6004 assert_eq!(
6005 pool.independent_transactions
6006 .get(&seq_id)
6007 .unwrap()
6008 .transaction
6009 .nonce(),
6010 3,
6011 "tx3 should be independent initially"
6012 );
6013 pool.assert_invariants();
6014
6015 let mut on_chain_ids = HashMap::default();
6017 on_chain_ids.insert(seq_id, 5u64);
6018 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
6019
6020 assert_eq!(mined.len(), 2, "tx3 and tx4 should be mined");
6021 assert!(promoted.is_empty(), "No promotions expected");
6022
6023 let (pending, queued) = pool.pending_and_queued_txn_count();
6025 assert_eq!(pending, 1, "Only tx5 should remain pending");
6026 assert_eq!(queued, 0);
6027 assert_eq!(pool.independent_transactions.len(), 1);
6028 assert_eq!(
6029 pool.independent_transactions
6030 .get(&seq_id)
6031 .unwrap()
6032 .transaction
6033 .hash(),
6034 &tx5_hash,
6035 "tx5 should be independent after mining"
6036 );
6037 pool.assert_invariants();
6038
6039 let mut on_chain_ids = HashMap::default();
6041 on_chain_ids.insert(seq_id, 3u64);
6042 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
6043
6044 assert!(mined.is_empty(), "No transactions should be mined");
6046 assert!(promoted.is_empty(), "No promotions expected");
6048
6049 let (pending, queued) = pool.pending_and_queued_txn_count();
6051 assert_eq!(pending, 0, "tx5 should not be pending (nonce gap)");
6052 assert_eq!(queued, 1, "tx5 should be queued");
6053
6054 assert!(
6056 !pool.independent_transactions.contains_key(&seq_id),
6057 "independent_transactions should not contain stale entry after reorg"
6058 );
6059
6060 pool.assert_invariants();
6061 }
6062
6063 #[test_case::test_case(U256::ZERO)]
6073 #[test_case::test_case(U256::random())]
6074 fn reorg_reinjection_via_add_transaction_restores_pending_state(nonce_key: U256) {
6075 let mut pool = AA2dPool::default();
6076 let sender = Address::random();
6077 let seq_id = AASequenceId::new(sender, nonce_key);
6078
6079 let tx3 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(3).build();
6081 let tx4 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(4).build();
6082 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
6083 let tx3_hash = *tx3.hash();
6084 let tx4_hash = *tx4.hash();
6085 let tx5_hash = *tx5.hash();
6086
6087 pool.add_transaction(
6088 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
6089 3,
6090 TempoHardfork::T1,
6091 )
6092 .unwrap();
6093 pool.add_transaction(
6094 Arc::new(wrap_valid_tx(tx4.clone(), TransactionOrigin::Local)),
6095 3,
6096 TempoHardfork::T1,
6097 )
6098 .unwrap();
6099 pool.add_transaction(
6100 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
6101 3,
6102 TempoHardfork::T1,
6103 )
6104 .unwrap();
6105
6106 let (pending, queued) = pool.pending_and_queued_txn_count();
6107 assert_eq!(pending, 3);
6108 assert_eq!(queued, 0);
6109 pool.assert_invariants();
6110
6111 let mut nonce_changes = HashMap::default();
6113 nonce_changes.insert(seq_id, 5u64);
6114 let (_promoted, mined) = pool.on_nonce_changes(nonce_changes);
6115 assert_eq!(mined.len(), 2);
6116
6117 let (pending, queued) = pool.pending_and_queued_txn_count();
6118 assert_eq!(pending, 1, "only tx5 should remain pending");
6119 assert_eq!(queued, 0);
6120 pool.assert_invariants();
6121
6122 pool.add_transaction(
6126 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::External)),
6127 3,
6128 TempoHardfork::T1,
6129 )
6130 .unwrap();
6131 pool.add_transaction(
6132 Arc::new(wrap_valid_tx(tx4, TransactionOrigin::External)),
6133 3,
6134 TempoHardfork::T1,
6135 )
6136 .unwrap();
6137
6138 let (pending, queued) = pool.pending_and_queued_txn_count();
6140 assert_eq!(pending, 3, "all txs should be pending after re-injection");
6141 assert_eq!(queued, 0);
6142
6143 assert_eq!(
6145 pool.independent_transactions
6146 .get(&seq_id)
6147 .unwrap()
6148 .transaction
6149 .nonce(),
6150 3,
6151 );
6152
6153 assert!(pool.contains(&tx3_hash));
6155 assert!(pool.contains(&tx4_hash));
6156 assert!(pool.contains(&tx5_hash));
6157
6158 pool.assert_invariants();
6159 }
6160
6161 #[test_case::test_case(U256::ZERO)]
6166 #[test_case::test_case(U256::random())]
6167 fn gap_demotion_marks_all_subsequent_transactions_as_queued(nonce_key: U256) {
6168 let mut pool = AA2dPool::default();
6169 let sender = Address::random();
6170 let seq_id = AASequenceId::new(sender, nonce_key);
6171
6172 let tx5 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(5).build();
6174 let tx6 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(6).build();
6175 let tx7 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(7).build();
6176 let tx8 = TxBuilder::aa(sender).nonce_key(nonce_key).nonce(8).build();
6177 let tx6_hash = *tx6.hash();
6178
6179 pool.add_transaction(
6180 Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)),
6181 5,
6182 TempoHardfork::T1,
6183 )
6184 .unwrap();
6185 pool.add_transaction(
6186 Arc::new(wrap_valid_tx(tx6, TransactionOrigin::Local)),
6187 5,
6188 TempoHardfork::T1,
6189 )
6190 .unwrap();
6191 pool.add_transaction(
6192 Arc::new(wrap_valid_tx(tx7, TransactionOrigin::Local)),
6193 5,
6194 TempoHardfork::T1,
6195 )
6196 .unwrap();
6197 pool.add_transaction(
6198 Arc::new(wrap_valid_tx(tx8, TransactionOrigin::Local)),
6199 5,
6200 TempoHardfork::T1,
6201 )
6202 .unwrap();
6203
6204 let (pending, queued) = pool.pending_and_queued_txn_count();
6206 assert_eq!(pending, 4, "All transactions should be pending initially");
6207 assert_eq!(queued, 0);
6208 assert_eq!(pool.independent_transactions.len(), 1);
6209 pool.assert_invariants();
6210
6211 let removed = pool.remove_transactions(std::iter::once(&tx6_hash));
6214 assert_eq!(removed.len(), 1, "Should remove exactly tx6");
6215
6216 let mut on_chain_ids = HashMap::default();
6219 on_chain_ids.insert(seq_id, 5u64);
6220 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
6221
6222 assert!(mined.is_empty(), "No transactions should be mined");
6223 assert!(promoted.is_empty(), "No promotions expected");
6224
6225 let (pending, queued) = pool.pending_and_queued_txn_count();
6228 assert_eq!(
6229 pending, 1,
6230 "Only tx5 should be pending (tx7 and tx8 are after the gap)"
6231 );
6232 assert_eq!(
6233 queued, 2,
6234 "tx7 and tx8 should both be queued due to gap at nonce 6"
6235 );
6236
6237 pool.assert_invariants();
6238 }
6239
6240 #[test]
6241 fn expiring_nonce_tx_increments_pending_count() {
6242 let mut pool = AA2dPool::default();
6243 let sender = Address::random();
6244
6245 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
6247 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
6248
6249 let result = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
6251 assert!(result.is_ok(), "Transaction should be added successfully");
6252 assert!(
6253 matches!(result.unwrap(), AddedTransaction::Pending(_)),
6254 "Expiring nonce transaction should be pending"
6255 );
6256
6257 let (pending, queued) = pool.pending_and_queued_txn_count();
6259 assert_eq!(pending, 1, "Should have 1 pending transaction");
6260 assert_eq!(queued, 0, "Should have 0 queued transactions");
6261
6262 pool.assert_invariants();
6264 }
6265
6266 #[test]
6267 fn expiring_nonce_tx_dedup_uses_expiring_nonce_hash() {
6268 let mut pool = AA2dPool::default();
6269 let sender = Address::random();
6270 let call_to = Address::random();
6271 let fee_token = Address::random();
6272 let calls = vec![Call {
6273 to: TxKind::Call(call_to),
6274 value: U256::ZERO,
6275 input: Bytes::new(),
6276 }];
6277
6278 let build_tx = |fee_payer_signature: Signature| {
6279 let tx = TempoTransaction {
6280 chain_id: 1,
6281 max_priority_fee_per_gas: 1_000_000_000,
6282 max_fee_per_gas: 2_000_000_000,
6283 gas_limit: 1_000_000,
6284 calls: calls.clone(),
6285 nonce_key: U256::MAX,
6286 nonce: 0,
6287 fee_token: Some(fee_token),
6288 fee_payer_signature: Some(fee_payer_signature),
6289 valid_after: None,
6290 valid_before: Some(core::num::NonZeroU64::new(123).unwrap()),
6291 access_list: AccessList::default(),
6292 tempo_authorization_list: Vec::new(),
6293 key_authorization: None,
6294 };
6295
6296 let signature = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
6297 Signature::test_signature(),
6298 ));
6299 let aa_signed = AASigned::new_unhashed(tx, signature);
6300 let envelope: TempoTxEnvelope = aa_signed.into();
6301 let recovered = Recovered::new_unchecked(envelope, sender);
6302 TempoPooledTransaction::new(recovered)
6303 };
6304
6305 let tx1 = build_tx(Signature::new(U256::from(1), U256::from(2), false));
6306 let tx2 = build_tx(Signature::new(U256::from(3), U256::from(4), false));
6307
6308 assert_ne!(tx1.hash(), tx2.hash(), "tx hashes must differ");
6309 let expiring_hash_1 = tx1
6310 .expiring_nonce_hash()
6311 .expect("expiring nonce tx must be AA");
6312 let expiring_hash_2 = tx2
6313 .expiring_nonce_hash()
6314 .expect("expiring nonce tx must be AA");
6315 assert_eq!(
6316 expiring_hash_1, expiring_hash_2,
6317 "expiring nonce hashes must match"
6318 );
6319
6320 let tx1_hash = *tx1.hash();
6321 pool.add_transaction(
6322 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
6323 0,
6324 TempoHardfork::T1,
6325 )
6326 .unwrap();
6327
6328 let tx2_hash = *tx2.hash();
6329 let result = pool.add_transaction(
6330 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
6331 0,
6332 TempoHardfork::T1,
6333 );
6334 assert!(result.is_err(), "Expected AlreadyImported error");
6335 let err = result.unwrap_err();
6336 assert_eq!(err.hash, tx2_hash);
6337 assert!(
6338 matches!(err.kind, PoolErrorKind::AlreadyImported),
6339 "Expected AlreadyImported, got {:?}",
6340 err.kind
6341 );
6342
6343 let (pending, queued) = pool.pending_and_queued_txn_count();
6344 assert_eq!(pending, 1, "Expected 1 pending transaction");
6345 assert_eq!(queued, 0, "Expected 0 queued transactions");
6346 assert!(pool.by_hash.contains_key(&tx1_hash));
6347 assert_eq!(pool.expiring_nonce_txs.len(), 1);
6348 assert_expiring_eviction_index_len(&pool, 1);
6349 pool.assert_invariants();
6350 }
6351
6352 #[test]
6355 fn remove_included_expiring_nonce_tx_uses_correct_key() {
6356 let mut pool = AA2dPool::default();
6357 let sender = Address::random();
6358 let fee_token = Address::random();
6359 let calls = vec![Call {
6360 to: TxKind::Call(Address::random()),
6361 value: U256::ZERO,
6362 input: Bytes::new(),
6363 }];
6364
6365 let tx = TempoTransaction {
6366 chain_id: 1,
6367 max_priority_fee_per_gas: 1_000_000_000,
6368 max_fee_per_gas: 2_000_000_000,
6369 gas_limit: 1_000_000,
6370 calls,
6371 nonce_key: U256::MAX,
6372 nonce: 0,
6373 fee_token: Some(fee_token),
6374 fee_payer_signature: Some(Signature::new(U256::from(1), U256::from(2), false)),
6375 valid_before: Some(core::num::NonZeroU64::new(123).unwrap()),
6376 access_list: AccessList::default(),
6377 tempo_authorization_list: Vec::new(),
6378 key_authorization: None,
6379 valid_after: None,
6380 };
6381
6382 let signature =
6383 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
6384 let aa_signed = AASigned::new_unhashed(tx, signature);
6385 let envelope: TempoTxEnvelope = aa_signed.into();
6386 let recovered = Recovered::new_unchecked(envelope, sender);
6387 let pooled = TempoPooledTransaction::new(recovered);
6388
6389 let tx_hash = *pooled.hash();
6390 pool.add_transaction(
6391 Arc::new(wrap_valid_tx(pooled, TransactionOrigin::Local)),
6392 0,
6393 TempoHardfork::T1,
6394 )
6395 .unwrap();
6396
6397 assert_eq!(pool.expiring_nonce_txs.len(), 1);
6398 assert_expiring_eviction_index_len(&pool, 1);
6399 assert!(pool.by_hash.contains_key(&tx_hash));
6400 pool.assert_invariants();
6401
6402 let removed = pool.remove_transactions(std::iter::once(&tx_hash));
6404 assert_eq!(removed.len(), 1, "should remove the tx by its tx_hash");
6405 assert_eq!(*removed[0].hash(), tx_hash);
6406
6407 assert!(
6409 pool.expiring_nonce_txs.is_empty(),
6410 "expiring_nonce_txs not cleaned up"
6411 );
6412 assert!(
6413 pool.expiring_nonce_eviction_order.is_empty(),
6414 "expiring_nonce_eviction_order not cleaned up"
6415 );
6416 assert!(
6417 !pool.by_hash.contains_key(&tx_hash),
6418 "by_hash not cleaned up"
6419 );
6420
6421 let (pending, queued) = pool.pending_and_queued_txn_count();
6422 assert_eq!(pending, 0);
6423 assert_eq!(queued, 0);
6424 pool.assert_invariants();
6425 }
6426
6427 #[test]
6428 fn on_state_updates_removes_included_expiring_nonce_from_eviction_index() {
6429 use revm::database::{AccountStatus, BundleAccount, states::StorageSlot};
6430
6431 let mut pool = AA2dPool::default();
6432 let sender = Address::random();
6433
6434 let tx = TxBuilder::aa(sender)
6435 .nonce_key(U256::MAX)
6436 .valid_before(123)
6437 .max_fee(30_000_000_000)
6438 .build();
6439 let tx_hash = *tx.hash();
6440 let expiring_hash = tx
6441 .expiring_nonce_hash()
6442 .expect("expiring nonce tx must have expiring hash");
6443 let slot = tx
6444 .expiring_nonce_slot()
6445 .expect("expiring nonce tx must have storage slot");
6446
6447 pool.add_transaction(
6448 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
6449 0,
6450 TempoHardfork::T1,
6451 )
6452 .unwrap();
6453
6454 assert_expiring_eviction_index_len(&pool, 1);
6455 assert_expiring_eviction_index_contains(&pool, expiring_hash);
6456
6457 let mut storage = HashMap::default();
6458 storage.insert(
6459 slot,
6460 StorageSlot::new_changed(U256::ZERO, U256::from(123u64)),
6461 );
6462 let mut state = AddressMap::default();
6463 state.insert(
6464 NONCE_PRECOMPILE_ADDRESS,
6465 BundleAccount::new(None, None, storage, AccountStatus::Changed),
6466 );
6467
6468 let (promoted, mined) = pool.on_state_updates(&state);
6469
6470 assert!(promoted.is_empty());
6471 assert_eq!(mined.len(), 1);
6472 assert_eq!(mined[0].hash(), &tx_hash);
6473 assert!(!pool.contains(&tx_hash));
6474 assert!(pool.expiring_nonce_txs.is_empty());
6475 assert!(pool.slot_to_expiring_nonce_hash.is_empty());
6476 assert_expiring_eviction_index_len(&pool, 0);
6477 pool.assert_invariants();
6478 assert!(pool.state_update_nonce_changes.is_empty());
6479 assert!(pool.state_update_included_expiring_nonce_hashes.is_empty());
6480 }
6481
6482 fn eviction_test_pool() -> AA2dPool {
6484 AA2dPool::new(AA2dPoolConfig {
6485 pending_limit: SubPoolLimit {
6486 max_txs: 2,
6487 max_size: usize::MAX,
6488 },
6489 queued_limit: SubPoolLimit {
6490 max_txs: 10,
6491 max_size: usize::MAX,
6492 },
6493 ..Default::default()
6494 })
6495 }
6496
6497 fn assert_expiring_eviction_index_len(pool: &AA2dPool, len: usize) {
6498 assert_eq!(pool.expiring_nonce_txs.len(), len);
6499 assert_eq!(pool.expiring_nonce_eviction_order.len(), len);
6500 pool.assert_invariants();
6501 }
6502
6503 fn assert_expiring_eviction_index_contains(pool: &AA2dPool, expiring_hash: B256) {
6504 assert!(
6505 pool.expiring_nonce_eviction_order
6506 .iter()
6507 .any(|key| key.expiring_hash() == expiring_hash),
6508 "expiring_nonce_eviction_order should contain {expiring_hash:?}"
6509 );
6510 }
6511
6512 fn assert_expiring_eviction_index_missing(pool: &AA2dPool, expiring_hash: B256) {
6513 assert!(
6514 pool.expiring_nonce_eviction_order
6515 .iter()
6516 .all(|key| key.expiring_hash() != expiring_hash),
6517 "expiring_nonce_eviction_order should not contain {expiring_hash:?}"
6518 );
6519 }
6520
6521 #[test]
6522 fn eviction_same_priority_evicts_newer() {
6523 let mut pool = eviction_test_pool();
6525 let sender = Address::random();
6526
6527 let tx1 = TxBuilder::aa(sender)
6528 .nonce_key(U256::from(1))
6529 .nonce(0)
6530 .build();
6531 let tx2 = TxBuilder::aa(sender)
6532 .nonce_key(U256::from(2))
6533 .nonce(0)
6534 .build();
6535 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
6536
6537 pool.add_transaction(
6538 Arc::new(wrap_valid_tx(tx1.clone(), TransactionOrigin::Local)),
6539 0,
6540 TempoHardfork::T1,
6541 )
6542 .unwrap();
6543 pool.add_transaction(
6544 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
6545 0,
6546 TempoHardfork::T1,
6547 )
6548 .unwrap();
6549 let result = pool
6550 .add_transaction(
6551 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
6552 0,
6553 TempoHardfork::T1,
6554 )
6555 .unwrap();
6556
6557 let AddedTransaction::Pending(pending) = result else {
6558 panic!("expected pending")
6559 };
6560 assert_eq!(pending.discarded[0].hash(), tx_exp.hash());
6561 assert!(pool.contains(tx1.hash()));
6562 assert!(pool.contains(tx2.hash()));
6563 assert!(!pool.contains(tx_exp.hash()));
6564 pool.assert_invariants();
6565
6566 let mut pool = eviction_test_pool();
6568 let sender = Address::random();
6569
6570 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
6571 let tx2 = TxBuilder::aa(sender)
6572 .nonce_key(U256::from(1))
6573 .nonce(0)
6574 .build();
6575 let tx3 = TxBuilder::aa(sender)
6576 .nonce_key(U256::from(2))
6577 .nonce(0)
6578 .build();
6579
6580 pool.add_transaction(
6581 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
6582 0,
6583 TempoHardfork::T1,
6584 )
6585 .unwrap();
6586 pool.add_transaction(
6587 Arc::new(wrap_valid_tx(tx2.clone(), TransactionOrigin::Local)),
6588 0,
6589 TempoHardfork::T1,
6590 )
6591 .unwrap();
6592 let result = pool
6593 .add_transaction(
6594 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
6595 0,
6596 TempoHardfork::T1,
6597 )
6598 .unwrap();
6599
6600 let AddedTransaction::Pending(pending) = result else {
6601 panic!("expected pending")
6602 };
6603 assert_eq!(pending.discarded[0].hash(), tx3.hash());
6604 assert!(pool.contains(tx_exp.hash()));
6605 assert!(pool.contains(tx2.hash()));
6606 assert!(!pool.contains(tx3.hash()));
6607 pool.assert_invariants();
6608 }
6609
6610 #[test]
6611 fn eviction_lower_priority_expiring_evicted() {
6612 let mut pool = eviction_test_pool();
6613 let sender = Address::random();
6614
6615 let tx_exp = TxBuilder::aa(sender)
6617 .nonce_key(U256::MAX)
6618 .max_priority_fee(100)
6619 .max_fee(200)
6620 .build();
6621 let tx2 = TxBuilder::aa(sender)
6622 .nonce_key(U256::from(1))
6623 .nonce(0)
6624 .build();
6625 let tx3 = TxBuilder::aa(sender)
6626 .nonce_key(U256::from(2))
6627 .nonce(0)
6628 .build();
6629
6630 pool.add_transaction(
6631 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
6632 0,
6633 TempoHardfork::T1,
6634 )
6635 .unwrap();
6636 pool.add_transaction(
6637 Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)),
6638 0,
6639 TempoHardfork::T1,
6640 )
6641 .unwrap();
6642 let result = pool
6643 .add_transaction(
6644 Arc::new(wrap_valid_tx(tx3.clone(), TransactionOrigin::Local)),
6645 0,
6646 TempoHardfork::T1,
6647 )
6648 .unwrap();
6649
6650 let AddedTransaction::Pending(pending) = result else {
6652 panic!("expected pending")
6653 };
6654 assert_eq!(pending.discarded[0].hash(), tx_exp.hash());
6655 assert!(!pool.contains(tx_exp.hash()));
6656 assert!(pool.contains(tx3.hash()));
6657 pool.assert_invariants();
6658 }
6659
6660 #[test]
6661 fn eviction_lower_priority_2d_evicted() {
6662 let mut pool = eviction_test_pool();
6663 let sender = Address::random();
6664
6665 let tx_low = TxBuilder::aa(sender)
6667 .nonce_key(U256::from(1))
6668 .nonce(0)
6669 .max_priority_fee(100)
6670 .max_fee(200)
6671 .build();
6672 let tx_exp = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
6673 let tx3 = TxBuilder::aa(sender)
6674 .nonce_key(U256::from(2))
6675 .nonce(0)
6676 .build();
6677
6678 pool.add_transaction(
6679 Arc::new(wrap_valid_tx(tx_low.clone(), TransactionOrigin::Local)),
6680 0,
6681 TempoHardfork::T1,
6682 )
6683 .unwrap();
6684 pool.add_transaction(
6685 Arc::new(wrap_valid_tx(tx_exp.clone(), TransactionOrigin::Local)),
6686 0,
6687 TempoHardfork::T1,
6688 )
6689 .unwrap();
6690 let result = pool
6691 .add_transaction(
6692 Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)),
6693 0,
6694 TempoHardfork::T1,
6695 )
6696 .unwrap();
6697
6698 let AddedTransaction::Pending(pending) = result else {
6700 panic!("expected pending")
6701 };
6702 assert_eq!(pending.discarded[0].hash(), tx_low.hash());
6703 assert!(!pool.contains(tx_low.hash()));
6704 assert!(pool.contains(tx_exp.hash()));
6705 pool.assert_invariants();
6706 }
6707
6708 #[test]
6709 fn expiring_nonce_eviction_order_evicts_lowest_priority() {
6710 let mut pool = eviction_test_pool();
6711
6712 let tx_low = TxBuilder::aa(Address::random())
6713 .nonce_key(U256::MAX)
6714 .max_priority_fee(1_000_000_000)
6715 .max_fee(30_000_000_000)
6716 .build();
6717 let tx_high = TxBuilder::aa(Address::random())
6718 .nonce_key(U256::MAX)
6719 .max_priority_fee(3_000_000_000)
6720 .max_fee(30_000_000_000)
6721 .build();
6722 let tx_mid = TxBuilder::aa(Address::random())
6723 .nonce_key(U256::MAX)
6724 .max_priority_fee(2_000_000_000)
6725 .max_fee(30_000_000_000)
6726 .build();
6727
6728 let low_expiring_hash = tx_low
6729 .expiring_nonce_hash()
6730 .expect("expiring nonce tx must have expiring hash");
6731 let mid_expiring_hash = tx_mid
6732 .expiring_nonce_hash()
6733 .expect("expiring nonce tx must have expiring hash");
6734 let high_expiring_hash = tx_high
6735 .expiring_nonce_hash()
6736 .expect("expiring nonce tx must have expiring hash");
6737
6738 pool.add_transaction(
6739 Arc::new(wrap_valid_tx(tx_low.clone(), TransactionOrigin::Local)),
6740 0,
6741 TempoHardfork::T1,
6742 )
6743 .unwrap();
6744 pool.add_transaction(
6745 Arc::new(wrap_valid_tx(tx_high.clone(), TransactionOrigin::Local)),
6746 0,
6747 TempoHardfork::T1,
6748 )
6749 .unwrap();
6750 let result = pool
6751 .add_transaction(
6752 Arc::new(wrap_valid_tx(tx_mid.clone(), TransactionOrigin::Local)),
6753 0,
6754 TempoHardfork::T1,
6755 )
6756 .unwrap();
6757
6758 let AddedTransaction::Pending(pending) = result else {
6759 panic!("expected pending")
6760 };
6761 assert_eq!(pending.discarded.len(), 1);
6762 assert_eq!(pending.discarded[0].hash(), tx_low.hash());
6763 assert!(!pool.contains(tx_low.hash()));
6764 assert!(pool.contains(tx_mid.hash()));
6765 assert!(pool.contains(tx_high.hash()));
6766 assert_expiring_eviction_index_len(&pool, 2);
6767 assert_expiring_eviction_index_missing(&pool, low_expiring_hash);
6768 assert_expiring_eviction_index_contains(&pool, mid_expiring_hash);
6769 assert_expiring_eviction_index_contains(&pool, high_expiring_hash);
6770 }
6771
6772 #[test]
6773 fn expiring_nonce_eviction_order_evicts_newer_same_priority() {
6774 let mut pool = eviction_test_pool();
6775
6776 let tx_old_1 = TxBuilder::aa(Address::random())
6777 .nonce_key(U256::MAX)
6778 .max_fee(30_000_000_000)
6779 .build();
6780 let tx_old_2 = TxBuilder::aa(Address::random())
6781 .nonce_key(U256::MAX)
6782 .max_fee(30_000_000_000)
6783 .build();
6784 let tx_new = TxBuilder::aa(Address::random())
6785 .nonce_key(U256::MAX)
6786 .max_fee(30_000_000_000)
6787 .build();
6788
6789 let new_expiring_hash = tx_new
6790 .expiring_nonce_hash()
6791 .expect("expiring nonce tx must have expiring hash");
6792
6793 pool.add_transaction(
6794 Arc::new(wrap_valid_tx(tx_old_1.clone(), TransactionOrigin::Local)),
6795 0,
6796 TempoHardfork::T1,
6797 )
6798 .unwrap();
6799 pool.add_transaction(
6800 Arc::new(wrap_valid_tx(tx_old_2.clone(), TransactionOrigin::Local)),
6801 0,
6802 TempoHardfork::T1,
6803 )
6804 .unwrap();
6805 let result = pool
6806 .add_transaction(
6807 Arc::new(wrap_valid_tx(tx_new.clone(), TransactionOrigin::Local)),
6808 0,
6809 TempoHardfork::T1,
6810 )
6811 .unwrap();
6812
6813 let AddedTransaction::Pending(pending) = result else {
6814 panic!("expected pending")
6815 };
6816 assert_eq!(pending.discarded.len(), 1);
6817 assert_eq!(pending.discarded[0].hash(), tx_new.hash());
6818 assert!(pool.contains(tx_old_1.hash()));
6819 assert!(pool.contains(tx_old_2.hash()));
6820 assert!(!pool.contains(tx_new.hash()));
6821 assert_expiring_eviction_index_len(&pool, 2);
6822 assert_expiring_eviction_index_missing(&pool, new_expiring_hash);
6823 }
6824
6825 #[test]
6826 fn expiring_nonce_tx_uses_separate_eviction_index() {
6827 let mut pool = AA2dPool::default();
6828 let sender = Address::random();
6829
6830 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).build();
6831 let expiring_hash = tx
6832 .expiring_nonce_hash()
6833 .expect("expiring nonce tx must have expiring hash");
6834
6835 pool.add_transaction(
6836 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
6837 0,
6838 TempoHardfork::T1,
6839 )
6840 .unwrap();
6841
6842 assert!(
6843 pool.by_eviction_order.is_empty(),
6844 "expiring nonce txs should not be inserted into by_eviction_order"
6845 );
6846 assert_expiring_eviction_index_len(&pool, 1);
6847 assert_expiring_eviction_index_contains(&pool, expiring_hash);
6848 }
6849
6850 #[test]
6851 fn expiring_nonce_tx_subject_to_eviction() {
6852 let config = AA2dPoolConfig {
6854 pending_limit: SubPoolLimit {
6855 max_txs: 2,
6856 max_size: usize::MAX,
6857 },
6858 queued_limit: SubPoolLimit {
6859 max_txs: 10,
6860 max_size: usize::MAX,
6861 },
6862 ..Default::default()
6863 };
6864 let mut pool = AA2dPool::new(config);
6865 let sender = Address::random();
6866
6867 for i in 0..3 {
6869 let tx = TxBuilder::aa(sender)
6870 .nonce_key(U256::MAX)
6871 .max_priority_fee(1_000_000_000 + i as u128 * 100_000_000)
6872 .max_fee(2_000_000_000 + i as u128 * 100_000_000)
6873 .build();
6874 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
6875 let _ = pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1);
6876 }
6877
6878 let (pending, queued) = pool.pending_and_queued_txn_count();
6880 assert!(
6881 pending <= 2,
6882 "Should have at most 2 pending transactions due to limit, got {pending}"
6883 );
6884 assert_eq!(queued, 0, "Should have 0 queued transactions");
6885
6886 pool.assert_invariants();
6887 }
6888
6889 #[test]
6890 fn remove_expiring_nonce_tx_decrements_pending_count() {
6891 let mut pool = AA2dPool::default();
6892 let sender = Address::random();
6893
6894 let tx1 = TxBuilder::aa(sender)
6896 .nonce_key(U256::MAX)
6897 .max_priority_fee(1_000_000_000)
6898 .max_fee(2_000_000_000)
6899 .build();
6900 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
6901 let tx1_hash = *valid_tx1.hash();
6902 let tx1_expiring_hash = valid_tx1
6903 .transaction
6904 .expiring_nonce_hash()
6905 .expect("expiring nonce tx must have expiring hash");
6906
6907 let tx2 = TxBuilder::aa(sender)
6908 .nonce_key(U256::MAX)
6909 .max_priority_fee(1_100_000_000)
6910 .max_fee(2_200_000_000)
6911 .build();
6912 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
6913
6914 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
6915 .unwrap();
6916 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
6917 .unwrap();
6918
6919 let (pending, _) = pool.pending_and_queued_txn_count();
6921 assert_eq!(pending, 2, "Should have 2 pending transactions");
6922 assert_expiring_eviction_index_len(&pool, 2);
6923
6924 let removed = pool.remove_transactions(std::iter::once(&tx1_hash));
6926 assert_eq!(removed.len(), 1, "Should remove exactly 1 transaction");
6927
6928 let (pending, _) = pool.pending_and_queued_txn_count();
6930 assert_eq!(
6931 pending, 1,
6932 "Should have 1 pending transaction after removal"
6933 );
6934 assert_expiring_eviction_index_len(&pool, 1);
6935 assert_expiring_eviction_index_missing(&pool, tx1_expiring_hash);
6936
6937 pool.assert_invariants();
6939 }
6940
6941 #[test]
6942 fn remove_expiring_nonce_tx_by_hash_updates_pending_count() {
6943 let mut pool = AA2dPool::default();
6944 let sender = Address::random();
6945
6946 let tx = TxBuilder::aa(sender)
6947 .nonce_key(U256::MAX)
6948 .max_priority_fee(1_000_000_000)
6949 .max_fee(2_000_000_000)
6950 .build();
6951 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
6952 let tx_hash = *valid_tx.hash();
6953 let expiring_hash = valid_tx
6954 .transaction
6955 .expiring_nonce_hash()
6956 .expect("expiring nonce tx must have expiring hash");
6957
6958 pool.add_transaction(Arc::new(valid_tx), 0, TempoHardfork::T1)
6959 .unwrap();
6960
6961 let (pending, _) = pool.pending_and_queued_txn_count();
6962 assert_eq!(pending, 1);
6963 assert_expiring_eviction_index_len(&pool, 1);
6964 assert_expiring_eviction_index_contains(&pool, expiring_hash);
6965
6966 let removed = pool.remove_transactions(std::iter::once(&tx_hash));
6968 assert_eq!(removed.len(), 1);
6969
6970 let (pending, _) = pool.pending_and_queued_txn_count();
6971 assert_eq!(pending, 0);
6972 assert_expiring_eviction_index_len(&pool, 0);
6973 assert_expiring_eviction_index_missing(&pool, expiring_hash);
6974 }
6975
6976 #[test]
6977 fn remove_expiring_nonce_tx_by_sender_updates_pending_count() {
6978 let mut pool = AA2dPool::default();
6979 let sender = Address::random();
6980
6981 let tx1 = TxBuilder::aa(sender)
6982 .nonce_key(U256::MAX)
6983 .max_priority_fee(1_000_000_000)
6984 .max_fee(2_000_000_000)
6985 .build();
6986 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
6987
6988 let tx2 = TxBuilder::aa(sender)
6989 .nonce_key(U256::MAX)
6990 .max_priority_fee(1_100_000_000)
6991 .max_fee(2_200_000_000)
6992 .build();
6993 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
6994
6995 pool.add_transaction(Arc::new(valid_tx1), 0, TempoHardfork::T1)
6996 .unwrap();
6997 pool.add_transaction(Arc::new(valid_tx2), 0, TempoHardfork::T1)
6998 .unwrap();
6999
7000 let (pending, _) = pool.pending_and_queued_txn_count();
7001 assert_eq!(pending, 2);
7002 assert_expiring_eviction_index_len(&pool, 2);
7003
7004 let removed = pool.remove_transactions_by_sender(sender);
7006 assert_eq!(removed.len(), 2);
7007
7008 let (pending, _) = pool.pending_and_queued_txn_count();
7009 assert_eq!(pending, 0);
7010 assert_expiring_eviction_index_len(&pool, 0);
7011 }
7012
7013 #[test]
7014 fn test_rejected_2d_tx_does_not_leak_slot_entries() {
7015 let config = AA2dPoolConfig {
7016 price_bump_config: PriceBumpConfig::default(),
7017 pending_limit: SubPoolLimit {
7018 max_txs: 1000,
7019 max_size: usize::MAX,
7020 },
7021 queued_limit: SubPoolLimit {
7022 max_txs: 1000,
7023 max_size: usize::MAX,
7024 },
7025 max_txs_per_sender: 1,
7026 };
7027 let mut pool = AA2dPool::new(config);
7028 let sender = Address::random();
7029
7030 let tx0 = TxBuilder::aa(sender)
7031 .nonce_key(U256::from(1))
7032 .nonce(0)
7033 .build();
7034 pool.add_transaction(
7035 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
7036 0,
7037 TempoHardfork::T1,
7038 )
7039 .unwrap();
7040
7041 assert_eq!(pool.slot_to_seq_id.len(), 1);
7042
7043 for i in 2..12u64 {
7044 let tx = TxBuilder::aa(sender)
7045 .nonce_key(U256::from(i))
7046 .nonce(0)
7047 .build();
7048 let result = pool.add_transaction(
7049 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
7050 0,
7051 TempoHardfork::T1,
7052 );
7053 assert!(
7054 result.is_err(),
7055 "tx with nonce_key {i} should be rejected by sender limit"
7056 );
7057 }
7058
7059 assert_eq!(
7060 pool.slot_to_seq_id.len(),
7061 1,
7062 "rejected txs with new nonce keys should not grow slot_to_seq_id"
7063 );
7064 pool.assert_invariants();
7065 }
7066
7067 #[test_case::test_case(false ; "live updates")]
7068 #[test_case::test_case(true ; "no updates")]
7069 fn best_transactions_live_new_tx(no_updates: bool) {
7070 let mut pool = AA2dPool::default();
7071 let sender = Address::random();
7072
7073 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).build();
7075 let tx0_hash = *tx0.hash();
7076 pool.add_transaction(
7077 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
7078 0,
7079 TempoHardfork::T1,
7080 )
7081 .unwrap();
7082
7083 let mut best = pool.best_transactions();
7084 if no_updates {
7085 best.no_updates();
7086 }
7087
7088 let sender2 = Address::random();
7090 let tx1 = TxBuilder::aa(sender2).nonce_key(U256::ZERO).build();
7091 let tx1_hash = *tx1.hash();
7092 pool.add_transaction(
7093 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
7094 0,
7095 TempoHardfork::T1,
7096 )
7097 .unwrap();
7098
7099 let mut yielded = HashSet::new();
7100 for tx in best {
7101 yielded.insert(*tx.hash());
7102 }
7103
7104 assert!(
7105 yielded.contains(&tx0_hash),
7106 "should always yield pre-existing tx"
7107 );
7108 assert_eq!(
7109 yielded.contains(&tx1_hash),
7110 !no_updates,
7111 "new tx should only be yielded when live updates are enabled"
7112 );
7113 }
7114
7115 #[test]
7116 fn best_transactions_live_promoted() {
7117 let mut pool = AA2dPool::default();
7118 let sender = Address::random();
7119
7120 let tx1 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(1).build();
7122 let tx1_hash = *tx1.hash();
7123 pool.add_transaction(
7124 Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)),
7125 0,
7126 TempoHardfork::T1,
7127 )
7128 .unwrap();
7129
7130 let mut best = pool.best_transactions();
7132 assert!(best.next().is_none(), "no pending txs yet");
7133
7134 let tx0 = TxBuilder::aa(sender).nonce_key(U256::ZERO).nonce(0).build();
7136 let tx0_hash = *tx0.hash();
7137 pool.add_transaction(
7138 Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)),
7139 0,
7140 TempoHardfork::T1,
7141 )
7142 .unwrap();
7143
7144 let mut yielded = HashSet::new();
7145 for tx in best {
7146 yielded.insert(*tx.hash());
7147 }
7148
7149 assert_eq!(yielded.len(), 2, "should yield both tx0 and promoted tx1");
7150 assert!(yielded.contains(&tx0_hash));
7151 assert!(yielded.contains(&tx1_hash));
7152 }
7153
7154 #[test]
7155 fn best_transactions_live_gapped_unblock_higher_fee_not_promoted() {
7156 let mut pool = AA2dPool::default();
7160
7161 let sender_low = Address::random();
7162 let sender_gapped = Address::random();
7163
7164 let tx_low = TxBuilder::aa(sender_low)
7167 .nonce_key(U256::ZERO)
7168 .max_priority_fee(1_000_000_000)
7169 .max_fee(30_000_000_000)
7170 .build();
7171 pool.add_transaction(
7172 Arc::new(wrap_valid_tx(tx_low, TransactionOrigin::Local)),
7173 0,
7174 TempoHardfork::T1,
7175 )
7176 .unwrap();
7177
7178 let tx_n1 = TxBuilder::aa(sender_gapped)
7180 .nonce_key(U256::ZERO)
7181 .nonce(1)
7182 .max_priority_fee(2_000_000_000)
7183 .max_fee(30_000_000_000)
7184 .build();
7185 let tx_n1_hash = *tx_n1.hash();
7186 pool.add_transaction(
7187 Arc::new(wrap_valid_tx(tx_n1, TransactionOrigin::Local)),
7188 0,
7189 TempoHardfork::T1,
7190 )
7191 .unwrap();
7192
7193 let mut best = pool.best_transactions();
7195 let first = best.next();
7196 assert!(first.is_some(), "should yield the low-priority tx");
7197
7198 let tx_n0 = TxBuilder::aa(sender_gapped)
7200 .nonce_key(U256::ZERO)
7201 .nonce(0)
7202 .max_priority_fee(2_000_000_000)
7203 .max_fee(30_000_000_000)
7204 .build();
7205 let tx_n0_hash = *tx_n0.hash();
7206 pool.add_transaction(
7207 Arc::new(wrap_valid_tx(tx_n0, TransactionOrigin::Local)),
7208 0,
7209 TempoHardfork::T1,
7210 )
7211 .unwrap();
7212
7213 let remaining: Vec<_> = best.map(|tx| *tx.hash()).collect();
7216 assert!(
7217 !remaining.contains(&tx_n0_hash),
7218 "gap-filler with higher fee must not be yielded"
7219 );
7220 assert!(
7221 !remaining.contains(&tx_n1_hash),
7222 "gapped tx must not be promoted when gap-filler is stashed"
7223 );
7224 }
7225
7226 #[test]
7227 fn best_transactions_live_expiring_nonce() {
7228 let mut pool = AA2dPool::default();
7229
7230 let mut best = pool.best_transactions();
7231
7232 let sender = Address::random();
7234 let tx = TxBuilder::aa(sender).nonce_key(U256::MAX).nonce(0).build();
7235 let tx_hash = *tx.hash();
7236 pool.add_transaction(
7237 Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)),
7238 0,
7239 TempoHardfork::T1,
7240 )
7241 .unwrap();
7242
7243 let first = best.next();
7244 assert!(first.is_some(), "should yield the expiring nonce tx");
7245 assert_eq!(*first.unwrap().hash(), tx_hash);
7246 assert!(best.next().is_none());
7247 }
7248}