1use crate::admission_queue::{
21 AdmissionQueueEntry, AdmissionQueueMetrics, PopAction, PriorityAdmissionQueue,
22};
23use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
24use crate::consensus_adapter::{
25 BlockStatusReceiver, ConsensusAdapterMetrics, ConsensusClient, ProcessedMethod,
26 processing_error,
27};
28use crate::consensus_handler::{SequencedConsensusTransactionKey, tx_type_label};
29use crate::execution_cache::ExecutionCacheWrite;
30use crate::staggered_submission::{StaggerQuota, StaggeredSlot, proposers_metric_label};
31use async_trait::async_trait;
32use consensus_core::{BlockStatus, ClientError, LimitReached, Transaction, TransactionPool};
33use consensus_types::block::{
34 BlockRef, NUM_RESERVED_TRANSACTION_INDICES, PING_TRANSACTION_INDEX, Round, TransactionIndex,
35};
36use itertools::Itertools;
37use mysten_common::debug_fatal;
38use mysten_common::sync::notify_read::OwnedRegistration;
39use mysten_metrics::monitored_scope;
40use parking_lot::Mutex;
41use prometheus::IntGauge;
42use std::collections::{BTreeMap, VecDeque};
43use std::sync::Arc;
44use std::sync::Weak;
45use std::time::{Duration, Instant};
46use sui_macros::fail_point_if;
47use sui_types::base_types::{EpochId, ObjectRef};
48use sui_types::digests::TransactionDigest;
49use sui_types::error::{SuiError, SuiErrorKind, SuiResult};
50use sui_types::messages_checkpoint::CheckpointSequenceNumber;
51use sui_types::messages_consensus::{
52 ConsensusPosition, ConsensusTransaction, ConsensusTransactionKey, ConsensusTransactionKind,
53};
54use sui_types::transaction::{InputObjectKind, TransactionDataAPI};
55use tokio::sync::{oneshot, watch};
56use tracing::warn;
57
58const MAX_PENDING_PINGS: usize = 1_024;
60
61const SYSTEM_LANE_WARN_THRESHOLD: usize = 10_000;
64
65const MAX_EXPIRED_PER_PROPOSAL: usize = 1_000;
69
70pub type PositionReceiver = oneshot::Receiver<SuiResult<Vec<ConsensusPosition>>>;
73
74type BlockInclusion = (
78 BlockRef,
79 Vec<TransactionIndex>,
80 oneshot::Receiver<BlockStatus>,
81);
82
83enum EntryAck {
85 User(oneshot::Sender<SuiResult<Vec<ConsensusPosition>>>),
87 SystemOrPing(oneshot::Sender<BlockInclusion>),
90}
91
92struct PendingAck {
94 ack: Option<EntryAck>,
95 keys: Vec<ConsensusTransactionKey>,
96 created_at: &'static std::panic::Location<'static>,
97 created: Instant,
98}
99
100impl PendingAck {
101 #[track_caller]
102 fn new(ack: EntryAck, keys: Vec<ConsensusTransactionKey>) -> Self {
103 Self {
104 ack: Some(ack),
105 keys,
106 created_at: std::panic::Location::caller(),
107 created: Instant::now(),
108 }
109 }
110
111 fn resolve_included(
112 mut self,
113 epoch: EpochId,
114 block_ref: BlockRef,
115 indices: Vec<TransactionIndex>,
116 block: &mut ProposedBlock,
117 ) {
118 match self.ack.take().expect("ack must be pending") {
119 EntryAck::User(sender) => {
120 let positions = indices
121 .into_iter()
122 .map(|index| ConsensusPosition {
123 epoch,
124 block: block_ref,
125 index,
126 })
127 .collect();
128 let _ = sender.send(Ok(positions));
129 }
130 EntryAck::SystemOrPing(sender) => {
131 let (status_sender, status_receiver) = oneshot::channel();
132 block.subscribers.push(status_sender);
133 let _ = sender.send((block_ref, indices, status_receiver));
134 }
135 }
136 }
137
138 fn resolve_error(mut self, error: SuiError) {
139 match self.ack.take().expect("ack must be pending") {
140 EntryAck::User(sender) => {
141 let _ = sender.send(Err(error));
142 }
143 EntryAck::SystemOrPing(sender) => {
144 drop(sender);
145 }
146 }
147 }
148
149 fn drop_deliberately(mut self, _reason: &'static str) {
150 drop(self.ack.take().expect("ack must be pending"));
151 }
152
153 fn is_abandoned_system(&self) -> bool {
154 matches!(&self.ack, Some(EntryAck::SystemOrPing(sender)) if sender.is_closed())
155 }
156}
157
158impl Drop for PendingAck {
159 fn drop(&mut self) {
160 let flavor = match self.ack {
161 Some(EntryAck::User(_)) => "User",
162 Some(EntryAck::SystemOrPing(_)) => "SystemOrPing",
163 None => return,
164 };
165 if !std::thread::panicking() {
166 debug_fatal!(
167 "{} ack created at {} dropped without resolution after {:?}; keys={:?}",
168 flavor,
169 self.created_at,
170 self.created.elapsed(),
171 self.keys
172 );
173 }
174 }
175}
176
177struct ProcessedWatch {
181 consensus: OwnedRegistration<SequencedConsensusTransactionKey, ()>,
182 checkpoint: Option<OwnedRegistration<TransactionDigest, CheckpointSequenceNumber>>,
183 observed: Option<ProcessedMethod>,
185}
186
187impl ProcessedWatch {
188 fn register(epoch_store: &Arc<AuthorityPerEpochStore>, key: ConsensusTransactionKey) -> Self {
189 let key = SequencedConsensusTransactionKey::External(key);
190 Self {
191 checkpoint: key
192 .user_transaction_digest()
193 .map(|digest| epoch_store.register_executed_in_checkpoint_notify(&digest)),
194 consensus: epoch_store.register_consensus_message_processed_notify(&key),
195 observed: None,
196 }
197 }
198
199 fn check_processed(&mut self) -> Option<ProcessedMethod> {
201 if self.observed.is_none() {
202 if self.consensus.try_recv().is_ok() {
203 self.observed = Some(ProcessedMethod::ConsensusMessageProcessed);
204 } else if let Some(checkpoint) = &mut self.checkpoint
205 && checkpoint.try_recv().is_ok()
206 {
207 self.observed = Some(ProcessedMethod::CheckpointExecuted);
208 }
209 }
210 self.observed
211 }
212}
213
214struct PoolEntry {
217 transactions: Vec<Transaction>,
218 total_bytes: usize,
219 gas_price: u64,
220 tx_type: &'static str, proposers: &'static str,
223 ack: PendingAck,
224 metrics: Arc<AdmissionQueueMetrics>,
225 processed: Vec<ProcessedWatch>,
228 owned_object_refs: Vec<ObjectRef>,
233 rejection: Option<SuiError>,
236 staggered_slot: Option<StaggeredSlot>,
244}
245
246fn all_processed(watches: &mut [ProcessedWatch]) -> Option<ProcessedMethod> {
249 let mut result: Option<ProcessedMethod> = None;
250 for watch in watches {
251 let observed = watch.check_processed()?;
252 result = result.max(Some(observed));
253 }
254 result
255}
256
257impl AdmissionQueueEntry for PoolEntry {
258 fn gas_price(&self) -> u64 {
259 self.gas_price
260 }
261
262 fn transaction_keys(&self) -> impl Iterator<Item = ConsensusTransactionKey> {
263 self.ack.keys.iter().cloned()
264 }
265
266 fn notify_evicted(self, min_gas_price: u64) {
267 self.metrics.pool_depth.with_label_values(&["user"]).dec();
268 self.metrics
269 .pool_bytes
270 .with_label_values(&["user"])
271 .sub(self.total_bytes as i64);
272 self.ack.resolve_error(
273 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price }
274 .into(),
275 );
276 }
277
278 fn notify_rejected(self, min_gas_price: u64) {
279 self.ack.resolve_error(
280 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price }
281 .into(),
282 );
283 }
284}
285
286enum UserLane {
288 Open(PriorityAdmissionQueue<PoolEntry>),
289 Closed,
290}
291
292impl UserLane {
293 #[must_use]
294 fn close(&mut self) -> Vec<PoolEntry> {
295 match std::mem::replace(self, Self::Closed) {
296 Self::Open(queue) => queue.into_entries(),
297 Self::Closed => Vec::new(),
298 }
299 }
300}
301
302struct UserByteBudget {
305 bytes_per_second: u64,
306 capacity: u64,
307 tokens: f64,
308 last_refill: Instant,
309}
310
311impl UserByteBudget {
312 fn new(bytes_per_second: u64, max_block_bytes: u64, now: Instant) -> Self {
313 let capacity = bytes_per_second.max(max_block_bytes);
314 Self {
315 bytes_per_second,
316 capacity,
317 tokens: capacity as f64,
318 last_refill: now,
319 }
320 }
321
322 fn available(&mut self, now: Instant) -> usize {
323 let elapsed = now
324 .saturating_duration_since(self.last_refill)
325 .as_secs_f64();
326 self.tokens =
327 (self.tokens + elapsed * self.bytes_per_second as f64).min(self.capacity as f64);
328 self.last_refill = now;
329 self.tokens as usize
330 }
331
332 fn consume(&mut self, bytes: usize) {
333 let bytes = bytes as f64;
334 if bytes > self.tokens {
335 debug_fatal!(
336 "user byte budget overdrawn: consuming {bytes} with {} tokens available",
337 self.tokens
338 );
339 }
340 self.tokens = (self.tokens - bytes).max(0.0);
341 }
342
343 fn refund(&mut self, bytes: usize) {
344 self.tokens = (self.tokens + bytes as f64).min(self.capacity as f64);
345 }
346}
347
348struct Pool {
349 user: UserLane,
350 user_byte_budget: Option<Box<UserByteBudget>>,
351 system: VecDeque<PoolEntry>,
354 pings: VecDeque<PendingAck>,
355 blocks: BTreeMap<BlockRef, ProposedBlock>,
358}
359
360#[derive(Default)]
361struct ProposedBlock {
362 subscribers: Vec<oneshot::Sender<BlockStatus>>,
364 entries: Vec<ProposedEntry>,
366}
367
368struct ProposedEntry {
369 lane: TakenLane,
370 tx_type: &'static str,
371 proposers: &'static str,
373 created: Instant,
374}
375
376enum Inner {
377 Open(Pool),
378 Closed,
379}
380
381pub struct ConsensusTransactionPool {
390 epoch_store: Arc<AuthorityPerEpochStore>,
391 cache_writer: Arc<dyn ExecutionCacheWrite>,
392 metrics: Arc<AdmissionQueueMetrics>,
393 adapter_metrics: ConsensusAdapterMetrics,
394 stagger_quota: StaggerQuota,
395 max_queue_age: Option<Duration>,
398 inner: Arc<Mutex<Inner>>,
399}
400
401impl ConsensusTransactionPool {
402 pub fn new(
403 epoch_store: Arc<AuthorityPerEpochStore>,
404 cache_writer: Arc<dyn ExecutionCacheWrite>,
405 max_pending_transactions: usize,
406 max_user_bytes_per_second: Option<u64>,
407 max_queue_age: Option<Duration>,
408 metrics: Arc<AdmissionQueueMetrics>,
409 adapter_metrics: ConsensusAdapterMetrics,
410 ) -> Self {
411 assert!(
412 max_pending_transactions > 0,
413 "consensus transaction pool max_pending_transactions must be > 0"
414 );
415 assert!(
416 max_queue_age.is_none_or(|ttl| !ttl.is_zero()),
417 "consensus transaction pool max_queue_age must be > 0"
418 );
419 assert!(
420 epoch_store
421 .protocol_config()
422 .max_num_transactions_in_block()
423 .saturating_sub(1)
424 < u64::from(TransactionIndex::MAX.saturating_sub(NUM_RESERVED_TRANSACTION_INDICES)),
425 "Unsupported max_num_transactions_in_block: {}",
426 epoch_store
427 .protocol_config()
428 .max_num_transactions_in_block()
429 );
430
431 let user_byte_budget = max_user_bytes_per_second.map(|bytes_per_second| {
432 assert!(
433 bytes_per_second > 0,
434 "consensus transaction pool max_user_bytes_per_second must be > 0"
435 );
436 Box::new(UserByteBudget::new(
437 bytes_per_second,
438 epoch_store
439 .protocol_config()
440 .max_transactions_in_block_bytes(),
441 Instant::now(),
442 ))
443 });
444 Self {
445 epoch_store,
446 cache_writer,
447 metrics: metrics.clone(),
448 adapter_metrics,
449 max_queue_age,
450 stagger_quota: StaggerQuota::new(
451 max_pending_transactions,
452 metrics.pool_staggered_held.clone(),
453 metrics.pool_staggered_rejected.clone(),
454 metrics.pool_stagger_shadow_decisions.clone(),
455 metrics.pool_stagger_shadow_delay.clone(),
456 ),
457 inner: Arc::new(Mutex::new(Inner::Open(Pool {
458 user: UserLane::Open(PriorityAdmissionQueue::new(
459 max_pending_transactions,
460 metrics,
461 )),
462 user_byte_budget,
463 system: VecDeque::new(),
464 pings: VecDeque::new(),
465 blocks: BTreeMap::new(),
466 }))),
467 }
468 }
469
470 #[cfg(test)]
471 pub(crate) fn new_for_tests(
472 epoch_store: Arc<AuthorityPerEpochStore>,
473 cache_writer: Arc<dyn ExecutionCacheWrite>,
474 max_pending_transactions: usize,
475 metrics: Arc<AdmissionQueueMetrics>,
476 ) -> Self {
477 Self::new(
478 epoch_store,
479 cache_writer,
480 max_pending_transactions,
481 None,
482 None,
483 metrics,
484 ConsensusAdapterMetrics::new_test(),
485 )
486 }
487
488 pub fn epoch(&self) -> EpochId {
489 self.epoch_store.epoch()
490 }
491
492 pub fn try_insert(
500 &self,
501 caller_epoch: EpochId,
502 gas_price: u64,
503 transactions: Vec<ConsensusTransaction>,
504 ) -> SuiResult<(PositionReceiver, bool)> {
505 let keys = transactions
508 .iter()
509 .map(ConsensusTransaction::key)
510 .collect::<Vec<_>>();
511 let mut processed = keys
512 .iter()
513 .map(|key| ProcessedWatch::register(&self.epoch_store, key.clone()))
514 .collect::<Vec<_>>();
515 if let Some(method) = self.check_already_processed(&mut processed)? {
516 self.metrics
517 .pool_already_processed
518 .with_label_values(&["insert", method.metric_label()])
519 .inc();
520 return Err(processing_error(
521 processed.iter().map(|watch| watch.consensus.key()),
522 method,
523 ));
524 }
525
526 let tx_type = tx_type_label(&transactions);
527 let (serialized, total_bytes) = self.serialize_and_validate(&transactions)?;
528 let owned_object_refs = owned_object_refs(&transactions);
529
530 let user_transactions: Vec<_> = transactions
531 .iter()
532 .filter_map(|transaction| transaction.kind.as_user_transaction())
533 .collect();
534 let staggered_slot = self
535 .stagger_quota
536 .submission_slot(&user_transactions, &self.epoch_store)?;
537 let stagger_delay = staggered_slot.as_ref().map(|slot| slot.delay());
538 let (sender, receiver) = oneshot::channel();
539 let entry = PoolEntry {
540 transactions: serialized,
541 total_bytes,
542 gas_price,
543 tx_type,
544 proposers: proposers_metric_label(&transactions, &self.epoch_store),
545 ack: PendingAck::new(EntryAck::User(sender), keys),
546 metrics: self.metrics.clone(),
547 processed,
548 owned_object_refs,
549 rejection: None,
550 staggered_slot,
551 };
552
553 let mut inner = self.inner.lock();
554 let user = match self.check_user_lane_open_locked(caller_epoch, &mut inner) {
555 Ok(user) => user,
556 Err(error) => {
557 drop(inner);
558 entry.ack.resolve_error(error.clone());
559 return Err(error);
560 }
561 };
562 let outcome = user.try_insert(entry);
563 drop(inner);
564
565 let newly_inserted = outcome.notify()?;
566 if let Some(delay) = stagger_delay {
570 self.metrics
571 .pool_staggered_delay
572 .observe(delay.as_secs_f64());
573 }
574 self.metrics.pool_depth.with_label_values(&["user"]).inc();
575 self.metrics
576 .pool_bytes
577 .with_label_values(&["user"])
578 .add(total_bytes as i64);
579 Ok((receiver, newly_inserted))
580 }
581
582 fn submit(
586 &self,
587 caller_epoch: EpochId,
588 transactions: &[ConsensusTransaction],
589 ) -> SuiResult<oneshot::Receiver<BlockInclusion>> {
590 let (sender, receiver) = oneshot::channel();
591 if transactions.is_empty() {
592 let mut inner = self.inner.lock();
593 let pool = self.check_epoch_and_open_locked(caller_epoch, &mut inner)?;
594 if pool.pings.len() >= MAX_PENDING_PINGS {
595 return Err(SuiErrorKind::TooManyTransactionsPendingConsensus.into());
596 }
597 pool.pings
598 .push_back(PendingAck::new(EntryAck::SystemOrPing(sender), Vec::new()));
599 self.metrics.pool_depth.with_label_values(&["ping"]).inc();
600 return Ok(receiver);
601 }
602
603 if transactions
605 .iter()
606 .any(ConsensusTransaction::is_user_transaction)
607 {
608 debug_fatal!(
609 "user transaction submitted through the consensus transaction pool client"
610 );
611 return Err(SuiErrorKind::GenericAuthorityError {
612 error: "user transactions cannot be submitted through the transaction pool client"
613 .to_string(),
614 }
615 .into());
616 }
617 let (serialized, total_bytes) = match self.serialize_and_validate(transactions) {
621 Ok(validated) => validated,
622 Err(error) => {
623 debug_fatal!("system transaction failed validation: {error}");
624 return Err(error);
625 }
626 };
627 let entry = PoolEntry {
628 transactions: serialized,
629 total_bytes,
630 gas_price: 0,
631 tx_type: tx_type_label(transactions),
632 proposers: "na",
633 ack: PendingAck::new(
634 EntryAck::SystemOrPing(sender),
635 transactions.iter().map(ConsensusTransaction::key).collect(),
636 ),
637 metrics: self.metrics.clone(),
638 processed: Vec::new(),
639 owned_object_refs: Vec::new(),
640 rejection: None,
641 staggered_slot: None,
642 };
643
644 {
645 let mut inner = self.inner.lock();
646 let pool = match self.check_epoch_and_open_locked(caller_epoch, &mut inner) {
647 Ok(pool) => pool,
648 Err(error) => {
649 drop(inner);
650 entry.ack.resolve_error(error.clone());
651 return Err(error);
652 }
653 };
654 pool.system.push_back(entry);
655 let depth = pool.system.len();
656 self.metrics
657 .pool_depth
658 .with_label_values(&["system"])
659 .set(depth as i64);
660 self.metrics
661 .pool_bytes
662 .with_label_values(&["system"])
663 .add(total_bytes as i64);
664 if depth == SYSTEM_LANE_WARN_THRESHOLD {
665 warn!(
666 depth,
667 "Consensus transaction pool system lane exceeded warning threshold"
668 );
669 }
670 }
671 Ok(receiver)
672 }
673
674 fn check_already_processed(
675 &self,
676 watches: &mut [ProcessedWatch],
677 ) -> SuiResult<Option<ProcessedMethod>> {
678 let consensus_processed = self.epoch_store.check_consensus_messages_processed(
680 watches.iter().map(|watch| watch.consensus.key().clone()),
681 );
682 for (watch, consensus_processed) in watches.iter_mut().zip_eq(consensus_processed) {
683 if consensus_processed {
684 watch.observed = Some(ProcessedMethod::ConsensusMessageProcessed);
685 }
686 }
687
688 let (unobserved, digests): (Vec<_>, Vec<_>) = watches
690 .iter_mut()
691 .filter_map(|watch| {
692 if watch.observed.is_some() {
693 return None;
694 }
695 let digest = *watch.checkpoint.as_ref()?.key();
696 Some((watch, digest))
697 })
698 .unzip();
699 if !digests.is_empty() {
700 let executed = self
701 .epoch_store
702 .multi_get_transaction_checkpoint(&digests)?;
703 for (watch, executed) in unobserved.into_iter().zip_eq(executed) {
704 if executed.is_some() {
705 watch.observed = Some(ProcessedMethod::CheckpointExecuted);
706 }
707 }
708 }
709 Ok(all_processed(watches))
710 }
711
712 fn check_epoch_and_open_locked<'a>(
713 &self,
714 caller_epoch: EpochId,
715 inner: &'a mut Inner,
716 ) -> SuiResult<&'a mut Pool> {
717 if caller_epoch != self.epoch() {
718 return Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
719 }
720 match inner {
721 Inner::Open(pool) => Ok(pool),
722 Inner::Closed => Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into()),
723 }
724 }
725
726 fn check_user_lane_open_locked<'a>(
727 &self,
728 caller_epoch: EpochId,
729 inner: &'a mut Inner,
730 ) -> SuiResult<&'a mut PriorityAdmissionQueue<PoolEntry>> {
731 let pool = self.check_epoch_and_open_locked(caller_epoch, inner)?;
732 match &mut pool.user {
733 UserLane::Open(user) => Ok(user),
734 UserLane::Closed => Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into()),
735 }
736 }
737
738 fn serialize_and_validate(
745 &self,
746 transactions: &[ConsensusTransaction],
747 ) -> SuiResult<(Vec<Transaction>, usize)> {
748 let protocol_config = self.epoch_store.protocol_config();
749 let bundle_count = u64::try_from(transactions.len()).expect("bundle count fits into u64");
750 if bundle_count > protocol_config.max_num_transactions_in_block() {
751 return Err(consensus_client_error(
752 ClientError::OversizedTransactionBundleCount(
753 bundle_count,
754 protocol_config.max_num_transactions_in_block(),
755 ),
756 ));
757 }
758
759 let mut total_bytes = 0usize;
760 let mut serialized = Vec::with_capacity(transactions.len());
761 for transaction in transactions {
762 let bytes =
763 bcs::to_bytes(transaction).expect("Serializing consensus transaction cannot fail");
764 let transaction_bytes =
765 u64::try_from(bytes.len()).expect("transaction size fits into u64");
766 if transaction_bytes > protocol_config.max_transaction_size_bytes() {
767 return Err(consensus_client_error(ClientError::OversizedTransaction(
768 transaction_bytes,
769 protocol_config.max_transaction_size_bytes(),
770 )));
771 }
772 total_bytes += bytes.len();
773 let bundle_bytes = u64::try_from(total_bytes).expect("bundle size fits into u64");
774 if bundle_bytes > protocol_config.max_transactions_in_block_bytes() {
775 return Err(consensus_client_error(
776 ClientError::OversizedTransactionBundleBytes(
777 bundle_bytes,
778 protocol_config.max_transactions_in_block_bytes(),
779 ),
780 ));
781 }
782 serialized.push(Transaction::new(bytes));
783 }
784 Ok((serialized, total_bytes))
785 }
786
787 pub fn close(&self) {
792 let mut pool = {
793 let mut inner = self.inner.lock();
794 match std::mem::replace(&mut *inner, Inner::Closed) {
795 Inner::Open(pool) => pool,
796 Inner::Closed => return,
797 }
798 };
799
800 self.resolve_flushed_user_entries(pool.user.close());
802 for entry in pool.system {
803 entry.ack.drop_deliberately("pool closed");
804 }
805 for ping in pool.pings {
806 ping.drop_deliberately("pool closed");
807 }
808 drop(pool.blocks);
809 self.metrics
810 .pool_depth
811 .with_label_values(&["system"])
812 .set(0);
813 self.metrics
814 .pool_bytes
815 .with_label_values(&["system"])
816 .set(0);
817 self.metrics.pool_depth.with_label_values(&["ping"]).set(0);
818 }
819
820 fn resolve_flushed_user_entries(&self, entries: Vec<PoolEntry>) {
824 for entry in entries {
825 self.decrement_lane_metrics("user", &entry);
826 entry
827 .ack
828 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
829 }
830 }
831
832 fn resolve_swept_user_entry(&self, mut entry: PoolEntry) {
836 let _scope = monitored_scope("ConsensusTransactionPool::resolve_swept_user_entry");
837 self.decrement_lane_metrics("user", &entry);
838 if let Some(method) = all_processed(&mut entry.processed) {
839 self.resolve_already_processed(entry, "sweep", method);
840 } else if let Err(error) = self
841 .cache_writer
842 .validate_owned_object_versions(&entry.owned_object_refs)
843 {
844 self.resolve_rejected(entry, error);
845 } else {
846 self.metrics.expired.inc();
847 entry.ack.resolve_error(
851 SuiErrorKind::ValidatorOverloadedRetryAfter {
852 retry_after_secs: 2,
853 }
854 .into(),
855 );
856 }
857 }
858
859 fn resolve_rejected(&self, entry: PoolEntry, error: SuiError) {
860 self.metrics
861 .pool_rejected_at_proposal
862 .with_label_values(&[error.as_inner().to_variant_name()])
863 .inc();
864 entry.ack.resolve_error(error);
865 }
866
867 fn resolve_already_processed(&self, entry: PoolEntry, stage: &str, method: ProcessedMethod) {
868 self.metrics
869 .pool_already_processed
870 .with_label_values(&[stage, method.metric_label()])
871 .inc();
872 let error = processing_error(
873 entry.processed.iter().map(|watch| watch.consensus.key()),
874 method,
875 );
876 entry.ack.resolve_error(error);
877 }
878
879 fn decrement_lane_metrics(&self, lane: &str, entry: &PoolEntry) {
880 self.metrics.pool_depth.with_label_values(&[lane]).dec();
881 self.metrics
882 .pool_bytes
883 .with_label_values(&[lane])
884 .sub(entry.total_bytes as i64);
885 }
886
887 #[cfg(test)]
888 pub fn queue_depth(&self, lane: &str) -> i64 {
889 self.metrics.pool_depth.with_label_values(&[lane]).get()
890 }
891
892 #[cfg(test)]
893 pub fn already_processed_count(&self, stage: &str, method: &str) -> u64 {
894 self.metrics
895 .pool_already_processed
896 .with_label_values(&[stage, method])
897 .get()
898 }
899
900 #[cfg(test)]
901 pub fn abandoned_count(&self, lane: &str) -> u64 {
902 self.metrics.pool_abandoned.with_label_values(&[lane]).get()
903 }
904
905 #[cfg(test)]
906 pub fn rejected_at_proposal_count(&self, error: &str) -> u64 {
907 self.metrics
908 .pool_rejected_at_proposal
909 .with_label_values(&[error])
910 .get()
911 }
912}
913
914#[derive(Clone, Copy)]
915enum TakenLane {
916 System,
917 User,
918}
919
920impl TakenLane {
921 fn label(self) -> &'static str {
922 match self {
923 TakenLane::System => "system",
924 TakenLane::User => "user",
925 }
926 }
927}
928
929struct TakenEntry {
930 lane: TakenLane,
931 entry: PoolEntry,
932}
933
934impl TakenEntry {
935 fn new(lane: TakenLane, mut entry: PoolEntry) -> Self {
938 if let Some(slot) = &mut entry.staggered_slot {
939 slot.release();
940 }
941 Self { lane, entry }
942 }
943}
944
945struct TakenTransactionsGuard {
952 epoch: EpochId,
953 inner: Arc<Mutex<Inner>>,
954 metrics: Arc<AdmissionQueueMetrics>,
955 entries: Option<Vec<TakenEntry>>,
956 pings: Option<Vec<PendingAck>>,
957}
958
959impl TakenTransactionsGuard {
960 fn acknowledge(mut self, block_ref: BlockRef) {
961 let entries = self.entries.take().expect("acknowledgement called once");
962 let pings = self.pings.take().expect("acknowledgement called once");
963 let mut inner = self.inner.lock();
964 let Inner::Open(pool) = &mut *inner else {
965 drop(inner);
966 Self::resolve_after_close(entries, pings, "pool closed before acknowledgement");
967 return;
968 };
969
970 let mut next_index = 0usize;
973 let mut watches_to_drop = Vec::with_capacity(entries.len());
974 let mut block = ProposedBlock::default();
975 for taken in entries {
976 let lane = taken.lane;
977 let mut entry = taken.entry;
978 block.entries.push(ProposedEntry {
979 lane,
980 tx_type: entry.tx_type,
981 proposers: entry.proposers,
982 created: entry.ack.created,
983 });
984 watches_to_drop.push(std::mem::take(&mut entry.processed));
985 let start = next_index;
986 next_index += entry.transactions.len();
987 let indices = (start..next_index)
988 .map(|index| {
989 TransactionIndex::try_from(index)
990 .expect("validated transaction index must fit TransactionIndex")
991 })
992 .collect::<Vec<_>>();
993 self.metrics
994 .queue_wait_latency
995 .with_label_values(&[lane.label(), entry.proposers])
996 .observe(entry.ack.created.elapsed().as_secs_f64());
997 entry
998 .ack
999 .resolve_included(self.epoch, block_ref, indices, &mut block);
1000 }
1001 for ping in pings {
1002 self.metrics
1003 .queue_wait_latency
1004 .with_label_values(&["ping", "na"])
1005 .observe(ping.created.elapsed().as_secs_f64());
1006 ping.resolve_included(
1007 self.epoch,
1008 block_ref,
1009 vec![PING_TRANSACTION_INDEX],
1010 &mut block,
1011 );
1012 }
1013 if !block.subscribers.is_empty() || !block.entries.is_empty() {
1014 pool.blocks.insert(block_ref, block);
1015 }
1016 drop(inner);
1017 drop(watches_to_drop); }
1019
1020 fn resolve_after_close(entries: Vec<TakenEntry>, pings: Vec<PendingAck>, reason: &'static str) {
1024 for taken in entries {
1025 taken
1026 .entry
1027 .ack
1028 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1029 }
1030 for ping in pings {
1031 ping.drop_deliberately(reason);
1032 }
1033 }
1034}
1035
1036impl Drop for TakenTransactionsGuard {
1037 fn drop(&mut self) {
1038 let Some(entries) = self.entries.take() else {
1039 return;
1040 };
1041 let pings = self.pings.take().unwrap_or_default();
1042 let mut inner = self.inner.lock();
1043 let Inner::Open(pool) = &mut *inner else {
1044 drop(inner);
1045 Self::resolve_after_close(entries, pings, "pool closed before dropped acknowledgement");
1046 return;
1047 };
1048
1049 let mut requeued = 0;
1050 let mut halted = Vec::new();
1051 for mut taken in entries.into_iter().rev() {
1054 match taken.lane {
1055 TakenLane::System => {
1056 requeued += 1;
1057 self.metrics.pool_depth.with_label_values(&["system"]).inc();
1058 self.metrics
1059 .pool_bytes
1060 .with_label_values(&["system"])
1061 .add(taken.entry.total_bytes as i64);
1062 pool.system.push_front(taken.entry);
1063 let depth = pool.system.len();
1064 if depth == SYSTEM_LANE_WARN_THRESHOLD {
1065 warn!(
1066 depth,
1067 "Consensus transaction pool system lane exceeded warning threshold"
1068 );
1069 }
1070 }
1071 TakenLane::User => match &mut pool.user {
1072 UserLane::Open(user) => {
1073 requeued += 1;
1074 self.metrics.pool_depth.with_label_values(&["user"]).inc();
1075 self.metrics
1076 .pool_bytes
1077 .with_label_values(&["user"])
1078 .add(taken.entry.total_bytes as i64);
1079 if let Some(slot) = &mut taken.entry.staggered_slot {
1080 slot.rearm();
1081 }
1082 if let Some(budget) = &mut pool.user_byte_budget {
1083 budget.refund(taken.entry.total_bytes);
1084 }
1085 user.reinsert_front(taken.entry);
1086 }
1087 UserLane::Closed => halted.push(taken.entry),
1088 },
1089 }
1090 }
1091 for ping in pings.into_iter().rev() {
1092 pool.pings.push_front(ping);
1093 self.metrics.pool_depth.with_label_values(&["ping"]).inc();
1094 }
1095 self.metrics
1096 .pool_requeued_on_dropped_ack
1097 .inc_by(requeued as u64);
1098 drop(inner);
1099
1100 for entry in halted {
1101 entry
1102 .ack
1103 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1104 }
1105 }
1106}
1107
1108impl TransactionPool for ConsensusTransactionPool {
1109 fn take(
1110 &self,
1111 max_count: usize,
1112 max_bytes: usize,
1113 ) -> (
1114 Vec<Transaction>,
1115 Box<dyn FnOnce(BlockRef) + Send>,
1116 LimitReached,
1117 ) {
1118 let _scope = monitored_scope("ConsensusTransactionPool::take");
1119 let should_accept_user_certs = self
1123 .epoch_store
1124 .get_reconfig_state_read_lock_guard()
1125 .should_accept_user_certs();
1126 #[allow(unused_mut)]
1127 let mut disabled = false;
1128 fail_point_if!("consensus_transaction_pool_disable_take", || {
1129 disabled = true;
1130 });
1131 #[allow(unused_mut)]
1132 let mut user_take_disabled = false;
1133 fail_point_if!("consensus_transaction_pool_disable_user_take", || {
1134 user_take_disabled = true;
1135 });
1136
1137 let mut inner = self.inner.lock();
1138 let pool = match &mut *inner {
1139 Inner::Open(pool) => pool,
1140 Inner::Closed => {
1141 return (
1142 Vec::new(),
1143 Box::new(|_| {}),
1144 LimitReached::AllTransactionsIncluded,
1145 );
1146 }
1147 };
1148 let flushed = if !should_accept_user_certs {
1149 pool.user.close()
1150 } else {
1151 Vec::new()
1152 };
1153 if disabled {
1154 drop(inner);
1155 self.resolve_flushed_user_entries(flushed); return (
1157 Vec::new(),
1158 Box::new(|_| {}),
1159 LimitReached::AllTransactionsIncluded,
1160 );
1161 }
1162
1163 let (abandoned_pings, pings): (Vec<_>, Vec<_>) = pool
1164 .pings
1165 .drain(..)
1166 .partition(PendingAck::is_abandoned_system);
1167 self.metrics
1168 .pool_depth
1169 .with_label_values(&["ping"])
1170 .sub((pings.len() + abandoned_pings.len()) as i64);
1171
1172 let mut transactions = Vec::new();
1173 let mut entries = Vec::new();
1174 let mut system_tx_total_bytes = 0usize;
1175 let mut limit_reached = LimitReached::AllTransactionsIncluded;
1176
1177 let mut abandoned = Vec::new();
1178 while let Some(entry) = pool.system.front() {
1179 if entry.ack.is_abandoned_system() {
1182 let entry = pool.system.pop_front().expect("front entry must exist");
1183 self.decrement_lane_metrics("system", &entry);
1184 abandoned.push(entry);
1185 continue;
1186 }
1187 if let Some(limit) = entry_limit(
1188 entry,
1189 transactions.len(),
1190 system_tx_total_bytes,
1191 max_count,
1192 max_bytes,
1193 ) {
1194 limit_reached = limit;
1195 break;
1196 }
1197 let entry = pool.system.pop_front().expect("front entry must exist");
1198 system_tx_total_bytes += entry.total_bytes;
1199 transactions.extend(entry.transactions.iter().cloned());
1200 self.decrement_lane_metrics("system", &entry);
1201 entries.push(TakenEntry::new(TakenLane::System, entry));
1202 }
1203
1204 let now = Instant::now();
1205 let mut excluded = Vec::new();
1206 let swept_user = match (self.max_queue_age, &mut pool.user) {
1211 (Some(ttl), UserLane::Open(user)) => {
1212 let mut swept = 0;
1213 user.pop_front_while(|entry| {
1214 let expired = swept < MAX_EXPIRED_PER_PROPOSAL
1215 && now.saturating_duration_since(entry.ack.created) >= ttl;
1216 swept += usize::from(expired);
1217 expired
1218 })
1219 }
1220 _ => Vec::new(),
1221 };
1222 if !user_take_disabled
1223 && matches!(limit_reached, LimitReached::AllTransactionsIncluded)
1224 && let UserLane::Open(user) = &mut pool.user
1225 {
1226 let mut pending_count = transactions.len();
1227 let mut pending_bytes = system_tx_total_bytes;
1228 let user_max_bytes = match &mut pool.user_byte_budget {
1229 Some(budget) => {
1230 max_bytes.min(system_tx_total_bytes.saturating_add(budget.available(now)))
1231 }
1232 None => max_bytes,
1233 };
1234 let popped;
1235 (popped, excluded) = user.pop_batch_while(|entry| {
1236 if all_processed(&mut entry.processed).is_some() {
1240 return PopAction::Exclude;
1241 }
1242 if entry
1248 .staggered_slot
1249 .as_ref()
1250 .is_some_and(|slot| slot.eligible_at() > now)
1251 {
1252 return PopAction::Skip;
1253 }
1254 if let Err(error) = self
1257 .cache_writer
1258 .validate_owned_object_versions(&entry.owned_object_refs)
1259 {
1260 entry.rejection = Some(error);
1261 return PopAction::Exclude;
1262 }
1263 match entry_limit(
1264 entry,
1265 pending_count,
1266 pending_bytes,
1267 max_count,
1268 user_max_bytes,
1269 ) {
1270 Some(limit) => {
1271 limit_reached = limit;
1272 PopAction::Stop
1273 }
1274 None => {
1275 pending_count += entry.transactions.len();
1276 pending_bytes += entry.total_bytes;
1277 PopAction::Include
1278 }
1279 }
1280 });
1281 if let Some(budget) = &mut pool.user_byte_budget {
1282 budget.consume(pending_bytes.saturating_sub(system_tx_total_bytes));
1283 self.metrics
1284 .pool_byte_budget_bytes
1285 .set(budget.tokens as i64);
1286 if user_max_bytes < max_bytes && limit_reached == LimitReached::MaxBytes {
1287 self.metrics.pool_byte_budget_exhausted.inc();
1288 }
1289 }
1290 for entry in popped {
1291 self.decrement_lane_metrics("user", &entry);
1292 transactions.extend(entry.transactions.iter().cloned());
1293 entries.push(TakenEntry::new(TakenLane::User, entry));
1294 }
1295 }
1296 drop(inner);
1297
1298 self.resolve_flushed_user_entries(flushed);
1300 for entry in swept_user {
1301 self.resolve_swept_user_entry(entry);
1302 }
1303 for entry in abandoned {
1304 self.metrics
1305 .pool_abandoned
1306 .with_label_values(&["system"])
1307 .inc();
1308 entry
1309 .ack
1310 .drop_deliberately("submitter stopped waiting before proposal");
1311 }
1312 for ping in abandoned_pings {
1313 self.metrics
1314 .pool_abandoned
1315 .with_label_values(&["ping"])
1316 .inc();
1317 ping.drop_deliberately("submitter stopped waiting before proposal");
1318 }
1319 for mut entry in excluded {
1320 self.decrement_lane_metrics("user", &entry);
1321 if let Some(error) = entry.rejection.take() {
1322 self.resolve_rejected(entry, error);
1323 continue;
1324 }
1325 let method = all_processed(&mut entry.processed)
1326 .expect("excluded entries are already processed");
1327 self.resolve_already_processed(entry, "proposal", method);
1328 }
1329
1330 self.metrics
1331 .pool_taken_per_proposal
1332 .observe(transactions.len() as f64);
1333 let guard = TakenTransactionsGuard {
1334 epoch: self.epoch(),
1335 inner: self.inner.clone(),
1336 metrics: self.metrics.clone(),
1337 entries: Some(entries),
1338 pings: Some(pings),
1339 };
1340 (
1341 transactions,
1342 Box::new(move |block_ref| guard.acknowledge(block_ref)),
1343 limit_reached,
1344 )
1345 }
1346
1347 fn notify_committed(&self, own_committed_blocks: Vec<BlockRef>, gc_round: Round) {
1351 let mut sequenced = Vec::new();
1352 let mut garbage_collected = Vec::new();
1353 {
1354 let mut inner = self.inner.lock();
1355 let Inner::Open(pool) = &mut *inner else {
1356 return;
1357 };
1358 for block_ref in own_committed_blocks {
1359 if let Some(block) = pool.blocks.remove(&block_ref) {
1360 for subscriber in block.subscribers {
1361 let _ = subscriber.send(BlockStatus::Sequenced(block_ref));
1362 }
1363 sequenced.extend(block.entries);
1364 }
1365 }
1366 while let Some((block_ref, block)) = pool.blocks.pop_first() {
1367 if block_ref.round > gc_round {
1368 pool.blocks.insert(block_ref, block);
1369 break;
1370 }
1371 self.metrics
1372 .pool_gc_notified
1373 .inc_by(block.subscribers.len() as u64);
1374 for subscriber in block.subscribers {
1375 let _ = subscriber.send(BlockStatus::GarbageCollected(block_ref));
1376 }
1377 garbage_collected.extend(block.entries);
1378 }
1379 }
1380
1381 self.report_proposed_status(&sequenced, "sequenced");
1383 self.report_proposed_status(&garbage_collected, "garbage_collected");
1384 self.report_commit_latency(&sequenced);
1385 }
1386}
1387
1388impl ConsensusTransactionPool {
1389 fn report_proposed_status(&self, entries: &[ProposedEntry], status: &'static str) {
1393 let mut counts: Vec<(&'static str, u64)> = Vec::new();
1394 for entry in entries
1395 .iter()
1396 .filter(|entry| matches!(entry.lane, TakenLane::User))
1397 {
1398 match counts
1399 .iter_mut()
1400 .find(|(tx_type, _)| *tx_type == entry.tx_type)
1401 {
1402 Some((_, count)) => *count += 1,
1403 None => counts.push((entry.tx_type, 1)),
1404 }
1405 }
1406 for (tx_type, count) in counts {
1407 self.adapter_metrics
1408 .sequencing_certificate_status
1409 .with_label_values(&[tx_type, status])
1410 .inc_by(count);
1411 }
1412 }
1413
1414 fn report_commit_latency(&self, entries: &[ProposedEntry]) {
1415 let now = Instant::now();
1416 for entry in entries {
1417 let lane = match entry.lane {
1418 TakenLane::User => "user",
1419 TakenLane::System => "system",
1420 };
1421 let histogram = self
1422 .metrics
1423 .pool_commit_latency
1424 .with_label_values(&[lane, entry.proposers]);
1425 histogram.observe(now.saturating_duration_since(entry.created).as_secs_f64());
1426 }
1427 }
1428}
1429
1430fn entry_limit(
1433 entry: &PoolEntry,
1434 current_count: usize,
1435 current_bytes: usize,
1436 max_count: usize,
1437 max_bytes: usize,
1438) -> Option<LimitReached> {
1439 if current_bytes.saturating_add(entry.total_bytes) > max_bytes {
1440 return Some(LimitReached::MaxBytes);
1441 }
1442 if current_count.saturating_add(entry.transactions.len()) > max_count {
1443 return Some(LimitReached::MaxNumOfTransactions);
1444 }
1445 None
1446}
1447
1448fn consensus_client_error(error: ClientError) -> SuiError {
1449 SuiErrorKind::FailedToSubmitToConsensus(error.to_string()).into()
1450}
1451
1452fn owned_object_refs(transactions: &[ConsensusTransaction]) -> Vec<ObjectRef> {
1456 let mut refs = Vec::new();
1457 for transaction in transactions {
1458 let ConsensusTransactionKind::UserTransactionV2(tx) = &transaction.kind else {
1459 continue;
1460 };
1461 let immutable = tx.get_immutable_objects();
1462 let Ok(input_objects) = tx.tx().data().transaction_data().input_objects() else {
1463 continue;
1464 };
1465 refs.extend(input_objects.into_iter().filter_map(|input| match input {
1466 InputObjectKind::ImmOrOwnedMoveObject(obj_ref) if !immutable.contains(&obj_ref.0) => {
1467 Some(obj_ref)
1468 }
1469 _ => None,
1470 }));
1471 }
1472 refs
1473}
1474
1475#[derive(Clone)]
1476enum PoolState {
1477 Absent,
1479 Active(EpochId, Weak<ConsensusTransactionPool>),
1480 Unavailable(EpochId),
1483}
1484
1485pub struct TransactionPoolContext {
1494 state: watch::Sender<PoolState>,
1495 metrics: Arc<AdmissionQueueMetrics>,
1496 adapter_metrics: ConsensusAdapterMetrics,
1497 cache_writer: Arc<dyn ExecutionCacheWrite>,
1498}
1499
1500impl TransactionPoolContext {
1501 pub fn new(
1502 metrics: Arc<AdmissionQueueMetrics>,
1503 adapter_metrics: ConsensusAdapterMetrics,
1504 cache_writer: Arc<dyn ExecutionCacheWrite>,
1505 ) -> Self {
1506 let (state, _) = watch::channel(PoolState::Absent);
1507 Self {
1508 state,
1509 metrics,
1510 adapter_metrics,
1511 cache_writer,
1512 }
1513 }
1514
1515 pub fn set_active(&self, epoch: EpochId, pool: Arc<ConsensusTransactionPool>) {
1516 assert_eq!(epoch, pool.epoch());
1517 drop(
1518 self.state
1519 .send_replace(PoolState::Active(epoch, Arc::downgrade(&pool))),
1520 );
1521 }
1522
1523 pub fn set_unavailable(&self, epoch: EpochId) {
1526 drop(self.state.send_replace(PoolState::Unavailable(epoch)));
1527 }
1528
1529 pub async fn try_insert(
1530 &self,
1531 epoch: EpochId,
1532 gas_price: u64,
1533 transactions: Vec<ConsensusTransaction>,
1534 ) -> SuiResult<(PositionReceiver, bool)> {
1535 self.wait_for_pool(epoch)
1536 .await?
1537 .try_insert(epoch, gas_price, transactions)
1538 }
1539
1540 pub async fn wait_for_pool(
1548 &self,
1549 caller_epoch: EpochId,
1550 ) -> SuiResult<Arc<ConsensusTransactionPool>> {
1551 self.wait_for_pool_after_subscribe(caller_epoch, || {})
1552 .await
1553 }
1554
1555 async fn wait_for_pool_after_subscribe(
1556 &self,
1557 caller_epoch: EpochId,
1558 after_subscribe: impl FnOnce(),
1559 ) -> SuiResult<Arc<ConsensusTransactionPool>> {
1560 let mut receiver = self.state.subscribe();
1561 after_subscribe();
1562 let start = Instant::now();
1563 loop {
1564 let state = receiver.borrow_and_update().clone();
1565 match state {
1566 PoolState::Active(epoch, pool) if epoch == caller_epoch => {
1567 return pool
1570 .upgrade()
1571 .ok_or_else(|| SuiErrorKind::TooManyTransactionsPendingConsensus.into());
1572 }
1573 PoolState::Active(epoch, _) if epoch > caller_epoch => {
1574 return Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1575 }
1576 PoolState::Unavailable(epoch) if epoch > caller_epoch => {
1577 return Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1578 }
1579 PoolState::Unavailable(epoch) if epoch == caller_epoch => {
1580 return Err(SuiErrorKind::TooManyTransactionsPendingConsensus.into());
1581 }
1582 PoolState::Absent | PoolState::Active(_, _) | PoolState::Unavailable(_) => {}
1583 }
1584
1585 let _waiting = WaitingInsertGuard::new(self.metrics.pool_waiting_inserts.clone());
1586 match tokio::time::timeout(Duration::from_secs(10), receiver.changed()).await {
1587 Ok(Ok(())) => {}
1588 Ok(Err(_)) => {
1589 debug_fatal!("transaction pool watch sender dropped while context is alive");
1594 return Err(SuiErrorKind::TooManyTransactionsPendingConsensus.into());
1595 }
1596 Err(_) => {
1597 warn!(
1598 caller_epoch,
1599 elapsed = ?start.elapsed(),
1600 "Waiting for consensus transaction pool to initialize"
1601 );
1602 }
1603 }
1604 }
1605 }
1606
1607 pub fn metrics(&self) -> &Arc<AdmissionQueueMetrics> {
1608 &self.metrics
1609 }
1610
1611 pub fn adapter_metrics(&self) -> &ConsensusAdapterMetrics {
1612 &self.adapter_metrics
1613 }
1614
1615 pub fn cache_writer(&self) -> &Arc<dyn ExecutionCacheWrite> {
1616 &self.cache_writer
1617 }
1618
1619 #[cfg(test)]
1620 pub(crate) fn new_for_tests(
1621 metrics: Arc<AdmissionQueueMetrics>,
1622 cache_writer: Arc<dyn ExecutionCacheWrite>,
1623 ) -> Self {
1624 Self::new(metrics, ConsensusAdapterMetrics::new_test(), cache_writer)
1625 }
1626}
1627
1628struct WaitingInsertGuard {
1629 gauge: IntGauge,
1630}
1631
1632impl WaitingInsertGuard {
1633 fn new(gauge: IntGauge) -> Self {
1634 gauge.inc();
1635 Self { gauge }
1636 }
1637}
1638
1639impl Drop for WaitingInsertGuard {
1640 fn drop(&mut self) {
1641 self.gauge.dec();
1642 }
1643}
1644
1645pub struct TransactionPoolClient {
1650 context: Arc<TransactionPoolContext>,
1651}
1652
1653impl TransactionPoolClient {
1654 pub fn new(context: Arc<TransactionPoolContext>) -> Self {
1655 Self { context }
1656 }
1657}
1658
1659#[async_trait]
1660impl ConsensusClient for TransactionPoolClient {
1661 async fn submit(
1662 &self,
1663 transactions: &[ConsensusTransaction],
1664 epoch_store: &Arc<AuthorityPerEpochStore>,
1665 ) -> SuiResult<(Vec<ConsensusPosition>, BlockStatusReceiver)> {
1666 let epoch = epoch_store.epoch();
1667 let pool = self.context.wait_for_pool(epoch).await?;
1668 let receiver = pool.submit(epoch, transactions)?;
1669 let (block_ref, indices, status_receiver) = receiver.await.map_err(|error| {
1670 consensus_client_error(ClientError::ConsensusShuttingDown(error.to_string()))
1671 })?;
1672 let positions = indices
1673 .into_iter()
1674 .map(|index| ConsensusPosition {
1675 epoch,
1676 block: block_ref,
1677 index,
1678 })
1679 .collect();
1680 Ok((positions, status_receiver))
1681 }
1682}
1683
1684#[cfg(test)]
1685mod tests {
1686 use super::*;
1687 use crate::authority::AuthorityState;
1688 use crate::authority::test_authority_builder::TestAuthorityBuilder;
1689 use consensus_config::AuthorityIndex;
1690 use consensus_types::block::BlockDigest;
1691 use sui_macros::sim_test;
1692 use sui_protocol_config::ProtocolConfig;
1693 use sui_types::base_types::{AuthorityName, ObjectID, random_object_ref};
1694 use sui_types::crypto::{AccountKeyPair, get_key_pair};
1695 use sui_types::error::UserInputError;
1696 use sui_types::object::{Object, Owner};
1697 use sui_types::transaction::PlainTransactionWithClaims;
1698
1699 async fn test_state_and_pool(
1700 capacity: usize,
1701 ) -> (Arc<AuthorityState>, Arc<ConsensusTransactionPool>) {
1702 let state = TestAuthorityBuilder::new().build().await;
1703 let pool = pool_for_current_epoch(&state, capacity);
1704 (state, pool)
1705 }
1706
1707 async fn test_state_and_pool_with_protocol_config(
1708 capacity: usize,
1709 protocol_config: ProtocolConfig,
1710 ) -> (Arc<AuthorityState>, Arc<ConsensusTransactionPool>) {
1711 let state = TestAuthorityBuilder::new()
1712 .with_protocol_config(protocol_config)
1713 .build()
1714 .await;
1715 let pool = pool_for_current_epoch(&state, capacity);
1716 (state, pool)
1717 }
1718
1719 fn pool_for_current_epoch(
1720 state: &Arc<AuthorityState>,
1721 capacity: usize,
1722 ) -> Arc<ConsensusTransactionPool> {
1723 Arc::new(ConsensusTransactionPool::new_for_tests(
1724 state.epoch_store_for_testing().clone(),
1725 state.get_cache_writer().clone(),
1726 capacity,
1727 Arc::new(AdmissionQueueMetrics::new_for_tests()),
1728 ))
1729 }
1730
1731 async fn test_state_and_configured_pool(
1735 byte_budget: Option<(u64, u64)>,
1736 max_queue_age: Option<Duration>,
1737 ) -> (Arc<AuthorityState>, Arc<ConsensusTransactionPool>) {
1738 let state = TestAuthorityBuilder::new().build().await;
1739 let pool = Arc::new(ConsensusTransactionPool::new(
1740 state.epoch_store_for_testing().clone(),
1741 state.get_cache_writer().clone(),
1742 10,
1743 byte_budget.map(|(_, per_second)| per_second),
1744 max_queue_age,
1745 Arc::new(AdmissionQueueMetrics::new_for_tests()),
1746 ConsensusAdapterMetrics::new_test(),
1747 ));
1748 if let Some((max_block_bytes, max_user_bytes_per_second)) = byte_budget {
1749 let mut inner = pool.inner.lock();
1750 let Inner::Open(open) = &mut *inner else {
1751 unreachable!("new pool must be open");
1752 };
1753 open.user_byte_budget = Some(Box::new(UserByteBudget::new(
1754 max_user_bytes_per_second,
1755 max_block_bytes,
1756 Instant::now(),
1757 )));
1758 }
1759 (state, pool)
1760 }
1761
1762 fn user_byte_budget_tokens(pool: &ConsensusTransactionPool) -> f64 {
1763 let inner = pool.inner.lock();
1764 let Inner::Open(open) = &*inner else {
1765 unreachable!("pool must be open");
1766 };
1767 open.user_byte_budget.as_ref().unwrap().tokens
1768 }
1769
1770 fn transaction() -> ConsensusTransaction {
1771 ConsensusTransaction::new_end_of_publish(AuthorityName::ZERO)
1772 }
1773
1774 fn user_transaction(state: &AuthorityState) -> ConsensusTransaction {
1777 let (sender, _) = get_key_pair::<AccountKeyPair>();
1778 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
1779 let gas_object_ref = gas_object.compute_object_reference();
1780 state.insert_genesis_object(gas_object);
1781 user_transaction_with_gas(gas_object_ref)
1782 }
1783
1784 fn user_transaction_with_gas(gas_object_ref: ObjectRef) -> ConsensusTransaction {
1786 let (sender, keypair) = get_key_pair::<AccountKeyPair>();
1787 let transaction = crate::test_utils::make_transfer_sui_transaction(
1788 gas_object_ref,
1789 sender,
1790 Some(1),
1791 sender,
1792 &keypair,
1793 1,
1794 );
1795 ConsensusTransaction::new_user_transaction_v2_message(
1796 &AuthorityName::ZERO,
1797 PlainTransactionWithClaims::no_aliases(transaction),
1798 )
1799 }
1800
1801 fn block(round: Round) -> BlockRef {
1802 BlockRef::new(round, AuthorityIndex::new_for_test(0), BlockDigest::MIN)
1803 }
1804
1805 fn consensus_key(transaction: &ConsensusTransaction) -> SequencedConsensusTransactionKey {
1806 SequencedConsensusTransactionKey::External(transaction.key())
1807 }
1808
1809 fn digest_of(transaction: &ConsensusTransaction) -> TransactionDigest {
1810 consensus_key(transaction)
1811 .user_transaction_digest()
1812 .expect("expected a user transaction key")
1813 }
1814
1815 #[cfg(debug_assertions)]
1816 #[tokio::test]
1817 #[should_panic(expected = "dropped without resolution")]
1818 async fn unresolved_pending_ack_panics() {
1819 let (sender, _receiver) = oneshot::channel();
1820 drop(PendingAck::new(
1821 EntryAck::User(sender),
1822 vec![transaction().key()],
1823 ));
1824 }
1825
1826 #[tokio::test]
1827 async fn user_lane_close_returns_pending_entries_and_closes_lane() {
1828 let metrics = Arc::new(AdmissionQueueMetrics::new_for_tests());
1829 let mut lane = UserLane::Open(PriorityAdmissionQueue::new(1, metrics.clone()));
1830 let consensus_transaction = transaction();
1831 let serialized = bcs::to_bytes(&consensus_transaction).unwrap();
1832 let (sender, receiver) = oneshot::channel();
1833 let entry = PoolEntry {
1834 transactions: vec![Transaction::new(serialized.clone())],
1835 total_bytes: serialized.len(),
1836 gas_price: 1,
1837 tx_type: tx_type_label(std::slice::from_ref(&consensus_transaction)),
1838 proposers: "na",
1839 ack: PendingAck::new(EntryAck::User(sender), vec![consensus_transaction.key()]),
1840 metrics,
1841 processed: Vec::new(),
1842 owned_object_refs: Vec::new(),
1843 rejection: None,
1844 staggered_slot: None,
1845 };
1846 let UserLane::Open(user) = &mut lane else {
1847 unreachable!("new user lane must be open");
1848 };
1849 user.insert(entry).unwrap();
1850
1851 let mut entries = lane.close();
1852 assert!(matches!(lane, UserLane::Closed));
1853 assert_eq!(entries.len(), 1);
1854 entries
1855 .pop()
1856 .unwrap()
1857 .ack
1858 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1859 assert!(matches!(
1860 receiver.await.unwrap().unwrap_err().as_inner(),
1861 SuiErrorKind::ValidatorHaltedAtEpochEnd
1862 ));
1863 }
1864
1865 #[sim_test]
1866 async fn locked_user_recheck_defuses_ack_after_pool_close() {
1867 let (_state, pool) = test_state_and_pool(1).await;
1868 pool.close();
1869 assert!(matches!(
1872 pool.try_insert(pool.epoch(), 1, vec![transaction()])
1873 .unwrap_err()
1874 .as_inner(),
1875 SuiErrorKind::ValidatorHaltedAtEpochEnd
1876 ));
1877 }
1878
1879 #[test]
1880 fn user_byte_budget_refills_at_rate_up_to_capacity() {
1881 let start = Instant::now();
1882 let mut budget = UserByteBudget::new(1_000, 4_000, start);
1883 assert_eq!(budget.available(start), 4_000);
1884 budget.consume(4_000);
1885 assert_eq!(budget.available(start), 0);
1886 assert_eq!(budget.available(start + Duration::from_millis(500)), 500);
1887 assert_eq!(budget.available(start + Duration::from_secs(10)), 4_000);
1888 budget.consume(1_000);
1889 budget.refund(10_000);
1890 assert_eq!(budget.available(start + Duration::from_secs(10)), 4_000);
1891
1892 let mut budget = UserByteBudget::new(5_000, 4_000, start);
1893 assert_eq!(budget.available(start), 5_000);
1894 }
1895
1896 #[sim_test]
1897 async fn take_rate_limits_user_bytes_but_not_system() {
1898 let serialized_len = bcs::to_bytes(&transaction()).unwrap().len();
1899 let block_bytes = u64::try_from(serialized_len * 2).unwrap();
1900 let (_state, pool) = test_state_and_configured_pool(Some((block_bytes, 1)), None).await;
1901 let epoch = pool.epoch();
1902 for gas_price in [10, 20, 30] {
1903 let (_receiver, _) = pool
1904 .try_insert(epoch, gas_price, vec![transaction()])
1905 .unwrap();
1906 }
1907
1908 let (transactions, ack, limit) = pool.take(usize::MAX, usize::MAX);
1909 assert_eq!(transactions.len(), 2);
1910 assert_eq!(limit, LimitReached::MaxBytes);
1911 ack(block(1));
1912 assert_eq!(pool.metrics.pool_byte_budget_exhausted.get(), 1);
1913
1914 let (transactions, ack, limit) = pool.take(usize::MAX, usize::MAX);
1915 assert!(transactions.is_empty());
1916 assert_eq!(limit, LimitReached::MaxBytes);
1917 drop(ack);
1918 assert_eq!(pool.queue_depth("user"), 1);
1919
1920 let _system_receiver = pool.submit(epoch, &[transaction()]).unwrap();
1921 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
1922 assert_eq!(transactions.len(), 1);
1923 assert_eq!(pool.queue_depth("system"), 0);
1924 assert_eq!(pool.queue_depth("user"), 1);
1925 drop(ack);
1926 pool.close();
1927 }
1928
1929 #[sim_test]
1930 async fn expiry_runs_when_system_lane_saturates_the_block() {
1931 let ttl = Duration::from_secs(1);
1932 let (state, pool) = test_state_and_configured_pool(None, Some(ttl)).await;
1933 let epoch = pool.epoch();
1934
1935 let (user_receiver, _) = pool
1936 .try_insert(epoch, 10, vec![user_transaction(&state)])
1937 .unwrap();
1938 assert_eq!(pool.queue_depth("user"), 1);
1939
1940 let _s1 = pool.submit(epoch, &[transaction()]).unwrap();
1943 let mut s2 = pool.submit(epoch, &[transaction()]).unwrap();
1944
1945 tokio::time::sleep(ttl + Duration::from_secs(1)).await;
1946
1947 let (transactions, ack, limit) = pool.take(1, usize::MAX);
1948 assert_eq!(transactions.len(), 1);
1949 assert_eq!(limit, LimitReached::MaxNumOfTransactions);
1950 ack(block(1));
1951
1952 assert_eq!(pool.queue_depth("user"), 0);
1953 assert_eq!(pool.queue_depth("system"), 1);
1954 assert!(s2.try_recv().is_err());
1955 assert!(matches!(
1956 user_receiver.await.unwrap().unwrap_err().as_inner(),
1957 SuiErrorKind::ValidatorOverloadedRetryAfter {
1958 retry_after_secs: 2
1959 }
1960 ));
1961 pool.close();
1962 }
1963
1964 #[sim_test]
1965 async fn expiry_releases_bytes_and_dedup_keys_without_refunding_budget() {
1966 let ttl = Duration::from_secs(1);
1967 let block_bytes = 1 << 20;
1968 let (state, pool) = test_state_and_configured_pool(Some((block_bytes, 1)), Some(ttl)).await;
1969 let epoch = pool.epoch();
1970 let user_tx = user_transaction(&state);
1971
1972 let (receiver, newly_inserted) = pool.try_insert(epoch, 10, vec![user_tx.clone()]).unwrap();
1973 assert!(newly_inserted);
1974 assert!(pool.metrics.pool_bytes.with_label_values(&["user"]).get() > 0);
1975
1976 tokio::time::sleep(ttl + Duration::from_secs(1)).await;
1977 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
1978 assert!(transactions.is_empty());
1979 drop(ack);
1980
1981 assert_eq!(pool.queue_depth("user"), 0);
1982 assert_eq!(
1983 pool.metrics.pool_bytes.with_label_values(&["user"]).get(),
1984 0
1985 );
1986 assert_eq!(pool.metrics.expired.get(), 1);
1987 assert_eq!(user_byte_budget_tokens(&pool), block_bytes as f64);
1988 assert!(matches!(
1989 receiver.await.unwrap().unwrap_err().as_inner(),
1990 SuiErrorKind::ValidatorOverloadedRetryAfter { .. }
1991 ));
1992
1993 let (_receiver, newly_inserted) = pool.try_insert(epoch, 10, vec![user_tx]).unwrap();
1994 assert!(newly_inserted);
1995 assert_eq!(pool.metrics.duplicate_inserts.get(), 0);
1996 pool.close();
1997 }
1998
1999 #[sim_test]
2000 async fn expiry_reports_already_processed_entries_as_processing() {
2001 let ttl = Duration::from_secs(1);
2002 let (state, pool) = test_state_and_configured_pool(None, Some(ttl)).await;
2003 let epoch = pool.epoch();
2004 let processed = user_transaction(&state);
2005 let (receiver, _) = pool.try_insert(epoch, 10, vec![processed.clone()]).unwrap();
2006
2007 state
2008 .epoch_store_for_testing()
2009 .process_notifications(std::iter::once(&consensus_key(&processed)));
2010 tokio::time::sleep(ttl + Duration::from_secs(1)).await;
2011 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
2012 assert!(transactions.is_empty());
2013 drop(ack);
2014
2015 assert_eq!(
2016 pool.already_processed_count("sweep", "consensus_message"),
2017 1
2018 );
2019 assert_eq!(pool.queue_depth("user"), 0);
2020 assert_eq!(
2021 pool.metrics.pool_bytes.with_label_values(&["user"]).get(),
2022 0
2023 );
2024 assert_eq!(pool.metrics.expired.get(), 0);
2025 assert!(matches!(
2026 receiver.await.unwrap().unwrap_err().as_inner(),
2027 SuiErrorKind::TransactionProcessing { .. }
2028 ));
2029 pool.close();
2030 }
2031
2032 #[sim_test]
2033 async fn requeued_entry_keeps_its_age() {
2034 let ttl = Duration::from_secs(10);
2035 let (state, pool) = test_state_and_configured_pool(None, Some(ttl)).await;
2036 let epoch = pool.epoch();
2037 let (receiver, _) = pool
2038 .try_insert(epoch, 10, vec![user_transaction(&state)])
2039 .unwrap();
2040
2041 tokio::time::sleep(ttl - Duration::from_secs(1)).await;
2042 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
2043 assert_eq!(transactions.len(), 1);
2044 drop(ack);
2045 assert_eq!(pool.queue_depth("user"), 1);
2046
2047 tokio::time::sleep(Duration::from_secs(2)).await;
2048 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
2049 assert!(transactions.is_empty());
2050 drop(ack);
2051 assert_eq!(pool.queue_depth("user"), 0);
2052 assert!(matches!(
2053 receiver.await.unwrap().unwrap_err().as_inner(),
2054 SuiErrorKind::ValidatorOverloadedRetryAfter { .. }
2055 ));
2056 pool.close();
2057 }
2058
2059 #[sim_test]
2060 async fn expiry_reports_consumed_owned_inputs_as_rejected() {
2061 let ttl = Duration::from_secs(1);
2062 let (state, pool) = test_state_and_configured_pool(None, Some(ttl)).await;
2063 let epoch = pool.epoch();
2064 let (sender, _) = get_key_pair::<AccountKeyPair>();
2065 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
2066 let gas_object_ref = gas_object.compute_object_reference();
2067 state.insert_genesis_object(gas_object);
2068 let (receiver, _) = pool
2069 .try_insert(epoch, 10, vec![user_transaction_with_gas(gas_object_ref)])
2070 .unwrap();
2071 state.get_cache_writer().write_object_entry_for_test(
2072 Object::with_id_owner_version_for_testing(
2073 gas_object_ref.0,
2074 gas_object_ref.1.next(),
2075 Owner::AddressOwner(sender),
2076 ),
2077 );
2078
2079 tokio::time::sleep(ttl + Duration::from_secs(1)).await;
2080 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
2081 assert!(transactions.is_empty());
2082 drop(ack);
2083
2084 assert_eq!(pool.queue_depth("user"), 0);
2085 assert_eq!(pool.metrics.expired.get(), 0);
2086 assert_eq!(
2087 pool.rejected_at_proposal_count("ObjectVersionUnavailableForConsumption"),
2088 1
2089 );
2090 assert!(matches!(
2091 receiver.await.unwrap().unwrap_err().as_inner(),
2092 SuiErrorKind::UserInputError {
2093 error: UserInputError::ObjectVersionUnavailableForConsumption { .. }
2094 }
2095 ));
2096 pool.close();
2097 }
2098
2099 #[sim_test]
2100 async fn dropped_ack_refunds_user_byte_budget() {
2101 let serialized_len = bcs::to_bytes(&transaction()).unwrap().len();
2102 let block_bytes = u64::try_from(serialized_len).unwrap();
2103 let (_state, pool) = test_state_and_configured_pool(Some((block_bytes, 1)), None).await;
2104 let epoch = pool.epoch();
2105 let (_receiver, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2106
2107 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
2108 assert_eq!(transactions.len(), 1);
2109 drop(ack);
2110 assert_eq!(pool.queue_depth("user"), 1);
2111
2112 let (transactions, ack, _) = pool.take(usize::MAX, usize::MAX);
2113 assert_eq!(transactions.len(), 1);
2114 ack(block(1));
2115
2116 let (transactions, ack, limit) = pool.take(usize::MAX, usize::MAX);
2117 assert!(transactions.is_empty());
2118 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
2119 drop(ack);
2120 pool.close();
2121 }
2122
2123 #[sim_test]
2124 async fn take_respects_priority_budgets_and_acknowledges_positions() {
2125 let (_state, pool) = test_state_and_pool(10).await;
2126 let epoch = pool.epoch();
2127 let system_receiver = pool.submit(epoch, &[transaction()]).unwrap();
2128 assert_eq!(pool.queue_depth("system"), 1);
2129 let (low_receiver, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2130 let (high_receiver, _) = pool.try_insert(epoch, 20, vec![transaction()]).unwrap();
2131
2132 let (transactions, ack, limit) = pool.take(2, usize::MAX);
2133 assert_eq!(transactions.len(), 2);
2134 assert_eq!(limit, LimitReached::MaxNumOfTransactions);
2135 assert_eq!(pool.queue_depth("system"), 0);
2136 let block_ref = block(5);
2137 ack(block_ref);
2138
2139 let (system_block, system_indices, system_status) = system_receiver.await.unwrap();
2140 assert_eq!(system_block, block_ref);
2141 assert_eq!(system_indices, vec![0]);
2142 let positions = high_receiver.await.unwrap().unwrap();
2143 assert_eq!(
2144 positions,
2145 vec![ConsensusPosition {
2146 epoch,
2147 block: block_ref,
2148 index: 1,
2149 }]
2150 );
2151 pool.notify_committed(vec![block_ref], 0);
2152 assert_eq!(
2153 system_status.await.unwrap(),
2154 BlockStatus::Sequenced(block_ref)
2155 );
2156
2157 let serialized_len = bcs::to_bytes(&transaction()).unwrap().len();
2158 let (transactions, ack, limit) = pool.take(1, serialized_len - 1);
2159 assert!(transactions.is_empty());
2160 assert_eq!(limit, LimitReached::MaxBytes);
2161 drop(ack);
2162
2163 let (transactions, ack, limit) = pool.take(1, serialized_len);
2164 assert_eq!(transactions.len(), 1);
2165 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
2166 drop(ack);
2167 assert_eq!(pool.queue_depth("user"), 1);
2168
2169 let (transactions, ack, _) = pool.take(1, serialized_len);
2170 assert_eq!(transactions.len(), 1);
2171 ack(block(6));
2172 assert_eq!(low_receiver.await.unwrap().unwrap()[0].index, 0);
2173 }
2174
2175 #[sim_test]
2176 async fn bundles_are_atomic() {
2177 let (_state, pool) = test_state_and_pool(10).await;
2178 let (_receiver, _) = pool
2179 .try_insert(pool.epoch(), 10, vec![transaction(), transaction()])
2180 .unwrap();
2181 let (transactions, ack, limit) = pool.take(1, usize::MAX);
2182 assert!(transactions.is_empty());
2183 assert_eq!(limit, LimitReached::MaxNumOfTransactions);
2184 drop(ack);
2185
2186 let bundle_bytes = 2 * bcs::to_bytes(&transaction()).unwrap().len();
2187 let (transactions, ack, limit) = pool.take(2, bundle_bytes - 1);
2188 assert!(transactions.is_empty());
2189 assert_eq!(limit, LimitReached::MaxBytes);
2190 drop(ack);
2191
2192 let (transactions, ack, _) = pool.take(2, bundle_bytes);
2193 assert_eq!(transactions.len(), 2);
2194 drop(ack);
2195 pool.close();
2196 }
2197
2198 #[sim_test]
2199 async fn dropped_ack_preserves_front_order() {
2200 let (_state, pool) = test_state_and_pool(10).await;
2201 let epoch = pool.epoch();
2202 let (first, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2203 let (mut second, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2204 let (_, ack, _) = pool.take(2, usize::MAX);
2205 drop(ack);
2206
2207 let (_, ack, _) = pool.take(1, usize::MAX);
2208 ack(block(1));
2209 assert_eq!(first.await.unwrap().unwrap()[0].index, 0);
2210 assert!(second.try_recv().is_err());
2211 pool.close();
2212 }
2213
2214 #[sim_test]
2215 async fn ping_capacity_and_reserved_index() {
2216 let (_state, pool) = test_state_and_pool(1).await;
2217 let epoch = pool.epoch();
2218 let mut receivers = Vec::with_capacity(MAX_PENDING_PINGS);
2219 for _ in 0..MAX_PENDING_PINGS {
2220 receivers.push(pool.submit(epoch, &[]).unwrap());
2221 }
2222 assert!(matches!(
2223 pool.submit(epoch, &[]).err().unwrap().as_inner(),
2224 SuiErrorKind::TooManyTransactionsPendingConsensus
2225 ));
2226
2227 let (transactions, ack, _) = pool.take(0, 0);
2228 assert!(transactions.is_empty());
2229 let block_ref = block(1);
2230 ack(block_ref);
2231 let (ping_block, indices, status) = receivers.remove(0).await.unwrap();
2232 assert_eq!(ping_block, block_ref);
2233 assert_eq!(indices, vec![PING_TRANSACTION_INDEX]);
2234 pool.notify_committed(vec![block_ref], 0);
2235 assert_eq!(status.await.unwrap(), BlockStatus::Sequenced(block_ref));
2236 }
2237
2238 #[sim_test]
2239 async fn system_lane_is_not_bounded_by_user_capacity() {
2240 let (_state, pool) = test_state_and_pool(1).await;
2241 let receivers = (0..3)
2242 .map(|_| pool.submit(pool.epoch(), &[transaction()]).unwrap())
2243 .collect::<Vec<_>>();
2244 assert_eq!(pool.queue_depth("system"), 3);
2245 let (transactions, ack, _) = pool.take(3, usize::MAX);
2246 assert_eq!(transactions.len(), 3);
2247 ack(block(1));
2248 for receiver in receivers {
2249 assert_eq!(receiver.await.unwrap().1.len(), 1);
2250 }
2251 }
2252
2253 #[sim_test]
2254 async fn take_skips_abandoned_system_entries_and_pings() {
2255 let (_state, pool) = test_state_and_pool(1).await;
2256 let epoch = pool.epoch();
2257
2258 let stale = pool.submit(epoch, &[transaction()]).unwrap();
2261 drop(stale);
2262 let live = pool.submit(epoch, &[transaction()]).unwrap();
2263 let stale_ping = pool.submit(epoch, &[]).unwrap();
2264 drop(stale_ping);
2265 let live_ping = pool.submit(epoch, &[]).unwrap();
2266
2267 let (transactions, ack, _) = pool.take(1, usize::MAX);
2268 assert_eq!(transactions.len(), 1);
2269 assert_eq!(pool.queue_depth("system"), 0);
2270 assert_eq!(pool.queue_depth("ping"), 0);
2271 assert_eq!(pool.abandoned_count("system"), 1);
2272 assert_eq!(pool.abandoned_count("ping"), 1);
2273
2274 ack(block(1));
2275 assert_eq!(live.await.unwrap().1.len(), 1);
2276 assert_eq!(live_ping.await.unwrap().1, vec![PING_TRANSACTION_INDEX]);
2277 }
2278
2279 #[sim_test]
2280 async fn eviction_rejection_and_duplicate_detection_match_admission_queue() {
2281 let (_state, pool) = test_state_and_pool(2).await;
2282 let epoch = pool.epoch();
2283 let (first, newly_inserted) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2284 assert!(newly_inserted);
2285 let (_second, newly_inserted) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2286 assert!(!newly_inserted);
2287
2288 let (_high, _) = pool.try_insert(epoch, 20, vec![transaction()]).unwrap();
2289 assert!(matches!(
2290 first.await.unwrap().unwrap_err().as_inner(),
2291 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price: 20 }
2292 ));
2293 assert!(matches!(
2294 pool.try_insert(epoch, 10, vec![transaction()])
2295 .err()
2296 .unwrap()
2297 .as_inner(),
2298 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price: 10 }
2299 ));
2300 pool.close();
2301 }
2302
2303 #[sim_test]
2304 async fn close_resolves_pending_and_taken_user_entries() {
2305 let (_state, pool) = test_state_and_pool(10).await;
2306 let epoch = pool.epoch();
2307 for wrong_epoch in [epoch + 1, epoch + 2] {
2308 assert!(matches!(
2309 pool.try_insert(wrong_epoch, 1, vec![transaction()])
2310 .err()
2311 .unwrap()
2312 .as_inner(),
2313 SuiErrorKind::ValidatorHaltedAtEpochEnd
2314 ));
2315 }
2316 let (pending, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2317 let (taken, _) = pool.try_insert(epoch, 20, vec![transaction()]).unwrap();
2318 let (_, ack, _) = pool.take(1, usize::MAX);
2319 pool.close();
2320
2321 assert!(matches!(
2322 pending.await.unwrap().unwrap_err().as_inner(),
2323 SuiErrorKind::ValidatorHaltedAtEpochEnd
2324 ));
2325 ack(block(1));
2326 assert!(matches!(
2327 taken.await.unwrap().unwrap_err().as_inner(),
2328 SuiErrorKind::ValidatorHaltedAtEpochEnd
2329 ));
2330 assert!(matches!(
2331 pool.try_insert(epoch, 30, vec![transaction()])
2332 .err()
2333 .unwrap()
2334 .as_inner(),
2335 SuiErrorKind::ValidatorHaltedAtEpochEnd
2336 ));
2337 }
2338
2339 #[sim_test]
2340 async fn dropped_ack_after_close_does_not_requeue() {
2341 let (_state, pool) = test_state_and_pool(10).await;
2342 let epoch = pool.epoch();
2343 let (receiver, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2344 let system_receiver = pool.submit(epoch, &[transaction()]).unwrap();
2345 let (_, ack, _) = pool.take(2, usize::MAX);
2346 pool.close();
2347 drop(ack);
2348 assert!(matches!(
2349 receiver.await.unwrap().unwrap_err().as_inner(),
2350 SuiErrorKind::ValidatorHaltedAtEpochEnd
2351 ));
2352 assert!(system_receiver.await.is_err());
2353 assert_eq!(pool.queue_depth("user"), 0);
2354 }
2355
2356 #[sim_test]
2357 async fn take_closes_user_lane_when_epoch_stops_accepting_user_certs() {
2358 let (state, pool) = test_state_and_pool(10).await;
2359 let epoch = pool.epoch();
2360 let (user_receiver, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2361 let system_receiver = pool.submit(epoch, &[transaction()]).unwrap();
2362
2363 let epoch_store = state.epoch_store_for_testing();
2364 epoch_store.close_user_certs_for_manual_epoch_close(
2365 epoch_store.get_reconfig_state_write_lock_guard(),
2366 );
2367 let (transactions, ack, limit) = pool.take(10, usize::MAX);
2368 assert_eq!(transactions.len(), 1);
2369 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
2370 assert!(matches!(
2371 user_receiver.await.unwrap().unwrap_err().as_inner(),
2372 SuiErrorKind::ValidatorHaltedAtEpochEnd
2373 ));
2374 assert!(matches!(
2375 pool.try_insert(epoch, 20, vec![transaction()])
2376 .err()
2377 .unwrap()
2378 .as_inner(),
2379 SuiErrorKind::ValidatorHaltedAtEpochEnd
2380 ));
2381
2382 let block_ref = block(1);
2383 ack(block_ref);
2384 assert_eq!(system_receiver.await.unwrap().0, block_ref);
2385 }
2386
2387 #[sim_test]
2388 async fn dropped_ack_after_user_lane_close_resolves_taken_entries() {
2389 let (state, pool) = test_state_and_pool(10).await;
2390 let epoch = pool.epoch();
2391 let (taken, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
2392 let (transactions, ack, _) = pool.take(1, usize::MAX);
2393 assert_eq!(transactions.len(), 1);
2394
2395 let epoch_store = state.epoch_store_for_testing();
2397 epoch_store.close_user_certs_for_manual_epoch_close(
2398 epoch_store.get_reconfig_state_write_lock_guard(),
2399 );
2400 let (flushed, flush_ack, _) = pool.take(10, usize::MAX);
2401 assert!(flushed.is_empty());
2402 drop(flush_ack);
2403
2404 drop(ack);
2407 assert!(matches!(
2408 taken.await.unwrap().unwrap_err().as_inner(),
2409 SuiErrorKind::ValidatorHaltedAtEpochEnd
2410 ));
2411 assert_eq!(pool.queue_depth("user"), 0);
2412 }
2413
2414 #[sim_test]
2415 async fn notify_committed_sequences_commits_before_garbage_collection() {
2416 let (_state, pool) = test_state_and_pool(10).await;
2417 let epoch = pool.epoch();
2418 let first = pool.submit(epoch, &[transaction()]).unwrap();
2419 let (_, ack, _) = pool.take(1, usize::MAX);
2420 let first_block = block(1);
2421 ack(first_block);
2422 let (_, _, first_status) = first.await.unwrap();
2423
2424 let second = pool.submit(epoch, &[transaction()]).unwrap();
2425 let (_, ack, _) = pool.take(1, usize::MAX);
2426 let second_block = block(2);
2427 ack(second_block);
2428 let (_, _, second_status) = second.await.unwrap();
2429
2430 pool.notify_committed(vec![second_block], 1);
2431 assert_eq!(
2432 second_status.await.unwrap(),
2433 BlockStatus::Sequenced(second_block)
2434 );
2435 assert_eq!(
2436 first_status.await.unwrap(),
2437 BlockStatus::GarbageCollected(first_block)
2438 );
2439 }
2440
2441 #[sim_test]
2442 async fn notify_committed_reports_proposed_user_outcomes() {
2443 let (state, pool) = test_state_and_pool(10).await;
2444 let epoch = pool.epoch();
2445 let status = |status: &str| {
2446 pool.adapter_metrics
2447 .sequencing_certificate_status
2448 .with_label_values(&["owned_user_transaction_v2", status])
2449 .get()
2450 };
2451 let commit_latency_count = |lane: &str, proposers: &str| {
2452 pool.metrics
2453 .pool_commit_latency
2454 .with_label_values(&[lane, proposers])
2455 .get_sample_count()
2456 };
2457
2458 let (_first, _) = pool
2460 .try_insert(epoch, 1, vec![user_transaction(&state)])
2461 .unwrap();
2462 let (_, ack, _) = pool.take(1, usize::MAX);
2463 ack(block(1));
2464 let (_second, _) = pool
2466 .try_insert(epoch, 1, vec![user_transaction(&state)])
2467 .unwrap();
2468 let _system = pool.submit(epoch, &[transaction()]).unwrap();
2469 let (_, ack, _) = pool.take(2, usize::MAX);
2470 ack(block(2));
2471 assert_eq!(status("sequenced"), 0);
2472
2473 pool.notify_committed(vec![block(2)], 1);
2474 assert_eq!(status("sequenced"), 1);
2475 assert_eq!(status("garbage_collected"), 1);
2476 assert_eq!(commit_latency_count("user", "unrestricted"), 1);
2478 assert_eq!(commit_latency_count("system", "na"), 1);
2479 }
2480
2481 #[tokio::test]
2482 async fn insert_validation_rejects_oversized_transactions_and_bundles() {
2483 let serialized_len = u64::try_from(bcs::to_bytes(&transaction()).unwrap().len()).unwrap();
2484
2485 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
2486 config.set_consensus_max_transaction_size_bytes_for_testing(serialized_len - 1);
2487 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
2488 assert!(
2489 pool.try_insert(pool.epoch(), 1, vec![transaction()])
2490 .err()
2491 .unwrap()
2492 .to_string()
2493 .contains("Transaction size")
2494 );
2495
2496 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
2497 config.set_consensus_max_num_transactions_in_block_for_testing(1);
2498 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
2499 assert!(
2500 pool.try_insert(pool.epoch(), 1, vec![transaction(), transaction()])
2501 .err()
2502 .unwrap()
2503 .to_string()
2504 .contains("bundle count")
2505 );
2506
2507 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
2508 config.set_consensus_max_transactions_in_block_bytes_for_testing(serialized_len);
2509 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
2510 assert!(
2511 pool.try_insert(pool.epoch(), 1, vec![transaction(), transaction()])
2512 .err()
2513 .unwrap()
2514 .to_string()
2515 .contains("bundle size")
2516 );
2517 }
2518
2519 #[cfg(debug_assertions)]
2520 #[tokio::test]
2521 #[should_panic(expected = "system transaction failed validation")]
2522 async fn oversized_system_transaction_is_an_invariant_violation() {
2523 let serialized_len = u64::try_from(bcs::to_bytes(&transaction()).unwrap().len()).unwrap();
2524 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
2525 config.set_consensus_max_transaction_size_bytes_for_testing(serialized_len - 1);
2526 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
2527 let _ = pool.submit(pool.epoch(), &[transaction()]);
2528 }
2529
2530 #[sim_test]
2531 async fn take_rejects_entries_with_stale_owned_objects_without_consuming_budget() {
2532 let block_bytes = 1 << 20;
2533 let (state, pool) = test_state_and_configured_pool(Some((block_bytes, 1)), None).await;
2534 let epoch = pool.epoch();
2535
2536 let missing = user_transaction_with_gas(random_object_ref());
2539 let (missing_receiver, _) = pool.try_insert(epoch, 100, vec![missing]).unwrap();
2540 let (sender, _) = get_key_pair::<AccountKeyPair>();
2541 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
2542 let gas_object_ref = gas_object.compute_object_reference();
2543 state.insert_genesis_object(gas_object);
2544 let stale = user_transaction_with_gas(gas_object_ref);
2545 let (stale_receiver, _) = pool.try_insert(epoch, 90, vec![stale]).unwrap();
2546 state.get_cache_writer().write_object_entry_for_test(
2547 Object::with_id_owner_version_for_testing(
2548 gas_object_ref.0,
2549 gas_object_ref.1.next(),
2550 Owner::AddressOwner(sender),
2551 ),
2552 );
2553
2554 let mut live_bytes = 0;
2555 let live = (0..2)
2556 .map(|_| {
2557 let transaction = user_transaction(&state);
2558 live_bytes += bcs::to_bytes(&transaction).unwrap().len() as i64;
2559 pool.try_insert(epoch, 10, vec![transaction]).unwrap().0
2560 })
2561 .collect::<Vec<_>>();
2562
2563 let (transactions, ack, limit) = pool.take(usize::MAX, usize::MAX);
2564 assert_eq!(transactions.len(), 2);
2565 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
2566 assert_eq!(pool.queue_depth("user"), 0);
2567 assert_eq!(
2568 pool.metrics.pool_byte_budget_bytes.get(),
2569 block_bytes as i64 - live_bytes
2570 );
2571 assert_eq!(pool.rejected_at_proposal_count("ObjectNotFound"), 1);
2572 assert_eq!(
2573 pool.rejected_at_proposal_count("ObjectVersionUnavailableForConsumption"),
2574 1
2575 );
2576 assert!(matches!(
2577 missing_receiver.await.unwrap().unwrap_err().as_inner(),
2578 SuiErrorKind::UserInputError {
2579 error: UserInputError::ObjectNotFound { .. }
2580 }
2581 ));
2582 assert!(matches!(
2583 stale_receiver.await.unwrap().unwrap_err().as_inner(),
2584 SuiErrorKind::UserInputError {
2585 error: UserInputError::ObjectVersionUnavailableForConsumption { .. }
2586 }
2587 ));
2588
2589 ack(block(1));
2590 for receiver in live {
2591 assert_eq!(receiver.await.unwrap().unwrap().len(), 1);
2592 }
2593 }
2594
2595 #[sim_test]
2596 async fn bundle_is_rejected_at_proposal_when_any_owned_object_is_stale() {
2597 let (state, pool) = test_state_and_pool(10).await;
2598 let epoch = pool.epoch();
2599
2600 let (receiver, _) = pool
2601 .try_insert(
2602 epoch,
2603 10,
2604 vec![
2605 user_transaction(&state),
2606 user_transaction_with_gas(random_object_ref()),
2607 ],
2608 )
2609 .unwrap();
2610
2611 let (transactions, ack, limit) = pool.take(usize::MAX, usize::MAX);
2612 assert!(transactions.is_empty());
2613 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
2614 assert_eq!(pool.queue_depth("user"), 0);
2615 assert!(matches!(
2616 receiver.await.unwrap().unwrap_err().as_inner(),
2617 SuiErrorKind::UserInputError {
2618 error: UserInputError::ObjectNotFound { .. }
2619 }
2620 ));
2621 drop(ack);
2622 }
2623
2624 #[sim_test]
2625 async fn take_skips_already_processed_entries_without_shrinking_the_block() {
2626 let (state, pool) = test_state_and_pool(10).await;
2627 let epoch = pool.epoch();
2628 let epoch_store = state.epoch_store_for_testing();
2629
2630 let processed = user_transaction(&state);
2634 let (processed_consensus, _) = pool
2635 .try_insert(epoch, 100, vec![processed.clone()])
2636 .unwrap();
2637 let executed = user_transaction(&state);
2638 let (processed_checkpoint, _) = pool.try_insert(epoch, 90, vec![executed.clone()]).unwrap();
2639 let live = (0..3)
2640 .map(|_| {
2641 pool.try_insert(epoch, 10, vec![user_transaction(&state)])
2642 .unwrap()
2643 .0
2644 })
2645 .collect::<Vec<_>>();
2646
2647 epoch_store.process_notifications(std::iter::once(&consensus_key(&processed)));
2648 epoch_store
2649 .insert_finalized_transactions(&[digest_of(&executed)], 1)
2650 .unwrap();
2651
2652 let (transactions, ack, limit) = pool.take(3, usize::MAX);
2653 assert_eq!(transactions.len(), 3);
2654 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
2655 assert_eq!(pool.queue_depth("user"), 0);
2656 assert_eq!(
2657 pool.already_processed_count("proposal", "consensus_message"),
2658 1
2659 );
2660 assert_eq!(
2661 pool.already_processed_count("proposal", "checkpoint_execution"),
2662 1
2663 );
2664 for receiver in [processed_consensus, processed_checkpoint] {
2665 assert!(matches!(
2666 receiver.await.unwrap().unwrap_err().as_inner(),
2667 SuiErrorKind::TransactionProcessing { .. }
2668 ));
2669 }
2670
2671 ack(block(1));
2672 for receiver in live {
2673 assert_eq!(receiver.await.unwrap().unwrap().len(), 1);
2674 }
2675 }
2676
2677 #[sim_test]
2678 async fn bundles_are_skipped_only_when_every_transaction_is_already_processed() {
2679 let (state, pool) = test_state_and_pool(10).await;
2680 let epoch = pool.epoch();
2681 let epoch_store = state.epoch_store_for_testing();
2682
2683 let first = user_transaction(&state);
2684 let second = user_transaction(&state);
2685 let (receiver, _) = pool
2686 .try_insert(epoch, 10, vec![first.clone(), second.clone()])
2687 .unwrap();
2688
2689 epoch_store.process_notifications(std::iter::once(&consensus_key(&first)));
2690 let (transactions, ack, _) = pool.take(10, usize::MAX);
2691 assert_eq!(transactions.len(), 2);
2692 assert_eq!(
2693 pool.already_processed_count("proposal", "consensus_message"),
2694 0
2695 );
2696 drop(ack);
2699
2700 epoch_store.process_notifications(std::iter::once(&consensus_key(&second)));
2701 let (transactions, ack, _) = pool.take(10, usize::MAX);
2702 assert!(transactions.is_empty());
2703 assert_eq!(
2704 pool.already_processed_count("proposal", "consensus_message"),
2705 1
2706 );
2707 assert!(matches!(
2708 receiver.await.unwrap().unwrap_err().as_inner(),
2709 SuiErrorKind::TransactionProcessing { .. }
2710 ));
2711 drop(ack);
2712 }
2713
2714 #[sim_test]
2715 async fn insert_rejects_already_processed_transactions() {
2716 let (state, pool) = test_state_and_pool(10).await;
2717 let epoch = pool.epoch();
2718 let epoch_store = state.epoch_store_for_testing();
2719
2720 let via_consensus = user_transaction(&state);
2723 epoch_store.test_insert_user_signature(digest_of(&via_consensus), vec![]);
2724 assert!(matches!(
2725 pool.try_insert(epoch, 10, vec![via_consensus])
2726 .unwrap_err()
2727 .as_inner(),
2728 SuiErrorKind::TransactionProcessing { .. }
2729 ));
2730 assert_eq!(
2731 pool.already_processed_count("insert", "consensus_message"),
2732 1
2733 );
2734
2735 let via_checkpoint = user_transaction(&state);
2736 epoch_store
2737 .insert_finalized_transactions(&[digest_of(&via_checkpoint)], 1)
2738 .unwrap();
2739 assert!(matches!(
2740 pool.try_insert(epoch, 10, vec![via_checkpoint])
2741 .unwrap_err()
2742 .as_inner(),
2743 SuiErrorKind::TransactionProcessing { .. }
2744 ));
2745 assert_eq!(
2746 pool.already_processed_count("insert", "checkpoint_execution"),
2747 1
2748 );
2749
2750 assert_eq!(pool.queue_depth("user"), 0);
2751 let (transactions, ack, _) = pool.take(10, usize::MAX);
2752 assert!(transactions.is_empty());
2753 drop(ack);
2754 }
2755
2756 #[sim_test]
2757 async fn departing_entries_deregister_their_processed_watches() {
2758 let (state, pool) = test_state_and_pool(1).await;
2759 let epoch = pool.epoch();
2760 let epoch_store = state.epoch_store_for_testing();
2761 let baseline = epoch_store.num_pending_processed_notifications();
2763
2764 let evicted_tx = user_transaction(&state);
2765 let (evicted, _) = pool
2766 .try_insert(epoch, 10, vec![evicted_tx.clone()])
2767 .unwrap();
2768 assert_eq!(
2769 epoch_store.num_pending_processed_notifications(),
2770 baseline + 2
2771 );
2772
2773 let taken_tx = user_transaction(&state);
2774 let (taken, _) = pool.try_insert(epoch, 20, vec![taken_tx.clone()]).unwrap();
2775 assert!(matches!(
2776 evicted.await.unwrap().unwrap_err().as_inner(),
2777 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. }
2778 ));
2779 assert_eq!(
2780 epoch_store.num_pending_processed_notifications(),
2781 baseline + 2
2782 );
2783
2784 let (transactions, ack, _) = pool.take(10, usize::MAX);
2785 assert_eq!(transactions.len(), 1);
2786 ack(block(1));
2787 taken.await.unwrap().unwrap();
2788 assert_eq!(epoch_store.num_pending_processed_notifications(), baseline);
2789
2790 let closed_tx = user_transaction(&state);
2791 let (closed, _) = pool.try_insert(epoch, 10, vec![closed_tx.clone()]).unwrap();
2792 assert_eq!(
2793 epoch_store.num_pending_processed_notifications(),
2794 baseline + 2
2795 );
2796 pool.close();
2797 assert!(matches!(
2798 closed.await.unwrap().unwrap_err().as_inner(),
2799 SuiErrorKind::ValidatorHaltedAtEpochEnd
2800 ));
2801 assert_eq!(epoch_store.num_pending_processed_notifications(), baseline);
2802
2803 for transaction in [&evicted_tx, &taken_tx, &closed_tx] {
2804 epoch_store.process_notifications(std::iter::once(&consensus_key(transaction)));
2805 epoch_store
2806 .insert_finalized_transactions(&[digest_of(transaction)], 1)
2807 .unwrap();
2808 }
2809 assert_eq!(epoch_store.num_pending_processed_notifications(), baseline);
2810 }
2811
2812 #[sim_test]
2813 async fn zero_user_capacity_is_rejected_at_startup() {
2814 let state = TestAuthorityBuilder::new().build().await;
2815 let epoch_store = state.epoch_store_for_testing().clone();
2816 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2817 ConsensusTransactionPool::new_for_tests(
2818 epoch_store,
2819 state.get_cache_writer().clone(),
2820 0,
2821 Arc::new(AdmissionQueueMetrics::new_for_tests()),
2822 )
2823 }));
2824 assert!(result.is_err());
2825 }
2826
2827 #[sim_test]
2828 async fn context_observes_installed_pool_and_waits_for_matching_epoch() {
2829 let metrics = Arc::new(AdmissionQueueMetrics::new_for_tests());
2830 let state = TestAuthorityBuilder::new().build().await;
2831 let context = Arc::new(TransactionPoolContext::new_for_tests(
2832 metrics,
2833 state.get_cache_writer().clone(),
2834 ));
2835 let pool = pool_for_current_epoch(&state, 10);
2836 let epoch = pool.epoch();
2837 let absent_waiter = tokio::spawn({
2838 let context = context.clone();
2839 async move { context.wait_for_pool(epoch).await }
2840 });
2841 while context.metrics().pool_waiting_inserts.get() == 0 {
2842 tokio::task::yield_now().await;
2843 }
2844 assert_eq!(context.metrics().pool_waiting_inserts.get(), 1);
2845 context.set_active(epoch, pool.clone());
2846 assert_eq!(absent_waiter.await.unwrap().unwrap().epoch(), epoch);
2847
2848 assert_eq!(context.wait_for_pool(epoch).await.unwrap().epoch(), epoch);
2850
2851 let race_context = Arc::new(TransactionPoolContext::new_for_tests(
2852 Arc::new(AdmissionQueueMetrics::new_for_tests()),
2853 state.get_cache_writer().clone(),
2854 ));
2855 let setter = race_context.clone();
2856 let race_pool = pool.clone();
2857 assert_eq!(
2858 race_context
2859 .wait_for_pool_after_subscribe(epoch, move || {
2860 setter.set_active(epoch, race_pool);
2861 })
2862 .await
2863 .unwrap()
2864 .epoch(),
2865 epoch
2866 );
2867
2868 let next_epoch = epoch + 1;
2869 let context_waiter = context.clone();
2870 let waiter = tokio::spawn(async move { context_waiter.wait_for_pool(next_epoch).await });
2871 while context.metrics().pool_waiting_inserts.get() == 0 {
2872 tokio::task::yield_now().await;
2873 }
2874 assert_eq!(context.metrics().pool_waiting_inserts.get(), 1);
2875 state.reconfigure_for_testing().await;
2876 let next_pool = pool_for_current_epoch(&state, 10);
2877 assert_eq!(next_pool.epoch(), next_epoch);
2878 context.set_active(next_epoch, next_pool.clone());
2879 assert_eq!(waiter.await.unwrap().unwrap().epoch(), next_epoch);
2880 }
2881
2882 #[sim_test]
2883 async fn context_fails_promptly_when_active_pool_is_dropped() {
2884 let state = TestAuthorityBuilder::new().build().await;
2885 let context = TransactionPoolContext::new_for_tests(
2886 Arc::new(AdmissionQueueMetrics::new_for_tests()),
2887 state.get_cache_writer().clone(),
2888 );
2889 let pool = pool_for_current_epoch(&state, 10);
2890 let epoch = pool.epoch();
2891 context.set_active(epoch, pool.clone());
2892 assert_eq!(context.wait_for_pool(epoch).await.unwrap().epoch(), epoch);
2893
2894 drop(pool);
2897 let Err(error) = context.wait_for_pool(epoch).await else {
2898 panic!("waiting on a dropped pool must fail");
2899 };
2900 assert!(matches!(
2901 error.as_inner(),
2902 SuiErrorKind::TooManyTransactionsPendingConsensus
2903 ));
2904 }
2905
2906 #[sim_test]
2907 async fn context_fails_stale_and_unavailable_epochs_promptly() {
2908 let state = TestAuthorityBuilder::new().build().await;
2910 let context = TransactionPoolContext::new_for_tests(
2911 Arc::new(AdmissionQueueMetrics::new_for_tests()),
2912 state.get_cache_writer().clone(),
2913 );
2914 state.reconfigure_for_testing().await;
2915 let pool = pool_for_current_epoch(&state, 10);
2916 let active_epoch = pool.epoch();
2917 context.set_active(active_epoch, pool);
2918 assert!(matches!(
2919 context
2920 .wait_for_pool(active_epoch - 1)
2921 .await
2922 .err()
2923 .unwrap()
2924 .as_inner(),
2925 SuiErrorKind::ValidatorHaltedAtEpochEnd
2926 ));
2927 context.set_unavailable(active_epoch + 1);
2928 assert!(matches!(
2929 context
2930 .wait_for_pool(active_epoch + 1)
2931 .await
2932 .err()
2933 .unwrap()
2934 .as_inner(),
2935 SuiErrorKind::TooManyTransactionsPendingConsensus
2936 ));
2937 assert!(matches!(
2938 context
2939 .wait_for_pool(active_epoch)
2940 .await
2941 .err()
2942 .unwrap()
2943 .as_inner(),
2944 SuiErrorKind::ValidatorHaltedAtEpochEnd
2945 ));
2946 }
2947
2948 #[tokio::test]
2949 async fn transaction_pool_client_maps_ping_ack_and_status() {
2950 let state = TestAuthorityBuilder::new().build().await;
2951 let epoch_store = state.epoch_store_for_testing().clone();
2952 let metrics = Arc::new(AdmissionQueueMetrics::new_for_tests());
2953 let context = Arc::new(TransactionPoolContext::new_for_tests(
2954 metrics.clone(),
2955 state.get_cache_writer().clone(),
2956 ));
2957 let pool = Arc::new(ConsensusTransactionPool::new_for_tests(
2958 epoch_store.clone(),
2959 state.get_cache_writer().clone(),
2960 10,
2961 metrics,
2962 ));
2963 context.set_active(epoch_store.epoch(), pool.clone());
2964 let client = Arc::new(TransactionPoolClient::new(context));
2965
2966 let task = tokio::spawn({
2967 let client = client.clone();
2968 let epoch_store = epoch_store.clone();
2969 async move { client.submit(&[], &epoch_store).await }
2970 });
2971 while pool.queue_depth("ping") == 0 {
2972 tokio::task::yield_now().await;
2973 }
2974 let (_, ack, _) = pool.take(0, 0);
2975 let block_ref = block(3);
2976 ack(block_ref);
2977 let (positions, status) = task.await.unwrap().unwrap();
2978 assert_eq!(positions[0].block, block_ref);
2979 assert_eq!(positions[0].index, PING_TRANSACTION_INDEX);
2980 pool.notify_committed(vec![block_ref], 0);
2981 assert_eq!(status.await.unwrap(), BlockStatus::Sequenced(block_ref));
2982
2983 let user_transaction = user_transaction(&state);
2984 let task = tokio::spawn({
2985 let client = client.clone();
2986 let epoch_store = epoch_store.clone();
2987 async move { client.submit(&[user_transaction], &epoch_store).await }
2988 });
2989 match task.await {
2990 Err(join_error) => assert!(join_error.is_panic()),
2992 Ok(result) => assert!(result.is_err()),
2994 }
2995 assert_eq!(pool.queue_depth("user"), 0);
2996 assert_eq!(pool.queue_depth("system"), 0);
2997 }
2998}