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