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