1use crate::authority::authority_per_epoch_store::{
5 AuthorityEpochTables, EncG, ExecutionIndicesWithStatsV2, LockDetails, LockDetailsWrapper, PkG,
6};
7use crate::authority::transaction_deferral::DeferralKey;
8use crate::checkpoints::BuilderCheckpointSummary;
9use crate::epoch::randomness::SINGLETON_KEY;
10use dashmap::DashMap;
11use fastcrypto_tbls::{dkg_v1, nodes::PartyId};
12use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
13use moka::policy::EvictionPolicy;
14use moka::sync::SegmentedCache as MokaCache;
15use mysten_common::ZipDebugEqIteratorExt;
16use mysten_common::fatal;
17use mysten_common::random_util::randomize_cache_capacity_in_tests;
18use parking_lot::Mutex;
19use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque, hash_map};
20use sui_types::authenticator_state::ActiveJwk;
21use sui_types::base_types::{AuthorityName, ObjectRef, SequenceNumber};
22use sui_types::crypto::RandomnessRound;
23use sui_types::error::SuiResult;
24use sui_types::executable_transaction::{
25 TrustedExecutableTransactionWithAliases, VerifiedExecutableTransactionWithAliases,
26};
27use sui_types::execution::ExecutionTimeObservationKey;
28use sui_types::messages_checkpoint::CheckpointSequenceNumber;
29use sui_types::messages_consensus::AuthorityIndex;
30use sui_types::{
31 base_types::{ConsensusObjectSequenceKey, ObjectID},
32 digests::TransactionDigest,
33 messages_consensus::{Round, TimestampMs, VersionedDkgConfirmation},
34 signature::GenericSignature,
35};
36use tracing::debug;
37use typed_store::Map;
38use typed_store::rocks::DBBatch;
39
40use crate::{
41 authority::{
42 authority_per_epoch_store::AuthorityPerEpochStore,
43 shared_object_congestion_tracker::CongestionPerObjectDebt,
44 },
45 checkpoints::{CheckpointHeight, PendingCheckpoint},
46 consensus_handler::SequencedConsensusTransactionKey,
47 epoch::{
48 randomness::{VersionedProcessedMessage, VersionedUsedProcessedMessages},
49 reconfiguration::ReconfigState,
50 },
51};
52
53use super::*;
54
55#[derive(Default)]
56#[allow(clippy::type_complexity)]
57pub(crate) struct ConsensusCommitOutput {
58 consensus_round: Round,
60 consensus_messages_processed: BTreeSet<SequencedConsensusTransactionKey>,
63 end_of_publish: BTreeSet<AuthorityName>,
64 reconfig_state: Option<ReconfigState>,
65 consensus_commit_stats: Option<ExecutionIndicesWithStatsV2>,
66
67 next_shared_object_versions: Option<HashMap<ConsensusObjectSequenceKey, SequenceNumber>>,
69
70 deferred_txns: Vec<(DeferralKey, Vec<VerifiedExecutableTransactionWithAliases>)>,
71 deleted_deferred_txns: BTreeSet<DeferralKey>,
72
73 pending_checkpoints: Vec<PendingCheckpoint>,
75
76 next_randomness_round: Option<(RandomnessRound, TimestampMs)>,
78
79 dkg_confirmations: BTreeMap<PartyId, VersionedDkgConfirmation>,
80 dkg_processed_messages: BTreeMap<PartyId, VersionedProcessedMessage>,
81 dkg_used_message: Option<VersionedUsedProcessedMessages>,
82 dkg_own_confirmation: Option<VersionedDkgConfirmation>,
83 dkg_output: Option<Option<dkg_v1::Output<PkG, EncG>>>,
84
85 pending_jwks: BTreeSet<(AuthorityName, JwkId, JWK)>,
87 active_jwks: BTreeSet<(u64, (JwkId, JWK))>,
88
89 congestion_control_object_debts: Vec<(ObjectID, u64)>,
91 congestion_control_randomness_object_debts: Vec<(ObjectID, u64)>,
92 execution_time_observations: Vec<(
93 AuthorityIndex,
94 u64, Vec<(ExecutionTimeObservationKey, Duration)>,
96 )>,
97
98 owned_object_locks: HashMap<ObjectRef, LockDetails>,
100
101 checkpoint_queue_drained: bool,
104}
105
106impl ConsensusCommitOutput {
107 pub fn new(consensus_round: Round) -> Self {
108 Self {
109 consensus_round,
110 ..Default::default()
111 }
112 }
113
114 pub fn get_deleted_deferred_txn_keys(&self) -> impl Iterator<Item = DeferralKey> + use<'_> {
115 self.deleted_deferred_txns.iter().cloned()
116 }
117
118 pub fn has_deferred_transactions(&self) -> bool {
119 !self.deferred_txns.is_empty()
120 }
121
122 fn get_randomness_last_round_timestamp(&self) -> Option<TimestampMs> {
123 self.next_randomness_round.as_ref().map(|(_, ts)| *ts)
124 }
125
126 fn get_highest_pending_checkpoint_height(&self) -> Option<CheckpointHeight> {
127 self.pending_checkpoints.last().map(|cp| cp.height())
128 }
129
130 fn get_pending_checkpoints(
131 &self,
132 last: Option<CheckpointHeight>,
133 ) -> impl Iterator<Item = &PendingCheckpoint> {
134 self.pending_checkpoints.iter().filter(move |cp| {
135 if let Some(last) = last {
136 cp.height() > last
137 } else {
138 true
139 }
140 })
141 }
142
143 fn pending_checkpoint_exists(&self, index: &CheckpointHeight) -> bool {
144 self.pending_checkpoints
145 .iter()
146 .any(|cp| cp.height() == *index)
147 }
148
149 fn get_round(&self) -> Option<u64> {
150 self.consensus_commit_stats
151 .as_ref()
152 .map(|stats| stats.index.last_committed_round)
153 }
154
155 pub fn insert_end_of_publish(&mut self, authority: AuthorityName) {
156 self.end_of_publish.insert(authority);
157 }
158
159 pub fn insert_execution_time_observation(
160 &mut self,
161 source: AuthorityIndex,
162 generation: u64,
163 estimates: Vec<(ExecutionTimeObservationKey, Duration)>,
164 ) {
165 self.execution_time_observations
166 .push((source, generation, estimates));
167 }
168
169 pub(crate) fn record_consensus_commit_stats(&mut self, stats: ExecutionIndicesWithStatsV2) {
170 self.consensus_commit_stats = Some(stats);
171 }
172
173 pub(crate) fn set_default_commit_stats_for_testing(&mut self) {
175 self.record_consensus_commit_stats(Default::default());
176 }
177
178 pub fn store_reconfig_state(&mut self, state: ReconfigState) {
179 self.reconfig_state = Some(state);
180 }
181
182 pub fn record_consensus_message_processed(&mut self, key: SequencedConsensusTransactionKey) {
183 self.consensus_messages_processed.insert(key);
184 }
185
186 pub fn get_consensus_messages_processed(
187 &self,
188 ) -> impl Iterator<Item = &SequencedConsensusTransactionKey> {
189 self.consensus_messages_processed.iter()
190 }
191
192 pub fn set_next_shared_object_versions(
193 &mut self,
194 next_versions: HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
195 ) {
196 assert!(self.next_shared_object_versions.is_none());
197 self.next_shared_object_versions = Some(next_versions);
198 }
199
200 pub fn defer_transactions(
201 &mut self,
202 key: DeferralKey,
203 transactions: Vec<VerifiedExecutableTransactionWithAliases>,
204 ) {
205 self.deferred_txns.push((key, transactions));
206 }
207
208 pub fn delete_loaded_deferred_transactions(&mut self, deferral_keys: &[DeferralKey]) {
209 self.deleted_deferred_txns
210 .extend(deferral_keys.iter().cloned());
211 }
212
213 pub fn clear_deferred_transactions(&mut self) {
216 self.deferred_txns.clear();
217 }
218
219 pub fn insert_pending_checkpoint(&mut self, checkpoint: PendingCheckpoint) {
220 self.pending_checkpoints.push(checkpoint);
221 }
222
223 pub fn reserve_next_randomness_round(
224 &mut self,
225 next_randomness_round: RandomnessRound,
226 commit_timestamp: TimestampMs,
227 ) {
228 assert!(self.next_randomness_round.is_none());
229 self.next_randomness_round = Some((next_randomness_round, commit_timestamp));
230 }
231
232 pub fn insert_dkg_confirmation(&mut self, conf: VersionedDkgConfirmation) {
233 self.dkg_confirmations.insert(conf.sender(), conf);
234 }
235
236 pub fn insert_dkg_processed_message(&mut self, message: VersionedProcessedMessage) {
237 self.dkg_processed_messages
238 .insert(message.sender(), message);
239 }
240
241 pub fn insert_dkg_used_messages(&mut self, used_messages: VersionedUsedProcessedMessages) {
242 self.dkg_used_message = Some(used_messages);
243 }
244
245 pub fn set_dkg_own_confirmation(&mut self, conf: VersionedDkgConfirmation) {
246 self.dkg_own_confirmation = Some(conf);
247 }
248
249 pub fn set_dkg_output(&mut self, output: Option<dkg_v1::Output<PkG, EncG>>) {
250 self.dkg_output = Some(output);
251 }
252
253 pub fn insert_pending_jwk(&mut self, authority: AuthorityName, id: JwkId, jwk: JWK) {
254 self.pending_jwks.insert((authority, id, jwk));
255 }
256
257 pub fn insert_active_jwk(&mut self, round: u64, key: (JwkId, JWK)) {
258 self.active_jwks.insert((round, key));
259 }
260
261 pub fn set_congestion_control_object_debts(&mut self, object_debts: Vec<(ObjectID, u64)>) {
262 self.congestion_control_object_debts = object_debts;
263 }
264
265 pub fn set_congestion_control_randomness_object_debts(
266 &mut self,
267 object_debts: Vec<(ObjectID, u64)>,
268 ) {
269 self.congestion_control_randomness_object_debts = object_debts;
270 }
271
272 pub fn set_checkpoint_queue_drained(&mut self, drained: bool) {
273 self.checkpoint_queue_drained = drained;
274 }
275
276 pub fn set_owned_object_locks(&mut self, locks: HashMap<ObjectRef, LockDetails>) {
277 assert!(self.owned_object_locks.is_empty());
278 self.owned_object_locks = locks;
279 }
280
281 pub fn write_to_batch(
282 self,
283 epoch_store: &AuthorityPerEpochStore,
284 batch: &mut DBBatch,
285 ) -> SuiResult {
286 let tables = epoch_store.tables();
287 batch.insert_batch(
288 &tables.consensus_message_processed,
289 self.consensus_messages_processed
290 .iter()
291 .map(|key| (key, true)),
292 )?;
293
294 batch.insert_batch(
295 &tables.end_of_publish,
296 self.end_of_publish.iter().map(|authority| (authority, ())),
297 )?;
298
299 if let Some(reconfig_state) = &self.reconfig_state {
300 batch.insert_batch(
301 &tables.reconfig_state,
302 [(RECONFIG_STATE_INDEX, reconfig_state)],
303 )?;
304 }
305
306 let consensus_commit_stats = self
307 .consensus_commit_stats
308 .expect("consensus_commit_stats must be set");
309 let round = consensus_commit_stats.index.last_committed_round;
310
311 batch.insert_batch(
312 &tables.last_consensus_stats_v2,
313 [(LAST_CONSENSUS_STATS_ADDR, consensus_commit_stats)],
314 )?;
315
316 if let Some(next_versions) = self.next_shared_object_versions {
317 batch.insert_batch(&tables.next_shared_object_versions_v2, next_versions)?;
318 }
319
320 if !self.owned_object_locks.is_empty() {
321 batch.insert_batch(
322 &tables.owned_object_locked_transactions,
323 self.owned_object_locks
324 .into_iter()
325 .map(|(obj_ref, lock)| (obj_ref, LockDetailsWrapper::from(lock))),
326 )?;
327 }
328
329 batch.delete_batch(
330 &tables.deferred_transactions_with_aliases_v3,
331 &self.deleted_deferred_txns,
332 )?;
333
334 batch.insert_batch(
335 &tables.deferred_transactions_with_aliases_v3,
336 self.deferred_txns.into_iter().map(|(key, txs)| {
337 (
338 key,
339 txs.into_iter()
340 .map(|tx| {
341 let tx: TrustedExecutableTransactionWithAliases = tx.serializable();
342 tx
343 })
344 .collect::<Vec<_>>(),
345 )
346 }),
347 )?;
348
349 if let Some((round, commit_timestamp)) = self.next_randomness_round {
350 batch.insert_batch(&tables.randomness_next_round, [(SINGLETON_KEY, round)])?;
351 batch.insert_batch(
352 &tables.randomness_last_round_timestamp,
353 [(SINGLETON_KEY, commit_timestamp)],
354 )?;
355 }
356
357 batch.insert_batch(&tables.dkg_confirmations_v2, self.dkg_confirmations)?;
358 batch.insert_batch(
359 &tables.dkg_processed_messages_v2,
360 self.dkg_processed_messages,
361 )?;
362 batch.insert_batch(
363 &tables.dkg_used_messages_v2,
364 self.dkg_used_message
366 .into_iter()
367 .map(|used_msgs| (SINGLETON_KEY, used_msgs)),
368 )?;
369 batch.insert_batch(
370 &tables.dkg_own_confirmation,
371 self.dkg_own_confirmation
373 .into_iter()
374 .map(|conf| (SINGLETON_KEY, conf)),
375 )?;
376 if let Some(output) = self.dkg_output {
377 batch.insert_batch(&tables.dkg_output_v2, [(SINGLETON_KEY, output)])?;
378 }
379
380 batch.insert_batch(
381 &tables.pending_jwks,
382 self.pending_jwks.into_iter().map(|j| (j, ())),
383 )?;
384 batch.insert_batch(
385 &tables.active_jwks,
386 self.active_jwks.into_iter().map(|j| {
387 assert_eq!(j.0, round);
389 (j, ())
390 }),
391 )?;
392
393 batch.insert_batch(
394 &tables.congestion_control_object_debts,
395 self.congestion_control_object_debts
396 .into_iter()
397 .map(|(object_id, debt)| {
398 (
399 object_id,
400 CongestionPerObjectDebt::new(self.consensus_round, debt),
401 )
402 }),
403 )?;
404 batch.insert_batch(
405 &tables.congestion_control_randomness_object_debts,
406 self.congestion_control_randomness_object_debts
407 .into_iter()
408 .map(|(object_id, debt)| {
409 (
410 object_id,
411 CongestionPerObjectDebt::new(self.consensus_round, debt),
412 )
413 }),
414 )?;
415
416 batch.insert_batch(
417 &tables.execution_time_observations,
418 self.execution_time_observations
419 .into_iter()
420 .map(|(authority, generation, estimates)| ((generation, authority), estimates)),
421 )?;
422
423 Ok(())
424 }
425}
426
427pub(crate) struct ConsensusOutputCache {
432 pub(crate) deferred_transactions:
435 Mutex<BTreeMap<DeferralKey, Vec<VerifiedExecutableTransactionWithAliases>>>,
436
437 #[allow(clippy::type_complexity)]
440 pub(crate) user_signatures_for_checkpoints:
441 Mutex<HashMap<TransactionDigest, Vec<(GenericSignature, Option<SequenceNumber>)>>>,
442
443 executed_in_epoch: RwLock<DashMap<TransactionDigest, ()>>,
444 executed_in_epoch_cache: MokaCache<TransactionDigest, ()>,
445}
446
447impl ConsensusOutputCache {
448 pub(crate) fn new(tables: &AuthorityEpochTables) -> Self {
449 let deferred_transactions = tables
450 .get_all_deferred_transactions()
451 .expect("load deferred transactions cannot fail");
452
453 let executed_in_epoch_cache_capacity = 50_000;
454
455 Self {
456 deferred_transactions: Mutex::new(deferred_transactions),
457 user_signatures_for_checkpoints: Default::default(),
458 executed_in_epoch: RwLock::new(DashMap::with_shard_amount(2048)),
459 executed_in_epoch_cache: MokaCache::builder(8)
460 .max_capacity(randomize_cache_capacity_in_tests(
462 executed_in_epoch_cache_capacity,
463 ))
464 .eviction_policy(EvictionPolicy::lru())
465 .build(),
466 }
467 }
468
469 pub fn executed_in_current_epoch(&self, digest: &TransactionDigest) -> bool {
470 self.executed_in_epoch
471 .read()
472 .contains_key(digest) ||
473 self.executed_in_epoch_cache.get(digest).is_some()
475 }
476
477 pub fn insert_executed_in_epoch(&self, tx_digest: TransactionDigest) {
479 assert!(
480 self.executed_in_epoch
481 .read()
482 .insert(tx_digest, ())
483 .is_none(),
484 "transaction already executed"
485 );
486 self.executed_in_epoch_cache.insert(tx_digest, ());
487 }
488
489 pub fn remove_executed_in_epoch(&self, tx_digests: &[TransactionDigest]) {
493 let executed_in_epoch = self.executed_in_epoch.read();
494 for tx_digest in tx_digests {
495 executed_in_epoch.remove(tx_digest);
496 }
497 }
498}
499
500pub(crate) struct ConsensusOutputQuarantine {
503 output_queue: VecDeque<ConsensusCommitOutput>,
505
506 highest_executed_checkpoint: CheckpointSequenceNumber,
508
509 builder_checkpoint_summary: BTreeMap<CheckpointSequenceNumber, BuilderCheckpointSummary>,
511
512 shared_object_next_versions: RefCountedHashMap<ConsensusObjectSequenceKey, SequenceNumber>,
514
515 congestion_control_randomness_object_debts:
518 RefCountedHashMap<ObjectID, CongestionPerObjectDebt>,
519 congestion_control_object_debts: RefCountedHashMap<ObjectID, CongestionPerObjectDebt>,
520
521 processed_consensus_messages: RefCountedHashMap<SequencedConsensusTransactionKey, ()>,
522
523 owned_object_locks: HashMap<ObjectRef, LockDetails>,
525
526 metrics: Arc<EpochMetrics>,
527}
528
529impl ConsensusOutputQuarantine {
530 pub(super) fn new(
531 highest_executed_checkpoint: CheckpointSequenceNumber,
532 authority_metrics: Arc<EpochMetrics>,
533 ) -> Self {
534 Self {
535 highest_executed_checkpoint,
536
537 output_queue: VecDeque::new(),
538 builder_checkpoint_summary: BTreeMap::new(),
539 shared_object_next_versions: RefCountedHashMap::new(),
540 processed_consensus_messages: RefCountedHashMap::new(),
541 congestion_control_randomness_object_debts: RefCountedHashMap::new(),
542 congestion_control_object_debts: RefCountedHashMap::new(),
543 owned_object_locks: HashMap::new(),
544 metrics: authority_metrics,
545 }
546 }
547}
548
549impl ConsensusOutputQuarantine {
552 pub(crate) fn push_consensus_output(
554 &mut self,
555 output: ConsensusCommitOutput,
556 epoch_store: &AuthorityPerEpochStore,
557 ) -> SuiResult {
558 self.insert_shared_object_next_versions(&output);
559 self.insert_congestion_control_debts(&output);
560 self.insert_processed_consensus_messages(&output);
561 self.insert_owned_object_locks(&output);
562 self.output_queue.push_back(output);
563
564 self.metrics
565 .consensus_quarantine_queue_size
566 .set(self.output_queue.len() as i64);
567
568 self.commit(epoch_store)
571 }
572
573 pub(super) fn insert_builder_summary(
575 &mut self,
576 sequence_number: CheckpointSequenceNumber,
577 summary: BuilderCheckpointSummary,
578 ) {
579 debug!(?sequence_number, "inserting builder summary {:?}", summary);
580 self.builder_checkpoint_summary
581 .insert(sequence_number, summary);
582 }
583}
584
585impl ConsensusOutputQuarantine {
587 pub(super) fn update_highest_executed_checkpoint(
590 &mut self,
591 checkpoint: CheckpointSequenceNumber,
592 epoch_store: &AuthorityPerEpochStore,
593 batch: &mut DBBatch,
594 ) -> SuiResult {
595 self.highest_executed_checkpoint = checkpoint;
596 self.commit_with_batch(epoch_store, batch)
597 }
598
599 pub(super) fn commit(&mut self, epoch_store: &AuthorityPerEpochStore) -> SuiResult {
600 let mut batch = epoch_store.db_batch();
601 self.commit_with_batch(epoch_store, &mut batch)?;
602 batch.write()?;
603 Ok(())
604 }
605
606 fn commit_with_batch(
608 &mut self,
609 epoch_store: &AuthorityPerEpochStore,
610 batch: &mut DBBatch,
611 ) -> SuiResult {
612 let tables = epoch_store.tables();
619
620 let mut highest_committed_height = None;
621
622 while self
623 .builder_checkpoint_summary
624 .first_key_value()
625 .map(|(seq, _)| *seq <= self.highest_executed_checkpoint)
626 == Some(true)
627 {
628 let (seq, builder_summary) = self.builder_checkpoint_summary.pop_first().unwrap();
629
630 batch.insert_batch(
631 &tables.builder_checkpoint_summary_v2,
632 [(seq, &builder_summary)],
633 )?;
634
635 let checkpoint_height = builder_summary
636 .checkpoint_height
637 .expect("non-genesis checkpoint must have height");
638 if let Some(highest) = highest_committed_height {
639 assert!(
640 checkpoint_height >= highest,
641 "current checkpoint height {} must be no less than highest committed height {}",
642 checkpoint_height,
643 highest
644 );
645 }
646
647 highest_committed_height = Some(checkpoint_height);
648 }
649
650 let Some(highest_committed_height) = highest_committed_height else {
651 return Ok(());
652 };
653
654 let mut last_drain_idx = None;
660 for (i, output) in self.output_queue.iter().enumerate() {
661 let stats = output
662 .consensus_commit_stats
663 .as_ref()
664 .expect("consensus_commit_stats must be set");
665 if stats.height > highest_committed_height {
666 break;
667 }
668 if output.checkpoint_queue_drained {
669 last_drain_idx = Some(i);
670 }
671 }
672 if let Some(idx) = last_drain_idx {
673 for _ in 0..=idx {
674 let output = self.output_queue.pop_front().unwrap();
675 self.remove_shared_object_next_versions(&output);
676 self.remove_processed_consensus_messages(&output);
677 self.remove_congestion_control_debts(&output);
678 self.remove_owned_object_locks(&output);
679 output.write_to_batch(epoch_store, batch)?;
680 }
681 }
682
683 self.metrics
684 .consensus_quarantine_queue_size
685 .set(self.output_queue.len() as i64);
686
687 Ok(())
688 }
689}
690
691impl ConsensusOutputQuarantine {
692 fn insert_shared_object_next_versions(&mut self, output: &ConsensusCommitOutput) {
693 if let Some(next_versions) = output.next_shared_object_versions.as_ref() {
694 for (object_id, next_version) in next_versions {
695 self.shared_object_next_versions
696 .insert(*object_id, *next_version);
697 }
698 }
699 }
700
701 fn insert_congestion_control_debts(&mut self, output: &ConsensusCommitOutput) {
702 let current_round = output.consensus_round;
703
704 for (object_id, debt) in output.congestion_control_object_debts.iter() {
705 self.congestion_control_object_debts.insert(
706 *object_id,
707 CongestionPerObjectDebt::new(current_round, *debt),
708 );
709 }
710
711 for (object_id, debt) in output.congestion_control_randomness_object_debts.iter() {
712 self.congestion_control_randomness_object_debts.insert(
713 *object_id,
714 CongestionPerObjectDebt::new(current_round, *debt),
715 );
716 }
717 }
718
719 fn remove_congestion_control_debts(&mut self, output: &ConsensusCommitOutput) {
720 for (object_id, _) in output.congestion_control_object_debts.iter() {
721 self.congestion_control_object_debts.remove(object_id);
722 }
723 for (object_id, _) in output.congestion_control_randomness_object_debts.iter() {
724 self.congestion_control_randomness_object_debts
725 .remove(object_id);
726 }
727 }
728
729 fn insert_processed_consensus_messages(&mut self, output: &ConsensusCommitOutput) {
730 for tx_key in output.consensus_messages_processed.iter() {
731 self.processed_consensus_messages.insert(tx_key.clone(), ());
732 }
733 }
734
735 fn remove_processed_consensus_messages(&mut self, output: &ConsensusCommitOutput) {
736 for tx_key in output.consensus_messages_processed.iter() {
737 self.processed_consensus_messages.remove(tx_key);
738 }
739 }
740
741 fn remove_shared_object_next_versions(&mut self, output: &ConsensusCommitOutput) {
742 if let Some(next_versions) = output.next_shared_object_versions.as_ref() {
743 for object_id in next_versions.keys() {
744 if !self.shared_object_next_versions.remove(object_id) {
745 fatal!(
746 "Shared object next version not found in quarantine: {:?}",
747 object_id
748 );
749 }
750 }
751 }
752 }
753
754 fn insert_owned_object_locks(&mut self, output: &ConsensusCommitOutput) {
755 for (obj_ref, lock) in &output.owned_object_locks {
756 self.owned_object_locks.insert(*obj_ref, *lock);
757 }
758 }
759
760 fn remove_owned_object_locks(&mut self, output: &ConsensusCommitOutput) {
761 for obj_ref in output.owned_object_locks.keys() {
762 self.owned_object_locks.remove(obj_ref);
763 }
764 }
765}
766
767impl ConsensusOutputQuarantine {
770 pub(super) fn last_built_summary(&self) -> Option<&BuilderCheckpointSummary> {
771 self.builder_checkpoint_summary.values().last()
772 }
773
774 pub(super) fn get_built_summary(
775 &self,
776 sequence: CheckpointSequenceNumber,
777 ) -> Option<&BuilderCheckpointSummary> {
778 self.builder_checkpoint_summary.get(&sequence)
779 }
780
781 pub(super) fn is_consensus_message_processed(
782 &self,
783 key: &SequencedConsensusTransactionKey,
784 ) -> bool {
785 self.processed_consensus_messages.contains_key(key)
786 }
787
788 pub(super) fn is_empty(&self) -> bool {
789 self.output_queue.is_empty()
790 }
791
792 pub(super) fn get_next_shared_object_versions(
793 &self,
794 tables: &AuthorityEpochTables,
795 objects_to_init: &[ConsensusObjectSequenceKey],
796 ) -> Vec<Option<SequenceNumber>> {
797 do_fallback_lookup(
798 objects_to_init,
799 |object_key| {
800 if let Some(next_version) = self.shared_object_next_versions.get(object_key) {
801 CacheResult::Hit(Some(*next_version))
802 } else {
803 CacheResult::Miss
804 }
805 },
806 |object_keys| {
807 tables
808 .next_shared_object_versions_v2
809 .multi_get(object_keys)
810 .expect("db error")
811 },
812 )
813 }
814
815 pub(super) fn get_owned_object_locks(
818 &self,
819 tables: &AuthorityEpochTables,
820 obj_refs: &[ObjectRef],
821 ) -> Vec<Option<LockDetails>> {
822 do_fallback_lookup(
823 obj_refs,
824 |obj_ref| {
825 if let Some(lock) = self.owned_object_locks.get(obj_ref) {
826 CacheResult::Hit(Some(*lock))
827 } else {
828 CacheResult::Miss
829 }
830 },
831 |obj_refs| {
832 tables
833 .multi_get_locked_transactions(obj_refs)
834 .expect("db error")
835 },
836 )
837 }
838
839 pub(super) fn get_highest_pending_checkpoint_height(&self) -> Option<CheckpointHeight> {
840 self.output_queue
841 .back()
842 .and_then(|output| output.get_highest_pending_checkpoint_height())
843 }
844
845 pub(super) fn get_pending_checkpoints(
846 &self,
847 last: Option<CheckpointHeight>,
848 ) -> Vec<(CheckpointHeight, PendingCheckpoint)> {
849 let mut checkpoints = Vec::new();
850 for output in &self.output_queue {
851 checkpoints.extend(
852 output
853 .get_pending_checkpoints(last)
854 .map(|cp| (cp.height(), cp.clone())),
855 );
856 }
857 if cfg!(debug_assertions) {
858 let mut prev = None;
859 for (height, _) in &checkpoints {
860 if let Some(prev) = prev {
861 assert!(prev < *height);
862 }
863 prev = Some(*height);
864 }
865 }
866 checkpoints
867 }
868
869 pub(super) fn pending_checkpoint_exists(&self, index: &CheckpointHeight) -> bool {
870 self.output_queue
871 .iter()
872 .any(|output| output.pending_checkpoint_exists(index))
873 }
874
875 pub(super) fn get_new_jwks(
876 &self,
877 epoch_store: &AuthorityPerEpochStore,
878 round: u64,
879 ) -> SuiResult<Vec<ActiveJwk>> {
880 let epoch = epoch_store.epoch();
881
882 for output in self.output_queue.iter().rev() {
884 let output_round = output.get_round().unwrap();
887 if round == output_round {
888 return Ok(output
889 .active_jwks
890 .iter()
891 .map(|(_, (jwk_id, jwk))| ActiveJwk {
892 jwk_id: jwk_id.clone(),
893 jwk: jwk.clone(),
894 epoch,
895 })
896 .collect());
897 }
898 }
899
900 let empty_jwk_id = JwkId::new(String::new(), String::new());
902 let empty_jwk = JWK {
903 kty: String::new(),
904 e: String::new(),
905 n: String::new(),
906 alg: String::new(),
907 };
908
909 let start = (round, (empty_jwk_id.clone(), empty_jwk.clone()));
910 let end = (round + 1, (empty_jwk_id, empty_jwk));
911
912 Ok(epoch_store
913 .tables()
914 .active_jwks
915 .safe_iter_with_bounds(Some(start), Some(end))
916 .map_ok(|((r, (jwk_id, jwk)), _)| {
917 debug_assert!(round == r);
918 ActiveJwk { jwk_id, jwk, epoch }
919 })
920 .collect::<Result<Vec<_>, _>>()?)
921 }
922
923 pub(super) fn get_randomness_last_round_timestamp(&self) -> Option<TimestampMs> {
924 self.output_queue
925 .iter()
926 .rev()
927 .filter_map(|output| output.get_randomness_last_round_timestamp())
928 .next()
929 }
930
931 pub(crate) fn load_initial_object_debts(
932 &self,
933 epoch_store: &AuthorityPerEpochStore,
934 current_round: Round,
935 for_randomness: bool,
936 transactions: &[VerifiedExecutableTransactionWithAliases],
937 ) -> impl IntoIterator<Item = (ObjectID, u64)> {
938 let protocol_config = epoch_store.protocol_config();
939 let tables = epoch_store.tables();
940 let default_per_commit_budget = protocol_config
941 .max_accumulated_txn_cost_per_object_in_mysticeti_commit_as_option()
942 .unwrap_or(0);
943 let (hash_table, db_table, per_commit_budget) = if for_randomness {
944 (
945 &self.congestion_control_randomness_object_debts,
946 &tables.congestion_control_randomness_object_debts,
947 protocol_config
948 .max_accumulated_randomness_txn_cost_per_object_in_mysticeti_commit_as_option()
949 .unwrap_or(default_per_commit_budget),
950 )
951 } else {
952 (
953 &self.congestion_control_object_debts,
954 &tables.congestion_control_object_debts,
955 default_per_commit_budget,
956 )
957 };
958 let mut shared_input_object_ids: Vec<_> = transactions
959 .iter()
960 .flat_map(|tx| tx.tx().shared_input_objects().map(|obj| obj.id))
961 .collect();
962 shared_input_object_ids.sort();
963 shared_input_object_ids.dedup();
964
965 let results = do_fallback_lookup(
966 &shared_input_object_ids,
967 |object_id| {
968 if let Some(debt) = hash_table.get(object_id) {
969 CacheResult::Hit(Some(debt.into_v1()))
970 } else {
971 CacheResult::Miss
972 }
973 },
974 |object_ids| {
975 db_table
976 .multi_get(object_ids)
977 .expect("db error")
978 .into_iter()
979 .map(|debt| debt.map(|debt| debt.into_v1()))
980 .collect()
981 },
982 );
983
984 results
985 .into_iter()
986 .zip_debug_eq(shared_input_object_ids)
987 .filter_map(|(debt, object_id)| debt.map(|debt| (debt, object_id)))
988 .map(move |((round, debt), object_id)| {
989 assert!(current_round > round);
993 let num_rounds = current_round - round - 1;
994 let debt = debt.saturating_sub(per_commit_budget * num_rounds);
995 (object_id, debt)
996 })
997 }
998}
999
1000#[derive(Debug, Default)]
1009struct RefCountedHashMap<K, V> {
1010 map: HashMap<K, (usize, V)>,
1011}
1012
1013impl<K, V> RefCountedHashMap<K, V>
1014where
1015 K: Clone + Eq + std::hash::Hash,
1016{
1017 pub fn new() -> Self {
1018 Self {
1019 map: HashMap::new(),
1020 }
1021 }
1022
1023 pub fn insert(&mut self, key: K, value: V) {
1024 let entry = self.map.entry(key);
1025 match entry {
1026 hash_map::Entry::Occupied(mut entry) => {
1027 let (ref_count, v) = entry.get_mut();
1028 *ref_count += 1;
1029 *v = value;
1030 }
1031 hash_map::Entry::Vacant(entry) => {
1032 entry.insert((1, value));
1033 }
1034 }
1035 }
1036
1037 pub fn remove(&mut self, key: &K) -> bool {
1040 let entry = self.map.entry(key.clone());
1041 match entry {
1042 hash_map::Entry::Occupied(mut entry) => {
1043 let (ref_count, _) = entry.get_mut();
1044 *ref_count -= 1;
1045 if *ref_count == 0 {
1046 entry.remove();
1047 }
1048 true
1049 }
1050 hash_map::Entry::Vacant(_) => false,
1051 }
1052 }
1053
1054 pub fn get(&self, key: &K) -> Option<&V> {
1055 self.map.get(key).map(|(_, v)| v)
1056 }
1057
1058 pub fn contains_key(&self, key: &K) -> bool {
1059 self.map.contains_key(key)
1060 }
1061}
1062
1063#[cfg(test)]
1064impl ConsensusOutputQuarantine {
1065 fn output_queue_len_for_testing(&self) -> usize {
1066 self.output_queue.len()
1067 }
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 use super::*;
1073 use crate::authority::test_authority_builder::TestAuthorityBuilder;
1074 use sui_types::base_types::ExecutionDigests;
1075 use sui_types::gas::GasCostSummary;
1076 use sui_types::messages_checkpoint::CheckpointContents;
1077
1078 fn make_output(height: u64, round: u64, drained: bool) -> ConsensusCommitOutput {
1079 let mut output = ConsensusCommitOutput::new(round);
1080 output.record_consensus_commit_stats(ExecutionIndicesWithStatsV2 {
1081 height,
1082 ..Default::default()
1083 });
1084 output.set_checkpoint_queue_drained(drained);
1085 output
1086 }
1087
1088 fn make_builder_summary(
1089 seq: CheckpointSequenceNumber,
1090 height: CheckpointHeight,
1091 protocol_config: &ProtocolConfig,
1092 ) -> BuilderCheckpointSummary {
1093 let contents =
1094 CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1095 let summary = CheckpointSummary::new(
1096 protocol_config,
1097 0,
1098 seq,
1099 0,
1100 &contents,
1101 None,
1102 GasCostSummary::default(),
1103 None,
1104 0,
1105 vec![],
1106 vec![],
1107 );
1108 BuilderCheckpointSummary {
1109 summary,
1110 checkpoint_height: Some(height),
1111 position_in_commit: 0,
1112 }
1113 }
1114
1115 #[tokio::test]
1116 async fn test_drain_boundary_prevents_premature_commit() {
1117 let state = TestAuthorityBuilder::new().build().await;
1118 let epoch_store = state.epoch_store_for_testing();
1119
1120 let metrics = epoch_store.metrics.clone();
1121 let mut quarantine = ConsensusOutputQuarantine::new(0, metrics);
1122
1123 let c = make_output(4, 1, false);
1125 quarantine.push_consensus_output(c, &epoch_store).unwrap();
1126
1127 let c2 = make_output(5, 2, true);
1129 quarantine.push_consensus_output(c2, &epoch_store).unwrap();
1130
1131 assert_eq!(quarantine.output_queue_len_for_testing(), 2);
1132
1133 let pc = epoch_store.protocol_config();
1135 for seq in 1..=4 {
1136 let summary = make_builder_summary(seq, seq, pc);
1137 quarantine.insert_builder_summary(seq, summary);
1138 }
1139
1140 let mut batch = epoch_store.db_batch_for_test();
1142 quarantine
1143 .update_highest_executed_checkpoint(4, &epoch_store, &mut batch)
1144 .unwrap();
1145 batch.write().unwrap();
1146
1147 assert_eq!(quarantine.output_queue_len_for_testing(), 2);
1151 }
1152
1153 #[tokio::test]
1154 async fn test_drain_boundary_commits_at_safe_point() {
1155 let state = TestAuthorityBuilder::new().build().await;
1156 let epoch_store = state.epoch_store_for_testing();
1157
1158 let metrics = epoch_store.metrics.clone();
1159 let mut quarantine = ConsensusOutputQuarantine::new(0, metrics);
1160
1161 let c = make_output(4, 1, false);
1162 quarantine.push_consensus_output(c, &epoch_store).unwrap();
1163
1164 let c2 = make_output(5, 2, true);
1165 quarantine.push_consensus_output(c2, &epoch_store).unwrap();
1166
1167 assert_eq!(quarantine.output_queue_len_for_testing(), 2);
1168
1169 let pc = epoch_store.protocol_config();
1171 for seq in 1..=5 {
1172 let summary = make_builder_summary(seq, seq, pc);
1173 quarantine.insert_builder_summary(seq, summary);
1174 }
1175
1176 let mut batch = epoch_store.db_batch_for_test();
1178 quarantine
1179 .update_highest_executed_checkpoint(5, &epoch_store, &mut batch)
1180 .unwrap();
1181 batch.write().unwrap();
1182
1183 assert_eq!(quarantine.output_queue_len_for_testing(), 0);
1187 }
1188}