1use crate::{
6 amm::AmmLiquidityCache, best::MergeBestTransactions, ordering::TempoTipOrdering,
7 transaction::TempoPooledTransaction, tt_2d_pool::AA2dPool,
8 validator::TempoTransactionValidator,
9};
10use alloy_consensus::Transaction;
11use alloy_primitives::{
12 Address, B256, TxHash, U256,
13 map::{AddressMap, AddressSet, Entry, HashMap},
14};
15use parking_lot::RwLock;
16use reth_chainspec::ChainSpecProvider;
17use reth_eth_wire_types::HandleMempoolData;
18use reth_provider::{ChangedAccount, StateProviderFactory};
19use reth_storage_api::StateProvider;
20use reth_transaction_pool::{
21 AddedTransactionOutcome, AllPoolTransactions, BestTransactions, BestTransactionsAttributes,
22 BlockInfo, CanonicalStateUpdate, GetPooledTransactionLimit, NewBlobSidecar, Pool, PoolResult,
23 PoolSize, PoolTransaction, PropagatedTransactions, TransactionEvents, TransactionOrigin,
24 TransactionPool, TransactionPoolExt, TransactionValidationOutcome,
25 TransactionValidationTaskExecutor, TransactionValidator, ValidPoolTransaction,
26 blobstore::InMemoryBlobStore,
27 error::{PoolError, PoolErrorKind},
28 identifier::TransactionId,
29};
30use revm::database::BundleAccount;
31use std::{sync::Arc, time::Instant};
32use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardfork};
33use tempo_precompiles::{
34 TIP_FEE_MANAGER_ADDRESS,
35 account_keychain::AccountKeychain,
36 error::Result as TempoPrecompileResult,
37 storage::{Handler, StorageActions},
38 tip20::TIP20Token,
39 tip403_registry::{REJECT_ALL_POLICY_ID, TIP403Registry},
40};
41use tempo_primitives::Block;
42use tempo_revm::TempoStateAccess;
43
44pub struct TempoTransactionPool<Client> {
46 protocol_pool: Pool<
48 TransactionValidationTaskExecutor<TempoTransactionValidator<Client>>,
49 TempoTipOrdering<TempoPooledTransaction>,
50 InMemoryBlobStore,
51 >,
52 aa_2d_pool: Arc<RwLock<AA2dPool>>,
54}
55
56impl<Client> TempoTransactionPool<Client>
57where
58 Client: StateProviderFactory + ChainSpecProvider<ChainSpec = TempoChainSpec> + 'static,
59{
60 pub fn new(
61 protocol_pool: Pool<
62 TransactionValidationTaskExecutor<TempoTransactionValidator<Client>>,
63 TempoTipOrdering<TempoPooledTransaction>,
64 InMemoryBlobStore,
65 >,
66 mut aa_2d_pool: AA2dPool,
67 ) -> Self {
68 aa_2d_pool.set_base_fee(protocol_pool.inner().block_info().pending_basefee);
69 Self {
70 protocol_pool,
71 aa_2d_pool: Arc::new(RwLock::new(aa_2d_pool)),
72 }
73 }
74}
75impl<Client> TempoTransactionPool<Client>
76where
77 Client: StateProviderFactory + ChainSpecProvider<ChainSpec = TempoChainSpec> + 'static,
78{
79 pub fn amm_liquidity_cache(&self) -> AmmLiquidityCache {
81 self.protocol_pool
82 .validator()
83 .validator()
84 .amm_liquidity_cache()
85 }
86
87 pub fn client(&self) -> &Client {
89 self.protocol_pool.validator().validator().client()
90 }
91
92 pub(crate) fn notify_aa_pool_on_state_updates(
96 &self,
97 state: &AddressMap<BundleAccount>,
98 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
99 let (promoted, mined) = self.aa_2d_pool.write().on_state_updates(state);
100 self.protocol_pool
102 .inner()
103 .notify_on_transaction_updates(promoted, Vec::new());
104 mined
105 }
106
107 pub fn evict_invalidated_transactions(
125 &self,
126 updates: &crate::maintain::TempoPoolUpdates,
127 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
128 if !updates.has_invalidation_events() {
129 return Vec::new();
130 }
131
132 let all_txs = self.all_transactions();
133 self.evict_invalidated_transactions_from(updates, all_txs.iter())
134 }
135
136 pub(crate) fn evict_invalidated_transactions_from<'a>(
139 &self,
140 updates: &crate::maintain::TempoPoolUpdates,
141 transactions: impl IntoIterator<Item = &'a Arc<ValidPoolTransaction<TempoPooledTransaction>>>,
142 ) -> Vec<Arc<ValidPoolTransaction<TempoPooledTransaction>>> {
143 if !updates.has_invalidation_events() {
144 return Vec::new();
145 }
146
147 let mut state_provider = if !updates.validator_token_changes.is_empty()
153 || !updates.blacklist_additions.is_empty()
154 || !updates.whitelist_removals.is_empty()
155 || !updates.fee_balance_changes.is_empty()
156 || !updates.spending_limit_spends.is_empty()
157 {
158 self.client().latest().ok()
159 } else {
160 None
161 };
162
163 let tip_timestamp = self
165 .protocol_pool
166 .validator()
167 .validator()
168 .inner
169 .fork_tracker()
170 .tip_timestamp();
171 let spec = self.protocol_pool.validator().validator().active_hardfork();
172
173 let mut policy_cache: AddressMap<Vec<u64>> = AddressMap::default();
177
178 let fee_manager_blacklisted: Vec<u64> = updates
182 .blacklist_additions
183 .iter()
184 .filter(|(_, account)| *account == TIP_FEE_MANAGER_ADDRESS)
185 .map(|(policy_id, _)| *policy_id)
186 .collect();
187 let fee_manager_unwhitelisted: Vec<u64> = updates
188 .whitelist_removals
189 .iter()
190 .filter(|(_, account)| *account == TIP_FEE_MANAGER_ADDRESS)
191 .map(|(policy_id, _)| *policy_id)
192 .collect();
193
194 let amm_cache = self.amm_liquidity_cache();
198 let has_active_validator_token_changes = !updates.validator_token_changes.is_empty() && {
199 let active_new_tokens: Vec<_> = updates
200 .validator_token_changes
201 .iter()
202 .filter(|(validator, _)| amm_cache.is_active_validator(validator))
203 .filter(|(_, new_token)| !amm_cache.is_active_validator_token(new_token))
204 .map(|(_, &new_token)| new_token)
205 .collect();
206 amm_cache.track_tokens(&active_new_tokens)
207 };
208
209 let mut to_remove = Vec::new();
210 let mut revoked_count = 0;
211 let mut key_authorization_target_count = 0;
212 let mut spending_limit_count = 0;
213 let mut spending_limit_spend_count = 0;
214 let mut key_authorization_witness_count = 0;
215 let mut liquidity_count = 0;
216 let mut user_token_count = 0;
217 let mut blacklisted_count = 0;
218 let mut unwhitelisted_count = 0;
219 let mut insolvent_fee_payer_count = 0;
220 let has_keychain_subject_updates = updates.has_keychain_subject_updates();
221 let has_key_authorization_target_updates =
222 !updates.key_authorization_target_changes.is_empty();
223 let mut fee_balance_cache: HashMap<(Address, Address), U256> = HashMap::default();
224
225 for tx in transactions {
226 if has_keychain_subject_updates || has_key_authorization_target_updates {
228 let keychain_subject = has_keychain_subject_updates
229 .then(|| tx.transaction.keychain_subject())
230 .flatten();
231 let key_authorization_subject = (!updates.revoked_keys.is_empty())
232 .then(|| tx.transaction.key_authorization_signer_subject())
233 .flatten();
234 let key_authorization_target = has_key_authorization_target_updates
235 .then(|| tx.transaction.key_authorization_target_subject())
236 .flatten();
237
238 if !updates.revoked_keys.is_empty()
240 && (keychain_subject
241 .as_ref()
242 .is_some_and(|subject| subject.matches_revoked(&updates.revoked_keys))
243 || key_authorization_subject
244 .as_ref()
245 .is_some_and(|subject| subject.matches_revoked(&updates.revoked_keys)))
246 {
247 to_remove.push(*tx.hash());
248 revoked_count += 1;
249 continue;
250 }
251
252 if !updates.key_authorization_target_changes.is_empty()
254 && key_authorization_target.as_ref().is_some_and(|subject| {
255 subject.matches_key_update(&updates.key_authorization_target_changes)
256 })
257 {
258 to_remove.push(*tx.hash());
259 key_authorization_target_count += 1;
260 continue;
261 }
262
263 if !updates.spending_limit_changes.is_empty()
266 && let Some(ref subject) = keychain_subject
267 && subject.matches_spending_limit_update(&updates.spending_limit_changes)
268 && tx.transaction.is_sender_paid_fee()
269 {
270 to_remove.push(*tx.hash());
271 spending_limit_count += 1;
272 continue;
273 }
274
275 if !updates.spending_limit_spends.is_empty()
281 && let Some(ref subject) = keychain_subject
282 && subject.matches_spending_limit_update(&updates.spending_limit_spends)
283 && tx.transaction.is_sender_paid_fee()
284 && let Some(ref mut provider) = state_provider
285 && exceeds_spending_limit(
286 provider,
287 subject,
288 tx.transaction.fee_token_cost(),
289 tip_timestamp,
290 spec,
291 )
292 {
293 to_remove.push(*tx.hash());
294 spending_limit_spend_count += 1;
295 continue;
296 }
297 }
298
299 if !updates.key_authorization_witness_burns.is_empty()
301 && let Some(subject) = tx.transaction.key_authorization_witness_subject()
302 && updates
303 .key_authorization_witness_burns
304 .get(&subject.account)
305 .is_some_and(|witnesses| witnesses.contains(&subject.witness))
306 {
307 to_remove.push(*tx.hash());
308 key_authorization_witness_count += 1;
309 continue;
310 }
311
312 if has_active_validator_token_changes && let Some(ref provider) = state_provider {
317 let user_token = tx.transaction.effective_fee_token();
318 let cost = tx.transaction.fee_token_cost();
319
320 match amm_cache.has_enough_liquidity(user_token, cost, provider) {
321 Ok(true) => {}
322 Ok(false) => {
323 to_remove.push(*tx.hash());
324 liquidity_count += 1;
325 continue;
326 }
327 Err(_) => continue,
328 }
329 }
330
331 if !updates.fee_balance_changes.is_empty()
335 && let Some(ref mut provider) = state_provider
336 {
337 let fee_token = tx.transaction.effective_fee_token();
338 if let Some(accounts) = updates.fee_balance_changes.get(&fee_token) {
340 let Ok(fee_payer) = tx.transaction.fee_payer() else {
341 continue;
342 };
343
344 if accounts.contains(&fee_payer) {
345 let balance = match fee_balance_cache.entry((fee_token, fee_payer)) {
346 Entry::Occupied(entry) => *entry.get(),
347 Entry::Vacant(entry) => {
348 let Ok(balance) = provider.get_token_balance(
349 fee_token,
350 fee_payer,
351 spec,
352 StorageActions::disabled(),
353 ) else {
354 continue;
355 };
356 *entry.insert(balance)
357 }
358 };
359
360 if balance < tx.transaction.fee_token_cost() {
361 to_remove.push(*tx.hash());
362 insolvent_fee_payer_count += 1;
363 continue;
364 }
365 }
366 }
367 }
368
369 if !updates.blacklist_additions.is_empty()
372 && let Some(ref mut provider) = state_provider
373 {
374 let fee_token = tx.transaction.effective_fee_token();
375 let fee_payer = tx
376 .transaction
377 .fee_payer()
378 .unwrap_or_else(|_| tx.transaction.sender());
379
380 let mut sender_evicted = false;
382 for &(blacklist_policy_id, blacklisted_account) in &updates.blacklist_additions {
383 if fee_payer != blacklisted_account {
384 continue;
385 }
386
387 let token_policies =
388 get_sender_policy_ids(provider, fee_token, spec, &mut policy_cache);
389
390 if token_policies
391 .as_ref()
392 .is_some_and(|ids| ids.contains(&blacklist_policy_id))
393 {
394 sender_evicted = true;
395 break;
396 }
397 }
398
399 let recipient_evicted = !sender_evicted
403 && !fee_manager_blacklisted.is_empty()
404 && get_recipient_policy_ids(provider, fee_token, spec)
405 .is_some_and(|ids| fee_manager_blacklisted.iter().any(|p| ids.contains(p)));
406
407 if sender_evicted || recipient_evicted {
408 to_remove.push(*tx.hash());
409 blacklisted_count += 1;
410 }
411 }
412
413 if !updates.whitelist_removals.is_empty()
417 && let Some(ref mut provider) = state_provider
418 {
419 let fee_token = tx.transaction.effective_fee_token();
420 let fee_payer = tx
421 .transaction
422 .fee_payer()
423 .unwrap_or_else(|_| tx.transaction.sender());
424
425 let mut sender_evicted = false;
426 for &(whitelist_policy_id, unwhitelisted_account) in &updates.whitelist_removals {
427 if fee_payer != unwhitelisted_account {
428 continue;
429 }
430
431 let token_policies =
432 get_sender_policy_ids(provider, fee_token, spec, &mut policy_cache);
433
434 if token_policies
435 .as_ref()
436 .is_some_and(|ids| ids.contains(&whitelist_policy_id))
437 {
438 sender_evicted = true;
439 break;
440 }
441 }
442
443 let recipient_evicted = !sender_evicted
446 && !fee_manager_unwhitelisted.is_empty()
447 && get_recipient_policy_ids(provider, fee_token, spec).is_some_and(|ids| {
448 fee_manager_unwhitelisted.iter().any(|p| ids.contains(p))
449 });
450
451 if sender_evicted || recipient_evicted {
452 to_remove.push(*tx.hash());
453 unwhitelisted_count += 1;
454 }
455 }
456
457 if !updates.user_token_changes.is_empty()
463 && tx.transaction.inner().fee_token().is_none()
464 && tx
465 .transaction
466 .fee_payer()
467 .is_ok_and(|fee_payer| updates.user_token_changes.contains(&fee_payer))
468 {
469 to_remove.push(*tx.hash());
470 user_token_count += 1;
471 }
472 }
473
474 if to_remove.is_empty() {
475 return Vec::new();
476 }
477
478 tracing::debug!(
479 target: "txpool",
480 total = to_remove.len(),
481 revoked_count,
482 key_authorization_target_count,
483 spending_limit_count,
484 spending_limit_spend_count,
485 key_authorization_witness_count,
486 liquidity_count,
487 user_token_count,
488 blacklisted_count,
489 unwhitelisted_count,
490 insolvent_fee_payer_count,
491 "Evicting invalidated transactions"
492 );
493 self.remove_transactions(to_remove)
494 }
495
496 fn add_validated_transaction(
502 &self,
503 origin: TransactionOrigin,
504 transaction: TransactionValidationOutcome<TempoPooledTransaction>,
505 ) -> PoolResult<AddedTransactionOutcome> {
506 match transaction {
507 TransactionValidationOutcome::Valid {
508 balance,
509 state_nonce,
510 bytecode_hash,
511 transaction,
512 propagate,
513 authorities,
514 } => {
515 if transaction.transaction().is_aa_2d() {
516 let transaction = transaction.into_transaction();
517 let sender_id = self
518 .protocol_pool
519 .inner()
520 .get_sender_id(transaction.sender());
521 let transaction_id = TransactionId::new(sender_id, transaction.nonce());
522 let tx = ValidPoolTransaction {
523 transaction,
524 transaction_id,
525 propagate,
526 timestamp: Instant::now(),
527 origin,
528 authority_ids: authorities
529 .map(|auths| self.protocol_pool.inner().get_sender_ids(auths)),
530 };
531
532 let hardfork = self.protocol_pool.validator().validator().active_hardfork();
534
535 let tx = Arc::new(tx);
536 let added =
537 self.aa_2d_pool
538 .write()
539 .add_transaction(tx, state_nonce, hardfork)?;
540 let hash = *added.hash();
541 if let Some(pending) = added.as_pending() {
542 if pending.discarded.iter().any(|tx| *tx.hash() == hash) {
543 return Err(PoolError::new(hash, PoolErrorKind::DiscardedOnInsert));
544 }
545 self.protocol_pool
546 .inner()
547 .on_new_pending_transaction(pending);
548 }
549
550 let state = added.transaction_state();
551 self.protocol_pool.inner().notify_event_listeners(&added);
553 self.protocol_pool
554 .inner()
555 .on_new_transaction(added.into_new_transaction_event());
556
557 Ok(AddedTransactionOutcome { hash, state })
558 } else {
559 self.protocol_pool
560 .inner()
561 .add_transactions(
562 origin,
563 std::iter::once(TransactionValidationOutcome::Valid {
564 balance,
565 state_nonce,
566 bytecode_hash,
567 transaction,
568 propagate,
569 authorities,
570 }),
571 )
572 .pop()
573 .unwrap()
574 }
575 }
576 invalid => {
577 self.protocol_pool
579 .inner()
580 .add_transactions(origin, Some(invalid))
581 .pop()
582 .unwrap()
583 }
584 }
585 }
586}
587
588impl<Client> Clone for TempoTransactionPool<Client> {
590 fn clone(&self) -> Self {
591 Self {
592 protocol_pool: self.protocol_pool.clone(),
593 aa_2d_pool: Arc::clone(&self.aa_2d_pool),
594 }
595 }
596}
597
598impl<Client> std::fmt::Debug for TempoTransactionPool<Client> {
600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601 f.debug_struct("TempoTransactionPool")
602 .field("protocol_pool", &"Pool<...>")
603 .field("aa_2d_nonce_pool", &"AA2dPool<...>")
604 .field("paused_fee_token_pool", &"PausedFeeTokenPool<...>")
605 .finish_non_exhaustive()
606 }
607}
608
609impl<Client> TransactionPool for TempoTransactionPool<Client>
611where
612 Client: StateProviderFactory
613 + ChainSpecProvider<ChainSpec = TempoChainSpec>
614 + Send
615 + Sync
616 + 'static,
617 TempoPooledTransaction: reth_transaction_pool::EthPoolTransaction,
618{
619 type Transaction = TempoPooledTransaction;
620
621 fn pool_size(&self) -> PoolSize {
622 let mut size = self.protocol_pool.pool_size();
623 let (pending, queued) = self.aa_2d_pool.read().pending_and_queued_txn_count();
624 size.pending += pending;
625 size.queued += queued;
626 size
627 }
628
629 fn block_info(&self) -> BlockInfo {
630 self.protocol_pool.block_info()
631 }
632
633 async fn add_transaction_and_subscribe(
634 &self,
635 origin: TransactionOrigin,
636 transaction: Self::Transaction,
637 ) -> PoolResult<TransactionEvents> {
638 let tx = self
639 .protocol_pool
640 .validator()
641 .validate_transaction(origin, transaction)
642 .await;
643 let res = self.add_validated_transaction(origin, tx)?;
644 self.transaction_event_listener(res.hash)
645 .ok_or_else(|| PoolError::new(res.hash, PoolErrorKind::DiscardedOnInsert))
646 }
647
648 async fn add_transaction(
649 &self,
650 origin: TransactionOrigin,
651 transaction: Self::Transaction,
652 ) -> PoolResult<AddedTransactionOutcome> {
653 let tx = self
654 .protocol_pool
655 .validator()
656 .validate_transaction(origin, transaction)
657 .await;
658 self.add_validated_transaction(origin, tx)
659 }
660
661 async fn add_transactions(
662 &self,
663 origin: TransactionOrigin,
664 transactions: Vec<Self::Transaction>,
665 ) -> Vec<PoolResult<AddedTransactionOutcome>> {
666 if transactions.is_empty() {
667 return Vec::new();
668 }
669
670 if !transactions.iter().any(|tx| tx.is_aa_2d()) {
672 return self
673 .protocol_pool
674 .add_transactions(origin, transactions)
675 .await;
676 }
677
678 self.protocol_pool
679 .validator()
680 .validate_transactions_with_origin(origin, transactions)
681 .await
682 .into_iter()
683 .map(|outcome| self.add_validated_transaction(origin, outcome))
684 .collect()
685 }
686
687 async fn add_transactions_with_origins(
688 &self,
689 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
690 ) -> Vec<PoolResult<AddedTransactionOutcome>> {
691 if transactions.is_empty() {
692 return Vec::new();
693 }
694
695 if !transactions.iter().any(|(_, tx)| tx.is_aa_2d()) {
697 return self
698 .protocol_pool
699 .add_transactions_with_origins(transactions)
700 .await;
701 }
702
703 let origins = transactions
704 .iter()
705 .map(|(origin, _)| *origin)
706 .collect::<Vec<_>>();
707
708 self.protocol_pool
709 .validator()
710 .validate_transactions(transactions)
711 .await
712 .into_iter()
713 .zip(origins)
714 .map(|(outcome, origin)| self.add_validated_transaction(origin, outcome))
715 .collect()
716 }
717
718 fn transaction_event_listener(&self, tx_hash: B256) -> Option<TransactionEvents> {
719 self.protocol_pool.transaction_event_listener(tx_hash)
720 }
721
722 fn all_transactions_event_listener(
723 &self,
724 ) -> reth_transaction_pool::AllTransactionsEvents<Self::Transaction> {
725 self.protocol_pool.all_transactions_event_listener()
726 }
727
728 fn pending_transactions_listener_for(
729 &self,
730 kind: reth_transaction_pool::TransactionListenerKind,
731 ) -> tokio::sync::mpsc::Receiver<B256> {
732 self.protocol_pool.pending_transactions_listener_for(kind)
733 }
734
735 fn blob_transaction_sidecars_listener(&self) -> tokio::sync::mpsc::Receiver<NewBlobSidecar> {
736 self.protocol_pool.blob_transaction_sidecars_listener()
737 }
738
739 fn new_transactions_listener_for(
740 &self,
741 kind: reth_transaction_pool::TransactionListenerKind,
742 ) -> tokio::sync::mpsc::Receiver<reth_transaction_pool::NewTransactionEvent<Self::Transaction>>
743 {
744 self.protocol_pool.new_transactions_listener_for(kind)
745 }
746
747 fn pooled_transaction_hashes(&self) -> Vec<B256> {
748 let mut hashes = self.protocol_pool.pooled_transaction_hashes();
749 hashes.extend(self.aa_2d_pool.read().pooled_transactions_hashes_iter());
750 hashes
751 }
752
753 fn pooled_transaction_hashes_max(&self, max: usize) -> Vec<B256> {
754 let protocol_hashes = self.protocol_pool.pooled_transaction_hashes_max(max);
755 if protocol_hashes.len() >= max {
756 return protocol_hashes;
757 }
758 let remaining = max - protocol_hashes.len();
759 let mut hashes = protocol_hashes;
760 hashes.extend(
761 self.aa_2d_pool
762 .read()
763 .pooled_transactions_hashes_iter()
764 .take(remaining),
765 );
766 hashes
767 }
768
769 fn pooled_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
770 let mut txs = self.protocol_pool.pooled_transactions();
771 txs.extend(self.aa_2d_pool.read().pooled_transactions_iter());
772 txs
773 }
774
775 fn pooled_transactions_max(
776 &self,
777 max: usize,
778 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
779 let mut txs = self.protocol_pool.pooled_transactions_max(max);
780 if txs.len() >= max {
781 return txs;
782 }
783
784 let remaining = max - txs.len();
785 txs.extend(
786 self.aa_2d_pool
787 .read()
788 .pooled_transactions_iter()
789 .take(remaining),
790 );
791 txs
792 }
793
794 fn get_pooled_transaction_elements(
795 &self,
796 tx_hashes: Vec<B256>,
797 limit: GetPooledTransactionLimit,
798 ) -> Vec<<Self::Transaction as PoolTransaction>::Pooled> {
799 let mut out = Vec::new();
800 self.append_pooled_transaction_elements(&tx_hashes, limit, &mut out);
801 out
802 }
803
804 fn append_pooled_transaction_elements(
805 &self,
806 tx_hashes: &[B256],
807 limit: GetPooledTransactionLimit,
808 out: &mut Vec<<Self::Transaction as PoolTransaction>::Pooled>,
809 ) {
810 let mut accumulated_size = 0;
811 self.aa_2d_pool.read().append_pooled_transaction_elements(
812 tx_hashes,
813 limit,
814 &mut accumulated_size,
815 out,
816 );
817
818 if limit.exceeds(accumulated_size) {
820 return;
821 }
822
823 let remaining_limit = match limit {
825 GetPooledTransactionLimit::None => GetPooledTransactionLimit::None,
826 GetPooledTransactionLimit::ResponseSizeSoftLimit(max) => {
827 GetPooledTransactionLimit::ResponseSizeSoftLimit(
828 max.saturating_sub(accumulated_size),
829 )
830 }
831 };
832
833 self.protocol_pool
834 .append_pooled_transaction_elements(tx_hashes, remaining_limit, out);
835 }
836
837 fn get_pooled_transaction_element(
838 &self,
839 tx_hash: B256,
840 ) -> Option<reth_primitives_traits::Recovered<<Self::Transaction as PoolTransaction>::Pooled>>
841 {
842 self.protocol_pool
843 .get_pooled_transaction_element(tx_hash)
844 .or_else(|| {
845 self.aa_2d_pool
846 .read()
847 .get(&tx_hash)
848 .and_then(|tx| tx.transaction.clone_into_pooled().ok())
849 })
850 }
851
852 fn best_transactions(
853 &self,
854 ) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>> {
855 let protocol_pool = self.protocol_pool.inner();
856 let base_fee = protocol_pool.block_info().pending_basefee;
857 let left = protocol_pool.best_transactions();
858 let right = self.aa_2d_pool.read().best_transactions();
859 Box::new(MergeBestTransactions::new(Box::new(left), right, base_fee))
860 }
861
862 fn best_transactions_with_attributes(
863 &self,
864 attributes: BestTransactionsAttributes,
865 ) -> Box<dyn BestTransactions<Item = Arc<ValidPoolTransaction<Self::Transaction>>>> {
866 let left = self
867 .protocol_pool
868 .best_transactions_with_attributes(attributes);
869 let right = self
870 .aa_2d_pool
871 .read()
872 .best_transactions_with_base_fee(attributes.basefee);
873 Box::new(MergeBestTransactions::new(left, right, attributes.basefee))
874 }
875
876 fn pending_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
877 let mut pending = self.protocol_pool.pending_transactions();
878 pending.extend(self.aa_2d_pool.read().pending_transactions());
879 pending
880 }
881
882 fn pending_transactions_max(
883 &self,
884 max: usize,
885 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
886 let protocol_txs = self.protocol_pool.pending_transactions_max(max);
887 if protocol_txs.len() >= max {
888 return protocol_txs;
889 }
890 let remaining = max - protocol_txs.len();
891 let mut txs = protocol_txs;
892 txs.extend(
893 self.aa_2d_pool
894 .read()
895 .pending_transactions()
896 .take(remaining),
897 );
898 txs
899 }
900
901 fn queued_transactions(&self) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
902 let mut queued = self.protocol_pool.queued_transactions();
903 queued.extend(self.aa_2d_pool.read().queued_transactions());
904 queued
905 }
906
907 fn pending_and_queued_txn_count(&self) -> (usize, usize) {
908 let (protocol_pending, protocol_queued) = self.protocol_pool.pending_and_queued_txn_count();
909 let (aa_pending, aa_queued) = self.aa_2d_pool.read().pending_and_queued_txn_count();
910 (protocol_pending + aa_pending, protocol_queued + aa_queued)
911 }
912
913 fn all_transactions(&self) -> AllPoolTransactions<Self::Transaction> {
914 let mut transactions = self.protocol_pool.all_transactions();
915 self.aa_2d_pool
916 .read()
917 .append_all_transactions(&mut transactions);
918 transactions
919 }
920
921 fn all_transaction_hashes(&self) -> Vec<B256> {
922 let mut hashes = self.protocol_pool.all_transaction_hashes();
923 hashes.extend(self.aa_2d_pool.read().all_transaction_hashes_iter());
924 hashes
925 }
926
927 fn remove_transactions(
928 &self,
929 hashes: Vec<B256>,
930 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
931 let mut txs = self.aa_2d_pool.write().remove_transactions(hashes.iter());
932 txs.extend(self.protocol_pool.remove_transactions(hashes));
933 txs
934 }
935
936 fn remove_transactions_and_descendants(
937 &self,
938 hashes: Vec<B256>,
939 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
940 let mut txs = self
941 .aa_2d_pool
942 .write()
943 .remove_transactions_and_descendants(hashes.iter());
944 txs.extend(
945 self.protocol_pool
946 .remove_transactions_and_descendants(hashes),
947 );
948 txs
949 }
950
951 fn remove_transactions_by_sender(
952 &self,
953 sender: Address,
954 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
955 let mut txs = self
956 .aa_2d_pool
957 .write()
958 .remove_transactions_by_sender(sender);
959 txs.extend(self.protocol_pool.remove_transactions_by_sender(sender));
960 txs
961 }
962
963 fn prune_transactions(
964 &self,
965 hashes: Vec<TxHash>,
966 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
967 let mut txs = self.aa_2d_pool.write().remove_transactions(hashes.iter());
968 txs.extend(self.protocol_pool.prune_transactions(hashes));
969 txs
970 }
971
972 fn retain_unknown<A: HandleMempoolData>(&self, announcement: &mut A) {
973 self.protocol_pool.retain_unknown(announcement);
974 if announcement.is_empty() {
975 return;
976 }
977 let aa_pool = self.aa_2d_pool.read();
978 announcement.retain_by_hash(|tx| !aa_pool.contains(tx))
979 }
980
981 fn retain_contains<A>(&self, announcement: &mut A)
982 where
983 A: HandleMempoolData,
984 {
985 if announcement.is_empty() {
986 return;
987 }
988 announcement.retain_by_hash(|tx| self.contains(tx))
989 }
990
991 fn contains(&self, tx_hash: &B256) -> bool {
992 self.protocol_pool.contains(tx_hash) || self.aa_2d_pool.read().contains(tx_hash)
993 }
994
995 fn get(&self, tx_hash: &B256) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
996 self.protocol_pool
997 .get(tx_hash)
998 .or_else(|| self.aa_2d_pool.read().get(tx_hash))
999 }
1000
1001 fn get_all(&self, txs: Vec<B256>) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1002 let mut result = self.aa_2d_pool.read().get_all(txs.iter());
1003 result.extend(self.protocol_pool.get_all(txs));
1004 result
1005 }
1006
1007 fn on_propagated(&self, txs: PropagatedTransactions) {
1008 self.protocol_pool.on_propagated(txs);
1009 }
1010
1011 fn get_transactions_by_sender(
1012 &self,
1013 sender: Address,
1014 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1015 let mut txs = self.protocol_pool.get_transactions_by_sender(sender);
1016 txs.extend(
1017 self.aa_2d_pool
1018 .read()
1019 .get_transactions_by_sender_iter(sender),
1020 );
1021 txs
1022 }
1023
1024 fn get_pending_transactions_with_predicate(
1025 &self,
1026 mut predicate: impl FnMut(&ValidPoolTransaction<Self::Transaction>) -> bool,
1027 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1028 let mut txs = self
1029 .protocol_pool
1030 .get_pending_transactions_with_predicate(&mut predicate);
1031 txs.extend(
1032 self.aa_2d_pool
1033 .read()
1034 .pending_transactions()
1035 .filter(|tx| predicate(tx)),
1036 );
1037 txs
1038 }
1039
1040 fn get_pending_transactions_by_sender(
1041 &self,
1042 sender: Address,
1043 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1044 let mut txs = self
1045 .protocol_pool
1046 .get_pending_transactions_by_sender(sender);
1047 txs.extend(
1048 self.aa_2d_pool
1049 .read()
1050 .pending_transactions()
1051 .filter(|tx| tx.sender() == sender),
1052 );
1053
1054 txs
1055 }
1056
1057 fn get_queued_transactions_by_sender(
1058 &self,
1059 sender: Address,
1060 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1061 self.protocol_pool.get_queued_transactions_by_sender(sender)
1062 }
1063
1064 fn get_highest_transaction_by_sender(
1065 &self,
1066 sender: Address,
1067 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
1068 self.protocol_pool.get_highest_transaction_by_sender(sender)
1071 }
1072
1073 fn get_highest_consecutive_transaction_by_sender(
1074 &self,
1075 sender: Address,
1076 on_chain_nonce: u64,
1077 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
1078 self.protocol_pool
1080 .get_highest_consecutive_transaction_by_sender(sender, on_chain_nonce)
1081 }
1082
1083 fn get_transaction_by_sender_and_nonce(
1084 &self,
1085 sender: Address,
1086 nonce: u64,
1087 ) -> Option<Arc<ValidPoolTransaction<Self::Transaction>>> {
1088 self.protocol_pool
1090 .get_transaction_by_sender_and_nonce(sender, nonce)
1091 }
1092
1093 fn get_transactions_by_origin(
1094 &self,
1095 origin: TransactionOrigin,
1096 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1097 let mut txs = self.protocol_pool.get_transactions_by_origin(origin);
1098 txs.extend(
1099 self.aa_2d_pool
1100 .read()
1101 .get_transactions_by_origin_iter(origin),
1102 );
1103 txs
1104 }
1105
1106 fn get_pending_transactions_by_origin(
1107 &self,
1108 origin: TransactionOrigin,
1109 ) -> Vec<Arc<ValidPoolTransaction<Self::Transaction>>> {
1110 let mut txs = self
1111 .protocol_pool
1112 .get_pending_transactions_by_origin(origin);
1113 txs.extend(
1114 self.aa_2d_pool
1115 .read()
1116 .get_pending_transactions_by_origin_iter(origin),
1117 );
1118 txs
1119 }
1120
1121 fn unique_senders(&self) -> AddressSet {
1122 let mut senders = self.protocol_pool.unique_senders();
1123 senders.extend(self.aa_2d_pool.read().senders_iter().copied());
1124 senders
1125 }
1126
1127 fn get_blob(
1128 &self,
1129 tx_hash: B256,
1130 ) -> Result<
1131 Option<Arc<alloy_eips::eip7594::BlobTransactionSidecarVariant>>,
1132 reth_transaction_pool::blobstore::BlobStoreError,
1133 > {
1134 self.protocol_pool.get_blob(tx_hash)
1135 }
1136
1137 fn get_all_blobs(
1138 &self,
1139 tx_hashes: Vec<B256>,
1140 ) -> Result<
1141 Vec<(
1142 B256,
1143 Arc<alloy_eips::eip7594::BlobTransactionSidecarVariant>,
1144 )>,
1145 reth_transaction_pool::blobstore::BlobStoreError,
1146 > {
1147 self.protocol_pool.get_all_blobs(tx_hashes)
1148 }
1149
1150 fn get_all_blobs_exact(
1151 &self,
1152 tx_hashes: Vec<B256>,
1153 ) -> Result<
1154 Vec<Arc<alloy_eips::eip7594::BlobTransactionSidecarVariant>>,
1155 reth_transaction_pool::blobstore::BlobStoreError,
1156 > {
1157 self.protocol_pool.get_all_blobs_exact(tx_hashes)
1158 }
1159
1160 fn get_blobs_for_versioned_hashes_v1(
1161 &self,
1162 versioned_hashes: &[B256],
1163 ) -> Result<
1164 Vec<Option<alloy_eips::eip4844::BlobAndProofV1>>,
1165 reth_transaction_pool::blobstore::BlobStoreError,
1166 > {
1167 self.protocol_pool
1168 .get_blobs_for_versioned_hashes_v1(versioned_hashes)
1169 }
1170
1171 fn get_blobs_for_versioned_hashes_v2(
1172 &self,
1173 versioned_hashes: &[B256],
1174 ) -> Result<
1175 Option<Vec<alloy_eips::eip4844::BlobAndProofV2>>,
1176 reth_transaction_pool::blobstore::BlobStoreError,
1177 > {
1178 self.protocol_pool
1179 .get_blobs_for_versioned_hashes_v2(versioned_hashes)
1180 }
1181
1182 fn get_blobs_for_versioned_hashes_v3(
1183 &self,
1184 versioned_hashes: &[B256],
1185 ) -> Result<
1186 Vec<Option<alloy_eips::eip4844::BlobAndProofV2>>,
1187 reth_transaction_pool::blobstore::BlobStoreError,
1188 > {
1189 self.protocol_pool
1190 .get_blobs_for_versioned_hashes_v3(versioned_hashes)
1191 }
1192
1193 fn get_blobs_for_versioned_hashes_v4(
1194 &self,
1195 versioned_hashes: &[B256],
1196 indices_bitarray: alloy_primitives::B128,
1197 ) -> Result<
1198 Vec<Option<alloy_eips::eip4844::BlobCellsAndProofsV1>>,
1199 reth_transaction_pool::blobstore::BlobStoreError,
1200 > {
1201 self.protocol_pool
1202 .get_blobs_for_versioned_hashes_v4(versioned_hashes, indices_bitarray)
1203 }
1204
1205 fn blob_store(&self) -> Box<dyn reth_transaction_pool::BlobStore> {
1206 TransactionPool::blob_store(&self.protocol_pool)
1207 }
1208}
1209
1210impl<Client> TransactionPoolExt for TempoTransactionPool<Client>
1211where
1212 Client: StateProviderFactory + ChainSpecProvider<ChainSpec = TempoChainSpec> + 'static,
1213{
1214 type Block = Block;
1215
1216 fn set_block_info(&self, info: BlockInfo) {
1217 self.protocol_pool.set_block_info(info);
1218 self.aa_2d_pool.write().set_base_fee(info.pending_basefee);
1219 }
1220
1221 fn on_canonical_state_change(&self, update: CanonicalStateUpdate<'_, Self::Block>) {
1222 self.protocol_pool.on_canonical_state_change(update)
1223 }
1224
1225 fn update_accounts(&self, accounts: Vec<ChangedAccount>) {
1226 self.protocol_pool.update_accounts(accounts)
1227 }
1228
1229 fn delete_blob(&self, tx: B256) {
1230 self.protocol_pool.delete_blob(tx)
1231 }
1232
1233 fn delete_blobs(&self, txs: Vec<B256>) {
1234 self.protocol_pool.delete_blobs(txs)
1235 }
1236
1237 fn cleanup_blobs(&self) {
1238 self.protocol_pool.cleanup_blobs()
1239 }
1240}
1241
1242pub(crate) fn exceeds_spending_limit(
1248 provider: &mut impl StateProvider,
1249 subject: &crate::transaction::KeychainSubject,
1250 fee_token_cost: alloy_primitives::U256,
1251 current_timestamp: u64,
1252 spec: TempoHardfork,
1253) -> bool {
1254 provider
1255 .with_read_only_storage_ctx(
1256 spec,
1257 StorageActions::disabled(),
1258 || -> TempoPrecompileResult<bool> {
1259 let keychain = AccountKeychain::new();
1260 if !keychain.keys[subject.account][subject.key_id]
1261 .read()?
1262 .enforce_limits
1263 {
1264 return Ok(false);
1265 }
1266
1267 let remaining = keychain.effective_remaining_limit(
1268 subject.account,
1269 subject.key_id,
1270 subject.fee_token,
1271 current_timestamp,
1272 )?;
1273 Ok(fee_token_cost > remaining)
1274 },
1275 )
1276 .unwrap_or_default()
1277}
1278
1279fn get_sender_policy_ids(
1287 provider: &mut impl StateProvider,
1288 fee_token: Address,
1289 spec: TempoHardfork,
1290 cache: &mut AddressMap<Vec<u64>>,
1291) -> Option<Vec<u64>> {
1292 if let Some(cached) = cache.get(&fee_token) {
1293 return Some(cached.clone());
1294 }
1295
1296 provider.with_read_only_storage_ctx(spec, StorageActions::disabled(), || {
1297 let policy_id = TIP20Token::from_address(fee_token)
1298 .and_then(|t| t.transfer_policy_id())
1299 .ok()
1300 .filter(|&id| id != REJECT_ALL_POLICY_ID)?;
1301
1302 let mut ids = vec![policy_id];
1303
1304 let registry = TIP403Registry::new();
1306 if let Ok(data) = registry.policy_records[policy_id].base.read()
1307 && data.is_compound()
1308 && let Ok(compound) = registry.policy_records[policy_id].compound.read()
1309 && compound.sender_policy_id != REJECT_ALL_POLICY_ID
1310 {
1311 ids.push(compound.sender_policy_id);
1312 }
1313
1314 cache.insert(fee_token, ids.clone());
1317 Some(ids)
1318 })
1319}
1320
1321fn get_recipient_policy_ids(
1331 provider: &mut impl StateProvider,
1332 fee_token: Address,
1333 spec: TempoHardfork,
1334) -> Option<Vec<u64>> {
1335 provider.with_read_only_storage_ctx(spec, StorageActions::disabled(), || {
1336 let policy_id = TIP20Token::from_address(fee_token)
1337 .and_then(|t| t.transfer_policy_id())
1338 .ok()
1339 .filter(|&id| id != REJECT_ALL_POLICY_ID)?;
1340
1341 let mut ids = vec![policy_id];
1342
1343 let registry = TIP403Registry::new();
1344 if let Ok(data) = registry.policy_records[policy_id].base.read()
1345 && data.is_compound()
1346 && let Ok(compound) = registry.policy_records[policy_id].compound.read()
1347 && compound.recipient_policy_id != REJECT_ALL_POLICY_ID
1348 {
1349 ids.push(compound.recipient_policy_id);
1350 }
1351
1352 Some(ids)
1353 })
1354}
1355
1356#[cfg(test)]
1357mod tests {
1358 use super::*;
1359 fn tx_hashes(txs: &[Arc<ValidPoolTransaction<TempoPooledTransaction>>]) -> Vec<TxHash> {
1361 txs.iter().map(|tx| *tx.hash()).collect()
1362 }
1363
1364 use crate::{test_utils::MockProviderStorageExt, transaction::KeychainSubject};
1365 use alloy_consensus::Header;
1366 use alloy_primitives::{Signature, U256, address, uint};
1367 use alloy_signer::SignerSync;
1368 use alloy_signer_local::PrivateKeySigner;
1369 use reth_primitives_traits::Recovered;
1370 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1371 use reth_storage_api::StateProviderFactory;
1372 use reth_transaction_pool::{
1373 PoolConfig, TransactionOrigin, TransactionPool, TransactionValidationTaskExecutor,
1374 blobstore::InMemoryBlobStore,
1375 validate::{EthTransactionValidatorBuilder, ValidTransaction},
1376 };
1377 use tempo_chainspec::{
1378 TempoChainSpec,
1379 hardfork::TempoHardfork,
1380 spec::{MODERATO, TEMPO_T1_TX_GAS_LIMIT_CAP},
1381 };
1382 use tempo_contracts::precompiles::ITIP403Registry;
1383 use tempo_evm::TempoEvmConfig;
1384 use tempo_precompiles::{
1385 PATH_USD_ADDRESS,
1386 account_keychain::{
1387 AccountKeychain, AuthorizedKey, SpendingLimitState, StoredSignatureType,
1388 },
1389 tip20::slots as tip20_slots,
1390 tip403_registry::{CompoundPolicyData, PolicyData, TIP403Registry},
1391 };
1392 use tempo_primitives::{
1393 Block, TempoHeader, TempoPrimitives, TempoTxEnvelope,
1394 transaction::{KeyAuthorization, PrimitiveSignature, SignatureType},
1395 };
1396
1397 fn provider_with_spending_limit(
1398 account: Address,
1399 key_id: Address,
1400 fee_token: Address,
1401 remaining_limit: alloy_primitives::U256,
1402 ) -> Box<dyn reth_storage_api::StateProvider> {
1403 provider_with_spending_limit_state(
1404 account,
1405 key_id,
1406 fee_token,
1407 SpendingLimitState {
1408 remaining: remaining_limit,
1409 ..Default::default()
1410 },
1411 TempoHardfork::default(),
1412 )
1413 }
1414
1415 fn provider_with_spending_limit_state(
1416 account: Address,
1417 key_id: Address,
1418 fee_token: Address,
1419 limit_state: SpendingLimitState,
1420 setup_spec: TempoHardfork,
1421 ) -> Box<dyn reth_storage_api::StateProvider> {
1422 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
1423 tempo_chainspec::spec::MODERATO.clone(),
1424 ));
1425
1426 provider
1428 .setup_storage(setup_spec, || {
1429 let mut keychain = AccountKeychain::new();
1430 keychain.keys[account][key_id].write(AuthorizedKey {
1431 signature_type: StoredSignatureType::Secp256k1,
1432 expiry: u64::MAX,
1433 enforce_limits: true,
1434 is_revoked: false,
1435 is_admin: false,
1436 })?;
1437 let limit_key = AccountKeychain::spending_limit_key(account, key_id);
1438 keychain.spending_limits[limit_key][fee_token].write(limit_state)?;
1439 Ok::<(), tempo_precompiles::error::TempoPrecompileError>(())
1440 })
1441 .unwrap();
1442
1443 provider.latest().unwrap()
1444 }
1445
1446 fn set_fee_token_balance(
1447 provider: &MockEthProvider<TempoPrimitives, TempoChainSpec>,
1448 fee_token: Address,
1449 account: Address,
1450 balance: U256,
1451 ) {
1452 let usd_currency_value =
1453 uint!(0x5553440000000000000000000000000000000000000000000000000000000006_U256);
1454 let transfer_policy_id_packed =
1455 uint!(0x0000000000000000000000010000000000000000000000000000000000000000_U256);
1456 let balance_slot = TIP20Token::from_address(fee_token)
1457 .expect("fee token must be a valid TIP20 token")
1458 .balances[account]
1459 .slot();
1460
1461 provider.add_account(
1462 fee_token,
1463 ExtendedAccount::new(0, U256::ZERO).extend_storage([
1464 (tip20_slots::CURRENCY.into(), usd_currency_value),
1465 (
1466 tip20_slots::TRANSFER_POLICY_ID.into(),
1467 transfer_policy_id_packed,
1468 ),
1469 (balance_slot.into(), balance),
1470 ]),
1471 );
1472 }
1473
1474 fn set_transfer_policy(
1475 provider: &MockEthProvider<TempoPrimitives, TempoChainSpec>,
1476 fee_token: Address,
1477 policy_id: u64,
1478 ) {
1479 let transfer_policy_id_packed =
1480 U256::from(policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
1481
1482 provider.add_account(
1483 fee_token,
1484 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
1485 tip20_slots::TRANSFER_POLICY_ID.into(),
1486 transfer_policy_id_packed,
1487 )]),
1488 );
1489 }
1490
1491 fn set_keychain_spending_limit(
1492 provider: &MockEthProvider<TempoPrimitives, TempoChainSpec>,
1493 account: Address,
1494 key_id: Address,
1495 fee_token: Address,
1496 remaining: U256,
1497 ) {
1498 provider
1499 .setup_storage(TempoHardfork::default(), || {
1500 let mut keychain = AccountKeychain::new();
1501 keychain.keys[account][key_id].write(AuthorizedKey {
1502 signature_type: StoredSignatureType::Secp256k1,
1503 expiry: u64::MAX,
1504 enforce_limits: true,
1505 is_revoked: false,
1506 is_admin: false,
1507 })?;
1508 let limit_key = AccountKeychain::spending_limit_key(account, key_id);
1509 keychain.spending_limits[limit_key][fee_token].write(SpendingLimitState {
1510 remaining,
1511 ..Default::default()
1512 })?;
1513 Ok::<(), tempo_precompiles::error::TempoPrecompileError>(())
1514 })
1515 .unwrap();
1516 }
1517
1518 fn create_test_pool(
1519 provider: MockEthProvider<TempoPrimitives, TempoChainSpec>,
1520 ) -> TempoTransactionPool<MockEthProvider<TempoPrimitives, TempoChainSpec>> {
1521 let inner =
1522 EthTransactionValidatorBuilder::new(provider.clone(), TempoEvmConfig::mainnet())
1523 .disable_balance_check()
1524 .build(InMemoryBlobStore::default());
1525 let amm_cache =
1526 AmmLiquidityCache::new(provider).expect("failed to setup AmmLiquidityCache");
1527 let validator = TempoTransactionValidator::new(
1528 inner,
1529 crate::validator::DEFAULT_AA_VALID_AFTER_MAX_SECS,
1530 crate::validator::DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
1531 amm_cache,
1532 );
1533
1534 let (executor, _task) = TransactionValidationTaskExecutor::new(validator);
1535 let protocol_pool = Pool::new(
1536 executor,
1537 TempoTipOrdering::default(),
1538 InMemoryBlobStore::default(),
1539 PoolConfig::default(),
1540 );
1541 TempoTransactionPool::new(protocol_pool, AA2dPool::new(Default::default()))
1542 }
1543
1544 fn add_validated(
1545 pool: &TempoTransactionPool<MockEthProvider<TempoPrimitives, TempoChainSpec>>,
1546 pooled: TempoPooledTransaction,
1547 ) {
1548 let validated = TransactionValidationOutcome::Valid {
1549 balance: *pooled.cost(),
1550 state_nonce: pooled.nonce(),
1551 bytecode_hash: None,
1552 transaction: ValidTransaction::new(pooled, None),
1553 propagate: true,
1554 authorities: None,
1555 };
1556 pool.add_validated_transaction(TransactionOrigin::External, validated)
1557 .expect("transaction should be admitted");
1558 }
1559
1560 fn create_provider_with_tip() -> MockEthProvider<TempoPrimitives, TempoChainSpec> {
1561 let provider = MockEthProvider::<TempoPrimitives>::new()
1562 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
1563 provider.add_block(
1564 B256::random(),
1565 Block {
1566 header: TempoHeader {
1567 inner: Header {
1568 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
1569 ..Default::default()
1570 },
1571 ..Default::default()
1572 },
1573 ..Default::default()
1574 },
1575 );
1576 provider
1577 }
1578
1579 fn sponsored_keychain_transaction(
1580 sender: Address,
1581 fee_token: Address,
1582 ) -> (TempoPooledTransaction, Address) {
1583 let access_key_signer = PrivateKeySigner::random();
1584 let key_id = access_key_signer.address();
1585 let envelope = crate::test_utils::TxBuilder::aa(sender)
1586 .fee_token(fee_token)
1587 .build_keychain(sender, &access_key_signer)
1588 .inner()
1589 .clone()
1590 .into_inner();
1591 let TempoTxEnvelope::AA(mut signed) = envelope else {
1592 panic!("expected AA transaction");
1593 };
1594
1595 let sponsor = PrivateKeySigner::random();
1596 signed.tx_mut().fee_payer_signature = Some(Signature::new(U256::ZERO, U256::ZERO, false));
1597 let fee_payer_hash = signed.tx().fee_payer_signature_hash(sender);
1598 signed.tx_mut().fee_payer_signature = Some(
1599 sponsor
1600 .sign_hash_sync(&fee_payer_hash)
1601 .expect("fee payer signing should succeed"),
1602 );
1603
1604 (
1605 TempoPooledTransaction::new(Recovered::new_unchecked(
1606 TempoTxEnvelope::AA(signed),
1607 sender,
1608 )),
1609 key_id,
1610 )
1611 }
1612
1613 fn sponsored_implicit_fee_transaction(sender: Address) -> (TempoPooledTransaction, Address) {
1614 let fee_payer_signer = loop {
1615 let signer = PrivateKeySigner::random();
1616 if signer.address() != sender {
1617 break signer;
1618 }
1619 };
1620 let fee_payer = fee_payer_signer.address();
1621 let envelope = crate::test_utils::TxBuilder::aa(sender)
1622 .build()
1623 .inner()
1624 .clone()
1625 .into_inner();
1626 let TempoTxEnvelope::AA(mut signed) = envelope else {
1627 panic!("expected AA transaction");
1628 };
1629 let fee_payer_hash = signed.tx().fee_payer_signature_hash(sender);
1630 signed.tx_mut().fee_payer_signature = Some(
1631 fee_payer_signer
1632 .sign_hash_sync(&fee_payer_hash)
1633 .expect("fee payer signing should succeed"),
1634 );
1635
1636 (
1637 TempoPooledTransaction::new(Recovered::new_unchecked(
1638 TempoTxEnvelope::AA(signed),
1639 sender,
1640 )),
1641 fee_payer,
1642 )
1643 }
1644
1645 #[tokio::test]
1646 async fn evicts_sponsored_implicit_fee_transaction_when_fee_payer_user_token_changes() {
1647 let sender = Address::random();
1648 let (pooled, fee_payer) = sponsored_implicit_fee_transaction(sender);
1649 assert_eq!(pooled.inner().fee_token(), None);
1650 assert_ne!(fee_payer, sender);
1651
1652 let provider = create_provider_with_tip();
1653 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1654 let pool = create_test_pool(provider);
1655 add_validated(&pool, pooled.clone());
1656
1657 let mut updates = crate::maintain::TempoPoolUpdates::new();
1658 updates.user_token_changes.insert(fee_payer);
1659
1660 let evicted = pool.evict_invalidated_transactions(&updates);
1661 assert_eq!(tx_hashes(&evicted), vec![*pooled.hash()]);
1662 assert!(pool.get(pooled.hash()).is_none());
1663 }
1664
1665 #[tokio::test]
1666 async fn keeps_sponsored_implicit_fee_transaction_when_sender_user_token_changes() {
1667 let sender = Address::random();
1668 let (pooled, fee_payer) = sponsored_implicit_fee_transaction(sender);
1669 assert_eq!(pooled.inner().fee_token(), None);
1670 assert_ne!(fee_payer, sender);
1671
1672 let provider = create_provider_with_tip();
1673 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1674 let pool = create_test_pool(provider);
1675 add_validated(&pool, pooled.clone());
1676
1677 let mut updates = crate::maintain::TempoPoolUpdates::new();
1678 updates.user_token_changes.insert(sender);
1679
1680 assert!(pool.evict_invalidated_transactions(&updates).is_empty());
1681 assert!(pool.get(pooled.hash()).is_some());
1682 }
1683
1684 #[tokio::test]
1685 async fn evicts_sender_paid_implicit_fee_transaction_when_sender_user_token_changes() {
1686 let sender = Address::random();
1687 let pooled = crate::test_utils::TxBuilder::aa(sender).build();
1688 assert_eq!(pooled.inner().fee_token(), None);
1689
1690 let provider = create_provider_with_tip();
1691 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1692 let pool = create_test_pool(provider);
1693 add_validated(&pool, pooled.clone());
1694
1695 let mut updates = crate::maintain::TempoPoolUpdates::new();
1696 updates.user_token_changes.insert(sender);
1697
1698 let evicted = pool.evict_invalidated_transactions(&updates);
1699 assert_eq!(tx_hashes(&evicted), vec![*pooled.hash()]);
1700 assert!(pool.get(pooled.hash()).is_none());
1701 }
1702
1703 #[tokio::test]
1704 async fn keeps_sponsored_keychain_transaction_on_spending_limit_invalidations() {
1705 let sender = Address::random();
1706 let fee_token = PATH_USD_ADDRESS;
1707 let (pooled, key_id) = sponsored_keychain_transaction(sender, fee_token);
1708
1709 let provider = create_provider_with_tip();
1710 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1711 set_keychain_spending_limit(
1712 &provider,
1713 sender,
1714 key_id,
1715 fee_token,
1716 pooled.fee_token_cost() - U256::from(1_u64),
1717 );
1718 let pool = create_test_pool(provider);
1719 add_validated(&pool, pooled.clone());
1720
1721 let mut limit_change = crate::maintain::TempoPoolUpdates::new();
1722 limit_change
1723 .spending_limit_changes
1724 .insert(sender, key_id, Some(fee_token));
1725
1726 assert!(
1727 pool.evict_invalidated_transactions(&limit_change)
1728 .is_empty()
1729 );
1730 assert!(pool.get(pooled.hash()).is_some());
1731
1732 let mut limit_spend = crate::maintain::TempoPoolUpdates::new();
1733 limit_spend
1734 .spending_limit_spends
1735 .insert(sender, key_id, Some(fee_token));
1736
1737 assert!(pool.evict_invalidated_transactions(&limit_spend).is_empty());
1738 assert!(pool.get(pooled.hash()).is_some());
1739 }
1740
1741 #[tokio::test]
1742 async fn evicts_sponsored_transactions_when_fee_payer_becomes_insolvent() {
1743 let fee_payer_signer = PrivateKeySigner::random();
1744 let fee_payer = fee_payer_signer.address();
1745 let sender = Address::random();
1746
1747 let envelope = crate::test_utils::TxBuilder::aa(sender)
1748 .fee_token(PATH_USD_ADDRESS)
1749 .build()
1750 .inner()
1751 .clone()
1752 .into_inner();
1753 let TempoTxEnvelope::AA(mut signed) = envelope else {
1754 panic!("expected AA transaction");
1755 };
1756 let fee_payer_hash = signed.tx().fee_payer_signature_hash(sender);
1757 signed.tx_mut().fee_payer_signature = Some(
1758 fee_payer_signer
1759 .sign_hash_sync(&fee_payer_hash)
1760 .expect("fee payer signing should succeed"),
1761 );
1762 let pooled = TempoPooledTransaction::new(Recovered::new_unchecked(
1763 TempoTxEnvelope::AA(signed),
1764 sender,
1765 ));
1766
1767 let provider = MockEthProvider::<TempoPrimitives>::new()
1768 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
1769 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1770 provider.add_block(
1771 B256::random(),
1772 Block {
1773 header: TempoHeader {
1774 inner: Header {
1775 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
1776 ..Default::default()
1777 },
1778 ..Default::default()
1779 },
1780 ..Default::default()
1781 },
1782 );
1783
1784 let initial_balance = pooled.fee_token_cost() + U256::from(1_u64);
1785 set_fee_token_balance(&provider, PATH_USD_ADDRESS, fee_payer, initial_balance);
1786
1787 let inner =
1788 EthTransactionValidatorBuilder::new(provider.clone(), TempoEvmConfig::mainnet())
1789 .disable_balance_check()
1790 .build(InMemoryBlobStore::default());
1791 let amm_cache =
1792 AmmLiquidityCache::new(provider.clone()).expect("failed to setup AmmLiquidityCache");
1793 let validator = TempoTransactionValidator::new(
1794 inner,
1795 crate::validator::DEFAULT_AA_VALID_AFTER_MAX_SECS,
1796 crate::validator::DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
1797 amm_cache,
1798 );
1799
1800 let (executor, _task) = TransactionValidationTaskExecutor::new(validator);
1801 let protocol_pool = Pool::new(
1802 executor,
1803 TempoTipOrdering::default(),
1804 InMemoryBlobStore::default(),
1805 PoolConfig::default(),
1806 );
1807 let pool = TempoTransactionPool::new(protocol_pool, AA2dPool::new(Default::default()));
1808
1809 pooled.set_resolved_fee_token(PATH_USD_ADDRESS);
1810 let validated = TransactionValidationOutcome::Valid {
1811 balance: *pooled.cost(),
1812 state_nonce: pooled.nonce(),
1813 bytecode_hash: None,
1814 transaction: ValidTransaction::new(pooled.clone(), None),
1815 propagate: true,
1816 authorities: None,
1817 };
1818 let add_result = pool.add_validated_transaction(TransactionOrigin::External, validated);
1819 assert!(
1820 add_result.is_ok(),
1821 "transaction should be admitted before sponsor drains balance: {add_result:?}"
1822 );
1823
1824 set_fee_token_balance(
1825 &provider,
1826 PATH_USD_ADDRESS,
1827 fee_payer,
1828 pooled.fee_token_cost() - U256::from(1_u64),
1829 );
1830
1831 let mut updates = crate::maintain::TempoPoolUpdates::new();
1832 updates
1833 .fee_balance_changes
1834 .entry(PATH_USD_ADDRESS)
1835 .or_default()
1836 .insert(fee_payer);
1837
1838 let evicted = pool.evict_invalidated_transactions(&updates);
1839 assert_eq!(tx_hashes(&evicted), vec![*pooled.hash()]);
1840 assert!(pool.get(pooled.hash()).is_none());
1841 }
1842
1843 #[tokio::test]
1844 async fn blacklist_eviction_uses_resolved_fee_token() {
1845 let sender = Address::random();
1846 let resolved_fee_token = address!("20C0000000000000000000000000000000000002");
1847 let policy_id = 7;
1848 let pooled = crate::test_utils::TxBuilder::aa(sender).build();
1849
1850 assert_eq!(pooled.inner().fee_token(), None);
1851 pooled.set_resolved_fee_token(resolved_fee_token);
1852
1853 let provider = MockEthProvider::<TempoPrimitives>::new()
1854 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
1855 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1856 provider.add_block(
1857 B256::random(),
1858 Block {
1859 header: TempoHeader {
1860 inner: Header {
1861 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
1862 ..Default::default()
1863 },
1864 ..Default::default()
1865 },
1866 ..Default::default()
1867 },
1868 );
1869 set_transfer_policy(&provider, resolved_fee_token, policy_id);
1870
1871 let pool = create_test_pool(provider);
1872 add_validated(&pool, pooled.clone());
1873
1874 let mut updates = crate::maintain::TempoPoolUpdates::new();
1875 updates.blacklist_additions.push((policy_id, sender));
1876
1877 let evicted = pool.evict_invalidated_transactions(&updates);
1878 assert_eq!(tx_hashes(&evicted), vec![*pooled.hash()]);
1879 assert!(pool.get(pooled.hash()).is_none());
1880 }
1881
1882 #[tokio::test]
1883 async fn whitelist_eviction_uses_resolved_fee_token() {
1884 let sender = Address::random();
1885 let resolved_fee_token = address!("20C0000000000000000000000000000000000002");
1886 let policy_id = 9;
1887 let pooled = crate::test_utils::TxBuilder::aa(sender).build();
1888
1889 assert_eq!(pooled.inner().fee_token(), None);
1890 pooled.set_resolved_fee_token(resolved_fee_token);
1891
1892 let provider = MockEthProvider::<TempoPrimitives>::new()
1893 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
1894 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1895 provider.add_block(
1896 B256::random(),
1897 Block {
1898 header: TempoHeader {
1899 inner: Header {
1900 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
1901 ..Default::default()
1902 },
1903 ..Default::default()
1904 },
1905 ..Default::default()
1906 },
1907 );
1908 set_transfer_policy(&provider, resolved_fee_token, policy_id);
1909
1910 let pool = create_test_pool(provider);
1911 add_validated(&pool, pooled.clone());
1912
1913 let mut updates = crate::maintain::TempoPoolUpdates::new();
1914 updates.whitelist_removals.push((policy_id, sender));
1915
1916 let evicted = pool.evict_invalidated_transactions(&updates);
1917 assert_eq!(tx_hashes(&evicted), vec![*pooled.hash()]);
1918 assert!(pool.get(pooled.hash()).is_none());
1919 }
1920
1921 #[tokio::test]
1922 async fn validator_token_change_uses_resolved_fee_token_for_liquidity_recheck() {
1923 let sender = Address::random();
1924 let validator_address = Address::random();
1925 let resolved_fee_token = address!("20C0000000000000000000000000000000000002");
1926 let pooled = crate::test_utils::TxBuilder::aa(sender).build();
1927
1928 assert_eq!(pooled.inner().fee_token(), None);
1929 pooled.set_resolved_fee_token(resolved_fee_token);
1930
1931 let provider = MockEthProvider::<TempoPrimitives>::new()
1932 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
1933 provider.add_account(sender, ExtendedAccount::new(pooled.nonce(), *pooled.cost()));
1934 provider.add_block(
1935 B256::random(),
1936 Block {
1937 header: TempoHeader {
1938 inner: Header {
1939 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
1940 ..Default::default()
1941 },
1942 ..Default::default()
1943 },
1944 ..Default::default()
1945 },
1946 );
1947
1948 let inner = EthTransactionValidatorBuilder::new(provider, TempoEvmConfig::mainnet())
1949 .disable_balance_check()
1950 .build(InMemoryBlobStore::default());
1951 let amm_cache = AmmLiquidityCache::with_unique_validators(vec![validator_address]);
1952 let validator = TempoTransactionValidator::new(
1953 inner,
1954 crate::validator::DEFAULT_AA_VALID_AFTER_MAX_SECS,
1955 crate::validator::DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
1956 amm_cache,
1957 );
1958
1959 let (executor, _task) = TransactionValidationTaskExecutor::new(validator);
1960 let protocol_pool = Pool::new(
1961 executor,
1962 TempoTipOrdering::default(),
1963 InMemoryBlobStore::default(),
1964 PoolConfig::default(),
1965 );
1966 let pool = TempoTransactionPool::new(protocol_pool, AA2dPool::new(Default::default()));
1967
1968 let validated = TransactionValidationOutcome::Valid {
1969 balance: *pooled.cost(),
1970 state_nonce: pooled.nonce(),
1971 bytecode_hash: None,
1972 transaction: ValidTransaction::new(pooled.clone(), None),
1973 propagate: true,
1974 authorities: None,
1975 };
1976 pool.add_validated_transaction(TransactionOrigin::External, validated)
1977 .expect("transaction should be admitted before validator token change");
1978
1979 let mut updates = crate::maintain::TempoPoolUpdates::new();
1980 updates
1981 .validator_token_changes
1982 .insert(validator_address, resolved_fee_token);
1983
1984 let evicted = pool.evict_invalidated_transactions(&updates);
1985 assert!(evicted.is_empty());
1986 assert!(pool.get(pooled.hash()).is_some());
1987 }
1988
1989 #[tokio::test]
1990 async fn evicts_transactions_with_burned_key_authorization_witness() {
1991 let sender = Address::random();
1992 let burned_witness = B256::random();
1993 let other_witness = B256::random();
1994
1995 let key_authorization = |witness| {
1996 KeyAuthorization::unrestricted(42431, SignatureType::Secp256k1, Address::random())
1997 .with_witness(witness)
1998 .into_signed(PrimitiveSignature::Secp256k1(Signature::test_signature()))
1999 };
2000
2001 let matching = crate::test_utils::TxBuilder::aa(sender)
2002 .nonce(0)
2003 .key_authorization(key_authorization(burned_witness))
2004 .build();
2005 let untouched = crate::test_utils::TxBuilder::aa(sender)
2006 .nonce(1)
2007 .key_authorization(key_authorization(other_witness))
2008 .build();
2009
2010 let provider = MockEthProvider::<TempoPrimitives>::new()
2011 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
2012 provider.add_account(sender, ExtendedAccount::new(matching.nonce(), U256::MAX));
2013 provider.add_block(
2014 B256::random(),
2015 Block {
2016 header: TempoHeader {
2017 inner: Header {
2018 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
2019 ..Default::default()
2020 },
2021 ..Default::default()
2022 },
2023 ..Default::default()
2024 },
2025 );
2026
2027 let inner =
2028 EthTransactionValidatorBuilder::new(provider.clone(), TempoEvmConfig::mainnet())
2029 .disable_balance_check()
2030 .build(InMemoryBlobStore::default());
2031 let amm_cache =
2032 AmmLiquidityCache::new(provider).expect("failed to setup AmmLiquidityCache");
2033 let validator = TempoTransactionValidator::new(
2034 inner,
2035 crate::validator::DEFAULT_AA_VALID_AFTER_MAX_SECS,
2036 crate::validator::DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
2037 amm_cache,
2038 );
2039
2040 let (executor, _task) = TransactionValidationTaskExecutor::new(validator);
2041 let protocol_pool = Pool::new(
2042 executor,
2043 TempoTipOrdering::default(),
2044 InMemoryBlobStore::default(),
2045 PoolConfig::default(),
2046 );
2047 let pool = TempoTransactionPool::new(protocol_pool, AA2dPool::new(Default::default()));
2048
2049 for pooled in [&matching, &untouched] {
2050 let validated = TransactionValidationOutcome::Valid {
2051 balance: *pooled.cost(),
2052 state_nonce: pooled.nonce(),
2053 bytecode_hash: None,
2054 transaction: ValidTransaction::new(pooled.clone(), None),
2055 propagate: true,
2056 authorities: None,
2057 };
2058 pool.add_validated_transaction(TransactionOrigin::External, validated)
2059 .expect("transaction should be admitted");
2060 }
2061
2062 let mut updates = crate::maintain::TempoPoolUpdates::new();
2063 updates
2064 .key_authorization_witness_burns
2065 .entry(sender)
2066 .or_default()
2067 .insert(burned_witness);
2068
2069 let evicted = pool.evict_invalidated_transactions(&updates);
2070 assert_eq!(tx_hashes(&evicted), vec![*matching.hash()]);
2071 assert!(pool.get(matching.hash()).is_none());
2072 assert!(pool.get(untouched.hash()).is_some());
2073 }
2074
2075 #[tokio::test]
2076 async fn evicts_transactions_with_revoked_key_authorization_signer() {
2077 let sender = Address::random();
2078 let admin_signer = PrivateKeySigner::random();
2079 let admin_key = alloy_signer::Signer::address(&admin_signer);
2080 let other_signer = PrivateKeySigner::random();
2081
2082 let key_authorization = |signer: &PrivateKeySigner| {
2083 let authorization =
2084 KeyAuthorization::unrestricted(42431, SignatureType::Secp256k1, Address::random())
2085 .with_account(sender);
2086 let signature = signer
2087 .sign_hash_sync(&authorization.signature_hash())
2088 .expect("key authorization signing should succeed");
2089 authorization.into_signed(PrimitiveSignature::Secp256k1(signature))
2090 };
2091
2092 let matching = crate::test_utils::TxBuilder::aa(sender)
2093 .nonce(0)
2094 .key_authorization(key_authorization(&admin_signer))
2095 .build();
2096 let untouched = crate::test_utils::TxBuilder::aa(sender)
2097 .nonce(1)
2098 .key_authorization(key_authorization(&other_signer))
2099 .build();
2100
2101 let provider = MockEthProvider::<TempoPrimitives>::new()
2102 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
2103 provider.add_account(sender, ExtendedAccount::new(matching.nonce(), U256::MAX));
2104 provider.add_block(
2105 B256::random(),
2106 Block {
2107 header: TempoHeader {
2108 inner: Header {
2109 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
2110 ..Default::default()
2111 },
2112 ..Default::default()
2113 },
2114 ..Default::default()
2115 },
2116 );
2117
2118 let inner =
2119 EthTransactionValidatorBuilder::new(provider.clone(), TempoEvmConfig::mainnet())
2120 .disable_balance_check()
2121 .build(InMemoryBlobStore::default());
2122 let amm_cache =
2123 AmmLiquidityCache::new(provider).expect("failed to setup AmmLiquidityCache");
2124 let validator = TempoTransactionValidator::new(
2125 inner,
2126 crate::validator::DEFAULT_AA_VALID_AFTER_MAX_SECS,
2127 crate::validator::DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
2128 amm_cache,
2129 );
2130
2131 let (executor, _task) = TransactionValidationTaskExecutor::new(validator);
2132 let protocol_pool = Pool::new(
2133 executor,
2134 TempoTipOrdering::default(),
2135 InMemoryBlobStore::default(),
2136 PoolConfig::default(),
2137 );
2138 let pool = TempoTransactionPool::new(protocol_pool, AA2dPool::new(Default::default()));
2139
2140 for pooled in [&matching, &untouched] {
2141 let validated = TransactionValidationOutcome::Valid {
2142 balance: *pooled.cost(),
2143 state_nonce: pooled.nonce(),
2144 bytecode_hash: None,
2145 transaction: ValidTransaction::new(pooled.clone(), None),
2146 propagate: true,
2147 authorities: None,
2148 };
2149 pool.add_validated_transaction(TransactionOrigin::External, validated)
2150 .expect("transaction should be admitted");
2151 }
2152
2153 let mut updates = crate::maintain::TempoPoolUpdates::new();
2154 updates.revoked_keys.insert(sender, admin_key);
2155
2156 let evicted = pool.evict_invalidated_transactions(&updates);
2157 assert_eq!(tx_hashes(&evicted), vec![*matching.hash()]);
2158 assert!(pool.get(matching.hash()).is_none());
2159 assert!(pool.get(untouched.hash()).is_some());
2160 }
2161
2162 #[tokio::test]
2163 async fn evicts_transactions_with_stale_key_authorization_target() {
2164 let sender = Address::random();
2165 let signer = PrivateKeySigner::random();
2166 let target_key = Address::random();
2167 let other_key = Address::random();
2168
2169 let key_authorization = |key_id| {
2170 let authorization =
2171 KeyAuthorization::unrestricted(42431, SignatureType::Secp256k1, key_id)
2172 .with_account(sender);
2173 let signature = signer
2174 .sign_hash_sync(&authorization.signature_hash())
2175 .expect("key authorization signing should succeed");
2176 authorization.into_signed(PrimitiveSignature::Secp256k1(signature))
2177 };
2178
2179 let matching = crate::test_utils::TxBuilder::aa(sender)
2180 .nonce(0)
2181 .key_authorization(key_authorization(target_key))
2182 .build();
2183 let untouched = crate::test_utils::TxBuilder::aa(sender)
2184 .nonce(1)
2185 .key_authorization(key_authorization(other_key))
2186 .build();
2187
2188 let provider = MockEthProvider::<TempoPrimitives>::new()
2189 .with_chain_spec(std::sync::Arc::unwrap_or_clone(MODERATO.clone()));
2190 provider.add_account(sender, ExtendedAccount::new(matching.nonce(), U256::MAX));
2191 provider.add_block(
2192 B256::random(),
2193 Block {
2194 header: TempoHeader {
2195 inner: Header {
2196 gas_limit: TEMPO_T1_TX_GAS_LIMIT_CAP,
2197 ..Default::default()
2198 },
2199 ..Default::default()
2200 },
2201 ..Default::default()
2202 },
2203 );
2204
2205 let inner =
2206 EthTransactionValidatorBuilder::new(provider.clone(), TempoEvmConfig::mainnet())
2207 .disable_balance_check()
2208 .build(InMemoryBlobStore::default());
2209 let amm_cache =
2210 AmmLiquidityCache::new(provider).expect("failed to setup AmmLiquidityCache");
2211 let validator = TempoTransactionValidator::new(
2212 inner,
2213 crate::validator::DEFAULT_AA_VALID_AFTER_MAX_SECS,
2214 crate::validator::DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
2215 amm_cache,
2216 );
2217
2218 let (executor, _task) = TransactionValidationTaskExecutor::new(validator);
2219 let protocol_pool = Pool::new(
2220 executor,
2221 TempoTipOrdering::default(),
2222 InMemoryBlobStore::default(),
2223 PoolConfig::default(),
2224 );
2225 let pool = TempoTransactionPool::new(protocol_pool, AA2dPool::new(Default::default()));
2226
2227 for pooled in [&matching, &untouched] {
2228 let validated = TransactionValidationOutcome::Valid {
2229 balance: *pooled.cost(),
2230 state_nonce: pooled.nonce(),
2231 bytecode_hash: None,
2232 transaction: ValidTransaction::new(pooled.clone(), None),
2233 propagate: true,
2234 authorities: None,
2235 };
2236 pool.add_validated_transaction(TransactionOrigin::External, validated)
2237 .expect("transaction should be admitted");
2238 }
2239
2240 let mut updates = crate::maintain::TempoPoolUpdates::new();
2241 updates
2242 .key_authorization_target_changes
2243 .insert(sender, target_key);
2244
2245 let evicted = pool.evict_invalidated_transactions(&updates);
2246 assert_eq!(tx_hashes(&evicted), vec![*matching.hash()]);
2247 assert!(pool.get(matching.hash()).is_none());
2248 assert!(pool.get(untouched.hash()).is_some());
2249 }
2250
2251 #[test]
2255 fn compound_policy_sub_policy_matches_eviction_check() {
2256 let fee_token = address!("20C0000000000000000000000000000000000001");
2257 let compound_policy_id: u64 = 5;
2258 let sender_sub_policy: u64 = 3;
2259 let recipient_sub_policy: u64 = 4;
2260
2261 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
2262 tempo_chainspec::spec::MODERATO.clone(),
2263 ));
2264
2265 let transfer_policy_id_packed =
2267 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
2268 provider.add_account(
2269 fee_token,
2270 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
2271 tip20_slots::TRANSFER_POLICY_ID.into(),
2272 transfer_policy_id_packed,
2273 )]),
2274 );
2275
2276 provider
2278 .setup_storage(TempoHardfork::default(), || {
2279 let mut registry = TIP403Registry::new();
2280 registry.policy_records[compound_policy_id]
2281 .base
2282 .write(PolicyData {
2283 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
2284 admin: Address::ZERO,
2285 })?;
2286 registry.policy_records[compound_policy_id]
2287 .compound
2288 .write(CompoundPolicyData {
2289 sender_policy_id: sender_sub_policy,
2290 recipient_policy_id: recipient_sub_policy,
2291 mint_recipient_policy_id: 0,
2292 })
2293 })
2294 .unwrap();
2295
2296 let mut state = provider.latest().unwrap();
2297 let mut cache: AddressMap<Vec<u64>> = AddressMap::default();
2298
2299 let ids =
2300 get_sender_policy_ids(&mut state, fee_token, TempoHardfork::default(), &mut cache)
2301 .expect("should resolve policy IDs");
2302
2303 assert!(
2304 ids.contains(&compound_policy_id),
2305 "should contain compound policy ID"
2306 );
2307 assert!(
2308 ids.contains(&sender_sub_policy),
2309 "should contain sender sub-policy"
2310 );
2311 }
2312
2313 #[test]
2316 fn compound_policy_sender_ids_exclude_recipient_sub_policy() {
2317 let fee_token = address!("20C0000000000000000000000000000000000001");
2318 let compound_policy_id: u64 = 5;
2319 let sender_sub_policy: u64 = 3;
2320 let recipient_sub_policy: u64 = 4;
2321
2322 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
2323 tempo_chainspec::spec::MODERATO.clone(),
2324 ));
2325
2326 let transfer_policy_id_packed =
2327 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
2328 provider.add_account(
2329 fee_token,
2330 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
2331 tip20_slots::TRANSFER_POLICY_ID.into(),
2332 transfer_policy_id_packed,
2333 )]),
2334 );
2335
2336 provider
2337 .setup_storage(TempoHardfork::default(), || {
2338 let mut registry = TIP403Registry::new();
2339 registry.policy_records[compound_policy_id]
2340 .base
2341 .write(PolicyData {
2342 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
2343 admin: Address::ZERO,
2344 })?;
2345 registry.policy_records[compound_policy_id]
2346 .compound
2347 .write(CompoundPolicyData {
2348 sender_policy_id: sender_sub_policy,
2349 recipient_policy_id: recipient_sub_policy,
2350 mint_recipient_policy_id: 0,
2351 })
2352 })
2353 .unwrap();
2354
2355 let mut state = provider.latest().unwrap();
2356 let mut cache: AddressMap<Vec<u64>> = AddressMap::default();
2357
2358 let ids =
2359 get_sender_policy_ids(&mut state, fee_token, TempoHardfork::default(), &mut cache)
2360 .expect("should resolve policy IDs");
2361
2362 assert!(ids.contains(&compound_policy_id));
2363 assert!(ids.contains(&sender_sub_policy));
2364 assert!(
2365 !ids.contains(&recipient_sub_policy),
2366 "sender policy IDs should not contain recipient_sub_policy"
2367 );
2368 }
2369
2370 #[test]
2373 fn compound_policy_excludes_mint_recipient() {
2374 let fee_token = address!("20C0000000000000000000000000000000000001");
2375 let compound_policy_id: u64 = 5;
2376 let sender_sub: u64 = 3;
2377 let recipient_sub: u64 = 4;
2378 let mint_recipient_sub: u64 = 6;
2379
2380 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
2381 tempo_chainspec::spec::MODERATO.clone(),
2382 ));
2383
2384 let transfer_policy_id_packed =
2385 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
2386 provider.add_account(
2387 fee_token,
2388 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
2389 tip20_slots::TRANSFER_POLICY_ID.into(),
2390 transfer_policy_id_packed,
2391 )]),
2392 );
2393
2394 provider
2395 .setup_storage(TempoHardfork::default(), || {
2396 let mut registry = TIP403Registry::new();
2397 registry.policy_records[compound_policy_id]
2398 .base
2399 .write(PolicyData {
2400 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
2401 admin: Address::ZERO,
2402 })?;
2403 registry.policy_records[compound_policy_id]
2404 .compound
2405 .write(CompoundPolicyData {
2406 sender_policy_id: sender_sub,
2407 recipient_policy_id: recipient_sub,
2408 mint_recipient_policy_id: mint_recipient_sub,
2409 })
2410 })
2411 .unwrap();
2412
2413 let mut state = provider.latest().unwrap();
2414 let mut cache: AddressMap<Vec<u64>> = AddressMap::default();
2415
2416 let ids =
2417 get_sender_policy_ids(&mut state, fee_token, TempoHardfork::default(), &mut cache)
2418 .expect("should resolve policy IDs");
2419
2420 assert!(
2421 !ids.contains(&mint_recipient_sub),
2422 "mint_recipient must be excluded from sender policy IDs"
2423 );
2424 }
2425
2426 #[test]
2428 fn recipient_policy_ids_includes_recipient_sub_policy() {
2429 let fee_token = address!("20C0000000000000000000000000000000000001");
2430 let compound_policy_id: u64 = 5;
2431 let sender_sub: u64 = 3;
2432 let recipient_sub: u64 = 4;
2433
2434 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
2435 tempo_chainspec::spec::MODERATO.clone(),
2436 ));
2437
2438 let transfer_policy_id_packed =
2439 U256::from(compound_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
2440 provider.add_account(
2441 fee_token,
2442 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
2443 tip20_slots::TRANSFER_POLICY_ID.into(),
2444 transfer_policy_id_packed,
2445 )]),
2446 );
2447
2448 provider
2449 .setup_storage(TempoHardfork::default(), || {
2450 let mut registry = TIP403Registry::new();
2451 registry.policy_records[compound_policy_id]
2452 .base
2453 .write(PolicyData {
2454 policy_type: ITIP403Registry::PolicyType::COMPOUND as u8,
2455 admin: Address::ZERO,
2456 })?;
2457 registry.policy_records[compound_policy_id]
2458 .compound
2459 .write(CompoundPolicyData {
2460 sender_policy_id: sender_sub,
2461 recipient_policy_id: recipient_sub,
2462 mint_recipient_policy_id: 0,
2463 })
2464 })
2465 .unwrap();
2466
2467 let mut state = provider.latest().unwrap();
2468 let ids = get_recipient_policy_ids(&mut state, fee_token, TempoHardfork::default())
2469 .expect("should resolve policy IDs");
2470
2471 assert!(
2472 ids.contains(&compound_policy_id),
2473 "should contain compound policy ID"
2474 );
2475 assert!(
2476 ids.contains(&recipient_sub),
2477 "should contain recipient sub-policy"
2478 );
2479 assert!(
2480 !ids.contains(&sender_sub),
2481 "recipient policy IDs should not contain sender sub-policy"
2482 );
2483 }
2484
2485 #[test]
2487 fn recipient_policy_ids_simple_policy() {
2488 let fee_token = address!("20C0000000000000000000000000000000000001");
2489 let simple_policy_id: u64 = 7;
2490
2491 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
2492 tempo_chainspec::spec::MODERATO.clone(),
2493 ));
2494
2495 let transfer_policy_id_packed =
2496 U256::from(simple_policy_id) << (tip20_slots::TRANSFER_POLICY_ID_OFFSET * 8);
2497 provider.add_account(
2498 fee_token,
2499 ExtendedAccount::new(0, U256::ZERO).extend_storage([(
2500 tip20_slots::TRANSFER_POLICY_ID.into(),
2501 transfer_policy_id_packed,
2502 )]),
2503 );
2504
2505 provider
2506 .setup_storage(TempoHardfork::default(), || {
2507 let mut registry = TIP403Registry::new();
2508 registry.policy_records[simple_policy_id]
2509 .base
2510 .write(PolicyData {
2511 policy_type: ITIP403Registry::PolicyType::BLACKLIST as u8,
2512 admin: Address::ZERO,
2513 })
2514 })
2515 .unwrap();
2516
2517 let mut state = provider.latest().unwrap();
2518 let ids = get_recipient_policy_ids(&mut state, fee_token, TempoHardfork::default())
2519 .expect("should resolve policy IDs");
2520
2521 assert_eq!(ids, vec![simple_policy_id]);
2522 }
2523
2524 #[test]
2525 fn exceeds_spending_limit_returns_true_when_cost_exceeds_remaining() {
2526 let account = Address::random();
2527 let key_id = Address::random();
2528 let fee_token = Address::random();
2529 let subject = KeychainSubject {
2530 account,
2531 key_id,
2532 fee_token,
2533 };
2534
2535 let mut state = provider_with_spending_limit(
2536 account,
2537 key_id,
2538 fee_token,
2539 alloy_primitives::U256::from(100),
2540 );
2541
2542 assert!(exceeds_spending_limit(
2543 &mut state,
2544 &subject,
2545 alloy_primitives::U256::from(200),
2546 0,
2547 TempoHardfork::default(),
2548 ));
2549 }
2550
2551 #[test]
2552 fn exceeds_spending_limit_returns_false_when_cost_within_limit() {
2553 let account = Address::random();
2554 let key_id = Address::random();
2555 let fee_token = Address::random();
2556 let subject = KeychainSubject {
2557 account,
2558 key_id,
2559 fee_token,
2560 };
2561
2562 let mut state = provider_with_spending_limit(
2563 account,
2564 key_id,
2565 fee_token,
2566 alloy_primitives::U256::from(500),
2567 );
2568
2569 assert!(!exceeds_spending_limit(
2570 &mut state,
2571 &subject,
2572 alloy_primitives::U256::from(200),
2573 0,
2574 TempoHardfork::default(),
2575 ));
2576 }
2577
2578 #[test]
2579 fn exceeds_spending_limit_returns_true_when_no_limit_set() {
2580 let account = Address::random();
2581 let key_id = Address::random();
2582 let fee_token = Address::random();
2583 let subject = KeychainSubject {
2584 account,
2585 key_id,
2586 fee_token,
2587 };
2588
2589 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
2591 tempo_chainspec::spec::MODERATO.clone(),
2592 ));
2593 provider
2594 .setup_storage(TempoHardfork::default(), || {
2595 AccountKeychain::new().keys[account][key_id].write(AuthorizedKey {
2596 signature_type: StoredSignatureType::Secp256k1,
2597 expiry: u64::MAX,
2598 enforce_limits: true,
2599 is_revoked: false,
2600 is_admin: false,
2601 })
2602 })
2603 .unwrap();
2604
2605 assert!(exceeds_spending_limit(
2606 &mut provider.latest().unwrap(),
2607 &subject,
2608 alloy_primitives::U256::from(1),
2609 0,
2610 TempoHardfork::default(),
2611 ));
2612 }
2613
2614 #[test]
2615 fn exceeds_spending_limit_returns_false_when_limits_not_enforced() {
2616 let account = Address::random();
2617 let key_id = Address::random();
2618 let fee_token = Address::random();
2619 let subject = KeychainSubject {
2620 account,
2621 key_id,
2622 fee_token,
2623 };
2624
2625 let provider = MockEthProvider::default().with_chain_spec(std::sync::Arc::unwrap_or_clone(
2627 tempo_chainspec::spec::MODERATO.clone(),
2628 ));
2629 provider
2630 .setup_storage(TempoHardfork::default(), || {
2631 AccountKeychain::new().keys[account][key_id].write(AuthorizedKey {
2632 signature_type: StoredSignatureType::Secp256k1,
2633 expiry: u64::MAX,
2634 enforce_limits: false,
2635 is_revoked: false,
2636 is_admin: false,
2637 })
2638 })
2639 .unwrap();
2640
2641 assert!(!exceeds_spending_limit(
2642 &mut provider.latest().unwrap(),
2643 &subject,
2644 alloy_primitives::U256::from(1),
2645 0,
2646 TempoHardfork::default(),
2647 ));
2648 }
2649
2650 #[test]
2651 fn exceeds_spending_limit_uses_period_reset_after_rollover() {
2652 let account = Address::random();
2653 let key_id = Address::random();
2654 let fee_token = Address::random();
2655 let subject = KeychainSubject {
2656 account,
2657 key_id,
2658 fee_token,
2659 };
2660
2661 let mut state = provider_with_spending_limit_state(
2662 account,
2663 key_id,
2664 fee_token,
2665 SpendingLimitState {
2666 remaining: alloy_primitives::U256::ZERO,
2667 max: 100,
2668 period: 60,
2669 period_end: 10,
2670 },
2671 TempoHardfork::T3,
2672 );
2673
2674 assert!(!exceeds_spending_limit(
2675 &mut state,
2676 &subject,
2677 alloy_primitives::U256::from(50),
2678 10,
2679 TempoHardfork::T3,
2680 ));
2681 assert!(exceeds_spending_limit(
2682 &mut state,
2683 &subject,
2684 alloy_primitives::U256::from(150),
2685 10,
2686 TempoHardfork::T3,
2687 ));
2688 }
2689}