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