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 async_trait::async_trait;
30use consensus_core::{BlockStatus, ClientError, LimitReached, Transaction, TransactionPool};
31use consensus_types::block::{
32 BlockRef, NUM_RESERVED_TRANSACTION_INDICES, PING_TRANSACTION_INDEX, Round, TransactionIndex,
33};
34use itertools::Itertools;
35use mysten_common::debug_fatal;
36use mysten_common::sync::notify_read::OwnedRegistration;
37use parking_lot::Mutex;
38use prometheus::IntGauge;
39use std::collections::{BTreeMap, VecDeque};
40use std::sync::Arc;
41use std::time::{Duration, Instant};
42use sui_macros::fail_point_if;
43use sui_types::base_types::EpochId;
44use sui_types::digests::TransactionDigest;
45use sui_types::error::{SuiError, SuiErrorKind, SuiResult};
46use sui_types::messages_checkpoint::CheckpointSequenceNumber;
47use sui_types::messages_consensus::{
48 ConsensusPosition, ConsensusTransaction, ConsensusTransactionKey,
49};
50use tokio::sync::{oneshot, watch};
51use tracing::warn;
52
53const MAX_PENDING_PINGS: usize = 1_024;
55
56const SYSTEM_LANE_WARN_THRESHOLD: usize = 10_000;
59
60pub type PositionReceiver = oneshot::Receiver<SuiResult<Vec<ConsensusPosition>>>;
63
64type BlockInclusion = (
68 BlockRef,
69 Vec<TransactionIndex>,
70 oneshot::Receiver<BlockStatus>,
71);
72
73enum EntryAck {
75 User(oneshot::Sender<SuiResult<Vec<ConsensusPosition>>>),
77 SystemOrPing(oneshot::Sender<BlockInclusion>),
80}
81
82struct PendingAck {
84 ack: Option<EntryAck>,
85 keys: Vec<ConsensusTransactionKey>,
86 created_at: &'static std::panic::Location<'static>,
87 created: Instant,
88}
89
90impl PendingAck {
91 #[track_caller]
92 fn new(ack: EntryAck, keys: Vec<ConsensusTransactionKey>) -> Self {
93 Self {
94 ack: Some(ack),
95 keys,
96 created_at: std::panic::Location::caller(),
97 created: Instant::now(),
98 }
99 }
100
101 fn resolve_included(
102 mut self,
103 epoch: EpochId,
104 block_ref: BlockRef,
105 indices: Vec<TransactionIndex>,
106 block: &mut ProposedBlock,
107 ) {
108 match self.ack.take().expect("ack must be pending") {
109 EntryAck::User(sender) => {
110 let positions = indices
111 .into_iter()
112 .map(|index| ConsensusPosition {
113 epoch,
114 block: block_ref,
115 index,
116 })
117 .collect();
118 let _ = sender.send(Ok(positions));
119 }
120 EntryAck::SystemOrPing(sender) => {
121 let (status_sender, status_receiver) = oneshot::channel();
122 block.subscribers.push(status_sender);
123 let _ = sender.send((block_ref, indices, status_receiver));
124 }
125 }
126 }
127
128 fn resolve_error(mut self, error: SuiError) {
129 match self.ack.take().expect("ack must be pending") {
130 EntryAck::User(sender) => {
131 let _ = sender.send(Err(error));
132 }
133 EntryAck::SystemOrPing(sender) => {
134 drop(sender);
135 }
136 }
137 }
138
139 fn drop_deliberately(mut self, _reason: &'static str) {
140 drop(self.ack.take().expect("ack must be pending"));
141 }
142
143 fn is_abandoned_system(&self) -> bool {
144 matches!(&self.ack, Some(EntryAck::SystemOrPing(sender)) if sender.is_closed())
145 }
146}
147
148impl Drop for PendingAck {
149 fn drop(&mut self) {
150 let flavor = match self.ack {
151 Some(EntryAck::User(_)) => "User",
152 Some(EntryAck::SystemOrPing(_)) => "SystemOrPing",
153 None => return,
154 };
155 if !std::thread::panicking() {
156 debug_fatal!(
157 "{} ack created at {} dropped without resolution after {:?}; keys={:?}",
158 flavor,
159 self.created_at,
160 self.created.elapsed(),
161 self.keys
162 );
163 }
164 }
165}
166
167struct ProcessedWatch {
171 consensus: OwnedRegistration<SequencedConsensusTransactionKey, ()>,
172 checkpoint: Option<OwnedRegistration<TransactionDigest, CheckpointSequenceNumber>>,
173 observed: Option<ProcessedMethod>,
175}
176
177impl ProcessedWatch {
178 fn register(epoch_store: &Arc<AuthorityPerEpochStore>, key: ConsensusTransactionKey) -> Self {
179 let key = SequencedConsensusTransactionKey::External(key);
180 Self {
181 checkpoint: key
182 .user_transaction_digest()
183 .map(|digest| epoch_store.register_executed_in_checkpoint_notify(&digest)),
184 consensus: epoch_store.register_consensus_message_processed_notify(&key),
185 observed: None,
186 }
187 }
188
189 fn check_processed(&mut self) -> Option<ProcessedMethod> {
191 if self.observed.is_none() {
192 if self.consensus.try_recv().is_ok() {
193 self.observed = Some(ProcessedMethod::ConsensusMessageProcessed);
194 } else if let Some(checkpoint) = &mut self.checkpoint
195 && checkpoint.try_recv().is_ok()
196 {
197 self.observed = Some(ProcessedMethod::CheckpointExecuted);
198 }
199 }
200 self.observed
201 }
202}
203
204struct PoolEntry {
207 transactions: Vec<Transaction>,
208 total_bytes: usize,
209 gas_price: u64,
210 tx_type: &'static str, ack: PendingAck,
212 metrics: Arc<AdmissionQueueMetrics>,
213 processed: Vec<ProcessedWatch>,
216}
217
218fn all_processed(watches: &mut [ProcessedWatch]) -> Option<ProcessedMethod> {
221 let mut result: Option<ProcessedMethod> = None;
222 for watch in watches {
223 let observed = watch.check_processed()?;
224 result = result.max(Some(observed));
225 }
226 result
227}
228
229impl AdmissionQueueEntry for PoolEntry {
230 fn gas_price(&self) -> u64 {
231 self.gas_price
232 }
233
234 fn transaction_keys(&self) -> impl Iterator<Item = ConsensusTransactionKey> {
235 self.ack.keys.iter().cloned()
236 }
237
238 fn notify_evicted(self, min_gas_price: u64) {
239 self.metrics.pool_depth.with_label_values(&["user"]).dec();
240 self.metrics
241 .pool_bytes
242 .with_label_values(&["user"])
243 .sub(self.total_bytes as i64);
244 self.ack.resolve_error(
245 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price }
246 .into(),
247 );
248 }
249
250 fn notify_rejected(self, min_gas_price: u64) {
251 self.ack.resolve_error(
252 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price }
253 .into(),
254 );
255 }
256}
257
258enum UserLane {
260 Open(PriorityAdmissionQueue<PoolEntry>),
261 Closed,
262}
263
264impl UserLane {
265 #[must_use]
266 fn close(&mut self) -> Vec<PoolEntry> {
267 match std::mem::replace(self, Self::Closed) {
268 Self::Open(queue) => queue.into_entries(),
269 Self::Closed => Vec::new(),
270 }
271 }
272}
273
274struct Pool {
275 user: UserLane,
276 system: VecDeque<PoolEntry>,
279 pings: VecDeque<PendingAck>,
280 blocks: BTreeMap<BlockRef, ProposedBlock>,
283}
284
285#[derive(Default)]
286struct ProposedBlock {
287 subscribers: Vec<oneshot::Sender<BlockStatus>>,
289 entries: Vec<ProposedEntry>,
291}
292
293struct ProposedEntry {
294 lane: TakenLane,
295 tx_type: &'static str,
296 created: Instant,
297}
298
299enum Inner {
300 Open(Pool),
301 Closed,
302}
303
304pub struct ConsensusTransactionPool {
312 epoch_store: Arc<AuthorityPerEpochStore>,
313 metrics: Arc<AdmissionQueueMetrics>,
314 adapter_metrics: ConsensusAdapterMetrics,
315 inner: Arc<Mutex<Inner>>,
316}
317
318impl ConsensusTransactionPool {
319 pub fn new(
320 epoch_store: Arc<AuthorityPerEpochStore>,
321 max_pending_transactions: usize,
322 metrics: Arc<AdmissionQueueMetrics>,
323 adapter_metrics: ConsensusAdapterMetrics,
324 ) -> Self {
325 assert!(
326 max_pending_transactions > 0,
327 "consensus transaction pool max_pending_transactions must be > 0"
328 );
329 assert!(
330 epoch_store
331 .protocol_config()
332 .max_num_transactions_in_block()
333 .saturating_sub(1)
334 < u64::from(TransactionIndex::MAX.saturating_sub(NUM_RESERVED_TRANSACTION_INDICES)),
335 "Unsupported max_num_transactions_in_block: {}",
336 epoch_store
337 .protocol_config()
338 .max_num_transactions_in_block()
339 );
340
341 Self {
342 epoch_store,
343 metrics: metrics.clone(),
344 adapter_metrics,
345 inner: Arc::new(Mutex::new(Inner::Open(Pool {
346 user: UserLane::Open(PriorityAdmissionQueue::new(
347 max_pending_transactions,
348 metrics,
349 )),
350 system: VecDeque::new(),
351 pings: VecDeque::new(),
352 blocks: BTreeMap::new(),
353 }))),
354 }
355 }
356
357 #[cfg(test)]
358 pub(crate) fn new_for_tests(
359 epoch_store: Arc<AuthorityPerEpochStore>,
360 max_pending_transactions: usize,
361 metrics: Arc<AdmissionQueueMetrics>,
362 ) -> Self {
363 Self::new(
364 epoch_store,
365 max_pending_transactions,
366 metrics,
367 ConsensusAdapterMetrics::new_test(),
368 )
369 }
370
371 pub fn epoch(&self) -> EpochId {
372 self.epoch_store.epoch()
373 }
374
375 pub fn try_insert(
383 &self,
384 caller_epoch: EpochId,
385 gas_price: u64,
386 transactions: Vec<ConsensusTransaction>,
387 ) -> SuiResult<(PositionReceiver, bool)> {
388 let keys = transactions
391 .iter()
392 .map(ConsensusTransaction::key)
393 .collect::<Vec<_>>();
394 let mut processed = keys
395 .iter()
396 .map(|key| ProcessedWatch::register(&self.epoch_store, key.clone()))
397 .collect::<Vec<_>>();
398 if let Some(method) = self.check_already_processed(&mut processed)? {
399 self.metrics
400 .pool_already_processed
401 .with_label_values(&["insert", method.metric_label()])
402 .inc();
403 return Err(processing_error(
404 processed.iter().map(|watch| watch.consensus.key()),
405 method,
406 ));
407 }
408
409 let tx_type = tx_type_label(&transactions);
410 let (transactions, total_bytes) = self.serialize_and_validate(&transactions)?;
411 let (sender, receiver) = oneshot::channel();
412 let entry = PoolEntry {
413 transactions,
414 total_bytes,
415 gas_price,
416 tx_type,
417 ack: PendingAck::new(EntryAck::User(sender), keys),
418 metrics: self.metrics.clone(),
419 processed,
420 };
421
422 let mut inner = self.inner.lock();
423 let user = match self.check_user_lane_open_locked(caller_epoch, &mut inner) {
424 Ok(user) => user,
425 Err(error) => {
426 drop(inner);
427 entry.ack.resolve_error(error.clone());
428 return Err(error);
429 }
430 };
431 let outcome = user.try_insert(entry);
432 drop(inner);
433
434 let newly_inserted = outcome.notify()?;
435 self.metrics.pool_depth.with_label_values(&["user"]).inc();
436 self.metrics
437 .pool_bytes
438 .with_label_values(&["user"])
439 .add(total_bytes as i64);
440 Ok((receiver, newly_inserted))
441 }
442
443 fn submit(
447 &self,
448 caller_epoch: EpochId,
449 transactions: &[ConsensusTransaction],
450 ) -> SuiResult<oneshot::Receiver<BlockInclusion>> {
451 let (sender, receiver) = oneshot::channel();
452 if transactions.is_empty() {
453 let mut inner = self.inner.lock();
454 let pool = self.check_epoch_and_open_locked(caller_epoch, &mut inner)?;
455 if pool.pings.len() >= MAX_PENDING_PINGS {
456 return Err(SuiErrorKind::TooManyTransactionsPendingConsensus.into());
457 }
458 pool.pings
459 .push_back(PendingAck::new(EntryAck::SystemOrPing(sender), Vec::new()));
460 self.metrics.pool_depth.with_label_values(&["ping"]).inc();
461 return Ok(receiver);
462 }
463
464 if transactions
466 .iter()
467 .any(ConsensusTransaction::is_user_transaction)
468 {
469 debug_fatal!(
470 "user transaction submitted through the consensus transaction pool client"
471 );
472 return Err(SuiErrorKind::GenericAuthorityError {
473 error: "user transactions cannot be submitted through the transaction pool client"
474 .to_string(),
475 }
476 .into());
477 }
478 let (serialized, total_bytes) = match self.serialize_and_validate(transactions) {
482 Ok(validated) => validated,
483 Err(error) => {
484 debug_fatal!("system transaction failed validation: {error}");
485 return Err(error);
486 }
487 };
488 let entry = PoolEntry {
489 transactions: serialized,
490 total_bytes,
491 gas_price: 0,
492 tx_type: tx_type_label(transactions),
493 ack: PendingAck::new(
494 EntryAck::SystemOrPing(sender),
495 transactions.iter().map(ConsensusTransaction::key).collect(),
496 ),
497 metrics: self.metrics.clone(),
498 processed: Vec::new(),
499 };
500
501 {
502 let mut inner = self.inner.lock();
503 let pool = match self.check_epoch_and_open_locked(caller_epoch, &mut inner) {
504 Ok(pool) => pool,
505 Err(error) => {
506 drop(inner);
507 entry.ack.resolve_error(error.clone());
508 return Err(error);
509 }
510 };
511 pool.system.push_back(entry);
512 let depth = pool.system.len();
513 self.metrics
514 .pool_depth
515 .with_label_values(&["system"])
516 .set(depth as i64);
517 self.metrics
518 .pool_bytes
519 .with_label_values(&["system"])
520 .add(total_bytes as i64);
521 if depth == SYSTEM_LANE_WARN_THRESHOLD {
522 warn!(
523 depth,
524 "Consensus transaction pool system lane exceeded warning threshold"
525 );
526 }
527 }
528 Ok(receiver)
529 }
530
531 fn check_already_processed(
532 &self,
533 watches: &mut [ProcessedWatch],
534 ) -> SuiResult<Option<ProcessedMethod>> {
535 let consensus_processed = self.epoch_store.check_consensus_messages_processed(
537 watches.iter().map(|watch| watch.consensus.key().clone()),
538 )?;
539 for (watch, consensus_processed) in watches.iter_mut().zip_eq(consensus_processed) {
540 if consensus_processed {
541 watch.observed = Some(ProcessedMethod::ConsensusMessageProcessed);
542 }
543 }
544
545 let (unobserved, digests): (Vec<_>, Vec<_>) = watches
547 .iter_mut()
548 .filter_map(|watch| {
549 if watch.observed.is_some() {
550 return None;
551 }
552 let digest = *watch.checkpoint.as_ref()?.key();
553 Some((watch, digest))
554 })
555 .unzip();
556 if !digests.is_empty() {
557 let executed = self
558 .epoch_store
559 .multi_get_transaction_checkpoint(&digests)?;
560 for (watch, executed) in unobserved.into_iter().zip_eq(executed) {
561 if executed.is_some() {
562 watch.observed = Some(ProcessedMethod::CheckpointExecuted);
563 }
564 }
565 }
566 Ok(all_processed(watches))
567 }
568
569 fn check_epoch_and_open_locked<'a>(
570 &self,
571 caller_epoch: EpochId,
572 inner: &'a mut Inner,
573 ) -> SuiResult<&'a mut Pool> {
574 if caller_epoch != self.epoch() {
575 return Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
576 }
577 match inner {
578 Inner::Open(pool) => Ok(pool),
579 Inner::Closed => Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into()),
580 }
581 }
582
583 fn check_user_lane_open_locked<'a>(
584 &self,
585 caller_epoch: EpochId,
586 inner: &'a mut Inner,
587 ) -> SuiResult<&'a mut PriorityAdmissionQueue<PoolEntry>> {
588 let pool = self.check_epoch_and_open_locked(caller_epoch, inner)?;
589 match &mut pool.user {
590 UserLane::Open(user) => Ok(user),
591 UserLane::Closed => Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into()),
592 }
593 }
594
595 fn serialize_and_validate(
602 &self,
603 transactions: &[ConsensusTransaction],
604 ) -> SuiResult<(Vec<Transaction>, usize)> {
605 let protocol_config = self.epoch_store.protocol_config();
606 let bundle_count = u64::try_from(transactions.len()).expect("bundle count fits into u64");
607 if bundle_count > protocol_config.max_num_transactions_in_block() {
608 return Err(consensus_client_error(
609 ClientError::OversizedTransactionBundleCount(
610 bundle_count,
611 protocol_config.max_num_transactions_in_block(),
612 ),
613 ));
614 }
615
616 let mut total_bytes = 0usize;
617 let mut serialized = Vec::with_capacity(transactions.len());
618 for transaction in transactions {
619 let bytes =
620 bcs::to_bytes(transaction).expect("Serializing consensus transaction cannot fail");
621 let transaction_bytes =
622 u64::try_from(bytes.len()).expect("transaction size fits into u64");
623 if transaction_bytes > protocol_config.max_transaction_size_bytes() {
624 return Err(consensus_client_error(ClientError::OversizedTransaction(
625 transaction_bytes,
626 protocol_config.max_transaction_size_bytes(),
627 )));
628 }
629 total_bytes += bytes.len();
630 let bundle_bytes = u64::try_from(total_bytes).expect("bundle size fits into u64");
631 if bundle_bytes > protocol_config.max_transactions_in_block_bytes() {
632 return Err(consensus_client_error(
633 ClientError::OversizedTransactionBundleBytes(
634 bundle_bytes,
635 protocol_config.max_transactions_in_block_bytes(),
636 ),
637 ));
638 }
639 serialized.push(Transaction::new(bytes));
640 }
641 Ok((serialized, total_bytes))
642 }
643
644 pub fn close(&self) {
649 let mut pool = {
650 let mut inner = self.inner.lock();
651 match std::mem::replace(&mut *inner, Inner::Closed) {
652 Inner::Open(pool) => pool,
653 Inner::Closed => return,
654 }
655 };
656
657 self.resolve_flushed_user_entries(pool.user.close());
659 for entry in pool.system {
660 entry.ack.drop_deliberately("pool closed");
661 }
662 for ping in pool.pings {
663 ping.drop_deliberately("pool closed");
664 }
665 drop(pool.blocks);
666 self.metrics
667 .pool_depth
668 .with_label_values(&["system"])
669 .set(0);
670 self.metrics
671 .pool_bytes
672 .with_label_values(&["system"])
673 .set(0);
674 self.metrics.pool_depth.with_label_values(&["ping"]).set(0);
675 }
676
677 fn resolve_flushed_user_entries(&self, entries: Vec<PoolEntry>) {
681 for entry in entries {
682 self.decrement_lane_metrics("user", &entry);
683 entry
684 .ack
685 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
686 }
687 }
688
689 fn decrement_lane_metrics(&self, lane: &str, entry: &PoolEntry) {
690 self.metrics.pool_depth.with_label_values(&[lane]).dec();
691 self.metrics
692 .pool_bytes
693 .with_label_values(&[lane])
694 .sub(entry.total_bytes as i64);
695 }
696
697 #[cfg(test)]
698 pub fn queue_depth(&self, lane: &str) -> i64 {
699 self.metrics.pool_depth.with_label_values(&[lane]).get()
700 }
701
702 #[cfg(test)]
703 pub fn already_processed_count(&self, stage: &str, method: &str) -> u64 {
704 self.metrics
705 .pool_already_processed
706 .with_label_values(&[stage, method])
707 .get()
708 }
709
710 #[cfg(test)]
711 pub fn abandoned_count(&self, lane: &str) -> u64 {
712 self.metrics.pool_abandoned.with_label_values(&[lane]).get()
713 }
714}
715
716#[derive(Clone, Copy)]
717enum TakenLane {
718 System,
719 User,
720}
721
722impl TakenLane {
723 fn label(self) -> &'static str {
724 match self {
725 TakenLane::System => "system",
726 TakenLane::User => "user",
727 }
728 }
729}
730
731struct TakenEntry {
732 lane: TakenLane,
733 entry: PoolEntry,
734}
735
736struct TakenTransactionsGuard {
743 epoch: EpochId,
744 inner: Arc<Mutex<Inner>>,
745 metrics: Arc<AdmissionQueueMetrics>,
746 entries: Option<Vec<TakenEntry>>,
747 pings: Option<Vec<PendingAck>>,
748}
749
750impl TakenTransactionsGuard {
751 fn acknowledge(mut self, block_ref: BlockRef) {
752 let entries = self.entries.take().expect("acknowledgement called once");
753 let pings = self.pings.take().expect("acknowledgement called once");
754 let mut inner = self.inner.lock();
755 let Inner::Open(pool) = &mut *inner else {
756 drop(inner);
757 Self::resolve_after_close(entries, pings, "pool closed before acknowledgement");
758 return;
759 };
760
761 let mut next_index = 0usize;
764 let mut watches_to_drop = Vec::with_capacity(entries.len());
765 let mut block = ProposedBlock::default();
766 for taken in entries {
767 let lane = taken.lane;
768 let mut entry = taken.entry;
769 block.entries.push(ProposedEntry {
770 lane,
771 tx_type: entry.tx_type,
772 created: entry.ack.created,
773 });
774 watches_to_drop.push(std::mem::take(&mut entry.processed));
775 let start = next_index;
776 next_index += entry.transactions.len();
777 let indices = (start..next_index)
778 .map(|index| {
779 TransactionIndex::try_from(index)
780 .expect("validated transaction index must fit TransactionIndex")
781 })
782 .collect::<Vec<_>>();
783 self.metrics
784 .queue_wait_latency
785 .with_label_values(&[lane.label()])
786 .observe(entry.ack.created.elapsed().as_secs_f64());
787 entry
788 .ack
789 .resolve_included(self.epoch, block_ref, indices, &mut block);
790 }
791 for ping in pings {
792 self.metrics
793 .queue_wait_latency
794 .with_label_values(&["ping"])
795 .observe(ping.created.elapsed().as_secs_f64());
796 ping.resolve_included(
797 self.epoch,
798 block_ref,
799 vec![PING_TRANSACTION_INDEX],
800 &mut block,
801 );
802 }
803 if !block.subscribers.is_empty() || !block.entries.is_empty() {
804 pool.blocks.insert(block_ref, block);
805 }
806 drop(inner);
807 drop(watches_to_drop); }
809
810 fn resolve_after_close(entries: Vec<TakenEntry>, pings: Vec<PendingAck>, reason: &'static str) {
814 for taken in entries {
815 taken
816 .entry
817 .ack
818 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
819 }
820 for ping in pings {
821 ping.drop_deliberately(reason);
822 }
823 }
824}
825
826impl Drop for TakenTransactionsGuard {
827 fn drop(&mut self) {
828 let Some(entries) = self.entries.take() else {
829 return;
830 };
831 let pings = self.pings.take().unwrap_or_default();
832 let mut inner = self.inner.lock();
833 let Inner::Open(pool) = &mut *inner else {
834 drop(inner);
835 Self::resolve_after_close(entries, pings, "pool closed before dropped acknowledgement");
836 return;
837 };
838
839 let mut requeued = 0;
840 let mut halted = Vec::new();
841 for taken in entries.into_iter().rev() {
844 match taken.lane {
845 TakenLane::System => {
846 requeued += 1;
847 self.metrics.pool_depth.with_label_values(&["system"]).inc();
848 self.metrics
849 .pool_bytes
850 .with_label_values(&["system"])
851 .add(taken.entry.total_bytes as i64);
852 pool.system.push_front(taken.entry);
853 let depth = pool.system.len();
854 if depth == SYSTEM_LANE_WARN_THRESHOLD {
855 warn!(
856 depth,
857 "Consensus transaction pool system lane exceeded warning threshold"
858 );
859 }
860 }
861 TakenLane::User => match &mut pool.user {
862 UserLane::Open(user) => {
863 requeued += 1;
864 self.metrics.pool_depth.with_label_values(&["user"]).inc();
865 self.metrics
866 .pool_bytes
867 .with_label_values(&["user"])
868 .add(taken.entry.total_bytes as i64);
869 user.reinsert_front(taken.entry);
870 }
871 UserLane::Closed => halted.push(taken.entry),
872 },
873 }
874 }
875 for ping in pings.into_iter().rev() {
876 pool.pings.push_front(ping);
877 self.metrics.pool_depth.with_label_values(&["ping"]).inc();
878 }
879 self.metrics
880 .pool_requeued_on_dropped_ack
881 .inc_by(requeued as u64);
882 drop(inner);
883
884 for entry in halted {
885 entry
886 .ack
887 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
888 }
889 }
890}
891
892impl TransactionPool for ConsensusTransactionPool {
893 fn take(
894 &self,
895 max_count: usize,
896 max_bytes: usize,
897 ) -> (
898 Vec<Transaction>,
899 Box<dyn FnOnce(BlockRef) + Send>,
900 LimitReached,
901 ) {
902 let should_accept_user_certs = self
906 .epoch_store
907 .get_reconfig_state_read_lock_guard()
908 .should_accept_user_certs();
909 #[allow(unused_mut)]
910 let mut disabled = false;
911 fail_point_if!("consensus_transaction_pool_disable_take", || {
912 disabled = true;
913 });
914 #[allow(unused_mut)]
915 let mut user_take_disabled = false;
916 fail_point_if!("consensus_transaction_pool_disable_user_take", || {
917 user_take_disabled = true;
918 });
919
920 let mut inner = self.inner.lock();
921 let pool = match &mut *inner {
922 Inner::Open(pool) => pool,
923 Inner::Closed => {
924 return (
925 Vec::new(),
926 Box::new(|_| {}),
927 LimitReached::AllTransactionsIncluded,
928 );
929 }
930 };
931 let flushed = if !should_accept_user_certs {
932 pool.user.close()
933 } else {
934 Vec::new()
935 };
936 if disabled {
937 drop(inner);
938 self.resolve_flushed_user_entries(flushed); return (
940 Vec::new(),
941 Box::new(|_| {}),
942 LimitReached::AllTransactionsIncluded,
943 );
944 }
945
946 let (abandoned_pings, pings): (Vec<_>, Vec<_>) = pool
947 .pings
948 .drain(..)
949 .partition(PendingAck::is_abandoned_system);
950 self.metrics
951 .pool_depth
952 .with_label_values(&["ping"])
953 .sub((pings.len() + abandoned_pings.len()) as i64);
954
955 let mut transactions = Vec::new();
956 let mut entries = Vec::new();
957 let mut total_bytes = 0usize;
958 let mut limit_reached = LimitReached::AllTransactionsIncluded;
959
960 let mut abandoned = Vec::new();
961 while let Some(entry) = pool.system.front() {
962 if entry.ack.is_abandoned_system() {
965 let entry = pool.system.pop_front().expect("front entry must exist");
966 self.decrement_lane_metrics("system", &entry);
967 abandoned.push(entry);
968 continue;
969 }
970 if let Some(limit) =
971 entry_limit(entry, transactions.len(), total_bytes, max_count, max_bytes)
972 {
973 limit_reached = limit;
974 break;
975 }
976 let entry = pool.system.pop_front().expect("front entry must exist");
977 total_bytes += entry.total_bytes;
978 transactions.extend(entry.transactions.iter().cloned());
979 self.decrement_lane_metrics("system", &entry);
980 entries.push(TakenEntry {
981 lane: TakenLane::System,
982 entry,
983 });
984 }
985
986 let mut already_processed = Vec::new();
987 if !user_take_disabled
988 && matches!(limit_reached, LimitReached::AllTransactionsIncluded)
989 && let UserLane::Open(user) = &mut pool.user
990 {
991 let mut pending_count = transactions.len();
992 let mut pending_bytes = total_bytes;
993 let popped;
994 (popped, already_processed) = user.pop_batch_while(|entry| {
995 if all_processed(&mut entry.processed).is_some() {
997 return PopAction::Exclude;
998 }
999 match entry_limit(entry, pending_count, pending_bytes, max_count, max_bytes) {
1000 Some(limit) => {
1001 limit_reached = limit;
1002 PopAction::Stop
1003 }
1004 None => {
1005 pending_count += entry.transactions.len();
1006 pending_bytes += entry.total_bytes;
1007 PopAction::Include
1008 }
1009 }
1010 });
1011 for entry in popped {
1012 self.decrement_lane_metrics("user", &entry);
1013 transactions.extend(entry.transactions.iter().cloned());
1014 entries.push(TakenEntry {
1015 lane: TakenLane::User,
1016 entry,
1017 });
1018 }
1019 }
1020 drop(inner);
1021
1022 self.resolve_flushed_user_entries(flushed);
1024 for entry in abandoned {
1025 self.metrics
1026 .pool_abandoned
1027 .with_label_values(&["system"])
1028 .inc();
1029 entry
1030 .ack
1031 .drop_deliberately("submitter stopped waiting before proposal");
1032 }
1033 for ping in abandoned_pings {
1034 self.metrics
1035 .pool_abandoned
1036 .with_label_values(&["ping"])
1037 .inc();
1038 ping.drop_deliberately("submitter stopped waiting before proposal");
1039 }
1040 for mut entry in already_processed {
1041 self.decrement_lane_metrics("user", &entry);
1042 let method = all_processed(&mut entry.processed)
1043 .expect("excluded entries are already processed");
1044 self.metrics
1045 .pool_already_processed
1046 .with_label_values(&["proposal", method.metric_label()])
1047 .inc();
1048 let error = processing_error(
1049 entry.processed.iter().map(|watch| watch.consensus.key()),
1050 method,
1051 );
1052 entry.ack.resolve_error(error);
1053 }
1054
1055 self.metrics
1056 .pool_taken_per_proposal
1057 .observe(transactions.len() as f64);
1058 let guard = TakenTransactionsGuard {
1059 epoch: self.epoch(),
1060 inner: self.inner.clone(),
1061 metrics: self.metrics.clone(),
1062 entries: Some(entries),
1063 pings: Some(pings),
1064 };
1065 (
1066 transactions,
1067 Box::new(move |block_ref| guard.acknowledge(block_ref)),
1068 limit_reached,
1069 )
1070 }
1071
1072 fn notify_committed(&self, own_committed_blocks: Vec<BlockRef>, gc_round: Round) {
1076 let mut sequenced = Vec::new();
1077 let mut garbage_collected = Vec::new();
1078 {
1079 let mut inner = self.inner.lock();
1080 let Inner::Open(pool) = &mut *inner else {
1081 return;
1082 };
1083 for block_ref in own_committed_blocks {
1084 if let Some(block) = pool.blocks.remove(&block_ref) {
1085 for subscriber in block.subscribers {
1086 let _ = subscriber.send(BlockStatus::Sequenced(block_ref));
1087 }
1088 sequenced.extend(block.entries);
1089 }
1090 }
1091 while let Some((block_ref, block)) = pool.blocks.pop_first() {
1092 if block_ref.round > gc_round {
1093 pool.blocks.insert(block_ref, block);
1094 break;
1095 }
1096 self.metrics
1097 .pool_gc_notified
1098 .inc_by(block.subscribers.len() as u64);
1099 for subscriber in block.subscribers {
1100 let _ = subscriber.send(BlockStatus::GarbageCollected(block_ref));
1101 }
1102 garbage_collected.extend(block.entries);
1103 }
1104 }
1105
1106 self.report_proposed_status(&sequenced, "sequenced");
1108 self.report_proposed_status(&garbage_collected, "garbage_collected");
1109 self.report_commit_latency(&sequenced);
1110 }
1111}
1112
1113impl ConsensusTransactionPool {
1114 fn report_proposed_status(&self, entries: &[ProposedEntry], status: &'static str) {
1118 let mut counts: Vec<(&'static str, u64)> = Vec::new();
1119 for entry in entries
1120 .iter()
1121 .filter(|entry| matches!(entry.lane, TakenLane::User))
1122 {
1123 match counts
1124 .iter_mut()
1125 .find(|(tx_type, _)| *tx_type == entry.tx_type)
1126 {
1127 Some((_, count)) => *count += 1,
1128 None => counts.push((entry.tx_type, 1)),
1129 }
1130 }
1131 for (tx_type, count) in counts {
1132 self.adapter_metrics
1133 .sequencing_certificate_status
1134 .with_label_values(&[tx_type, status])
1135 .inc_by(count);
1136 }
1137 }
1138
1139 fn report_commit_latency(&self, entries: &[ProposedEntry]) {
1140 let now = Instant::now();
1141 let user = self
1142 .metrics
1143 .pool_commit_latency
1144 .with_label_values(&["user"]);
1145 let system = self
1146 .metrics
1147 .pool_commit_latency
1148 .with_label_values(&["system"]);
1149 for entry in entries {
1150 let histogram = match entry.lane {
1151 TakenLane::User => &user,
1152 TakenLane::System => &system,
1153 };
1154 histogram.observe(now.saturating_duration_since(entry.created).as_secs_f64());
1155 }
1156 }
1157}
1158
1159fn entry_limit(
1162 entry: &PoolEntry,
1163 current_count: usize,
1164 current_bytes: usize,
1165 max_count: usize,
1166 max_bytes: usize,
1167) -> Option<LimitReached> {
1168 if current_bytes.saturating_add(entry.total_bytes) > max_bytes {
1169 return Some(LimitReached::MaxBytes);
1170 }
1171 if current_count.saturating_add(entry.transactions.len()) > max_count {
1172 return Some(LimitReached::MaxNumOfTransactions);
1173 }
1174 None
1175}
1176
1177fn consensus_client_error(error: ClientError) -> SuiError {
1178 SuiErrorKind::FailedToSubmitToConsensus(error.to_string()).into()
1179}
1180
1181#[derive(Clone)]
1182enum PoolState {
1183 Absent,
1185 Active(EpochId, Arc<ConsensusTransactionPool>),
1186 Unavailable(EpochId),
1189}
1190
1191pub struct TransactionPoolContext {
1200 state: watch::Sender<PoolState>,
1201 metrics: Arc<AdmissionQueueMetrics>,
1202 adapter_metrics: ConsensusAdapterMetrics,
1203}
1204
1205impl TransactionPoolContext {
1206 pub fn new(
1207 metrics: Arc<AdmissionQueueMetrics>,
1208 adapter_metrics: ConsensusAdapterMetrics,
1209 ) -> Self {
1210 let (state, _) = watch::channel(PoolState::Absent);
1211 Self {
1212 state,
1213 metrics,
1214 adapter_metrics,
1215 }
1216 }
1217
1218 pub fn set_active(&self, epoch: EpochId, pool: Arc<ConsensusTransactionPool>) {
1219 assert_eq!(epoch, pool.epoch());
1220 drop(self.state.send_replace(PoolState::Active(epoch, pool)));
1221 }
1222
1223 pub fn set_unavailable(&self, epoch: EpochId) {
1226 drop(self.state.send_replace(PoolState::Unavailable(epoch)));
1227 }
1228
1229 pub async fn try_insert(
1230 &self,
1231 epoch: EpochId,
1232 gas_price: u64,
1233 transactions: Vec<ConsensusTransaction>,
1234 ) -> SuiResult<(PositionReceiver, bool)> {
1235 self.wait_for_pool(epoch)
1236 .await?
1237 .try_insert(epoch, gas_price, transactions)
1238 }
1239
1240 pub async fn wait_for_pool(
1248 &self,
1249 caller_epoch: EpochId,
1250 ) -> SuiResult<Arc<ConsensusTransactionPool>> {
1251 self.wait_for_pool_after_subscribe(caller_epoch, || {})
1252 .await
1253 }
1254
1255 async fn wait_for_pool_after_subscribe(
1256 &self,
1257 caller_epoch: EpochId,
1258 after_subscribe: impl FnOnce(),
1259 ) -> SuiResult<Arc<ConsensusTransactionPool>> {
1260 let mut receiver = self.state.subscribe();
1261 after_subscribe();
1262 let start = Instant::now();
1263 loop {
1264 let state = receiver.borrow_and_update().clone();
1265 match state {
1266 PoolState::Active(epoch, pool) if epoch == caller_epoch => {
1267 return Ok(pool);
1268 }
1269 PoolState::Active(epoch, _) if epoch > caller_epoch => {
1270 return Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1271 }
1272 PoolState::Unavailable(epoch) if epoch > caller_epoch => {
1273 return Err(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1274 }
1275 PoolState::Unavailable(epoch) if epoch == caller_epoch => {
1276 return Err(SuiErrorKind::TooManyTransactionsPendingConsensus.into());
1277 }
1278 PoolState::Absent | PoolState::Active(_, _) | PoolState::Unavailable(_) => {}
1279 }
1280
1281 let _waiting = WaitingInsertGuard::new(self.metrics.pool_waiting_inserts.clone());
1282 match tokio::time::timeout(Duration::from_secs(10), receiver.changed()).await {
1283 Ok(Ok(())) => {}
1284 Ok(Err(_)) => {
1285 debug_fatal!("transaction pool watch sender dropped while context is alive");
1290 return Err(SuiErrorKind::TooManyTransactionsPendingConsensus.into());
1291 }
1292 Err(_) => {
1293 warn!(
1294 caller_epoch,
1295 elapsed = ?start.elapsed(),
1296 "Waiting for consensus transaction pool to initialize"
1297 );
1298 }
1299 }
1300 }
1301 }
1302
1303 pub fn metrics(&self) -> &Arc<AdmissionQueueMetrics> {
1304 &self.metrics
1305 }
1306
1307 pub fn adapter_metrics(&self) -> &ConsensusAdapterMetrics {
1308 &self.adapter_metrics
1309 }
1310
1311 #[cfg(test)]
1312 pub(crate) fn new_for_tests(metrics: Arc<AdmissionQueueMetrics>) -> Self {
1313 Self::new(metrics, ConsensusAdapterMetrics::new_test())
1314 }
1315}
1316
1317struct WaitingInsertGuard {
1318 gauge: IntGauge,
1319}
1320
1321impl WaitingInsertGuard {
1322 fn new(gauge: IntGauge) -> Self {
1323 gauge.inc();
1324 Self { gauge }
1325 }
1326}
1327
1328impl Drop for WaitingInsertGuard {
1329 fn drop(&mut self) {
1330 self.gauge.dec();
1331 }
1332}
1333
1334pub struct TransactionPoolClient {
1339 context: Arc<TransactionPoolContext>,
1340}
1341
1342impl TransactionPoolClient {
1343 pub fn new(context: Arc<TransactionPoolContext>) -> Self {
1344 Self { context }
1345 }
1346}
1347
1348#[async_trait]
1349impl ConsensusClient for TransactionPoolClient {
1350 async fn submit(
1351 &self,
1352 transactions: &[ConsensusTransaction],
1353 epoch_store: &Arc<AuthorityPerEpochStore>,
1354 ) -> SuiResult<(Vec<ConsensusPosition>, BlockStatusReceiver)> {
1355 let epoch = epoch_store.epoch();
1356 let pool = self.context.wait_for_pool(epoch).await?;
1357 let receiver = pool.submit(epoch, transactions)?;
1358 let (block_ref, indices, status_receiver) = receiver.await.map_err(|error| {
1359 consensus_client_error(ClientError::ConsensusShuttingDown(error.to_string()))
1360 })?;
1361 let positions = indices
1362 .into_iter()
1363 .map(|index| ConsensusPosition {
1364 epoch,
1365 block: block_ref,
1366 index,
1367 })
1368 .collect();
1369 Ok((positions, status_receiver))
1370 }
1371}
1372
1373#[cfg(test)]
1374mod tests {
1375 use super::*;
1376 use crate::authority::AuthorityState;
1377 use crate::authority::test_authority_builder::TestAuthorityBuilder;
1378 use consensus_config::AuthorityIndex;
1379 use consensus_types::block::BlockDigest;
1380 use sui_protocol_config::ProtocolConfig;
1381 use sui_types::base_types::{AuthorityName, random_object_ref};
1382 use sui_types::crypto::{AccountKeyPair, get_key_pair};
1383 use sui_types::transaction::PlainTransactionWithClaims;
1384
1385 async fn test_state_and_pool(
1386 capacity: usize,
1387 ) -> (Arc<AuthorityState>, Arc<ConsensusTransactionPool>) {
1388 let state = TestAuthorityBuilder::new().build().await;
1389 let pool = pool_for_current_epoch(&state, capacity);
1390 (state, pool)
1391 }
1392
1393 async fn test_state_and_pool_with_protocol_config(
1394 capacity: usize,
1395 protocol_config: ProtocolConfig,
1396 ) -> (Arc<AuthorityState>, Arc<ConsensusTransactionPool>) {
1397 let state = TestAuthorityBuilder::new()
1398 .with_protocol_config(protocol_config)
1399 .build()
1400 .await;
1401 let pool = pool_for_current_epoch(&state, capacity);
1402 (state, pool)
1403 }
1404
1405 fn pool_for_current_epoch(
1406 state: &Arc<AuthorityState>,
1407 capacity: usize,
1408 ) -> Arc<ConsensusTransactionPool> {
1409 Arc::new(ConsensusTransactionPool::new_for_tests(
1410 state.epoch_store_for_testing().clone(),
1411 capacity,
1412 Arc::new(AdmissionQueueMetrics::new_for_tests()),
1413 ))
1414 }
1415
1416 fn transaction() -> ConsensusTransaction {
1417 ConsensusTransaction::new_end_of_publish(AuthorityName::ZERO)
1418 }
1419
1420 fn user_transaction() -> ConsensusTransaction {
1421 let (sender, keypair) = get_key_pair::<AccountKeyPair>();
1422 let transaction = crate::test_utils::make_transfer_sui_transaction(
1423 random_object_ref(),
1424 sender,
1425 Some(1),
1426 sender,
1427 &keypair,
1428 1,
1429 );
1430 ConsensusTransaction::new_user_transaction_v2_message(
1431 &AuthorityName::ZERO,
1432 PlainTransactionWithClaims::no_aliases(transaction),
1433 )
1434 }
1435
1436 fn block(round: Round) -> BlockRef {
1437 BlockRef::new(round, AuthorityIndex::new_for_test(0), BlockDigest::MIN)
1438 }
1439
1440 fn consensus_key(transaction: &ConsensusTransaction) -> SequencedConsensusTransactionKey {
1441 SequencedConsensusTransactionKey::External(transaction.key())
1442 }
1443
1444 fn digest_of(transaction: &ConsensusTransaction) -> TransactionDigest {
1445 consensus_key(transaction)
1446 .user_transaction_digest()
1447 .expect("expected a user transaction key")
1448 }
1449
1450 #[cfg(debug_assertions)]
1451 #[tokio::test]
1452 #[should_panic(expected = "dropped without resolution")]
1453 async fn unresolved_pending_ack_panics() {
1454 let (sender, _receiver) = oneshot::channel();
1455 drop(PendingAck::new(
1456 EntryAck::User(sender),
1457 vec![transaction().key()],
1458 ));
1459 }
1460
1461 #[tokio::test]
1462 async fn user_lane_close_returns_pending_entries_and_closes_lane() {
1463 let metrics = Arc::new(AdmissionQueueMetrics::new_for_tests());
1464 let mut lane = UserLane::Open(PriorityAdmissionQueue::new(1, metrics.clone()));
1465 let consensus_transaction = transaction();
1466 let serialized = bcs::to_bytes(&consensus_transaction).unwrap();
1467 let (sender, receiver) = oneshot::channel();
1468 let entry = PoolEntry {
1469 transactions: vec![Transaction::new(serialized.clone())],
1470 total_bytes: serialized.len(),
1471 gas_price: 1,
1472 tx_type: tx_type_label(std::slice::from_ref(&consensus_transaction)),
1473 ack: PendingAck::new(EntryAck::User(sender), vec![consensus_transaction.key()]),
1474 metrics,
1475 processed: Vec::new(),
1476 };
1477 let UserLane::Open(user) = &mut lane else {
1478 unreachable!("new user lane must be open");
1479 };
1480 user.insert(entry).unwrap();
1481
1482 let mut entries = lane.close();
1483 assert!(matches!(lane, UserLane::Closed));
1484 assert_eq!(entries.len(), 1);
1485 entries
1486 .pop()
1487 .unwrap()
1488 .ack
1489 .resolve_error(SuiErrorKind::ValidatorHaltedAtEpochEnd.into());
1490 assert!(matches!(
1491 receiver.await.unwrap().unwrap_err().as_inner(),
1492 SuiErrorKind::ValidatorHaltedAtEpochEnd
1493 ));
1494 }
1495
1496 #[tokio::test]
1497 async fn locked_user_recheck_defuses_ack_after_pool_close() {
1498 let (_state, pool) = test_state_and_pool(1).await;
1499 pool.close();
1500 assert!(matches!(
1503 pool.try_insert(pool.epoch(), 1, vec![transaction()])
1504 .unwrap_err()
1505 .as_inner(),
1506 SuiErrorKind::ValidatorHaltedAtEpochEnd
1507 ));
1508 }
1509
1510 #[tokio::test]
1511 async fn take_respects_priority_budgets_and_acknowledges_positions() {
1512 let (_state, pool) = test_state_and_pool(10).await;
1513 let epoch = pool.epoch();
1514 let system_receiver = pool.submit(epoch, &[transaction()]).unwrap();
1515 assert_eq!(pool.queue_depth("system"), 1);
1516 let (low_receiver, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1517 let (high_receiver, _) = pool.try_insert(epoch, 20, vec![transaction()]).unwrap();
1518
1519 let (transactions, ack, limit) = pool.take(2, usize::MAX);
1520 assert_eq!(transactions.len(), 2);
1521 assert_eq!(limit, LimitReached::MaxNumOfTransactions);
1522 assert_eq!(pool.queue_depth("system"), 0);
1523 let block_ref = block(5);
1524 ack(block_ref);
1525
1526 let (system_block, system_indices, system_status) = system_receiver.await.unwrap();
1527 assert_eq!(system_block, block_ref);
1528 assert_eq!(system_indices, vec![0]);
1529 let positions = high_receiver.await.unwrap().unwrap();
1530 assert_eq!(
1531 positions,
1532 vec![ConsensusPosition {
1533 epoch,
1534 block: block_ref,
1535 index: 1,
1536 }]
1537 );
1538 pool.notify_committed(vec![block_ref], 0);
1539 assert_eq!(
1540 system_status.await.unwrap(),
1541 BlockStatus::Sequenced(block_ref)
1542 );
1543
1544 let serialized_len = bcs::to_bytes(&transaction()).unwrap().len();
1545 let (transactions, ack, limit) = pool.take(1, serialized_len - 1);
1546 assert!(transactions.is_empty());
1547 assert_eq!(limit, LimitReached::MaxBytes);
1548 drop(ack);
1549
1550 let (transactions, ack, limit) = pool.take(1, serialized_len);
1551 assert_eq!(transactions.len(), 1);
1552 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
1553 drop(ack);
1554 assert_eq!(pool.queue_depth("user"), 1);
1555
1556 let (transactions, ack, _) = pool.take(1, serialized_len);
1557 assert_eq!(transactions.len(), 1);
1558 ack(block(6));
1559 assert_eq!(low_receiver.await.unwrap().unwrap()[0].index, 0);
1560 }
1561
1562 #[tokio::test]
1563 async fn bundles_are_atomic() {
1564 let (_state, pool) = test_state_and_pool(10).await;
1565 let (_receiver, _) = pool
1566 .try_insert(pool.epoch(), 10, vec![transaction(), transaction()])
1567 .unwrap();
1568 let (transactions, ack, limit) = pool.take(1, usize::MAX);
1569 assert!(transactions.is_empty());
1570 assert_eq!(limit, LimitReached::MaxNumOfTransactions);
1571 drop(ack);
1572
1573 let bundle_bytes = 2 * bcs::to_bytes(&transaction()).unwrap().len();
1574 let (transactions, ack, limit) = pool.take(2, bundle_bytes - 1);
1575 assert!(transactions.is_empty());
1576 assert_eq!(limit, LimitReached::MaxBytes);
1577 drop(ack);
1578
1579 let (transactions, ack, _) = pool.take(2, bundle_bytes);
1580 assert_eq!(transactions.len(), 2);
1581 drop(ack);
1582 pool.close();
1583 }
1584
1585 #[tokio::test]
1586 async fn dropped_ack_preserves_front_order() {
1587 let (_state, pool) = test_state_and_pool(10).await;
1588 let epoch = pool.epoch();
1589 let (first, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1590 let (mut second, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1591 let (_, ack, _) = pool.take(2, usize::MAX);
1592 drop(ack);
1593
1594 let (_, ack, _) = pool.take(1, usize::MAX);
1595 ack(block(1));
1596 assert_eq!(first.await.unwrap().unwrap()[0].index, 0);
1597 assert!(second.try_recv().is_err());
1598 pool.close();
1599 }
1600
1601 #[tokio::test]
1602 async fn ping_capacity_and_reserved_index() {
1603 let (_state, pool) = test_state_and_pool(1).await;
1604 let epoch = pool.epoch();
1605 let mut receivers = Vec::with_capacity(MAX_PENDING_PINGS);
1606 for _ in 0..MAX_PENDING_PINGS {
1607 receivers.push(pool.submit(epoch, &[]).unwrap());
1608 }
1609 assert!(matches!(
1610 pool.submit(epoch, &[]).err().unwrap().as_inner(),
1611 SuiErrorKind::TooManyTransactionsPendingConsensus
1612 ));
1613
1614 let (transactions, ack, _) = pool.take(0, 0);
1615 assert!(transactions.is_empty());
1616 let block_ref = block(1);
1617 ack(block_ref);
1618 let (ping_block, indices, status) = receivers.remove(0).await.unwrap();
1619 assert_eq!(ping_block, block_ref);
1620 assert_eq!(indices, vec![PING_TRANSACTION_INDEX]);
1621 pool.notify_committed(vec![block_ref], 0);
1622 assert_eq!(status.await.unwrap(), BlockStatus::Sequenced(block_ref));
1623 }
1624
1625 #[tokio::test]
1626 async fn system_lane_is_not_bounded_by_user_capacity() {
1627 let (_state, pool) = test_state_and_pool(1).await;
1628 let receivers = (0..3)
1629 .map(|_| pool.submit(pool.epoch(), &[transaction()]).unwrap())
1630 .collect::<Vec<_>>();
1631 assert_eq!(pool.queue_depth("system"), 3);
1632 let (transactions, ack, _) = pool.take(3, usize::MAX);
1633 assert_eq!(transactions.len(), 3);
1634 ack(block(1));
1635 for receiver in receivers {
1636 assert_eq!(receiver.await.unwrap().1.len(), 1);
1637 }
1638 }
1639
1640 #[tokio::test]
1641 async fn take_skips_abandoned_system_entries_and_pings() {
1642 let (_state, pool) = test_state_and_pool(1).await;
1643 let epoch = pool.epoch();
1644
1645 let stale = pool.submit(epoch, &[transaction()]).unwrap();
1648 drop(stale);
1649 let live = pool.submit(epoch, &[transaction()]).unwrap();
1650 let stale_ping = pool.submit(epoch, &[]).unwrap();
1651 drop(stale_ping);
1652 let live_ping = pool.submit(epoch, &[]).unwrap();
1653
1654 let (transactions, ack, _) = pool.take(1, usize::MAX);
1655 assert_eq!(transactions.len(), 1);
1656 assert_eq!(pool.queue_depth("system"), 0);
1657 assert_eq!(pool.queue_depth("ping"), 0);
1658 assert_eq!(pool.abandoned_count("system"), 1);
1659 assert_eq!(pool.abandoned_count("ping"), 1);
1660
1661 ack(block(1));
1662 assert_eq!(live.await.unwrap().1.len(), 1);
1663 assert_eq!(live_ping.await.unwrap().1, vec![PING_TRANSACTION_INDEX]);
1664 }
1665
1666 #[tokio::test]
1667 async fn eviction_rejection_and_duplicate_detection_match_admission_queue() {
1668 let (_state, pool) = test_state_and_pool(2).await;
1669 let epoch = pool.epoch();
1670 let (first, newly_inserted) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1671 assert!(newly_inserted);
1672 let (_second, newly_inserted) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1673 assert!(!newly_inserted);
1674
1675 let (_high, _) = pool.try_insert(epoch, 20, vec![transaction()]).unwrap();
1676 assert!(matches!(
1677 first.await.unwrap().unwrap_err().as_inner(),
1678 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price: 20 }
1679 ));
1680 assert!(matches!(
1681 pool.try_insert(epoch, 10, vec![transaction()])
1682 .err()
1683 .unwrap()
1684 .as_inner(),
1685 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { min_gas_price: 10 }
1686 ));
1687 pool.close();
1688 }
1689
1690 #[tokio::test]
1691 async fn close_resolves_pending_and_taken_user_entries() {
1692 let (_state, pool) = test_state_and_pool(10).await;
1693 let epoch = pool.epoch();
1694 for wrong_epoch in [epoch + 1, epoch + 2] {
1695 assert!(matches!(
1696 pool.try_insert(wrong_epoch, 1, vec![transaction()])
1697 .err()
1698 .unwrap()
1699 .as_inner(),
1700 SuiErrorKind::ValidatorHaltedAtEpochEnd
1701 ));
1702 }
1703 let (pending, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1704 let (taken, _) = pool.try_insert(epoch, 20, vec![transaction()]).unwrap();
1705 let (_, ack, _) = pool.take(1, usize::MAX);
1706 pool.close();
1707
1708 assert!(matches!(
1709 pending.await.unwrap().unwrap_err().as_inner(),
1710 SuiErrorKind::ValidatorHaltedAtEpochEnd
1711 ));
1712 ack(block(1));
1713 assert!(matches!(
1714 taken.await.unwrap().unwrap_err().as_inner(),
1715 SuiErrorKind::ValidatorHaltedAtEpochEnd
1716 ));
1717 assert!(matches!(
1718 pool.try_insert(epoch, 30, vec![transaction()])
1719 .err()
1720 .unwrap()
1721 .as_inner(),
1722 SuiErrorKind::ValidatorHaltedAtEpochEnd
1723 ));
1724 }
1725
1726 #[tokio::test]
1727 async fn dropped_ack_after_close_does_not_requeue() {
1728 let (_state, pool) = test_state_and_pool(10).await;
1729 let epoch = pool.epoch();
1730 let (receiver, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1731 let system_receiver = pool.submit(epoch, &[transaction()]).unwrap();
1732 let (_, ack, _) = pool.take(2, usize::MAX);
1733 pool.close();
1734 drop(ack);
1735 assert!(matches!(
1736 receiver.await.unwrap().unwrap_err().as_inner(),
1737 SuiErrorKind::ValidatorHaltedAtEpochEnd
1738 ));
1739 assert!(system_receiver.await.is_err());
1740 assert_eq!(pool.queue_depth("user"), 0);
1741 }
1742
1743 #[tokio::test]
1744 async fn take_closes_user_lane_when_epoch_stops_accepting_user_certs() {
1745 let (state, pool) = test_state_and_pool(10).await;
1746 let epoch = pool.epoch();
1747 let (user_receiver, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1748 let system_receiver = pool.submit(epoch, &[transaction()]).unwrap();
1749
1750 let epoch_store = state.epoch_store_for_testing();
1751 epoch_store.close_user_certs_for_manual_epoch_close(
1752 epoch_store.get_reconfig_state_write_lock_guard(),
1753 );
1754 let (transactions, ack, limit) = pool.take(10, usize::MAX);
1755 assert_eq!(transactions.len(), 1);
1756 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
1757 assert!(matches!(
1758 user_receiver.await.unwrap().unwrap_err().as_inner(),
1759 SuiErrorKind::ValidatorHaltedAtEpochEnd
1760 ));
1761 assert!(matches!(
1762 pool.try_insert(epoch, 20, vec![transaction()])
1763 .err()
1764 .unwrap()
1765 .as_inner(),
1766 SuiErrorKind::ValidatorHaltedAtEpochEnd
1767 ));
1768
1769 let block_ref = block(1);
1770 ack(block_ref);
1771 assert_eq!(system_receiver.await.unwrap().0, block_ref);
1772 }
1773
1774 #[tokio::test]
1775 async fn dropped_ack_after_user_lane_close_resolves_taken_entries() {
1776 let (state, pool) = test_state_and_pool(10).await;
1777 let epoch = pool.epoch();
1778 let (taken, _) = pool.try_insert(epoch, 10, vec![transaction()]).unwrap();
1779 let (transactions, ack, _) = pool.take(1, usize::MAX);
1780 assert_eq!(transactions.len(), 1);
1781
1782 let epoch_store = state.epoch_store_for_testing();
1784 epoch_store.close_user_certs_for_manual_epoch_close(
1785 epoch_store.get_reconfig_state_write_lock_guard(),
1786 );
1787 let (flushed, flush_ack, _) = pool.take(10, usize::MAX);
1788 assert!(flushed.is_empty());
1789 drop(flush_ack);
1790
1791 drop(ack);
1794 assert!(matches!(
1795 taken.await.unwrap().unwrap_err().as_inner(),
1796 SuiErrorKind::ValidatorHaltedAtEpochEnd
1797 ));
1798 assert_eq!(pool.queue_depth("user"), 0);
1799 }
1800
1801 #[tokio::test]
1802 async fn notify_committed_sequences_commits_before_garbage_collection() {
1803 let (_state, pool) = test_state_and_pool(10).await;
1804 let epoch = pool.epoch();
1805 let first = pool.submit(epoch, &[transaction()]).unwrap();
1806 let (_, ack, _) = pool.take(1, usize::MAX);
1807 let first_block = block(1);
1808 ack(first_block);
1809 let (_, _, first_status) = first.await.unwrap();
1810
1811 let second = pool.submit(epoch, &[transaction()]).unwrap();
1812 let (_, ack, _) = pool.take(1, usize::MAX);
1813 let second_block = block(2);
1814 ack(second_block);
1815 let (_, _, second_status) = second.await.unwrap();
1816
1817 pool.notify_committed(vec![second_block], 1);
1818 assert_eq!(
1819 second_status.await.unwrap(),
1820 BlockStatus::Sequenced(second_block)
1821 );
1822 assert_eq!(
1823 first_status.await.unwrap(),
1824 BlockStatus::GarbageCollected(first_block)
1825 );
1826 }
1827
1828 #[tokio::test]
1829 async fn notify_committed_reports_proposed_user_outcomes() {
1830 let (_state, pool) = test_state_and_pool(10).await;
1831 let epoch = pool.epoch();
1832 let status = |status: &str| {
1833 pool.adapter_metrics
1834 .sequencing_certificate_status
1835 .with_label_values(&["owned_user_transaction_v2", status])
1836 .get()
1837 };
1838 let commit_latency_count = |lane: &str| {
1839 pool.metrics
1840 .pool_commit_latency
1841 .with_label_values(&[lane])
1842 .get_sample_count()
1843 };
1844
1845 let (_first, _) = pool.try_insert(epoch, 1, vec![user_transaction()]).unwrap();
1847 let (_, ack, _) = pool.take(1, usize::MAX);
1848 ack(block(1));
1849 let (_second, _) = pool.try_insert(epoch, 1, vec![user_transaction()]).unwrap();
1851 let _system = pool.submit(epoch, &[transaction()]).unwrap();
1852 let (_, ack, _) = pool.take(2, usize::MAX);
1853 ack(block(2));
1854 assert_eq!(status("sequenced"), 0);
1855
1856 pool.notify_committed(vec![block(2)], 1);
1857 assert_eq!(status("sequenced"), 1);
1858 assert_eq!(status("garbage_collected"), 1);
1859 assert_eq!(commit_latency_count("user"), 1);
1861 assert_eq!(commit_latency_count("system"), 1);
1862 }
1863
1864 #[tokio::test]
1865 async fn insert_validation_rejects_oversized_transactions_and_bundles() {
1866 let serialized_len = u64::try_from(bcs::to_bytes(&transaction()).unwrap().len()).unwrap();
1867
1868 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
1869 config.set_consensus_max_transaction_size_bytes_for_testing(serialized_len - 1);
1870 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
1871 assert!(
1872 pool.try_insert(pool.epoch(), 1, vec![transaction()])
1873 .err()
1874 .unwrap()
1875 .to_string()
1876 .contains("Transaction size")
1877 );
1878
1879 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
1880 config.set_consensus_max_num_transactions_in_block_for_testing(1);
1881 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
1882 assert!(
1883 pool.try_insert(pool.epoch(), 1, vec![transaction(), transaction()])
1884 .err()
1885 .unwrap()
1886 .to_string()
1887 .contains("bundle count")
1888 );
1889
1890 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
1891 config.set_consensus_max_transactions_in_block_bytes_for_testing(serialized_len);
1892 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
1893 assert!(
1894 pool.try_insert(pool.epoch(), 1, vec![transaction(), transaction()])
1895 .err()
1896 .unwrap()
1897 .to_string()
1898 .contains("bundle size")
1899 );
1900 }
1901
1902 #[cfg(debug_assertions)]
1903 #[tokio::test]
1904 #[should_panic(expected = "system transaction failed validation")]
1905 async fn oversized_system_transaction_is_an_invariant_violation() {
1906 let serialized_len = u64::try_from(bcs::to_bytes(&transaction()).unwrap().len()).unwrap();
1907 let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
1908 config.set_consensus_max_transaction_size_bytes_for_testing(serialized_len - 1);
1909 let (_state, pool) = test_state_and_pool_with_protocol_config(10, config).await;
1910 let _ = pool.submit(pool.epoch(), &[transaction()]);
1911 }
1912
1913 #[tokio::test]
1914 async fn take_skips_already_processed_entries_without_shrinking_the_block() {
1915 let (state, pool) = test_state_and_pool(10).await;
1916 let epoch = pool.epoch();
1917 let epoch_store = state.epoch_store_for_testing();
1918
1919 let processed = user_transaction();
1923 let (processed_consensus, _) = pool
1924 .try_insert(epoch, 100, vec![processed.clone()])
1925 .unwrap();
1926 let executed = user_transaction();
1927 let (processed_checkpoint, _) = pool.try_insert(epoch, 90, vec![executed.clone()]).unwrap();
1928 let live = (0..3)
1929 .map(|_| {
1930 pool.try_insert(epoch, 10, vec![user_transaction()])
1931 .unwrap()
1932 .0
1933 })
1934 .collect::<Vec<_>>();
1935
1936 epoch_store.process_notifications(std::iter::once(&consensus_key(&processed)));
1937 epoch_store
1938 .insert_finalized_transactions(&[digest_of(&executed)], 1)
1939 .unwrap();
1940
1941 let (transactions, ack, limit) = pool.take(3, usize::MAX);
1942 assert_eq!(transactions.len(), 3);
1943 assert_eq!(limit, LimitReached::AllTransactionsIncluded);
1944 assert_eq!(pool.queue_depth("user"), 0);
1945 assert_eq!(
1946 pool.already_processed_count("proposal", "consensus_message"),
1947 1
1948 );
1949 assert_eq!(
1950 pool.already_processed_count("proposal", "checkpoint_execution"),
1951 1
1952 );
1953 for receiver in [processed_consensus, processed_checkpoint] {
1954 assert!(matches!(
1955 receiver.await.unwrap().unwrap_err().as_inner(),
1956 SuiErrorKind::TransactionProcessing { .. }
1957 ));
1958 }
1959
1960 ack(block(1));
1961 for receiver in live {
1962 assert_eq!(receiver.await.unwrap().unwrap().len(), 1);
1963 }
1964 }
1965
1966 #[tokio::test]
1967 async fn bundles_are_skipped_only_when_every_transaction_is_already_processed() {
1968 let (state, pool) = test_state_and_pool(10).await;
1969 let epoch = pool.epoch();
1970 let epoch_store = state.epoch_store_for_testing();
1971
1972 let first = user_transaction();
1973 let second = user_transaction();
1974 let (receiver, _) = pool
1975 .try_insert(epoch, 10, vec![first.clone(), second.clone()])
1976 .unwrap();
1977
1978 epoch_store.process_notifications(std::iter::once(&consensus_key(&first)));
1979 let (transactions, ack, _) = pool.take(10, usize::MAX);
1980 assert_eq!(transactions.len(), 2);
1981 assert_eq!(
1982 pool.already_processed_count("proposal", "consensus_message"),
1983 0
1984 );
1985 drop(ack);
1988
1989 epoch_store.process_notifications(std::iter::once(&consensus_key(&second)));
1990 let (transactions, ack, _) = pool.take(10, usize::MAX);
1991 assert!(transactions.is_empty());
1992 assert_eq!(
1993 pool.already_processed_count("proposal", "consensus_message"),
1994 1
1995 );
1996 assert!(matches!(
1997 receiver.await.unwrap().unwrap_err().as_inner(),
1998 SuiErrorKind::TransactionProcessing { .. }
1999 ));
2000 drop(ack);
2001 }
2002
2003 #[tokio::test]
2004 async fn insert_rejects_already_processed_transactions() {
2005 let (state, pool) = test_state_and_pool(10).await;
2006 let epoch = pool.epoch();
2007 let epoch_store = state.epoch_store_for_testing();
2008
2009 let via_consensus = user_transaction();
2012 epoch_store.test_insert_user_signature(digest_of(&via_consensus), vec![]);
2013 assert!(matches!(
2014 pool.try_insert(epoch, 10, vec![via_consensus])
2015 .unwrap_err()
2016 .as_inner(),
2017 SuiErrorKind::TransactionProcessing { .. }
2018 ));
2019 assert_eq!(
2020 pool.already_processed_count("insert", "consensus_message"),
2021 1
2022 );
2023
2024 let via_checkpoint = user_transaction();
2025 epoch_store
2026 .insert_finalized_transactions(&[digest_of(&via_checkpoint)], 1)
2027 .unwrap();
2028 assert!(matches!(
2029 pool.try_insert(epoch, 10, vec![via_checkpoint])
2030 .unwrap_err()
2031 .as_inner(),
2032 SuiErrorKind::TransactionProcessing { .. }
2033 ));
2034 assert_eq!(
2035 pool.already_processed_count("insert", "checkpoint_execution"),
2036 1
2037 );
2038
2039 assert_eq!(pool.queue_depth("user"), 0);
2040 let (transactions, ack, _) = pool.take(10, usize::MAX);
2041 assert!(transactions.is_empty());
2042 drop(ack);
2043 }
2044
2045 #[tokio::test]
2046 async fn departing_entries_deregister_their_processed_watches() {
2047 let (state, pool) = test_state_and_pool(1).await;
2048 let epoch = pool.epoch();
2049 let epoch_store = state.epoch_store_for_testing();
2050 let baseline = epoch_store.num_pending_processed_notifications();
2052
2053 let evicted_tx = user_transaction();
2054 let (evicted, _) = pool
2055 .try_insert(epoch, 10, vec![evicted_tx.clone()])
2056 .unwrap();
2057 assert_eq!(
2058 epoch_store.num_pending_processed_notifications(),
2059 baseline + 2
2060 );
2061
2062 let taken_tx = user_transaction();
2063 let (taken, _) = pool.try_insert(epoch, 20, vec![taken_tx.clone()]).unwrap();
2064 assert!(matches!(
2065 evicted.await.unwrap().unwrap_err().as_inner(),
2066 SuiErrorKind::TransactionRejectedDueToOutbiddingDuringCongestion { .. }
2067 ));
2068 assert_eq!(
2069 epoch_store.num_pending_processed_notifications(),
2070 baseline + 2
2071 );
2072
2073 let (transactions, ack, _) = pool.take(10, usize::MAX);
2074 assert_eq!(transactions.len(), 1);
2075 ack(block(1));
2076 taken.await.unwrap().unwrap();
2077 assert_eq!(epoch_store.num_pending_processed_notifications(), baseline);
2078
2079 let closed_tx = user_transaction();
2080 let (closed, _) = pool.try_insert(epoch, 10, vec![closed_tx.clone()]).unwrap();
2081 assert_eq!(
2082 epoch_store.num_pending_processed_notifications(),
2083 baseline + 2
2084 );
2085 pool.close();
2086 assert!(matches!(
2087 closed.await.unwrap().unwrap_err().as_inner(),
2088 SuiErrorKind::ValidatorHaltedAtEpochEnd
2089 ));
2090 assert_eq!(epoch_store.num_pending_processed_notifications(), baseline);
2091
2092 for transaction in [&evicted_tx, &taken_tx, &closed_tx] {
2093 epoch_store.process_notifications(std::iter::once(&consensus_key(transaction)));
2094 epoch_store
2095 .insert_finalized_transactions(&[digest_of(transaction)], 1)
2096 .unwrap();
2097 }
2098 assert_eq!(epoch_store.num_pending_processed_notifications(), baseline);
2099 }
2100
2101 #[tokio::test]
2102 async fn zero_user_capacity_is_rejected_at_startup() {
2103 let state = TestAuthorityBuilder::new().build().await;
2104 let epoch_store = state.epoch_store_for_testing().clone();
2105 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2106 ConsensusTransactionPool::new_for_tests(
2107 epoch_store,
2108 0,
2109 Arc::new(AdmissionQueueMetrics::new_for_tests()),
2110 )
2111 }));
2112 assert!(result.is_err());
2113 }
2114
2115 #[tokio::test]
2116 async fn context_observes_installed_pool_and_waits_for_matching_epoch() {
2117 let metrics = Arc::new(AdmissionQueueMetrics::new_for_tests());
2118 let context = Arc::new(TransactionPoolContext::new_for_tests(metrics));
2119 let state = TestAuthorityBuilder::new().build().await;
2120 let pool = pool_for_current_epoch(&state, 10);
2121 let epoch = pool.epoch();
2122 let absent_waiter = tokio::spawn({
2123 let context = context.clone();
2124 async move { context.wait_for_pool(epoch).await }
2125 });
2126 while context.metrics().pool_waiting_inserts.get() == 0 {
2127 tokio::task::yield_now().await;
2128 }
2129 assert_eq!(context.metrics().pool_waiting_inserts.get(), 1);
2130 context.set_active(epoch, pool.clone());
2131 assert_eq!(absent_waiter.await.unwrap().unwrap().epoch(), epoch);
2132
2133 assert_eq!(context.wait_for_pool(epoch).await.unwrap().epoch(), epoch);
2135
2136 let race_context = Arc::new(TransactionPoolContext::new_for_tests(Arc::new(
2137 AdmissionQueueMetrics::new_for_tests(),
2138 )));
2139 let setter = race_context.clone();
2140 let race_pool = pool.clone();
2141 assert_eq!(
2142 race_context
2143 .wait_for_pool_after_subscribe(epoch, move || {
2144 setter.set_active(epoch, race_pool);
2145 })
2146 .await
2147 .unwrap()
2148 .epoch(),
2149 epoch
2150 );
2151
2152 let next_epoch = epoch + 1;
2153 let context_waiter = context.clone();
2154 let waiter = tokio::spawn(async move { context_waiter.wait_for_pool(next_epoch).await });
2155 while context.metrics().pool_waiting_inserts.get() == 0 {
2156 tokio::task::yield_now().await;
2157 }
2158 assert_eq!(context.metrics().pool_waiting_inserts.get(), 1);
2159 state.reconfigure_for_testing().await;
2160 let next_pool = pool_for_current_epoch(&state, 10);
2161 assert_eq!(next_pool.epoch(), next_epoch);
2162 context.set_active(next_epoch, next_pool);
2163 assert_eq!(waiter.await.unwrap().unwrap().epoch(), next_epoch);
2164 }
2165
2166 #[tokio::test]
2167 async fn context_fails_stale_and_unavailable_epochs_promptly() {
2168 let context =
2169 TransactionPoolContext::new_for_tests(Arc::new(AdmissionQueueMetrics::new_for_tests()));
2170 let state = TestAuthorityBuilder::new().build().await;
2172 state.reconfigure_for_testing().await;
2173 let pool = pool_for_current_epoch(&state, 10);
2174 let active_epoch = pool.epoch();
2175 context.set_active(active_epoch, pool);
2176 assert!(matches!(
2177 context
2178 .wait_for_pool(active_epoch - 1)
2179 .await
2180 .err()
2181 .unwrap()
2182 .as_inner(),
2183 SuiErrorKind::ValidatorHaltedAtEpochEnd
2184 ));
2185 context.set_unavailable(active_epoch + 1);
2186 assert!(matches!(
2187 context
2188 .wait_for_pool(active_epoch + 1)
2189 .await
2190 .err()
2191 .unwrap()
2192 .as_inner(),
2193 SuiErrorKind::TooManyTransactionsPendingConsensus
2194 ));
2195 assert!(matches!(
2196 context
2197 .wait_for_pool(active_epoch)
2198 .await
2199 .err()
2200 .unwrap()
2201 .as_inner(),
2202 SuiErrorKind::ValidatorHaltedAtEpochEnd
2203 ));
2204 }
2205
2206 #[tokio::test]
2207 async fn transaction_pool_client_maps_ping_ack_and_status() {
2208 let state = TestAuthorityBuilder::new().build().await;
2209 let epoch_store = state.epoch_store_for_testing().clone();
2210 let metrics = Arc::new(AdmissionQueueMetrics::new_for_tests());
2211 let context = Arc::new(TransactionPoolContext::new_for_tests(metrics.clone()));
2212 let pool = Arc::new(ConsensusTransactionPool::new_for_tests(
2213 epoch_store.clone(),
2214 10,
2215 metrics,
2216 ));
2217 context.set_active(epoch_store.epoch(), pool.clone());
2218 let client = Arc::new(TransactionPoolClient::new(context));
2219
2220 let task = tokio::spawn({
2221 let client = client.clone();
2222 let epoch_store = epoch_store.clone();
2223 async move { client.submit(&[], &epoch_store).await }
2224 });
2225 while pool.queue_depth("ping") == 0 {
2226 tokio::task::yield_now().await;
2227 }
2228 let (_, ack, _) = pool.take(0, 0);
2229 let block_ref = block(3);
2230 ack(block_ref);
2231 let (positions, status) = task.await.unwrap().unwrap();
2232 assert_eq!(positions[0].block, block_ref);
2233 assert_eq!(positions[0].index, PING_TRANSACTION_INDEX);
2234 pool.notify_committed(vec![block_ref], 0);
2235 assert_eq!(status.await.unwrap(), BlockStatus::Sequenced(block_ref));
2236
2237 let user_transaction = user_transaction();
2238 let task = tokio::spawn({
2239 let client = client.clone();
2240 let epoch_store = epoch_store.clone();
2241 async move { client.submit(&[user_transaction], &epoch_store).await }
2242 });
2243 match task.await {
2244 Err(join_error) => assert!(join_error.is_panic()),
2246 Ok(result) => assert!(result.is_err()),
2248 }
2249 assert_eq!(pool.queue_depth("user"), 0);
2250 assert_eq!(pool.queue_depth("system"), 0);
2251 }
2252}