1use crate::{metrics::AA2dPoolMetrics, transaction::TempoPooledTransaction};
3use alloy_primitives::{Address, B256, TxHash, U256, map::HashMap};
4use reth_primitives_traits::transaction::error::InvalidTransactionError;
5use reth_tracing::tracing::trace;
6use reth_transaction_pool::{
7 BestTransactions, CoinbaseTipOrdering, PoolResult, PoolTransaction, PriceBumpConfig, Priority,
8 SubPool, SubPoolLimit, TransactionOrdering, TransactionOrigin, ValidPoolTransaction,
9 error::{InvalidPoolTransactionError, PoolError, PoolErrorKind},
10 pool::{AddedPendingTransaction, AddedTransaction, QueuedReason, pending::PendingTransaction},
11};
12use revm::database::BundleAccount;
13use std::{
14 collections::{
15 BTreeMap, BTreeSet,
16 Bound::{Excluded, Unbounded},
17 HashSet,
18 btree_map::Entry,
19 hash_map,
20 },
21 sync::Arc,
22};
23use tempo_chainspec::spec::TEMPO_BASE_FEE;
24use tempo_precompiles::NONCE_PRECOMPILE_ADDRESS;
25
26type Ordering = CoinbaseTipOrdering<TempoPooledTransaction>;
27
28#[derive(Debug)]
40pub struct AA2dPool {
41 submission_id: u64,
45 independent_transactions: HashMap<AASequenceId, PendingTransaction<Ordering>>,
47 by_id: BTreeMap<AA2dTransactionId, AA2dInternalTransaction>,
49 by_hash: HashMap<TxHash, Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
51 address_slots: HashMap<U256, (Address, U256)>,
59 config: AA2dPoolConfig,
61 metrics: AA2dPoolMetrics,
63}
64
65impl Default for AA2dPool {
66 fn default() -> Self {
67 Self::new(AA2dPoolConfig::default())
68 }
69}
70
71impl AA2dPool {
72 pub fn new(config: AA2dPoolConfig) -> Self {
74 Self {
75 submission_id: 0,
76 independent_transactions: Default::default(),
77 by_id: Default::default(),
78 by_hash: Default::default(),
79 address_slots: Default::default(),
80 config,
81 metrics: AA2dPoolMetrics::default(),
82 }
83 }
84
85 fn update_metrics(&self) {
87 let (pending, queued) = self.pending_and_queued_txn_count();
88 let total = self.by_id.len();
89 self.metrics.set_transaction_counts(total, pending, queued);
90 }
91
92 pub(crate) fn add_transaction(
98 &mut self,
99 transaction: Arc<ValidPoolTransaction<TempoPooledTransaction>>,
100 on_chain_nonce: u64,
101 ) -> PoolResult<AddedTransaction<TempoPooledTransaction>> {
102 debug_assert!(
103 transaction.transaction.is_aa(),
104 "only AA transactions are supported"
105 );
106 if self.contains(transaction.hash()) {
107 return Err(PoolError::new(
108 *transaction.hash(),
109 PoolErrorKind::AlreadyImported,
110 ));
111 }
112
113 let tx_id = transaction
114 .transaction
115 .aa_transaction_id()
116 .expect("Transaction added to AA2D pool must be an AA transaction");
117
118 if transaction.transaction.is_aa_2d() {
120 self.record_2d_slot(&transaction.transaction);
121 }
122
123 if transaction.nonce() < on_chain_nonce {
124 return Err(PoolError::new(
126 *transaction.hash(),
127 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
128 InvalidTransactionError::NonceNotConsistent {
129 tx: transaction.nonce(),
130 state: on_chain_nonce,
131 },
132 )),
133 ));
134 }
135
136 let mut inserted_as_pending = false;
138 let tx = AA2dInternalTransaction {
139 inner: PendingTransaction {
140 submission_id: self.next_id(),
141 priority: CoinbaseTipOrdering::default()
142 .priority(&transaction.transaction, TEMPO_BASE_FEE),
143 transaction: transaction.clone(),
144 },
145 is_pending: inserted_as_pending,
146 };
147
148 let replaced = match self.by_id.entry(tx_id) {
150 Entry::Occupied(mut entry) => {
151 if entry
153 .get()
154 .inner
155 .transaction
156 .is_underpriced(&tx.inner.transaction, &self.config.price_bump_config)
157 {
158 return Err(PoolError::new(
159 *transaction.hash(),
160 PoolErrorKind::ReplacementUnderpriced,
161 ));
162 }
163
164 Some(entry.insert(tx.clone()))
165 }
166 Entry::Vacant(entry) => {
167 entry.insert(tx.clone());
168 None
169 }
170 };
171
172 if let Some(replaced) = &replaced {
174 self.by_hash.remove(replaced.inner.transaction.hash());
177 }
178
179 self.by_hash
181 .insert(*tx.inner.transaction.hash(), tx.inner.transaction.clone());
182
183 let mut promoted = Vec::new();
185 let on_chain_id = AA2dTransactionId::new(tx_id.seq_id, on_chain_nonce);
187 let mut next_nonce = on_chain_id.nonce;
189
190 for (existing_id, existing_tx) in self.descendant_txs_mut(&on_chain_id) {
193 if existing_id.nonce == next_nonce {
194 match existing_id.nonce.cmp(&tx_id.nonce) {
195 std::cmp::Ordering::Less => {
196 }
198 std::cmp::Ordering::Equal => {
199 existing_tx.is_pending = true;
200 inserted_as_pending = true;
201 }
202 std::cmp::Ordering::Greater => {
203 let is_promoted = !std::mem::replace(&mut existing_tx.is_pending, true);
205 if is_promoted {
206 promoted.push(existing_tx.inner.transaction.clone());
207 }
208 }
209 }
210 next_nonce = existing_id.nonce.saturating_add(1);
212 } else {
213 break;
215 }
216 }
217
218 self.metrics.inc_inserted();
220
221 if inserted_as_pending {
222 if !promoted.is_empty() {
223 self.metrics.inc_promoted(promoted.len());
224 }
225 if tx_id.nonce == on_chain_nonce {
227 self.independent_transactions.insert(tx_id.seq_id, tx.inner);
228 }
229
230 return Ok(AddedTransaction::Pending(AddedPendingTransaction {
231 transaction,
232 replaced: replaced.map(|tx| tx.inner.transaction),
233 promoted,
234 discarded: self.discard(),
235 }));
236 }
237
238 Ok(AddedTransaction::Parked {
239 transaction,
240 replaced: replaced.map(|tx| tx.inner.transaction),
241 subpool: SubPool::Queued,
242 queued_reason: Some(QueuedReason::NonceGap),
243 })
244 }
245
246 pub(crate) fn pending_and_queued_txn_count(&self) -> (usize, usize) {
248 self.by_id.values().fold((0, 0), |mut acc, tx| {
249 if tx.is_pending {
250 acc.0 += 1;
251 } else {
252 acc.1 += 1;
253 }
254 acc
255 })
256 }
257
258 pub(crate) fn get_transactions_by_origin_iter(
260 &self,
261 origin: TransactionOrigin,
262 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
263 self.by_id
264 .values()
265 .filter(move |tx| tx.inner.transaction.origin == origin)
266 .map(|tx| tx.inner.transaction.clone())
267 }
268
269 pub(crate) fn get_pending_transactions_by_origin_iter(
271 &self,
272 origin: TransactionOrigin,
273 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
274 self.by_id
275 .values()
276 .filter(move |tx| tx.is_pending && tx.inner.transaction.origin == origin)
277 .map(|tx| tx.inner.transaction.clone())
278 }
279
280 pub(crate) fn get_transactions_by_sender_iter(
282 &self,
283 sender: Address,
284 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
285 self.by_id
286 .values()
287 .filter(move |tx| tx.inner.transaction.sender() == sender)
288 .map(|tx| tx.inner.transaction.clone())
289 }
290
291 pub(crate) fn all_transaction_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
293 self.by_hash.keys().copied()
294 }
295
296 pub(crate) fn queued_transactions(
298 &self,
299 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
300 self.by_id
301 .values()
302 .filter(|tx| !tx.is_pending)
303 .map(|tx| tx.inner.transaction.clone())
304 }
305
306 pub(crate) fn pending_transactions(
308 &self,
309 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
310 self.by_id
311 .values()
312 .filter(|tx| tx.is_pending)
313 .map(|tx| tx.inner.transaction.clone())
314 }
315
316 pub(crate) fn best_transactions(&self) -> BestAA2dTransactions {
318 BestAA2dTransactions {
319 independent: self.independent_transactions.values().cloned().collect(),
320 by_id: self
321 .by_id
322 .iter()
323 .filter(|(_, tx)| tx.is_pending)
324 .map(|(id, tx)| (*id, tx.inner.clone()))
325 .collect(),
326 invalid: Default::default(),
327 }
328 }
329
330 pub(crate) fn get(
332 &self,
333 tx_hash: &TxHash,
334 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
335 self.by_hash.get(tx_hash).cloned()
336 }
337
338 pub(crate) fn get_all<'a, I>(
340 &self,
341 tx_hashes: I,
342 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
343 where
344 I: Iterator<Item = &'a TxHash> + 'a,
345 {
346 let mut ret = Vec::new();
347 for tx_hash in tx_hashes {
348 if let Some(tx) = self.get(tx_hash) {
349 ret.push(tx);
350 }
351 }
352 ret
353 }
354
355 pub(crate) fn get_all_iter<'a, I>(
357 &'a self,
358 tx_hashes: I,
359 ) -> impl Iterator<Item = &'a Arc<ValidPoolTransaction<TempoPooledTransaction>>> + 'a
360 where
361 I: IntoIterator<Item = &'a TxHash> + 'a,
362 {
363 tx_hashes
364 .into_iter()
365 .filter_map(|tx_hash| self.by_hash.get(tx_hash))
366 }
367
368 pub(crate) fn senders_iter(&self) -> impl Iterator<Item = &Address> {
370 self.by_id
371 .values()
372 .map(|tx| tx.inner.transaction.sender_ref())
373 }
374
375 fn descendant_txs_mut<'a, 'b: 'a>(
380 &'a mut self,
381 id: &'b AA2dTransactionId,
382 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a mut AA2dInternalTransaction)> + 'a {
383 self.by_id
384 .range_mut(id..)
385 .take_while(|(other, _)| id.seq_id == other.seq_id)
386 }
387
388 fn descendant_txs_exclusive<'a, 'b: 'a>(
392 &'a self,
393 id: &'b AA2dTransactionId,
394 ) -> impl Iterator<Item = (&'a AA2dTransactionId, &'a AA2dInternalTransaction)> + 'a {
395 self.by_id
396 .range((Excluded(id), Unbounded))
397 .take_while(|(other, _)| id.seq_id == other.seq_id)
398 }
399
400 fn remove_transaction_by_id(
404 &mut self,
405 id: &AA2dTransactionId,
406 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
407 let tx = self.by_id.remove(id)?;
408 self.remove_independent(id);
409 self.by_hash.remove(tx.inner.transaction.hash());
410 Some(tx.inner.transaction)
411 }
412
413 fn remove_independent(
415 &mut self,
416 id: &AA2dTransactionId,
417 ) -> Option<PendingTransaction<Ordering>> {
418 match self.independent_transactions.entry(id.seq_id) {
420 hash_map::Entry::Occupied(entry) => {
421 if entry.get().transaction.nonce() == id.nonce {
423 return Some(entry.remove());
424 }
425 }
426 hash_map::Entry::Vacant(_) => {}
427 };
428 None
429 }
430
431 pub(crate) fn remove_transactions<'a, I>(
433 &mut self,
434 tx_hashes: I,
435 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
436 where
437 I: Iterator<Item = &'a TxHash> + 'a,
438 {
439 let mut txs = Vec::new();
440 for tx_hash in tx_hashes {
441 if let Some(tx) = self.remove_transaction_by_hash(tx_hash) {
442 txs.push(tx);
443 }
444 }
445 txs
446 }
447
448 fn remove_transaction_by_hash(
452 &mut self,
453 tx_hash: &B256,
454 ) -> Option<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
455 let tx = self.by_hash.remove(tx_hash)?;
456 let id = tx
457 .transaction
458 .aa_transaction_id()
459 .expect("is AA transaction");
460 self.by_id.remove(&id)?;
461 self.remove_independent(&id);
462 Some(tx)
463 }
464
465 pub(crate) fn remove_transactions_and_descendants<'a, I>(
468 &mut self,
469 hashes: I,
470 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>
471 where
472 I: Iterator<Item = &'a TxHash> + 'a,
473 {
474 let mut removed = Vec::new();
475 for hash in hashes {
476 if let Some(tx) = self.remove_transaction_by_hash(hash) {
477 let id = tx.transaction.aa_transaction_id();
478 removed.push(tx);
479 if let Some(id) = id {
480 self.remove_descendants(&id, &mut removed);
481 }
482 }
483 }
484 removed
485 }
486
487 pub(crate) fn remove_transactions_by_sender(
489 &mut self,
490 sender_id: Address,
491 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
492 let mut removed = Vec::new();
493 let txs = self
494 .get_transactions_by_sender_iter(sender_id)
495 .collect::<Vec<_>>();
496 for tx in txs {
497 if let Some(tx) = tx
498 .transaction
499 .aa_transaction_id()
500 .and_then(|id| self.remove_transaction_by_id(&id))
501 {
502 removed.push(tx);
503 }
504 }
505 removed
506 }
507
508 fn remove_descendants(
512 &mut self,
513 tx: &AA2dTransactionId,
514 removed: &mut Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
515 ) {
516 let mut id = *tx;
517
518 loop {
520 let descendant = self.descendant_txs_exclusive(&id).map(|(id, _)| *id).next();
521 if let Some(descendant) = descendant {
522 if let Some(tx) = self.remove_transaction_by_id(&descendant) {
523 removed.push(tx)
524 }
525 id = descendant;
526 } else {
527 return;
528 }
529 }
530 }
531
532 #[allow(clippy::type_complexity)]
538 pub(crate) fn on_nonce_changes(
539 &mut self,
540 on_chain_ids: HashMap<AASequenceId, u64>,
541 ) -> (
542 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
543 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
544 ) {
545 trace!(target: "txpool::2d", ?on_chain_ids, "processing nonce changes");
546
547 let mut promoted = Vec::new();
548 let mut mined_ids = Vec::new();
549
550 'changes: for (sender_id, on_chain_nonce) in on_chain_ids {
552 let mut iter = self
553 .by_id
554 .range_mut((sender_id.start_bound(), Unbounded))
555 .take_while(move |(other, _)| sender_id == other.seq_id)
556 .peekable();
557
558 let Some(mut current) = iter.next() else {
559 continue;
560 };
561
562 'mined: loop {
564 if current.0.nonce < on_chain_nonce {
565 mined_ids.push(*current.0);
566 let Some(next) = iter.next() else {
567 continue 'changes;
568 };
569 current = next;
570 } else {
571 break 'mined;
572 }
573 }
574
575 let mut next_nonce = on_chain_nonce;
577 for (existing_id, existing_tx) in std::iter::once(current).chain(iter) {
578 if existing_id.nonce == next_nonce {
579 if !std::mem::replace(&mut existing_tx.is_pending, true) {
581 promoted.push(existing_tx.inner.transaction.clone());
582 }
583
584 if existing_id.nonce == on_chain_nonce {
585 self.independent_transactions
587 .insert(existing_id.seq_id, existing_tx.inner.clone());
588 }
589
590 next_nonce = next_nonce.saturating_add(1);
591 } else {
592 existing_tx.is_pending = false;
594 break;
595 }
596 }
597 }
598
599 let mut mined = Vec::with_capacity(mined_ids.len());
601 for id in mined_ids {
602 if let Some(removed) = self.remove_transaction_by_id(&id) {
603 mined.push(removed);
604 }
605 }
606
607 if !promoted.is_empty() {
609 self.metrics.inc_promoted(promoted.len());
610 }
611 if !mined.is_empty() {
612 self.metrics.inc_removed(mined.len());
613 }
614 self.update_metrics();
615
616 (promoted, mined)
617 }
618
619 fn discard(&mut self) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
621 let mut removed = Vec::new();
622
623 while self.by_id.len() > self.config.aa_2d_limit.max_txs {
624 let Some(id) = self.by_id.last_key_value().map(|(id, _)| *id) else {
626 return removed;
627 };
628 removed.push(self.remove_transaction_by_id(&id).unwrap());
629 }
630
631 if !removed.is_empty() {
632 self.metrics.inc_removed(removed.len());
633 }
634
635 removed
636 }
637
638 pub fn metrics(&self) -> &AA2dPoolMetrics {
640 &self.metrics
641 }
642
643 pub(crate) fn contains(&self, tx_hash: &TxHash) -> bool {
645 self.by_hash.contains_key(tx_hash)
646 }
647
648 pub(crate) fn pooled_transactions_hashes_iter(&self) -> impl Iterator<Item = TxHash> {
650 self.by_hash
651 .values()
652 .filter(|tx| tx.propagate)
653 .map(|tx| *tx.hash())
654 }
655
656 pub(crate) fn pooled_transactions_iter(
658 &self,
659 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
660 self.by_hash.values().filter(|tx| tx.propagate).cloned()
661 }
662
663 const fn next_id(&mut self) -> u64 {
664 let id = self.submission_id;
665 self.submission_id = self.submission_id.wrapping_add(1);
666 id
667 }
668
669 fn record_2d_slot(&mut self, transaction: &TempoPooledTransaction) {
671 let address = transaction.sender();
672 let nonce_key = transaction.nonce_key().unwrap_or_default();
673 let Some(slot) = transaction.nonce_key_slot() else {
674 return;
675 };
676
677 trace!(target: "txpool::2d", ?address, ?nonce_key, "recording 2d nonce slot");
678
679 if self
680 .address_slots
681 .insert(slot, (address, nonce_key))
682 .is_none()
683 {
684 self.metrics.inc_nonce_key_count(1);
685 }
686 }
687
688 #[expect(clippy::type_complexity)]
690 pub(crate) fn on_state_updates(
691 &mut self,
692 state: &HashMap<Address, BundleAccount>,
693 ) -> (
694 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
695 Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
696 ) {
697 let mut changes = HashMap::default();
698
699 for (account, state) in state {
700 if account == &NONCE_PRECOMPILE_ADDRESS {
701 for (slot, value) in state.storage.iter() {
703 if let Some((address, nonce_key)) = self.address_slots.get(slot) {
704 changes.insert(
705 AASequenceId::new(*address, *nonce_key),
706 value.present_value.saturating_to(),
707 );
708 }
709 }
710 }
711 let nonce = state
712 .account_info()
713 .map(|info| info.nonce)
714 .unwrap_or_default();
715 changes.insert(AASequenceId::new(*account, U256::ZERO), nonce);
716 }
717
718 self.on_nonce_changes(changes)
719 }
720
721 #[cfg(test)]
723 pub(crate) fn assert_invariants(&self) {
724 assert!(
726 self.independent_transactions.len() <= self.by_id.len(),
727 "independent_transactions.len() ({}) > by_id.len() ({})",
728 self.independent_transactions.len(),
729 self.by_id.len()
730 );
731 assert_eq!(
732 self.by_id.len(),
733 self.by_hash.len(),
734 "by_id.len() ({}) != by_hash.len() ({})",
735 self.by_id.len(),
736 self.by_hash.len()
737 );
738
739 for (seq_id, independent_tx) in &self.independent_transactions {
741 let tx_id = independent_tx
742 .transaction
743 .transaction
744 .aa_transaction_id()
745 .expect("Independent transaction must have AA transaction ID");
746 assert!(
747 self.by_id.contains_key(&tx_id),
748 "Independent transaction {tx_id:?} not in by_id"
749 );
750 assert_eq!(
751 seq_id, &tx_id.seq_id,
752 "Independent transactions sequence ID {seq_id:?} does not match transaction sequence ID {tx_id:?}"
753 );
754
755 let tx_in_pool = self.by_id.get(&tx_id).unwrap();
757 assert!(
758 tx_in_pool.is_pending,
759 "Independent transaction {tx_id:?} is not pending"
760 );
761
762 assert_eq!(
764 independent_tx.transaction.hash(),
765 tx_in_pool.inner.transaction.hash(),
766 "Independent transaction hash mismatch for {tx_id:?}"
767 );
768 }
769
770 let mut seen_senders = std::collections::HashSet::new();
772 for id in self.independent_transactions.keys() {
773 assert!(
774 seen_senders.insert(*id),
775 "Duplicate sender {id:?} in independent transactions"
776 );
777 }
778
779 for (hash, tx) in &self.by_hash {
781 assert_eq!(
783 tx.hash(),
784 hash,
785 "Hash mismatch in by_hash: expected {:?}, got {:?}",
786 hash,
787 tx.hash()
788 );
789
790 let id = tx
792 .transaction
793 .aa_transaction_id()
794 .expect("Transaction in pool should be AA transaction");
795 assert!(
796 self.by_id.contains_key(&id),
797 "Transaction with hash {hash:?} in by_hash but not in by_id"
798 );
799
800 let tx_in_by_id = &self.by_id.get(&id).unwrap().inner.transaction;
802 assert_eq!(
803 tx.hash(),
804 tx_in_by_id.hash(),
805 "Transaction hash mismatch between by_hash and by_id for id {id:?}"
806 );
807 }
808
809 for (id, tx) in &self.by_id {
811 let hash = tx.inner.transaction.hash();
813 assert!(
814 self.by_hash.contains_key(hash),
815 "Transaction with id {id:?} in by_id but not in by_hash"
816 );
817
818 let tx_id = tx
820 .inner
821 .transaction
822 .transaction
823 .aa_transaction_id()
824 .expect("Transaction in pool should be AA transaction");
825 assert_eq!(
826 &tx_id, id,
827 "Transaction ID mismatch: expected {id:?}, got {tx_id:?}"
828 );
829
830 if let Some(independent_tx) = self.independent_transactions.get(&id.seq_id)
832 && independent_tx.transaction.hash() == tx.inner.transaction.hash()
833 {
834 assert!(
835 tx.is_pending,
836 "Transaction {id:?} is in independent set but not pending"
837 );
838 }
839 }
840
841 let (pending_count, queued_count) = self.pending_and_queued_txn_count();
843 assert_eq!(
844 pending_count + queued_count,
845 self.by_id.len(),
846 "Pending ({}) + queued ({}) != total transactions ({})",
847 pending_count,
848 queued_count,
849 self.by_id.len()
850 );
851 }
852}
853
854#[derive(Debug, Clone, Default)]
856pub struct AA2dPoolConfig {
857 pub price_bump_config: PriceBumpConfig,
859 pub aa_2d_limit: SubPoolLimit,
861}
862
863#[derive(Debug, Clone)]
864struct AA2dInternalTransaction {
865 inner: PendingTransaction<CoinbaseTipOrdering<TempoPooledTransaction>>,
869 is_pending: bool,
873}
874
875#[derive(Debug)]
877pub(crate) struct BestAA2dTransactions {
878 independent: BTreeSet<PendingTransaction<Ordering>>,
880 by_id: BTreeMap<AA2dTransactionId, PendingTransaction<Ordering>>,
882
883 invalid: HashSet<AASequenceId>,
885}
886
887impl BestAA2dTransactions {
888 fn pop_best(&mut self) -> Option<(AA2dTransactionId, PendingTransaction<Ordering>)> {
890 let tx = self.independent.pop_last()?;
891 let id = tx
892 .transaction
893 .transaction
894 .aa_transaction_id()
895 .expect("Transaction in AA2D pool must be an AA transaction with valid nonce key");
896 self.by_id.remove(&id);
897 Some((id, tx))
898 }
899
900 pub(crate) fn next_tx_and_priority(
902 &mut self,
903 ) -> Option<(
904 Arc<ValidPoolTransaction<TempoPooledTransaction>>,
905 Priority<u128>,
906 )> {
907 loop {
908 let (id, best) = self.pop_best()?;
909 if self.invalid.contains(&id.seq_id) {
910 continue;
911 }
912 if let Some(unlocked) = self.by_id.get(&id.unlocks()) {
914 self.independent.insert(unlocked.clone());
915 }
916 return Some((best.transaction, best.priority));
917 }
918 }
919}
920
921impl Iterator for BestAA2dTransactions {
922 type Item = Arc<ValidPoolTransaction<TempoPooledTransaction>>;
923
924 fn next(&mut self) -> Option<Self::Item> {
925 self.next_tx_and_priority().map(|(tx, _)| tx)
926 }
927}
928
929impl BestTransactions for BestAA2dTransactions {
930 fn mark_invalid(&mut self, transaction: &Self::Item, _kind: &InvalidPoolTransactionError) {
931 if let Some(id) = transaction.transaction.aa_transaction_id() {
932 self.invalid.insert(id.seq_id);
933 }
934 }
935
936 fn no_updates(&mut self) {}
937
938 fn set_skip_blobs(&mut self, _skip_blobs: bool) {}
939}
940
941#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
945pub(crate) struct AASequenceId {
946 pub(crate) address: Address,
947 pub(crate) nonce_key: U256,
948}
949
950impl AASequenceId {
951 pub(crate) const fn new(address: Address, nonce_key: U256) -> Self {
953 Self { address, nonce_key }
954 }
955
956 const fn start_bound(self) -> std::ops::Bound<AA2dTransactionId> {
957 std::ops::Bound::Included(AA2dTransactionId::new(self, 0))
958 }
959}
960
961#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
965pub(crate) struct AA2dTransactionId {
966 pub(crate) seq_id: AASequenceId,
968 pub(crate) nonce: u64,
970}
971
972impl AA2dTransactionId {
973 pub(crate) const fn new(seq_id: AASequenceId, nonce: u64) -> Self {
975 Self { seq_id, nonce }
976 }
977
978 pub(crate) fn unlocks(&self) -> Self {
980 Self::new(self.seq_id, self.nonce.saturating_add(1))
981 }
982}
983
984#[cfg(test)]
985mod tests {
986 use super::*;
987 use crate::transaction::TempoPooledTransaction;
988 use alloy_consensus::Transaction;
989 use alloy_primitives::{Address, Signature, TxKind, U256};
990 use reth_primitives_traits::Recovered;
991 use reth_transaction_pool::{PoolTransaction, ValidPoolTransaction};
992 use std::{collections::HashSet, time::Instant};
993 use tempo_primitives::{
994 TempoTxEnvelope,
995 transaction::{
996 TempoTransaction,
997 tempo_transaction::Call,
998 tt_signature::{PrimitiveSignature, TempoSignature},
999 tt_signed::AASigned,
1000 },
1001 };
1002
1003 fn create_aa_tx(sender: Address, nonce_key: U256, nonce: u64) -> TempoPooledTransaction {
1005 create_aa_tx_with_gas(sender, nonce_key, nonce, 1_000_000_000, 2_000_000_000)
1006 }
1007
1008 fn create_aa_tx_with_gas(
1010 sender: Address,
1011 nonce_key: U256,
1012 nonce: u64,
1013 max_priority_fee: u128,
1014 max_fee: u128,
1015 ) -> TempoPooledTransaction {
1016 let tx = TempoTransaction {
1017 max_priority_fee_per_gas: max_priority_fee,
1018 max_fee_per_gas: max_fee,
1019 gas_limit: 100_000,
1020 calls: vec![Call {
1021 to: TxKind::Call(Address::random()),
1022 value: U256::from(1000),
1023 input: Default::default(),
1024 }],
1025 nonce_key,
1026 nonce,
1027 fee_token: None,
1028 fee_payer_signature: None,
1029 ..Default::default()
1030 };
1031
1032 let signature =
1034 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1035 let aa_signed = AASigned::new_unhashed(tx, signature);
1036 let envelope: TempoTxEnvelope = aa_signed.into();
1037
1038 let recovered = Recovered::new_unchecked(envelope, sender);
1040 TempoPooledTransaction::new(recovered)
1041 }
1042
1043 fn wrap_valid_tx(
1047 tx: TempoPooledTransaction,
1048 origin: TransactionOrigin,
1049 ) -> ValidPoolTransaction<TempoPooledTransaction> {
1050 let tx_id = reth_transaction_pool::identifier::TransactionId::new(0u64.into(), tx.nonce());
1051 ValidPoolTransaction {
1052 transaction: tx,
1053 transaction_id: tx_id,
1054 propagate: true,
1055 timestamp: Instant::now(),
1056 origin,
1057 authority_ids: None,
1058 }
1059 }
1060
1061 #[test_case::test_case(U256::ZERO)]
1062 #[test_case::test_case(U256::random())]
1063 fn insert_pending(nonce_key: U256) {
1064 let mut pool = AA2dPool::default();
1065
1066 let sender = Address::random();
1068
1069 let tx = create_aa_tx(sender, nonce_key, 0);
1071 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
1072
1073 let result = pool.add_transaction(Arc::new(valid_tx), 0);
1075
1076 assert!(result.is_ok(), "Transaction should be added successfully");
1078 let added = result.unwrap();
1079 assert!(
1080 matches!(added, AddedTransaction::Pending(_)),
1081 "Transaction should be pending, got: {added:?}"
1082 );
1083
1084 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1086 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
1087 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1088
1089 pool.assert_invariants();
1090 }
1091
1092 #[test_case::test_case(U256::ZERO)]
1093 #[test_case::test_case(U256::random())]
1094 fn insert_with_nonce_gap_then_fill(nonce_key: U256) {
1095 let mut pool = AA2dPool::default();
1096
1097 let sender = Address::random();
1099
1100 let tx1 = create_aa_tx(sender, nonce_key, 1);
1102 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
1103 let tx1_hash = *valid_tx1.hash();
1104
1105 let result1 = pool.add_transaction(Arc::new(valid_tx1), 0);
1106
1107 assert!(
1109 result1.is_ok(),
1110 "Transaction 1 should be added successfully"
1111 );
1112 let added1 = result1.unwrap();
1113 assert!(
1114 matches!(
1115 added1,
1116 AddedTransaction::Parked {
1117 subpool: SubPool::Queued,
1118 ..
1119 }
1120 ),
1121 "Transaction 1 should be queued due to nonce gap, got: {added1:?}"
1122 );
1123
1124 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1126 assert_eq!(pending_count, 0, "Should have 0 pending transactions");
1127 assert_eq!(queued_count, 1, "Should have 1 queued transaction");
1128
1129 let seq_id = AASequenceId::new(sender, nonce_key);
1131 let tx1_id = AA2dTransactionId::new(seq_id, 1);
1132 assert!(
1133 !pool.independent_transactions.contains_key(&tx1_id.seq_id),
1134 "Transaction 1 should not be in independent set yet"
1135 );
1136
1137 pool.assert_invariants();
1138
1139 let tx0 = create_aa_tx(sender, nonce_key, 0);
1141 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
1142 let tx0_hash = *valid_tx0.hash();
1143
1144 let result0 = pool.add_transaction(Arc::new(valid_tx0), 0);
1145
1146 assert!(
1148 result0.is_ok(),
1149 "Transaction 0 should be added successfully"
1150 );
1151 let added0 = result0.unwrap();
1152
1153 match added0 {
1155 AddedTransaction::Pending(ref pending) => {
1156 assert_eq!(pending.transaction.hash(), &tx0_hash, "Should be tx0");
1157 assert_eq!(
1158 pending.promoted.len(),
1159 1,
1160 "Should have promoted 1 transaction"
1161 );
1162 assert_eq!(
1163 pending.promoted[0].hash(),
1164 &tx1_hash,
1165 "Should have promoted tx1"
1166 );
1167 }
1168 _ => panic!("Transaction 0 should be pending, got: {added0:?}"),
1169 }
1170
1171 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1173 assert_eq!(pending_count, 2, "Should have 2 pending transactions");
1174 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1175
1176 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1178 assert!(
1179 pool.by_id.get(&tx0_id).unwrap().is_pending,
1180 "Transaction 0 should be pending"
1181 );
1182 assert!(
1183 pool.by_id.get(&tx1_id).unwrap().is_pending,
1184 "Transaction 1 should be pending after promotion"
1185 );
1186
1187 assert!(
1189 pool.independent_transactions.contains_key(&tx0_id.seq_id),
1190 "Transaction 0 should be in independent set (at on-chain nonce)"
1191 );
1192
1193 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
1195 assert_eq!(
1196 independent_tx.transaction.hash(),
1197 &tx0_hash,
1198 "Independent transaction should be tx0, not tx1"
1199 );
1200
1201 pool.assert_invariants();
1202 }
1203
1204 #[test_case::test_case(U256::ZERO)]
1205 #[test_case::test_case(U256::random())]
1206 fn replace_pending_transaction(nonce_key: U256) {
1207 let mut pool = AA2dPool::default();
1208
1209 let sender = Address::random();
1211
1212 let tx_low = create_aa_tx_with_gas(sender, nonce_key, 0, 1_000_000_000, 2_000_000_000);
1214 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
1215 let tx_low_hash = *valid_tx_low.hash();
1216
1217 let result_low = pool.add_transaction(Arc::new(valid_tx_low), 0);
1218
1219 assert!(
1221 result_low.is_ok(),
1222 "Initial transaction should be added successfully"
1223 );
1224 let added_low = result_low.unwrap();
1225 assert!(
1226 matches!(added_low, AddedTransaction::Pending(_)),
1227 "Initial transaction should be pending"
1228 );
1229
1230 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1232 assert_eq!(pending_count, 1, "Should have 1 pending transaction");
1233 assert_eq!(queued_count, 0, "Should have 0 queued transactions");
1234
1235 let seq_id = AASequenceId::new(sender, nonce_key);
1237 let tx_id = AA2dTransactionId::new(seq_id, 0);
1238 assert!(
1239 pool.independent_transactions.contains_key(&tx_id.seq_id),
1240 "Initial transaction should be in independent set"
1241 );
1242
1243 let independent_tx = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
1245 assert_eq!(
1246 independent_tx.transaction.hash(),
1247 &tx_low_hash,
1248 "Independent set should contain tx_low"
1249 );
1250
1251 pool.assert_invariants();
1252
1253 let tx_high = create_aa_tx_with_gas(sender, nonce_key, 0, 1_200_000_000, 2_400_000_000);
1256 let valid_tx_high = wrap_valid_tx(tx_high, TransactionOrigin::Local);
1257 let tx_high_hash = *valid_tx_high.hash();
1258
1259 let result_high = pool.add_transaction(Arc::new(valid_tx_high), 0);
1260
1261 assert!(
1263 result_high.is_ok(),
1264 "Replacement transaction should be added successfully"
1265 );
1266 let added_high = result_high.unwrap();
1267
1268 match added_high {
1270 AddedTransaction::Pending(ref pending) => {
1271 assert_eq!(
1272 pending.transaction.hash(),
1273 &tx_high_hash,
1274 "Should be tx_high"
1275 );
1276 assert!(
1277 pending.replaced.is_some(),
1278 "Should have replaced a transaction"
1279 );
1280 assert_eq!(
1281 pending.replaced.as_ref().unwrap().hash(),
1282 &tx_low_hash,
1283 "Should have replaced tx_low"
1284 );
1285 }
1286 _ => panic!("Replacement transaction should be pending, got: {added_high:?}"),
1287 }
1288
1289 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1291 assert_eq!(
1292 pending_count, 1,
1293 "Should still have 1 pending transaction after replacement"
1294 );
1295 assert_eq!(queued_count, 0, "Should still have 0 queued transactions");
1296
1297 assert!(
1299 !pool.contains(&tx_low_hash),
1300 "Old transaction should be removed from pool"
1301 );
1302
1303 assert!(
1305 pool.contains(&tx_high_hash),
1306 "New transaction should be in pool"
1307 );
1308
1309 assert!(
1311 pool.independent_transactions.contains_key(&tx_id.seq_id),
1312 "Transaction ID should still be in independent set"
1313 );
1314
1315 let independent_tx_after = pool.independent_transactions.get(&tx_id.seq_id).unwrap();
1316 assert_eq!(
1317 independent_tx_after.transaction.hash(),
1318 &tx_high_hash,
1319 "Independent set should now contain tx_high (not tx_low)"
1320 );
1321
1322 let tx_in_pool = pool.by_id.get(&tx_id).unwrap();
1324 assert_eq!(
1325 tx_in_pool.inner.transaction.hash(),
1326 &tx_high_hash,
1327 "Transaction in by_id should be tx_high"
1328 );
1329 assert!(tx_in_pool.is_pending, "Transaction should be pending");
1330
1331 pool.assert_invariants();
1332 }
1333
1334 #[test_case::test_case(U256::ZERO)]
1335 #[test_case::test_case(U256::random())]
1336 fn on_chain_nonce_update_with_gaps(nonce_key: U256) {
1337 let mut pool = AA2dPool::default();
1338
1339 let sender = Address::random();
1341
1342 let tx0 = create_aa_tx(sender, nonce_key, 0);
1347 let tx1 = create_aa_tx(sender, nonce_key, 1);
1348 let tx3 = create_aa_tx(sender, nonce_key, 3);
1349 let tx4 = create_aa_tx(sender, nonce_key, 4);
1350 let tx6 = create_aa_tx(sender, nonce_key, 6);
1351
1352 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
1353 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
1354 let valid_tx3 = wrap_valid_tx(tx3, TransactionOrigin::Local);
1355 let valid_tx4 = wrap_valid_tx(tx4, TransactionOrigin::Local);
1356 let valid_tx6 = wrap_valid_tx(tx6, TransactionOrigin::Local);
1357
1358 let tx0_hash = *valid_tx0.hash();
1359 let tx1_hash = *valid_tx1.hash();
1360 let tx3_hash = *valid_tx3.hash();
1361 let tx4_hash = *valid_tx4.hash();
1362 let tx6_hash = *valid_tx6.hash();
1363
1364 pool.add_transaction(Arc::new(valid_tx0), 0).unwrap();
1366 pool.add_transaction(Arc::new(valid_tx1), 0).unwrap();
1367 pool.add_transaction(Arc::new(valid_tx3), 0).unwrap();
1368 pool.add_transaction(Arc::new(valid_tx4), 0).unwrap();
1369 pool.add_transaction(Arc::new(valid_tx6), 0).unwrap();
1370
1371 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1373 assert_eq!(
1374 pending_count, 2,
1375 "Should have 2 pending transactions (0, 1)"
1376 );
1377 assert_eq!(
1378 queued_count, 3,
1379 "Should have 3 queued transactions (3, 4, 6)"
1380 );
1381
1382 let seq_id = AASequenceId::new(sender, nonce_key);
1384 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1385 assert!(
1386 pool.independent_transactions.contains_key(&tx0_id.seq_id),
1387 "Transaction 0 should be in independent set"
1388 );
1389
1390 pool.assert_invariants();
1391
1392 let mut on_chain_ids = HashMap::default();
1395 on_chain_ids.insert(seq_id, 2u64);
1396
1397 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
1398
1399 assert_eq!(mined.len(), 2, "Should have mined 2 transactions (0, 1)");
1401 let mined_hashes: HashSet<_> = mined.iter().map(|tx| tx.hash()).collect();
1402 assert!(
1403 mined_hashes.contains(&&tx0_hash),
1404 "Transaction 0 should be mined"
1405 );
1406 assert!(
1407 mined_hashes.contains(&&tx1_hash),
1408 "Transaction 1 should be mined"
1409 );
1410
1411 assert_eq!(
1413 promoted.len(),
1414 0,
1415 "No transactions should be promoted (gap at nonce 2)"
1416 );
1417
1418 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1420 assert_eq!(
1421 pending_count, 0,
1422 "Should have 0 pending transactions (gap at nonce 2)"
1423 );
1424 assert_eq!(
1425 queued_count, 3,
1426 "Should have 3 queued transactions (3, 4, 6)"
1427 );
1428
1429 assert!(!pool.contains(&tx0_hash), "Transaction 0 should be removed");
1431 assert!(!pool.contains(&tx1_hash), "Transaction 1 should be removed");
1432
1433 assert!(pool.contains(&tx3_hash), "Transaction 3 should remain");
1435 assert!(pool.contains(&tx4_hash), "Transaction 4 should remain");
1436 assert!(pool.contains(&tx6_hash), "Transaction 6 should remain");
1437
1438 let tx3_id = AA2dTransactionId::new(seq_id, 3);
1440 let tx4_id = AA2dTransactionId::new(seq_id, 4);
1441 let tx6_id = AA2dTransactionId::new(seq_id, 6);
1442
1443 assert!(
1444 !pool.by_id.get(&tx3_id).unwrap().is_pending,
1445 "Transaction 3 should be queued (gap at nonce 2)"
1446 );
1447 assert!(
1448 !pool.by_id.get(&tx4_id).unwrap().is_pending,
1449 "Transaction 4 should be queued"
1450 );
1451 assert!(
1452 !pool.by_id.get(&tx6_id).unwrap().is_pending,
1453 "Transaction 6 should be queued"
1454 );
1455
1456 assert!(
1458 pool.independent_transactions.is_empty(),
1459 "Independent set should be empty (gap at on-chain nonce 2)"
1460 );
1461
1462 pool.assert_invariants();
1463
1464 let mut on_chain_ids = HashMap::default();
1467 on_chain_ids.insert(seq_id, 3u64);
1468
1469 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
1470
1471 assert_eq!(
1473 mined.len(),
1474 0,
1475 "No transactions should be mined (nonce 2 was never in pool)"
1476 );
1477
1478 assert_eq!(promoted.len(), 2, "Transactions 3 and 4 should be promoted");
1480 let promoted_hashes: HashSet<_> = promoted.iter().map(|tx| tx.hash()).collect();
1481 assert!(
1482 promoted_hashes.contains(&&tx3_hash),
1483 "Transaction 3 should be promoted"
1484 );
1485 assert!(
1486 promoted_hashes.contains(&&tx4_hash),
1487 "Transaction 4 should be promoted"
1488 );
1489
1490 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1492 assert_eq!(
1493 pending_count, 2,
1494 "Should have 2 pending transactions (3, 4)"
1495 );
1496 assert_eq!(queued_count, 1, "Should have 1 queued transaction (6)");
1497
1498 assert!(
1500 pool.by_id.get(&tx3_id).unwrap().is_pending,
1501 "Transaction 3 should be pending"
1502 );
1503 assert!(
1504 pool.by_id.get(&tx4_id).unwrap().is_pending,
1505 "Transaction 4 should be pending"
1506 );
1507
1508 assert!(
1510 !pool.by_id.get(&tx6_id).unwrap().is_pending,
1511 "Transaction 6 should still be queued (gap at nonce 5)"
1512 );
1513
1514 assert!(
1516 pool.independent_transactions.contains_key(&tx3_id.seq_id),
1517 "Transaction 3 should be in independent set (at on-chain nonce 3)"
1518 );
1519
1520 let independent_tx = pool.independent_transactions.get(&seq_id).unwrap();
1522 assert_eq!(
1523 independent_tx.transaction.hash(),
1524 &tx3_hash,
1525 "Independent transaction should be tx3 (nonce 3), not tx4 or tx6"
1526 );
1527
1528 pool.assert_invariants();
1529 }
1530
1531 #[test_case::test_case(U256::ZERO)]
1532 #[test_case::test_case(U256::random())]
1533 fn reject_outdated_transaction(nonce_key: U256) {
1534 let mut pool = AA2dPool::default();
1535
1536 let sender = Address::random();
1538
1539 let tx = create_aa_tx(sender, nonce_key, 3);
1541 let valid_tx = wrap_valid_tx(tx, TransactionOrigin::Local);
1542
1543 let result = pool.add_transaction(Arc::new(valid_tx), 5);
1545
1546 assert!(result.is_err(), "Should reject outdated transaction");
1548
1549 let err = result.unwrap_err();
1550 assert!(
1551 matches!(
1552 err.kind,
1553 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus(
1554 InvalidTransactionError::NonceNotConsistent { tx: 3, state: 5 }
1555 ))
1556 ),
1557 "Should fail with NonceNotConsistent error, got: {:?}",
1558 err.kind
1559 );
1560
1561 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1563 assert_eq!(pending_count, 0, "Pool should be empty");
1564 assert_eq!(queued_count, 0, "Pool should be empty");
1565
1566 pool.assert_invariants();
1567 }
1568
1569 #[test_case::test_case(U256::ZERO)]
1570 #[test_case::test_case(U256::random())]
1571 fn replace_with_insufficient_price_bump(nonce_key: U256) {
1572 let mut pool = AA2dPool::default();
1573
1574 let sender = Address::random();
1576
1577 let tx_low = create_aa_tx_with_gas(sender, nonce_key, 0, 1_000_000_000, 2_000_000_000);
1579 let valid_tx_low = wrap_valid_tx(tx_low, TransactionOrigin::Local);
1580
1581 pool.add_transaction(Arc::new(valid_tx_low), 0).unwrap();
1582
1583 let tx_insufficient =
1585 create_aa_tx_with_gas(sender, nonce_key, 0, 1_050_000_000, 2_100_000_000);
1586 let valid_tx_insufficient = wrap_valid_tx(tx_insufficient, TransactionOrigin::Local);
1587
1588 let result = pool.add_transaction(Arc::new(valid_tx_insufficient), 0);
1589
1590 assert!(
1592 result.is_err(),
1593 "Should reject replacement with insufficient price bump"
1594 );
1595 let err = result.unwrap_err();
1596 assert!(
1597 matches!(err.kind, PoolErrorKind::ReplacementUnderpriced),
1598 "Should fail with ReplacementUnderpriced, got: {:?}",
1599 err.kind
1600 );
1601
1602 pool.assert_invariants();
1603 }
1604
1605 #[test_case::test_case(U256::ZERO)]
1606 #[test_case::test_case(U256::random())]
1607 fn fill_gap_in_middle(nonce_key: U256) {
1608 let mut pool = AA2dPool::default();
1609
1610 let sender = Address::random();
1611
1612 let tx0 = create_aa_tx(sender, nonce_key, 0);
1614 let tx1 = create_aa_tx(sender, nonce_key, 1);
1615 let tx3 = create_aa_tx(sender, nonce_key, 3);
1616 let tx4 = create_aa_tx(sender, nonce_key, 4);
1617
1618 pool.add_transaction(Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)), 0)
1619 .unwrap();
1620 pool.add_transaction(Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)), 0)
1621 .unwrap();
1622 pool.add_transaction(Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)), 0)
1623 .unwrap();
1624 pool.add_transaction(Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)), 0)
1625 .unwrap();
1626
1627 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1629 assert_eq!(pending_count, 2, "Should have 2 pending (0, 1)");
1630 assert_eq!(queued_count, 2, "Should have 2 queued (3, 4)");
1631
1632 let tx2 = create_aa_tx(sender, nonce_key, 2);
1634 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
1635
1636 let result = pool.add_transaction(Arc::new(valid_tx2), 0);
1637 assert!(result.is_ok(), "Should successfully add tx2");
1638
1639 match result.unwrap() {
1641 AddedTransaction::Pending(ref pending) => {
1642 assert_eq!(pending.promoted.len(), 2, "Should promote tx3 and tx4");
1643 }
1644 _ => panic!("tx2 should be added as pending"),
1645 }
1646
1647 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1649 assert_eq!(pending_count, 5, "All 5 transactions should be pending");
1650 assert_eq!(queued_count, 0, "No transactions should be queued");
1651
1652 let seq_id = AASequenceId::new(sender, nonce_key);
1654 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1655 assert!(
1656 pool.independent_transactions.contains_key(&tx0_id.seq_id),
1657 "tx0 should be in independent set"
1658 );
1659
1660 pool.assert_invariants();
1661 }
1662
1663 #[test_case::test_case(U256::ZERO)]
1664 #[test_case::test_case(U256::random())]
1665 fn remove_pending_transaction(nonce_key: U256) {
1666 let mut pool = AA2dPool::default();
1667
1668 let sender = Address::random();
1669
1670 let tx0 = create_aa_tx(sender, nonce_key, 0);
1672 let tx1 = create_aa_tx(sender, nonce_key, 1);
1673 let tx2 = create_aa_tx(sender, nonce_key, 2);
1674
1675 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
1676 let valid_tx1 = wrap_valid_tx(tx1, TransactionOrigin::Local);
1677 let valid_tx2 = wrap_valid_tx(tx2, TransactionOrigin::Local);
1678
1679 let tx1_hash = *valid_tx1.hash();
1680
1681 pool.add_transaction(Arc::new(valid_tx0), 0).unwrap();
1682 pool.add_transaction(Arc::new(valid_tx1), 0).unwrap();
1683 pool.add_transaction(Arc::new(valid_tx2), 0).unwrap();
1684
1685 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1687 assert_eq!(pending_count, 3, "All 3 should be pending");
1688 assert_eq!(queued_count, 0, "None should be queued");
1689
1690 let seq_id = AASequenceId::new(sender, nonce_key);
1691 let tx1_id = AA2dTransactionId::new(seq_id, 1);
1692 let tx2_id = AA2dTransactionId::new(seq_id, 2);
1693
1694 assert!(
1696 pool.by_id.get(&tx2_id).unwrap().is_pending,
1697 "tx2 should be pending before removal"
1698 );
1699
1700 let removed = pool.remove_transactions([&tx1_hash].into_iter());
1702 assert_eq!(removed.len(), 1, "Should remove tx1");
1703
1704 assert!(!pool.by_id.contains_key(&tx1_id), "tx1 should be removed");
1712 assert!(!pool.contains(&tx1_hash), "tx1 should be removed");
1713
1714 assert_eq!(pool.by_id.len(), 2, "Should have 2 transactions left");
1716
1717 pool.assert_invariants();
1718 }
1719
1720 #[test_case::test_case(U256::ZERO, U256::random())]
1721 #[test_case::test_case(U256::random(), U256::ZERO)]
1722 #[test_case::test_case(U256::random(), U256::random())]
1723 fn multiple_senders_independent_set(nonce_key_a: U256, nonce_key_b: U256) {
1724 let mut pool = AA2dPool::default();
1725
1726 let sender_a = Address::random();
1728 let sender_b = Address::random();
1729
1730 let tx_a0 = create_aa_tx(sender_a, nonce_key_a, 0);
1733 let tx_a1 = create_aa_tx(sender_a, nonce_key_a, 1);
1734
1735 let tx_b0 = create_aa_tx(sender_b, nonce_key_b, 0);
1737 let tx_b1 = create_aa_tx(sender_b, nonce_key_b, 1);
1738
1739 let valid_tx_a0 = wrap_valid_tx(tx_a0, TransactionOrigin::Local);
1740 let valid_tx_a1 = wrap_valid_tx(tx_a1, TransactionOrigin::Local);
1741 let valid_tx_b0 = wrap_valid_tx(tx_b0, TransactionOrigin::Local);
1742 let valid_tx_b1 = wrap_valid_tx(tx_b1, TransactionOrigin::Local);
1743
1744 let tx_a0_hash = *valid_tx_a0.hash();
1745
1746 pool.add_transaction(Arc::new(valid_tx_a0), 0).unwrap();
1747 pool.add_transaction(Arc::new(valid_tx_a1), 0).unwrap();
1748 pool.add_transaction(Arc::new(valid_tx_b0), 0).unwrap();
1749 pool.add_transaction(Arc::new(valid_tx_b1), 0).unwrap();
1750
1751 let sender_a_id = AASequenceId::new(sender_a, nonce_key_a);
1753 let sender_b_id = AASequenceId::new(sender_b, nonce_key_b);
1754 let tx_a0_id = AA2dTransactionId::new(sender_a_id, 0);
1755 let tx_b0_id = AA2dTransactionId::new(sender_b_id, 0);
1756
1757 assert_eq!(
1758 pool.independent_transactions.len(),
1759 2,
1760 "Should have 2 independent transactions"
1761 );
1762 assert!(
1763 pool.independent_transactions.contains_key(&tx_a0_id.seq_id),
1764 "Sender A's tx0 should be independent"
1765 );
1766 assert!(
1767 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
1768 "Sender B's tx0 should be independent"
1769 );
1770
1771 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1773 assert_eq!(pending_count, 4, "All 4 transactions should be pending");
1774 assert_eq!(queued_count, 0, "No transactions should be queued");
1775
1776 let mut on_chain_ids = HashMap::default();
1778 on_chain_ids.insert(sender_a_id, 1u64);
1779
1780 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
1781
1782 assert_eq!(mined.len(), 1, "Only sender A's tx0 should be mined");
1784 assert_eq!(mined[0].hash(), &tx_a0_hash, "Should mine tx_a0");
1785
1786 assert_eq!(
1788 promoted.len(),
1789 0,
1790 "No transactions should be promoted (tx_a1 was already pending)"
1791 );
1792
1793 let tx_a1_id = AA2dTransactionId::new(sender_a_id, 1);
1795 assert_eq!(
1796 pool.independent_transactions.len(),
1797 2,
1798 "Should still have 2 independent transactions"
1799 );
1800 assert!(
1801 pool.independent_transactions.contains_key(&tx_a1_id.seq_id),
1802 "Sender A's tx1 should now be independent"
1803 );
1804 assert!(
1805 pool.independent_transactions.contains_key(&tx_b0_id.seq_id),
1806 "Sender B's tx0 should still be independent"
1807 );
1808
1809 pool.assert_invariants();
1810 }
1811
1812 #[test_case::test_case(U256::ZERO)]
1813 #[test_case::test_case(U256::random())]
1814 fn concurrent_replacements_same_nonce(nonce_key: U256) {
1815 let mut pool = AA2dPool::default();
1816 let sender = Address::random();
1817 let seq_id = AASequenceId {
1818 address: sender,
1819 nonce_key,
1820 };
1821
1822 let tx0 = create_aa_tx_with_gas(sender, nonce_key, 0, 1_000_000_000, 2_000_000_000);
1824 let tx0_hash = *tx0.hash();
1825 let valid_tx0 = wrap_valid_tx(tx0, TransactionOrigin::Local);
1826 let result = pool.add_transaction(Arc::new(valid_tx0), 0);
1827 assert!(result.is_ok());
1828 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1829 assert_eq!(pending_count + queued_count, 1);
1830
1831 let tx0_replacement1 =
1833 create_aa_tx_with_gas(sender, nonce_key, 0, 1_050_000_000, 2_100_000_000);
1834 let valid_tx1 = wrap_valid_tx(tx0_replacement1, TransactionOrigin::Local);
1835 let result = pool.add_transaction(Arc::new(valid_tx1), 0);
1836 assert!(result.is_err(), "Should reject insufficient price bump");
1837 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1838 assert_eq!(pending_count + queued_count, 1);
1839 assert!(
1840 pool.contains(&tx0_hash),
1841 "Original tx should still be present"
1842 );
1843
1844 let tx0_replacement2 =
1846 create_aa_tx_with_gas(sender, nonce_key, 0, 1_100_000_000, 2_200_000_000);
1847 let tx0_replacement2_hash = *tx0_replacement2.hash();
1848 let valid_tx2 = wrap_valid_tx(tx0_replacement2, TransactionOrigin::Local);
1849 let result = pool.add_transaction(Arc::new(valid_tx2), 0);
1850 assert!(result.is_ok(), "Should accept 10% price bump");
1851 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1852 assert_eq!(pending_count + queued_count, 1, "Pool size should remain 1");
1853 assert!(!pool.contains(&tx0_hash), "Old tx should be removed");
1854 assert!(
1855 pool.contains(&tx0_replacement2_hash),
1856 "New tx should be present"
1857 );
1858
1859 let tx0_replacement3 =
1861 create_aa_tx_with_gas(sender, nonce_key, 0, 1_500_000_000, 3_000_000_000);
1862 let tx0_replacement3_hash = *tx0_replacement3.hash();
1863 let valid_tx3 = wrap_valid_tx(tx0_replacement3, TransactionOrigin::Local);
1864 let result = pool.add_transaction(Arc::new(valid_tx3), 0);
1865 assert!(result.is_ok(), "Should accept higher price bump");
1866 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1867 assert_eq!(pending_count + queued_count, 1);
1868 assert!(
1869 !pool.contains(&tx0_replacement2_hash),
1870 "Previous tx should be removed"
1871 );
1872 assert!(
1873 pool.contains(&tx0_replacement3_hash),
1874 "Highest priority tx should win"
1875 );
1876
1877 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1879 assert!(pool.independent_transactions.contains_key(&tx0_id.seq_id));
1880
1881 pool.assert_invariants();
1882 }
1883
1884 #[test_case::test_case(U256::ZERO)]
1885 #[test_case::test_case(U256::random())]
1886 fn long_gap_chain(nonce_key: U256) {
1887 let mut pool = AA2dPool::default();
1888 let sender = Address::random();
1889 let seq_id = AASequenceId {
1890 address: sender,
1891 nonce_key,
1892 };
1893
1894 let tx0 = create_aa_tx(sender, nonce_key, 0);
1896 let tx5 = create_aa_tx(sender, nonce_key, 5);
1897 let tx10 = create_aa_tx(sender, nonce_key, 10);
1898 let tx15 = create_aa_tx(sender, nonce_key, 15);
1899
1900 pool.add_transaction(Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)), 0)
1901 .unwrap();
1902 pool.add_transaction(Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)), 0)
1903 .unwrap();
1904 pool.add_transaction(Arc::new(wrap_valid_tx(tx10, TransactionOrigin::Local)), 0)
1905 .unwrap();
1906 pool.add_transaction(Arc::new(wrap_valid_tx(tx15, TransactionOrigin::Local)), 0)
1907 .unwrap();
1908
1909 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1910 assert_eq!(pending_count + queued_count, 4);
1911
1912 let tx0_id = AA2dTransactionId::new(seq_id, 0);
1914 assert!(pool.by_id.get(&tx0_id).unwrap().is_pending);
1915 assert!(
1916 !pool
1917 .by_id
1918 .get(&AA2dTransactionId::new(seq_id, 5))
1919 .unwrap()
1920 .is_pending
1921 );
1922 assert!(
1923 !pool
1924 .by_id
1925 .get(&AA2dTransactionId::new(seq_id, 10))
1926 .unwrap()
1927 .is_pending
1928 );
1929 assert!(
1930 !pool
1931 .by_id
1932 .get(&AA2dTransactionId::new(seq_id, 15))
1933 .unwrap()
1934 .is_pending
1935 );
1936 assert_eq!(pool.independent_transactions.len(), 1);
1937
1938 for nonce in 1..=4 {
1940 let tx = create_aa_tx(sender, nonce_key, nonce);
1941 pool.add_transaction(Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)), 0)
1942 .unwrap();
1943 }
1944
1945 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1946 assert_eq!(pending_count + queued_count, 8);
1947
1948 for nonce in 0..=5 {
1950 let id = AA2dTransactionId::new(seq_id, nonce);
1951 assert!(
1952 pool.by_id.get(&id).unwrap().is_pending,
1953 "Nonce {nonce} should be pending"
1954 );
1955 }
1956 assert!(
1958 !pool
1959 .by_id
1960 .get(&AA2dTransactionId::new(seq_id, 10))
1961 .unwrap()
1962 .is_pending
1963 );
1964 assert!(
1965 !pool
1966 .by_id
1967 .get(&AA2dTransactionId::new(seq_id, 15))
1968 .unwrap()
1969 .is_pending
1970 );
1971
1972 for nonce in 6..=9 {
1974 let tx = create_aa_tx(sender, nonce_key, nonce);
1975 pool.add_transaction(Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)), 0)
1976 .unwrap();
1977 }
1978
1979 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
1980 assert_eq!(pending_count + queued_count, 12);
1981
1982 for nonce in 0..=10 {
1984 let id = AA2dTransactionId::new(seq_id, nonce);
1985 assert!(
1986 pool.by_id.get(&id).unwrap().is_pending,
1987 "Nonce {nonce} should be pending"
1988 );
1989 }
1990 assert!(
1992 !pool
1993 .by_id
1994 .get(&AA2dTransactionId::new(seq_id, 15))
1995 .unwrap()
1996 .is_pending
1997 );
1998
1999 for nonce in 11..=14 {
2001 let tx = create_aa_tx(sender, nonce_key, nonce);
2002 pool.add_transaction(Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)), 0)
2003 .unwrap();
2004 }
2005
2006 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2007 assert_eq!(pending_count + queued_count, 16);
2008
2009 for nonce in 0..=15 {
2011 let id = AA2dTransactionId::new(seq_id, nonce);
2012 assert!(
2013 pool.by_id.get(&id).unwrap().is_pending,
2014 "Nonce {nonce} should be pending"
2015 );
2016 }
2017
2018 pool.assert_invariants();
2019 }
2020
2021 #[test_case::test_case(U256::ZERO)]
2022 #[test_case::test_case(U256::random())]
2023 fn remove_from_middle_of_chain(nonce_key: U256) {
2024 let mut pool = AA2dPool::default();
2025 let sender = Address::random();
2026 let seq_id = AASequenceId {
2027 address: sender,
2028 nonce_key,
2029 };
2030
2031 for nonce in 0..=4 {
2033 let tx = create_aa_tx(sender, nonce_key, nonce);
2034 pool.add_transaction(Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)), 0)
2035 .unwrap();
2036 }
2037
2038 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2039 assert_eq!(pending_count + queued_count, 5);
2040
2041 for nonce in 0..=4 {
2043 assert!(
2044 pool.by_id
2045 .get(&AA2dTransactionId::new(seq_id, nonce))
2046 .unwrap()
2047 .is_pending
2048 );
2049 }
2050
2051 let tx2_id = AA2dTransactionId::new(seq_id, 2);
2053 let tx2_hash = *pool.by_id.get(&tx2_id).unwrap().inner.transaction.hash();
2054 let removed = pool.remove_transactions([&tx2_hash].into_iter());
2055 assert_eq!(removed.len(), 1, "Should remove transaction");
2056
2057 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2058 assert_eq!(pending_count + queued_count, 4);
2059
2060 assert!(!pool.by_id.contains_key(&tx2_id));
2062
2063 pool.assert_invariants();
2068 }
2069
2070 #[test_case::test_case(U256::ZERO)]
2071 #[test_case::test_case(U256::random())]
2072 fn independent_set_after_multiple_promotions(nonce_key: U256) {
2073 let mut pool = AA2dPool::default();
2074 let sender = Address::random();
2075 let seq_id = AASequenceId {
2076 address: sender,
2077 nonce_key,
2078 };
2079
2080 let tx0 = create_aa_tx(sender, nonce_key, 0);
2082 let tx2 = create_aa_tx(sender, nonce_key, 2);
2083 let tx4 = create_aa_tx(sender, nonce_key, 4);
2084
2085 pool.add_transaction(Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)), 0)
2086 .unwrap();
2087 pool.add_transaction(Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)), 0)
2088 .unwrap();
2089 pool.add_transaction(Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)), 0)
2090 .unwrap();
2091
2092 assert_eq!(pool.independent_transactions.len(), 1);
2094 assert!(pool.independent_transactions.contains_key(&seq_id));
2095
2096 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2098 assert_eq!(pending_count, 1);
2099 assert_eq!(queued_count, 2);
2100
2101 let tx1 = create_aa_tx(sender, nonce_key, 1);
2103 pool.add_transaction(Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)), 0)
2104 .unwrap();
2105
2106 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2108 assert_eq!(pending_count, 3);
2109 assert_eq!(queued_count, 1);
2110
2111 assert_eq!(pool.independent_transactions.len(), 1);
2113 assert!(pool.independent_transactions.contains_key(&seq_id));
2114
2115 let tx3 = create_aa_tx(sender, nonce_key, 3);
2117 pool.add_transaction(Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)), 0)
2118 .unwrap();
2119
2120 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2122 assert_eq!(pending_count, 5);
2123 assert_eq!(queued_count, 0);
2124
2125 let mut on_chain_ids = HashMap::default();
2127 on_chain_ids.insert(seq_id, 2u64);
2128 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2129
2130 assert_eq!(mined.len(), 2);
2132 assert_eq!(promoted.len(), 0);
2133
2134 assert_eq!(pool.independent_transactions.len(), 1);
2136 assert!(pool.independent_transactions.contains_key(&seq_id));
2137
2138 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2140 assert_eq!(pending_count + queued_count, 3);
2141
2142 pool.assert_invariants();
2143 }
2144
2145 #[test]
2146 fn stress_test_many_senders() {
2147 let mut pool = AA2dPool::default();
2148 const NUM_SENDERS: usize = 100;
2149 const TXS_PER_SENDER: u64 = 5;
2150
2151 let mut senders = Vec::new();
2153 for i in 0..NUM_SENDERS {
2154 let sender = Address::from_word(B256::from(U256::from(i)));
2155 let nonce_key = U256::from(i);
2156 senders.push((sender, nonce_key));
2157
2158 for nonce in 0..TXS_PER_SENDER {
2160 let tx = create_aa_tx(sender, nonce_key, nonce);
2161 pool.add_transaction(Arc::new(wrap_valid_tx(tx, TransactionOrigin::Local)), 0)
2162 .unwrap();
2163 }
2164 }
2165
2166 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2168 assert_eq!(
2169 pending_count + queued_count,
2170 NUM_SENDERS * TXS_PER_SENDER as usize
2171 );
2172
2173 for (sender, nonce_key) in &senders {
2175 let seq_id = AASequenceId {
2176 address: *sender,
2177 nonce_key: *nonce_key,
2178 };
2179 for nonce in 0..TXS_PER_SENDER {
2180 let id = AA2dTransactionId::new(seq_id, nonce);
2181 assert!(pool.by_id.get(&id).unwrap().is_pending);
2182 }
2183 }
2184
2185 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
2187 for (sender, nonce_key) in &senders {
2188 let seq_id = AASequenceId {
2189 address: *sender,
2190 nonce_key: *nonce_key,
2191 };
2192 let tx0_id = AA2dTransactionId::new(seq_id, 0);
2193 assert!(
2194 pool.independent_transactions.contains_key(&tx0_id.seq_id),
2195 "Sender {sender:?} should have tx0 in independent set"
2196 );
2197 }
2198
2199 let mut on_chain_ids = HashMap::default();
2201 for (sender, nonce_key) in &senders {
2202 let seq_id = AASequenceId {
2203 address: *sender,
2204 nonce_key: *nonce_key,
2205 };
2206 on_chain_ids.insert(seq_id, 1u64);
2207 }
2208
2209 let (promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2210
2211 assert_eq!(mined.len(), NUM_SENDERS);
2213 assert_eq!(promoted.len(), 0);
2215
2216 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2218 assert_eq!(
2219 pending_count + queued_count,
2220 NUM_SENDERS * (TXS_PER_SENDER - 1) as usize
2221 );
2222
2223 assert_eq!(pool.independent_transactions.len(), NUM_SENDERS);
2225 for (sender, nonce_key) in &senders {
2226 let seq_id = AASequenceId {
2227 address: *sender,
2228 nonce_key: *nonce_key,
2229 };
2230 let tx1_id = AA2dTransactionId::new(seq_id, 1);
2231 assert!(
2232 pool.independent_transactions.contains_key(&tx1_id.seq_id),
2233 "Sender {sender:?} should have tx1 in independent set"
2234 );
2235 }
2236
2237 pool.assert_invariants();
2238 }
2239
2240 #[test_case::test_case(U256::ZERO)]
2241 #[test_case::test_case(U256::random())]
2242 fn on_chain_nonce_update_to_queued_tx_with_gaps(nonce_key: U256) {
2243 let mut pool = AA2dPool::default();
2244 let sender = Address::random();
2245 let seq_id = AASequenceId {
2246 address: sender,
2247 nonce_key,
2248 };
2249
2250 let tx0 = create_aa_tx(sender, nonce_key, 0);
2253 let tx3 = create_aa_tx(sender, nonce_key, 3);
2254 let tx5 = create_aa_tx(sender, nonce_key, 5);
2255
2256 pool.add_transaction(Arc::new(wrap_valid_tx(tx0, TransactionOrigin::Local)), 0)
2257 .unwrap();
2258 pool.add_transaction(Arc::new(wrap_valid_tx(tx3, TransactionOrigin::Local)), 0)
2259 .unwrap();
2260 pool.add_transaction(Arc::new(wrap_valid_tx(tx5, TransactionOrigin::Local)), 0)
2261 .unwrap();
2262
2263 assert_eq!(pool.independent_transactions.len(), 1);
2265 assert!(pool.independent_transactions.contains_key(&seq_id));
2266
2267 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2269 assert_eq!(pending_count, 1, "Only tx0 should be pending");
2270 assert_eq!(queued_count, 2, "tx3 and tx5 should be queued");
2271
2272 let tx1 = create_aa_tx(sender, nonce_key, 1);
2274 pool.add_transaction(Arc::new(wrap_valid_tx(tx1, TransactionOrigin::Local)), 0)
2275 .unwrap();
2276
2277 let tx2 = create_aa_tx(sender, nonce_key, 2);
2278 pool.add_transaction(Arc::new(wrap_valid_tx(tx2, TransactionOrigin::Local)), 0)
2279 .unwrap();
2280
2281 let (pending_count, queued_count) = pool.pending_and_queued_txn_count();
2283 assert_eq!(pending_count, 4, "Transactions [0,1,2,3] should be pending");
2284 assert_eq!(queued_count, 1, "tx5 should still be queued");
2285
2286 assert_eq!(pool.independent_transactions.len(), 1);
2288 assert!(pool.independent_transactions.contains_key(&seq_id));
2289
2290 let mut on_chain_ids = HashMap::default();
2291 on_chain_ids.insert(seq_id, 3u64);
2292 let (_promoted, mined) = pool.on_nonce_changes(on_chain_ids);
2293
2294 assert_eq!(mined.len(), 3, "Should mine transactions [0,1,2]");
2296
2297 assert_eq!(
2300 pool.independent_transactions.len(),
2301 1,
2302 "Should have one independent transaction"
2303 );
2304 let key = AA2dTransactionId::new(seq_id, 3);
2305 assert!(
2306 pool.independent_transactions.contains_key(&key.seq_id),
2307 "tx3 should be in independent set"
2308 );
2309
2310 let (_pending_count, _queued_count) = pool.pending_and_queued_txn_count();
2312 pool.assert_invariants();
2315
2316 let tx4 = create_aa_tx(sender, nonce_key, 4);
2319 pool.add_transaction(Arc::new(wrap_valid_tx(tx4, TransactionOrigin::Local)), 3)
2320 .unwrap();
2321
2322 let (_pending_count_after, _queued_count_after) = pool.pending_and_queued_txn_count();
2324 pool.assert_invariants();
2325 }
2326}