1use anemo::PeerId;
5use fastcrypto::encoding::{Encoding, Hex};
6use fastcrypto::error::{FastCryptoError, FastCryptoResult};
7use fastcrypto::groups::bls12381;
8use fastcrypto::serde_helpers::ToFromByteArray;
9use fastcrypto::traits::{KeyPair, ToFromBytes};
10use fastcrypto_threshold::{dkg_v1, dkg_v1::Output, nodes, nodes::PartyId};
11use futures::StreamExt;
12use futures::stream::FuturesUnordered;
13use mysten_common::debug_fatal;
14use parking_lot::Mutex;
15use rand::SeedableRng;
16use rand::rngs::{OsRng, StdRng};
17use serde::{Deserialize, Serialize};
18use std::collections::{BTreeMap, HashMap};
19use std::sync::{Arc, Weak};
20use std::time::Instant;
21use sui_macros::{fail_point, fail_point_if};
22use sui_network::randomness;
23use sui_protocol_config::assert_reachable_gated;
24use sui_types::base_types::AuthorityName;
25use sui_types::committee::{Committee, EpochId, StakeUnit};
26use sui_types::crypto::{AuthorityKeyPair, RandomnessRound};
27use sui_types::error::{SuiError, SuiErrorKind, SuiResult};
28use sui_types::messages_consensus::{
29 ConsensusTransaction, Round, TimestampMs, VersionedDkgConfirmation, VersionedDkgMessage,
30};
31use sui_types::sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait;
32use tokio::sync::OnceCell;
33use tokio::task::JoinHandle;
34use tracing::{debug, error, info, warn};
35use typed_store::Map;
36
37use crate::authority::authority_per_epoch_store::{
38 AuthorityPerEpochStore, consensus_quarantine::ConsensusCommitOutput,
39};
40use crate::authority::epoch_start_configuration::EpochStartConfigTrait;
41use crate::consensus_adapter::SubmitToConsensus;
42use crate::randomness_round_receiver::RandomnessRoundReceiverHandle;
43
44type PkG = bls12381::G2Element;
45type EncG = bls12381::G2Element;
46
47pub const SINGLETON_KEY: u64 = 0;
48
49#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
52#[allow(clippy::large_enum_variant)]
53pub enum VersionedProcessedMessage {
54 V0(), V1(dkg_v1::ProcessedMessage<PkG, EncG>),
56}
57
58impl VersionedProcessedMessage {
59 pub fn sender(&self) -> PartyId {
60 match self {
61 VersionedProcessedMessage::V0() => {
62 panic!("BUG: invalid VersionedProcessedMessage version V0")
63 }
64 VersionedProcessedMessage::V1(msg) => msg.message.sender,
65 }
66 }
67
68 pub fn unwrap_v1(self) -> dkg_v1::ProcessedMessage<PkG, EncG> {
69 if let VersionedProcessedMessage::V1(msg) = self {
70 msg
71 } else {
72 panic!("BUG: expected message version is 1")
73 }
74 }
75
76 pub fn as_v1(&self) -> Option<&dkg_v1::ProcessedMessage<PkG, EncG>> {
77 if let VersionedProcessedMessage::V1(msg) = self {
78 Some(msg)
79 } else {
80 None
81 }
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
86pub enum VersionedUsedProcessedMessages {
87 V0(), V1(dkg_v1::UsedProcessedMessages<PkG, EncG>),
89}
90
91impl VersionedUsedProcessedMessages {
92 pub fn as_v1(&self) -> Option<&dkg_v1::UsedProcessedMessages<PkG, EncG>> {
93 if let VersionedUsedProcessedMessages::V1(msg) = self {
94 Some(msg)
95 } else {
96 None
97 }
98 }
99}
100
101enum DkgRole {
103 Party(dkg_v1::Party<PkG, EncG>),
104 Observer(dkg_v1::Observer<PkG, EncG>),
105}
106
107impl DkgRole {
108 fn try_new(
117 authority_key_pair: Option<&AuthorityKeyPair>,
118 nodes: nodes::Nodes<EncG>,
119 t: u16,
120 random_oracle: fastcrypto_threshold::random_oracle::RandomOracle,
121 ) -> Option<Self> {
122 let total_weight = nodes.total_weight();
123 let num_nodes = nodes.num_nodes();
124
125 if let Some(authority_key_pair) = authority_key_pair {
126 let randomness_private_key = bls12381::Scalar::from_byte_array(
127 authority_key_pair
128 .copy()
129 .private()
130 .as_bytes()
131 .try_into()
132 .expect("key length should match"),
133 )
134 .expect("should work to convert BLS key to Scalar");
135 let party = match dkg_v1::Party::<PkG, EncG>::new(
136 fastcrypto_threshold::ecies_v1::PrivateKey::<bls12381::G2Element>::from(
137 randomness_private_key,
138 ),
139 nodes,
140 t,
141 random_oracle,
142 &mut rand::thread_rng(),
143 ) {
144 Ok(party) => party,
145 Err(err) => {
146 debug_fatal!("random beacon: error while initializing Party: {err:?}");
147 return None;
148 }
149 };
150 let name: AuthorityName = authority_key_pair.public().into();
151 info!(
152 "random beacon: Party initialized with authority={name}, total_weight={total_weight}, t={t}, num_nodes={num_nodes}",
153 );
154 Some(DkgRole::Party(party))
155 } else {
156 let observer = match dkg_v1::Observer::<PkG, EncG>::new(nodes, t, random_oracle) {
157 Ok(observer) => observer,
158 Err(err) => {
159 debug_fatal!("random beacon: error while initializing Observer: {err:?}");
160 return None;
161 }
162 };
163 info!(
164 "random beacon: Observer initialized with total_weight={total_weight}, t={t}, num_nodes={num_nodes}",
165 );
166 Some(DkgRole::Observer(observer))
167 }
168 }
169
170 fn is_party(&self) -> bool {
171 matches!(self, DkgRole::Party(_))
172 }
173
174 fn is_observer(&self) -> bool {
175 matches!(self, DkgRole::Observer(_))
176 }
177
178 fn process_message(
180 &self,
181 message: VersionedDkgMessage,
182 ) -> FastCryptoResult<VersionedProcessedMessage> {
183 match self {
184 DkgRole::Party(party) => {
185 let processed =
186 party.process_message(message.unwrap_v1(), &mut rand::thread_rng())?;
187 Ok(VersionedProcessedMessage::V1(processed))
188 }
189 DkgRole::Observer(observer) => {
190 let raw_msg = message.unwrap_v1();
191 observer.process_message(raw_msg.clone())?;
192 Ok(VersionedProcessedMessage::V1(dkg_v1::ProcessedMessage {
193 message: raw_msg,
194 shares: vec![],
195 complaint: None,
196 }))
197 }
198 }
199 }
200
201 fn merge_messages(
205 &self,
206 messages: Vec<VersionedProcessedMessage>,
207 ) -> FastCryptoResult<(
208 Option<VersionedDkgConfirmation>,
209 VersionedUsedProcessedMessages,
210 )> {
211 match self {
212 DkgRole::Party(party) => {
213 let (conf, msgs) = party.merge(
214 &messages
215 .into_iter()
216 .map(|vm| vm.unwrap_v1())
217 .collect::<Vec<_>>(),
218 )?;
219 Ok((
220 Some(VersionedDkgConfirmation::V1(conf)),
221 VersionedUsedProcessedMessages::V1(msgs),
222 ))
223 }
224 DkgRole::Observer(observer) => {
225 let raw_messages: Vec<_> = messages
226 .into_iter()
227 .map(|pm| pm.unwrap_v1().message)
228 .collect();
229 let used = observer.merge(raw_messages)?;
230 Ok((
231 None,
232 VersionedUsedProcessedMessages::V1(dkg_v1::UsedProcessedMessages(
233 used.into_iter()
234 .map(|m| dkg_v1::ProcessedMessage {
235 message: m,
236 shares: vec![],
237 complaint: None,
238 })
239 .collect(),
240 )),
241 ))
242 }
243 }
244 }
245
246 fn complete_dkg<'a>(
250 &self,
251 used_messages: &VersionedUsedProcessedMessages,
252 confirmations: impl Iterator<Item = &'a VersionedDkgConfirmation>,
253 ) -> FastCryptoResult<Output<PkG, EncG>> {
254 match self {
255 DkgRole::Party(party) => {
256 let rng = &mut StdRng::from_rng(OsRng).expect("RNG construction should not fail");
257 let msg = used_messages
258 .as_v1()
259 .expect("expected V1 used processed messages");
260 party.complete(
261 msg,
262 &confirmations
263 .map(|vm| vm.as_v1().expect("expected V1 confirmation"))
264 .cloned()
265 .collect::<Vec<_>>(),
266 rng,
267 )
268 }
269 DkgRole::Observer(observer) => {
270 let raw_messages: Vec<_> = used_messages
271 .as_v1()
272 .expect("expected V1 used processed messages")
273 .0
274 .iter()
275 .map(|pm| pm.message.clone())
276 .collect();
277 let confirmations: Vec<_> = confirmations
278 .map(|c| c.as_v1().expect("expected V1 confirmation").clone())
279 .collect();
280 observer.complete(&raw_messages, &confirmations)
281 }
282 }
283 }
284}
285
286#[derive(Debug)]
289pub(crate) enum AdvanceDkgError {
290 Sui(SuiError),
291 RuntimeShutdown,
294}
295
296impl From<SuiError> for AdvanceDkgError {
297 fn from(e: SuiError) -> Self {
298 Self::Sui(e)
299 }
300}
301
302pub struct RandomnessManager {
322 epoch_store: Weak<AuthorityPerEpochStore>,
323 epoch: EpochId,
324 consensus_adapter: Box<dyn SubmitToConsensus>,
325 network_handle: randomness::Handle,
326 authority_info: HashMap<AuthorityName, (PeerId, PartyId)>,
327
328 dkg_start_time: OnceCell<Instant>,
330 role: Arc<DkgRole>,
331 enqueued_messages: BTreeMap<PartyId, JoinHandle<Option<VersionedProcessedMessage>>>,
332 processed_messages: BTreeMap<PartyId, VersionedProcessedMessage>,
333 used_messages: OnceCell<VersionedUsedProcessedMessages>,
334 confirmations: BTreeMap<PartyId, VersionedDkgConfirmation>,
335 dkg_output: OnceCell<Option<dkg_v1::Output<PkG, EncG>>>,
336 dkg_timeout_reported: bool,
337
338 next_randomness_round: RandomnessRound,
340 highest_completed_round: Arc<Mutex<Option<RandomnessRound>>>,
341
342 randomness_receiver_handle: Arc<RandomnessRoundReceiverHandle>,
343}
344
345impl RandomnessManager {
346 pub async fn try_new(
348 epoch_store_weak: Weak<AuthorityPerEpochStore>,
349 consensus_adapter: Box<dyn SubmitToConsensus>,
350 network_handle: randomness::Handle,
351 authority_key_pair: Option<&AuthorityKeyPair>,
352 randomness_receiver_handle: Arc<RandomnessRoundReceiverHandle>,
353 ) -> Option<Self> {
354 let epoch_store = match epoch_store_weak.upgrade() {
355 Some(epoch_store) => epoch_store,
356 None => {
357 error!(
358 "could not construct RandomnessManager: AuthorityPerEpochStore already gone"
359 );
360 return None;
361 }
362 };
363 let tables = epoch_store.tables();
364 let protocol_config = epoch_store.protocol_config();
365 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(0);
366 epoch_store
367 .metrics
368 .epoch_random_beacon_dkg_completed_after_timeout
369 .set(0);
370 epoch_store
371 .metrics
372 .epoch_random_beacon_dkg_num_shares
373 .set(0);
374
375 let committee = epoch_store.committee();
376 let info = RandomnessManager::randomness_dkg_info_from_committee(committee);
377 if tracing::enabled!(tracing::Level::DEBUG) {
378 for (id, name, pk, stake) in info.iter().filter(|(id, _, _, _)| *id < 3) {
380 let pk_bytes = pk.as_element().to_byte_array();
381 debug!(
382 "random beacon: DKG info: id={id}, stake={stake}, name={name}, pk={pk_bytes:x?}"
383 );
384 }
385 }
386 let authority_ids: HashMap<_, _> =
387 info.iter().map(|(id, name, _, _)| (*name, *id)).collect();
388 let authority_peer_ids = epoch_store
389 .epoch_start_config()
390 .epoch_start_state()
391 .get_authority_names_to_peer_ids();
392 let authority_info = authority_ids
393 .into_iter()
394 .map(|(name, id)| {
395 let peer_id = *authority_peer_ids
396 .get(&name)
397 .expect("authority name should be in peer_ids");
398 (name, (peer_id, id))
399 })
400 .collect();
401 let nodes = info
402 .iter()
403 .map(|(id, _, pk, stake)| nodes::Node::<EncG> {
404 id: *id,
405 pk: pk.clone(),
406 weight: (*stake).try_into().expect("stake should fit in u16"),
407 })
408 .collect();
409 let (nodes, t) = match nodes::Nodes::new_reduced(
410 nodes,
411 committee
412 .validity_threshold()
413 .try_into()
414 .expect("validity threshold should fit in u16"),
415 protocol_config.random_beacon_reduction_allowed_delta(),
416 protocol_config
417 .random_beacon_reduction_lower_bound()
418 .try_into()
419 .expect("should fit u16"),
420 ) {
421 Ok((nodes, t)) => (nodes, t),
422 Err(err) => {
423 error!("random beacon: error while initializing Nodes: {err:?}");
424 return None;
425 }
426 };
427 let random_oracle = fastcrypto_threshold::random_oracle::RandomOracle::new(&format!(
428 "dkg {} {}",
429 Hex::encode(epoch_store.get_chain_identifier().as_bytes()),
430 committee.epoch()
431 ));
432
433 let role = Arc::new(DkgRole::try_new(
434 authority_key_pair,
435 nodes,
436 t,
437 random_oracle,
438 )?);
439
440 let highest_completed_round = tables
442 .randomness_highest_completed_round
443 .get(&SINGLETON_KEY)
444 .expect("typed_store should not fail");
445 let mut rm = RandomnessManager {
446 epoch_store: epoch_store_weak,
447 epoch: committee.epoch(),
448 consensus_adapter,
449 network_handle: network_handle.clone(),
450 authority_info,
451 dkg_start_time: OnceCell::new(),
452 role,
453 enqueued_messages: BTreeMap::new(),
454 processed_messages: BTreeMap::new(),
455 used_messages: OnceCell::new(),
456 confirmations: BTreeMap::new(),
457 dkg_output: OnceCell::new(),
458 dkg_timeout_reported: false,
459 next_randomness_round: RandomnessRound(0),
460 highest_completed_round: Arc::new(Mutex::new(highest_completed_round)),
461 randomness_receiver_handle,
462 };
463 let dkg_output = tables
464 .dkg_output_v2
465 .get(&SINGLETON_KEY)
466 .expect("typed_store should not fail");
467 match dkg_output {
468 Some(Some(dkg_output)) => {
469 info!(
470 "random beacon: loaded existing DKG output for epoch {}",
471 committee.epoch()
472 );
473 epoch_store
474 .metrics
475 .epoch_random_beacon_dkg_num_shares
476 .set(dkg_output.shares.as_ref().map_or(0, |shares| shares.len()) as i64);
477 rm.dkg_output
478 .set(Some(dkg_output.clone()))
479 .expect("setting new OnceCell should succeed");
480 rm.randomness_receiver_handle
483 .set_public_key(dkg_output.vss_pk.c0());
484
485 if let DkgRole::Party(party) = rm.role.as_ref() {
486 network_handle.update_epoch(
487 committee.epoch(),
488 rm.authority_info.clone(),
489 dkg_output,
490 party.t(),
491 highest_completed_round,
492 );
493 }
494 }
495 Some(None) => {
496 error!(
500 "random beacon: loaded failed DKG for epoch {}. Randomness disabled for this epoch. All randomness-using transactions will fail.",
501 committee.epoch()
502 );
503 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
504 rm.dkg_output
505 .set(None)
506 .expect("setting new OnceCell should succeed");
507 }
508 None => {
509 info!(
510 "random beacon: no existing DKG output found for epoch {}",
511 committee.epoch()
512 );
513
514 assert!(
516 epoch_store.protocol_config().dkg_version() > 0,
517 "BUG: DKG version 0 is deprecated"
518 );
519 rm.processed_messages.extend(
520 tables
521 .dkg_processed_messages_v2
522 .safe_iter()
523 .map(|result| result.expect("typed_store should not fail")),
524 );
525 if let Some(used_messages) = tables
526 .dkg_used_messages_v2
527 .get(&SINGLETON_KEY)
528 .expect("typed_store should not fail")
529 {
530 rm.used_messages
531 .set(used_messages.clone())
532 .expect("setting new OnceCell should succeed");
533 }
534 rm.confirmations.extend(
535 tables
536 .dkg_confirmations_v2
537 .safe_iter()
538 .map(|result| result.expect("typed_store should not fail")),
539 );
540 }
541 }
542
543 rm.next_randomness_round = tables
548 .randomness_next_round
549 .get(&SINGLETON_KEY)
550 .expect("typed_store should not fail")
551 .unwrap_or(RandomnessRound(0));
552 info!(
553 "random beacon: starting from next_randomness_round={}",
554 rm.next_randomness_round.0
555 );
556
557 if rm.role.is_party() {
559 let first_incomplete_round = highest_completed_round
560 .map(|r| r + 1)
561 .unwrap_or(RandomnessRound(0));
562 if first_incomplete_round < rm.next_randomness_round {
563 info!(
564 "random beacon: resuming generation for randomness rounds from {} to {}",
565 first_incomplete_round,
566 rm.next_randomness_round - 1,
567 );
568 for r in first_incomplete_round.0..rm.next_randomness_round.0 {
569 network_handle.send_partial_signatures(committee.epoch(), RandomnessRound(r));
570 }
571 }
572 }
573
574 Some(rm)
575 }
576
577 pub async fn start_dkg(&mut self) -> SuiResult {
580 let party = match self.role.as_ref() {
581 DkgRole::Observer(_) => {
582 info!("random beacon: observer started observing DKG");
583 return Ok(());
584 }
585 DkgRole::Party(party) => party,
586 };
587
588 if self.dkg_output.initialized() {
589 return Ok(()); }
591
592 let epoch_store = self.epoch_store()?;
593 if self.used_messages.initialized() {
594 if !epoch_store
595 .protocol_config()
596 .allow_dkg_completion_after_timeout()
597 {
598 return Ok(());
599 }
600
601 if self.confirmations.contains_key(&party.id) {
604 return Ok(());
605 }
606
607 if let Some(confirmation) = epoch_store
611 .tables()
612 .dkg_own_confirmation
613 .get(&SINGLETON_KEY)
614 .expect("typed_store should not fail")
615 {
616 info!(
617 "random beacon: re-sending DKG Confirmation with {} complaints",
618 confirmation.num_of_complaints()
619 );
620 assert_reachable_gated!(
621 "random beacon: re-sent own DKG Confirmation after restart",
622 |pc| pc.allow_dkg_completion_after_timeout()
623 );
624 self.submit_dkg_confirmation(&epoch_store, &confirmation)?;
625 } else {
626 debug_fatal!(
627 "random beacon: `used_messages` is initialized but own DKG Confirmation was not persisted"
628 );
629 }
630 return Ok(());
631 }
632
633 let _ = self.dkg_start_time.set(Instant::now());
634
635 let dkg_version = epoch_store.protocol_config().dkg_version();
636 info!("random beacon: starting DKG, version {dkg_version}");
637
638 let msg = match VersionedDkgMessage::create(dkg_version, party) {
639 Ok(msg) => msg,
640 Err(FastCryptoError::IgnoredMessage) => {
641 info!(
642 "random beacon: no DKG Message for party id={} (zero weight)",
643 party.id
644 );
645 return Ok(());
646 }
647 Err(e) => {
648 error!("random beacon: error while creating a DKG Message: {e:?}");
649 return Ok(());
650 }
651 };
652
653 info!("random beacon: created {msg:?} with dkg version {dkg_version}");
654 let transaction = ConsensusTransaction::new_randomness_dkg_message(epoch_store.name, &msg);
655
656 #[allow(unused_mut)]
657 let mut fail_point_skip_sending = false;
658 fail_point_if!("rb-dkg", || {
659 fail_point_skip_sending = true;
661 });
662 if !fail_point_skip_sending {
663 self.consensus_adapter
664 .submit_to_consensus(&[transaction], &epoch_store)?;
665 }
666
667 epoch_store
668 .metrics
669 .epoch_random_beacon_dkg_message_time_ms
670 .set(
671 self.dkg_start_time
672 .get()
673 .unwrap() .elapsed()
675 .as_millis() as i64,
676 );
677 Ok(())
678 }
679
680 pub(crate) async fn advance_dkg(
683 &mut self,
684 consensus_output: &mut ConsensusCommitOutput,
685 round: Round,
686 ) -> Result<(), AdvanceDkgError> {
687 let epoch_store = self.epoch_store()?;
688
689 self.try_merge_messages(consensus_output, &epoch_store)
690 .await?;
691 self.try_complete_dkg(consensus_output, round, &epoch_store)?;
692
693 if !epoch_store
695 .protocol_config()
696 .allow_dkg_completion_after_timeout()
697 && !self.dkg_output.initialized()
698 && Self::is_past_dkg_timeout_round(round, epoch_store.protocol_config())
699 {
700 error!(
701 "random beacon: DKG timed out. Randomness disabled for this epoch. All randomness-using transactions will fail."
702 );
703 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
704 self.dkg_output
705 .set(None)
706 .expect("checked above that `dkg_output` is uninitialized");
707 consensus_output.set_dkg_output(None);
708 }
709
710 Ok(())
711 }
712
713 async fn try_merge_messages(
716 &mut self,
717 consensus_output: &mut ConsensusCommitOutput,
718 epoch_store: &Arc<AuthorityPerEpochStore>,
719 ) -> Result<(), AdvanceDkgError> {
720 if self.dkg_output.initialized() || self.used_messages.initialized() {
721 return Ok(());
722 }
723
724 let mut handles: FuturesUnordered<_> = std::mem::take(&mut self.enqueued_messages)
726 .into_values()
727 .collect();
728 while let Some(res) = handles.next().await {
729 let processed = match res {
730 Ok(processed) => processed,
731 Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()),
732 Err(_) => return Err(AdvanceDkgError::RuntimeShutdown),
735 };
736 if let Some(processed) = processed {
737 self.processed_messages
738 .insert(processed.sender(), processed.clone());
739 consensus_output.insert_dkg_processed_message(processed);
740 }
741 }
742
743 let messages: Vec<_> = self.processed_messages.values().cloned().collect();
744
745 match self.role.merge_messages(messages) {
746 Ok((conf, used_msgs)) => {
747 if let Some(conf) = &conf {
748 info!(
749 "random beacon: sending DKG Confirmation with {} complaints",
750 conf.num_of_complaints()
751 );
752 } else {
753 info!(
754 "random beacon: observer merged {} DKG messages",
755 used_msgs
756 .as_v1()
757 .expect("expected V1 used processed messages")
758 .0
759 .len()
760 );
761 }
762 if self.used_messages.set(used_msgs.clone()).is_err() {
763 error!("BUG: used_messages should only ever be set once");
764 }
765 consensus_output.insert_dkg_used_messages(used_msgs);
766
767 if let Some(conf) = conf {
768 consensus_output.set_dkg_own_confirmation(conf.clone());
771 self.submit_dkg_confirmation(epoch_store, &conf)?;
772
773 let elapsed = self.dkg_start_time.get().map(|t| t.elapsed().as_millis());
774 if let Some(elapsed) = elapsed {
775 epoch_store
776 .metrics
777 .epoch_random_beacon_dkg_confirmation_time_ms
778 .set(elapsed as i64);
779 }
780 }
781 }
782 Err(FastCryptoError::NotEnoughInputs) => (), Err(e) => debug!("random beacon: error while merging DKG Messages: {e:?}"),
784 }
785
786 Ok(())
787 }
788
789 fn try_complete_dkg(
793 &mut self,
794 consensus_output: &mut ConsensusCommitOutput,
795 round: Round,
796 epoch_store: &Arc<AuthorityPerEpochStore>,
797 ) -> SuiResult {
798 if !self.dkg_output.initialized() && self.used_messages.initialized() {
799 let used_messages = self
800 .used_messages
801 .get()
802 .expect("checked above that `used_messages` is initialized");
803 let complete_result = self
804 .role
805 .complete_dkg(used_messages, self.confirmations.values());
806
807 let epoch = epoch_store.committee().epoch();
808 let num_confirmations = self.confirmations.len();
809 let num_messages = self.processed_messages.len();
810
811 match complete_result {
812 Ok(output) => {
813 self.dkg_output
815 .set(Some(output.clone()))
816 .expect("checked above that `dkg_output` is uninitialized");
817 consensus_output.set_dkg_output(Some(output.clone()));
818
819 self.randomness_receiver_handle
820 .set_public_key(output.vss_pk.c0());
821
822 let epoch_elapsed = epoch_store.epoch_open_time.elapsed().as_millis();
823 let completed_after_timeout =
824 Self::is_past_dkg_timeout_round(round, epoch_store.protocol_config());
825 epoch_store
826 .metrics
827 .epoch_random_beacon_dkg_epoch_start_completion_time_ms
828 .set(epoch_elapsed as i64);
829 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(0);
830 epoch_store
831 .metrics
832 .epoch_random_beacon_dkg_completed_after_timeout
833 .set(i64::from(completed_after_timeout));
834 if completed_after_timeout {
835 fail_point!("rb-dkg-completed-after-timeout");
836 }
837
838 match self.role.as_ref() {
839 DkgRole::Party(party) => {
840 let num_shares =
841 output.shares.as_ref().map_or(0, |shares| shares.len());
842 let elapsed =
843 self.dkg_start_time.get().map(|t| t.elapsed().as_millis());
844 info!(
845 "random beacon: DKG complete for Party epoch={epoch} commit_round={round} \
846 num_messages={num_messages} num_confirmations={num_confirmations} \
847 num_shares={num_shares} epoch_elapsed_ms={epoch_elapsed} dkg_elapsed_ms={elapsed:?} \
848 completed_after_timeout={completed_after_timeout}"
849 );
850 epoch_store
851 .metrics
852 .epoch_random_beacon_dkg_num_shares
853 .set(num_shares as i64);
854
855 if let Some(elapsed) = elapsed {
856 epoch_store
857 .metrics
858 .epoch_random_beacon_dkg_completion_time_ms
859 .set(elapsed as i64);
860 }
861
862 self.network_handle.update_epoch(
863 epoch_store.committee().epoch(),
864 self.authority_info.clone(),
865 output,
866 party.t(),
867 None,
868 );
869 }
870 DkgRole::Observer(_) => {
871 info!(
872 "random beacon: DKG complete for Observer epoch={epoch} commit_round={round} \
873 num_messages={num_messages} num_confirmations={num_confirmations} \
874 epoch_elapsed_ms={epoch_elapsed} \
875 completed_after_timeout={completed_after_timeout}"
876 );
877 }
878 }
879 }
880 Err(FastCryptoError::NotEnoughInputs) => (), Err(e) => error!("random beacon: error while processing DKG Confirmations: {e:?}"),
882 }
883 }
884
885 Ok(())
886 }
887
888 pub fn add_message(
890 &mut self,
891 authority: &AuthorityName,
892 msg: VersionedDkgMessage,
893 ) -> SuiResult {
894 let dkg_version = self.epoch_store()?.protocol_config().dkg_version();
897 if !msg.is_valid_version(dkg_version) {
898 warn!("ignoring DKG Message from authority {authority:?} with unsupported version");
899 return Ok(());
900 }
901
902 if self.used_messages.initialized() || self.dkg_output.initialized() {
903 return Ok(());
905 }
906 let Some((_, party_id)) = self.authority_info.get(authority) else {
907 debug_fatal!(
908 "random beacon: received DKG Message from unknown authority: {authority:?}"
909 );
910 return Ok(());
911 };
912 if *party_id != msg.sender() {
913 warn!(
914 "ignoring equivocating DKG Message from authority {authority:?} pretending to be PartyId {party_id:?}"
915 );
916 return Ok(());
917 }
918 if self.enqueued_messages.contains_key(&msg.sender())
919 || self.processed_messages.contains_key(&msg.sender())
920 {
921 info!("ignoring duplicate DKG Message from authority {authority:?}");
922 return Ok(());
923 }
924
925 let sender = msg.sender();
926 let role = self.role.clone();
927 let handle = tokio::task::spawn_blocking(move || match role.process_message(msg) {
929 Ok(processed) => Some(processed),
930 Err(err) => {
931 debug!("random beacon: error while processing DKG Message: {err:?}");
932 None
933 }
934 });
935 self.enqueued_messages.insert(sender, handle);
936 Ok(())
937 }
938
939 pub(crate) fn add_confirmation(
941 &mut self,
942 output: &mut ConsensusCommitOutput,
943 authority: &AuthorityName,
944 conf: VersionedDkgConfirmation,
945 ) -> SuiResult {
946 let dkg_version = self.epoch_store()?.protocol_config().dkg_version();
949 if !conf.is_valid_version(dkg_version) {
950 warn!(
951 "ignoring DKG Confirmation from authority {authority:?} with unsupported version"
952 );
953 return Ok(());
954 }
955
956 if self.dkg_output.initialized() {
957 return Ok(());
959 }
960 let Some((_, party_id)) = self.authority_info.get(authority) else {
961 error!(
962 "random beacon: received DKG Confirmation from unknown authority: {authority:?}"
963 );
964 return Ok(());
965 };
966 if *party_id != conf.sender() {
967 warn!(
968 "ignoring equivocating DKG Confirmation from authority {authority:?} pretending to be PartyId {party_id:?}"
969 );
970 return Ok(());
971 }
972 self.confirmations.insert(conf.sender(), conf.clone());
973 output.insert_dkg_confirmation(conf);
974 Ok(())
975 }
976
977 pub(crate) fn reserve_next_randomness(
982 &mut self,
983 commit_timestamp: TimestampMs,
984 output: &mut ConsensusCommitOutput,
985 ) -> SuiResult<Option<RandomnessRound>> {
986 let epoch_store = self.epoch_store()?;
987
988 let last_round_timestamp = epoch_store
989 .get_randomness_last_round_timestamp()
990 .expect("read should not fail");
991
992 if let Some(last_round_timestamp) = last_round_timestamp
993 && commit_timestamp - last_round_timestamp
994 < epoch_store
995 .protocol_config()
996 .random_beacon_min_round_interval_ms()
997 {
998 return Ok(None);
999 }
1000
1001 let randomness_round = self.next_randomness_round;
1002 self.next_randomness_round = self
1003 .next_randomness_round
1004 .checked_add(1)
1005 .expect("RandomnessRound should not overflow");
1006
1007 output.reserve_next_randomness_round(self.next_randomness_round, commit_timestamp);
1008
1009 Ok(Some(randomness_round))
1010 }
1011
1012 pub fn generate_randomness(&self, epoch: EpochId, randomness_round: RandomnessRound) {
1014 if self.role.is_party() {
1015 self.network_handle
1016 .send_partial_signatures(epoch, randomness_round);
1017 }
1018 }
1019
1020 pub fn dkg_status(&self) -> DkgStatus {
1021 match self.dkg_output.get() {
1022 Some(Some(_)) => DkgStatus::Successful,
1023 Some(None) => DkgStatus::TimedOut,
1024 None => DkgStatus::Pending,
1025 }
1026 }
1027
1028 fn is_past_dkg_timeout_round(
1032 round: Round,
1033 protocol_config: &sui_protocol_config::ProtocolConfig,
1034 ) -> bool {
1035 round > protocol_config.random_beacon_dkg_timeout_round().into()
1036 }
1037
1038 pub fn dkg_status_for_commit_round(&mut self, round: Round) -> DkgStatus {
1043 let status = self.dkg_status();
1044 if status != DkgStatus::Pending {
1045 return status;
1046 }
1047
1048 let epoch_store = self
1049 .epoch_store()
1050 .expect("epoch store must be alive while computing DKG status for a commit");
1051 if !epoch_store
1052 .protocol_config()
1053 .allow_dkg_completion_after_timeout()
1054 || !Self::is_past_dkg_timeout_round(round, epoch_store.protocol_config())
1055 {
1056 return status;
1057 }
1058
1059 if !self.dkg_timeout_reported {
1060 error!(
1061 "random beacon: DKG timed out; randomness-using transactions will be canceled unless/until DKG completes"
1062 );
1063 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
1064 self.dkg_timeout_reported = true;
1065 }
1066 DkgStatus::TimedOut
1067 }
1068
1069 fn submit_dkg_confirmation(
1070 &self,
1071 epoch_store: &Arc<AuthorityPerEpochStore>,
1072 confirmation: &VersionedDkgConfirmation,
1073 ) -> SuiResult {
1074 let transaction =
1075 ConsensusTransaction::new_randomness_dkg_confirmation(epoch_store.name, confirmation);
1076
1077 #[allow(unused_mut)]
1078 let mut fail_point_skip_sending = false;
1079 fail_point_if!("rb-dkg", || {
1080 fail_point_skip_sending = true;
1082 });
1083 if !fail_point_skip_sending {
1084 self.consensus_adapter
1085 .submit_to_consensus(&[transaction], epoch_store)?;
1086 }
1087 Ok(())
1088 }
1089
1090 pub fn reporter(&self) -> Option<RandomnessReporter> {
1093 if self.role.is_observer() {
1094 return None;
1095 }
1096 Some(RandomnessReporter {
1097 epoch_store: self.epoch_store.clone(),
1098 epoch: self.epoch,
1099 network_handle: self.network_handle.clone(),
1100 highest_completed_round: self.highest_completed_round.clone(),
1101 })
1102 }
1103
1104 #[cfg(test)]
1105 fn dkg_output(&self) -> Option<&dkg_v1::Output<PkG, EncG>> {
1106 self.dkg_output.get().and_then(|o| o.as_ref())
1107 }
1108
1109 fn epoch_store(&self) -> SuiResult<Arc<AuthorityPerEpochStore>> {
1110 self.epoch_store
1111 .upgrade()
1112 .ok_or(SuiErrorKind::EpochEnded(self.epoch).into())
1113 }
1114
1115 fn randomness_dkg_info_from_committee(
1116 committee: &Committee,
1117 ) -> Vec<(
1118 u16,
1119 AuthorityName,
1120 fastcrypto_threshold::ecies_v1::PublicKey<bls12381::G2Element>,
1121 StakeUnit,
1122 )> {
1123 committee
1124 .members()
1125 .map(|(name, stake)| {
1126 let index: u16 = committee
1127 .authority_index(name)
1128 .expect("lookup of known committee member should succeed")
1129 .try_into()
1130 .expect("authority index should fit in u16");
1131 let pk = bls12381::G2Element::from_byte_array(
1132 committee
1133 .public_key(name)
1134 .expect("lookup of known committee member should succeed")
1135 .as_bytes()
1136 .try_into()
1137 .expect("key length should match"),
1138 )
1139 .expect("should work to convert BLS key to G2Element");
1140 (
1141 index,
1142 *name,
1143 fastcrypto_threshold::ecies_v1::PublicKey::from(pk),
1144 *stake,
1145 )
1146 })
1147 .collect()
1148 }
1149}
1150
1151#[derive(Clone)]
1153pub struct RandomnessReporter {
1154 epoch_store: Weak<AuthorityPerEpochStore>,
1155 epoch: EpochId,
1156 network_handle: randomness::Handle,
1157 highest_completed_round: Arc<Mutex<Option<RandomnessRound>>>,
1158}
1159
1160impl RandomnessReporter {
1161 pub fn notify_randomness_in_checkpoint(&self, round: RandomnessRound) -> SuiResult {
1165 let epoch_store = self
1166 .epoch_store
1167 .upgrade()
1168 .ok_or(SuiErrorKind::EpochEnded(self.epoch))?;
1169 let mut highest_completed_round = self.highest_completed_round.lock();
1170 if Some(round) > *highest_completed_round {
1171 *highest_completed_round = Some(round);
1172 epoch_store
1173 .tables()
1174 .randomness_highest_completed_round
1175 .insert(&SINGLETON_KEY, &round)?;
1176 self.network_handle
1177 .complete_round(epoch_store.committee().epoch(), round);
1178 }
1179 Ok(())
1180 }
1181}
1182
1183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1184pub enum DkgStatus {
1185 Pending,
1186 TimedOut,
1189 Successful,
1190}
1191
1192#[cfg(test)]
1193mod tests {
1194 use crate::{
1195 authority::{
1196 authority_per_epoch_store::{ExecutionIndices, ExecutionIndicesWithStatsV2},
1197 test_authority_builder::TestAuthorityBuilder,
1198 },
1199 checkpoints::CheckpointStore,
1200 consensus_adapter::{ConsensusAdapter, ConsensusAdapterMetrics, MockConsensusClient},
1201 epoch::randomness::*,
1202 mock_consensus::with_block_status,
1203 randomness_round_receiver::RandomnessRoundReceiverHandle,
1204 };
1205 use consensus_core::BlockStatus;
1206 use consensus_types::block::BlockRef;
1207 use fastcrypto::groups::bls12381;
1208 use fastcrypto::serde_helpers::ToFromByteArray;
1209 use fastcrypto_threshold::{mocked_dkg, nodes};
1210 use std::num::NonZeroUsize;
1211 use sui_macros::sim_test;
1212 use sui_protocol_config::ProtocolConfig;
1213 use sui_protocol_config::{Chain, ProtocolVersion};
1214 use sui_types::{base_types::AuthorityName, messages_consensus::ConsensusTransactionKind};
1215 use tokio::sync::mpsc;
1216 use tokio::sync::mpsc::error::TrySendError;
1217 use typed_store::Map;
1218
1219 use arc_swap::Guard;
1220
1221 fn forward_submission(
1227 tx_consensus: &mpsc::Sender<Vec<ConsensusTransaction>>,
1228 transactions: &[ConsensusTransaction],
1229 ) {
1230 match tx_consensus.try_send(transactions.to_vec()) {
1231 Ok(()) | Err(TrySendError::Closed(_)) => {}
1232 Err(TrySendError::Full(_)) => panic!("test consensus channel is full"),
1233 }
1234 }
1235
1236 struct DkgTestSetup {
1239 network_config: sui_swarm_config::network_config::NetworkConfig,
1240 epoch_stores: Vec<Guard<Arc<AuthorityPerEpochStore>>>,
1241 randomness_managers: Vec<RandomnessManager>,
1242 tx_consensus: mpsc::Sender<Vec<ConsensusTransaction>>,
1243 rx_consensus: mpsc::Receiver<Vec<ConsensusTransaction>>,
1244 num_validators: usize,
1245 }
1246
1247 impl DkgTestSetup {
1248 async fn new(include_observer: bool) -> Self {
1249 let mut protocol_config =
1250 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1251 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1252 Self::new_with_protocol_config(include_observer, protocol_config).await
1253 }
1254
1255 async fn new_with_protocol_config(
1256 include_observer: bool,
1257 protocol_config: ProtocolConfig,
1258 ) -> Self {
1259 let network_config =
1260 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1261 .committee_size(NonZeroUsize::new(4).unwrap())
1262 .with_reference_gas_price(500)
1263 .build();
1264
1265 let num_validators = network_config.validator_configs.len();
1266 let mut epoch_stores = Vec::new();
1267 let mut randomness_managers = Vec::new();
1268 let (tx_consensus, rx_consensus) = mpsc::channel(100);
1269
1270 for validator in network_config.validator_configs.iter() {
1271 let mut mock_consensus_client = MockConsensusClient::new();
1272 let tx_consensus = tx_consensus.clone();
1273 mock_consensus_client
1274 .expect_submit()
1275 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1276 forward_submission(&tx_consensus, transactions);
1277 true
1278 })
1279 .returning(|_, _| {
1280 Ok((
1281 Vec::new(),
1282 with_block_status(BlockStatus::Sequenced(BlockRef::MIN)),
1283 ))
1284 });
1285
1286 let state = TestAuthorityBuilder::new()
1287 .with_protocol_config(protocol_config.clone())
1288 .with_genesis_and_keypair(
1289 &network_config.genesis,
1290 validator.protocol_key_pair(),
1291 )
1292 .build()
1293 .await;
1294 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1295 Arc::new(mock_consensus_client),
1296 CheckpointStore::new_for_tests(),
1297 state.name,
1298 100_000,
1299 100_000,
1300 ConsensusAdapterMetrics::new_test(),
1301 Arc::new(tokio::sync::Notify::new()),
1302 ));
1303 let epoch_store = state.epoch_store_for_testing();
1304 let randomness_manager = RandomnessManager::try_new(
1305 Arc::downgrade(&epoch_store),
1306 Box::new(consensus_adapter.clone()),
1307 sui_network::randomness::Handle::new_stub(),
1308 Some(validator.protocol_key_pair()),
1309 RandomnessRoundReceiverHandle::new_for_testing(),
1310 )
1311 .await
1312 .unwrap();
1313
1314 epoch_stores.push(epoch_store);
1315 randomness_managers.push(randomness_manager);
1316 }
1317
1318 if include_observer {
1319 let observer_epoch_store = epoch_stores[0].clone();
1320 let mut mock_observer_consensus = MockConsensusClient::new();
1321 mock_observer_consensus
1322 .expect_submit()
1323 .returning(|_, _| panic!("observer should not submit to consensus"));
1324 let observer_adapter = Arc::new(ConsensusAdapter::new(
1325 Arc::new(mock_observer_consensus),
1326 CheckpointStore::new_for_tests(),
1327 observer_epoch_store.name,
1328 100_000,
1329 100_000,
1330 ConsensusAdapterMetrics::new_test(),
1331 Arc::new(tokio::sync::Notify::new()),
1332 ));
1333 let observer_manager = RandomnessManager::try_new(
1334 Arc::downgrade(&observer_epoch_store),
1335 Box::new(observer_adapter),
1336 sui_network::randomness::Handle::new_stub(),
1337 None,
1338 RandomnessRoundReceiverHandle::new_for_testing(),
1339 )
1340 .await
1341 .unwrap();
1342
1343 epoch_stores.push(observer_epoch_store.into());
1344 randomness_managers.push(observer_manager);
1345 }
1346
1347 Self {
1348 network_config,
1349 epoch_stores,
1350 randomness_managers,
1351 tx_consensus,
1352 rx_consensus,
1353 num_validators,
1354 }
1355 }
1356
1357 fn consensus_adapter(&self, authority: AuthorityName) -> Arc<ConsensusAdapter> {
1358 let mut mock_consensus_client = MockConsensusClient::new();
1359 let tx_consensus = self.tx_consensus.clone();
1360 mock_consensus_client
1361 .expect_submit()
1362 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1363 forward_submission(&tx_consensus, transactions);
1364 true
1365 })
1366 .returning(|_, _| {
1367 Ok((
1368 Vec::new(),
1369 with_block_status(BlockStatus::Sequenced(BlockRef::MIN)),
1370 ))
1371 });
1372
1373 Arc::new(ConsensusAdapter::new(
1374 Arc::new(mock_consensus_client),
1375 CheckpointStore::new_for_tests(),
1376 authority,
1377 100_000,
1378 100_000,
1379 ConsensusAdapterMetrics::new_test(),
1380 Arc::new(tokio::sync::Notify::new()),
1381 ))
1382 }
1383
1384 async fn recover_validator_randomness_managers(&mut self) -> usize {
1385 let mut recovered_randomness_managers = Vec::new();
1386 for (i, validator) in self
1387 .network_config
1388 .validator_configs
1389 .iter()
1390 .enumerate()
1391 .take(self.num_validators)
1392 {
1393 let consensus_adapter = self.consensus_adapter(self.epoch_stores[i].name);
1394 recovered_randomness_managers.push(
1395 RandomnessManager::try_new(
1396 Arc::downgrade(&self.epoch_stores[i]),
1397 Box::new(consensus_adapter),
1398 sui_network::randomness::Handle::new_stub(),
1399 Some(validator.protocol_key_pair()),
1400 RandomnessRoundReceiverHandle::new_for_testing(),
1401 )
1402 .await
1403 .unwrap(),
1404 );
1405 }
1406 let recovered_count = recovered_randomness_managers.len();
1407 self.randomness_managers = recovered_randomness_managers;
1408 recovered_count
1409 }
1410
1411 async fn start_dkg_and_collect_messages(&mut self) -> Vec<VersionedDkgMessage> {
1413 let mut dkg_messages = Vec::new();
1414 for randomness_manager in self.randomness_managers.iter_mut() {
1415 randomness_manager.start_dkg().await.unwrap();
1416 }
1417 for _ in 0..self.num_validators {
1418 let mut dkg_message = self.rx_consensus.recv().await.unwrap();
1419 assert!(dkg_message.len() == 1);
1420 match dkg_message.remove(0).kind {
1421 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
1422 let msg: VersionedDkgMessage = bcs::from_bytes(&bytes)
1423 .expect("DKG message deserialization should not fail");
1424 dkg_messages.push(msg);
1425 }
1426 _ => panic!("wrong type of message sent"),
1427 }
1428 }
1429 dkg_messages
1430 }
1431
1432 async fn distribute_messages_and_advance(
1434 &mut self,
1435 dkg_messages: &[VersionedDkgMessage],
1436 advance_round: Round,
1437 ) {
1438 let indexed_messages: Vec<_> = dkg_messages.iter().cloned().enumerate().collect();
1439 self.distribute_indexed_messages_and_advance(&indexed_messages, advance_round)
1440 .await;
1441 }
1442
1443 async fn distribute_indexed_messages_and_advance(
1444 &mut self,
1445 dkg_messages: &[(usize, VersionedDkgMessage)],
1446 advance_round: Round,
1447 ) {
1448 for i in 0..self.randomness_managers.len() {
1449 let mut output = ConsensusCommitOutput::new(0);
1450 output.record_consensus_commit_stats(ExecutionIndicesWithStatsV2 {
1451 index: ExecutionIndices {
1452 last_committed_round: 0,
1453 ..Default::default()
1454 },
1455 ..Default::default()
1456 });
1457 for (j, dkg_message) in dkg_messages.iter().cloned() {
1458 self.randomness_managers[i]
1459 .add_message(&self.epoch_stores[j].name, dkg_message)
1460 .unwrap();
1461 }
1462 self.randomness_managers[i]
1463 .advance_dkg(&mut output, advance_round)
1464 .await
1465 .unwrap();
1466 let mut batch = self.epoch_stores[i].db_batch_for_test();
1467 output
1468 .write_to_batch(&self.epoch_stores[i], &mut batch)
1469 .unwrap();
1470 batch.write().unwrap();
1471 }
1472 }
1473
1474 async fn collect_confirmations(&mut self) -> Vec<VersionedDkgConfirmation> {
1475 let mut dkg_confirmations = Vec::new();
1476 for _ in 0..self.num_validators {
1477 let mut dkg_confirmation = self.rx_consensus.recv().await.unwrap();
1478 assert!(dkg_confirmation.len() == 1);
1479 match dkg_confirmation.remove(0).kind {
1480 ConsensusTransactionKind::RandomnessDkgConfirmation(_, bytes) => {
1481 let msg: VersionedDkgConfirmation = bcs::from_bytes(&bytes)
1482 .expect("DKG confirmation deserialization should not fail");
1483 dkg_confirmations.push(msg);
1484 }
1485 _ => panic!("wrong type of message sent"),
1486 }
1487 }
1488 dkg_confirmations
1489 }
1490
1491 async fn distribute_confirmations_and_advance(
1492 &mut self,
1493 dkg_confirmations: &[VersionedDkgConfirmation],
1494 advance_round: Round,
1495 ) {
1496 for i in 0..self.randomness_managers.len() {
1497 let mut output = ConsensusCommitOutput::new(0);
1498 output.record_consensus_commit_stats(ExecutionIndicesWithStatsV2 {
1499 index: ExecutionIndices {
1500 last_committed_round: 1,
1501 ..Default::default()
1502 },
1503 ..Default::default()
1504 });
1505 for (j, dkg_confirmation) in dkg_confirmations.iter().cloned().enumerate() {
1506 self.randomness_managers[i]
1507 .add_confirmation(&mut output, &self.epoch_stores[j].name, dkg_confirmation)
1508 .unwrap();
1509 }
1510 self.randomness_managers[i]
1511 .advance_dkg(&mut output, advance_round)
1512 .await
1513 .unwrap();
1514 let mut batch = self.epoch_stores[i].db_batch_for_test();
1515 output
1516 .write_to_batch(&self.epoch_stores[i], &mut batch)
1517 .unwrap();
1518 batch.write().unwrap();
1519 }
1520 }
1521
1522 async fn collect_and_distribute_confirmations(&mut self, advance_round: Round) {
1524 let dkg_confirmations = self.collect_confirmations().await;
1525 self.distribute_confirmations_and_advance(&dkg_confirmations, advance_round)
1526 .await;
1527 }
1528 }
1529
1530 #[tokio::test]
1531 async fn test_dkg() {
1532 telemetry_subscribers::init_for_testing();
1533
1534 let mut setup = DkgTestSetup::new(false).await;
1535 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1536 setup
1537 .distribute_messages_and_advance(&dkg_messages, 0)
1538 .await;
1539 setup.collect_and_distribute_confirmations(0).await;
1540
1541 for rm in &setup.randomness_managers {
1542 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1543 }
1544 }
1545
1546 #[sim_test]
1547 async fn test_dkg_expiration() {
1548 telemetry_subscribers::init_for_testing();
1549
1550 let mut protocol_config =
1551 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1552 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1553 protocol_config.set_allow_dkg_completion_after_timeout_for_testing(false);
1554 let mut setup = DkgTestSetup::new_with_protocol_config(false, protocol_config).await;
1555 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1556
1557 assert_eq!(
1560 DkgStatus::Pending,
1561 setup.randomness_managers[0].dkg_status_for_commit_round(u64::MAX)
1562 );
1563
1564 setup
1566 .distribute_messages_and_advance(&dkg_messages, u64::MAX)
1567 .await;
1568
1569 for rm in &setup.randomness_managers {
1570 assert_eq!(DkgStatus::TimedOut, rm.dkg_status());
1571 }
1572
1573 let recovered_count = setup.recover_validator_randomness_managers().await;
1574 assert_eq!(setup.num_validators, recovered_count);
1575
1576 for rm in &setup.randomness_managers {
1580 assert_eq!(DkgStatus::TimedOut, rm.dkg_status());
1581 }
1582 }
1583
1584 #[tokio::test]
1585 async fn test_dkg_completes_after_timeout() {
1586 telemetry_subscribers::init_for_testing();
1587
1588 let mut setup = DkgTestSetup::new(false).await;
1589 let timeout: Round = setup.epoch_stores[0]
1590 .protocol_config()
1591 .random_beacon_dkg_timeout_round()
1592 .into();
1593 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1594 setup
1595 .distribute_messages_and_advance(&dkg_messages, timeout + 1)
1596 .await;
1597
1598 for rm in &mut setup.randomness_managers {
1599 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1600 assert_eq!(
1601 DkgStatus::TimedOut,
1602 rm.dkg_status_for_commit_round(timeout + 1)
1603 );
1604 }
1605
1606 setup
1607 .collect_and_distribute_confirmations(timeout + 2)
1608 .await;
1609 for rm in &setup.randomness_managers {
1610 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1611 }
1612 }
1613
1614 #[tokio::test]
1615 async fn test_dkg_timeout_then_restart_then_complete() {
1616 telemetry_subscribers::init_for_testing();
1617
1618 let mut setup = DkgTestSetup::new(false).await;
1619 let timeout: Round = setup.epoch_stores[0]
1620 .protocol_config()
1621 .random_beacon_dkg_timeout_round()
1622 .into();
1623 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1624 setup
1625 .distribute_messages_and_advance(&dkg_messages, timeout + 1)
1626 .await;
1627
1628 let recovered_count = setup.recover_validator_randomness_managers().await;
1629 assert_eq!(setup.num_validators, recovered_count);
1630 for rm in &mut setup.randomness_managers {
1631 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1632 assert!(rm.used_messages.initialized());
1633 assert_eq!(
1634 DkgStatus::TimedOut,
1635 rm.dkg_status_for_commit_round(timeout + 1)
1636 );
1637 }
1638
1639 setup
1640 .collect_and_distribute_confirmations(timeout + 2)
1641 .await;
1642 for rm in &setup.randomness_managers {
1643 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1644 }
1645 }
1646
1647 #[tokio::test]
1648 async fn test_dkg_confirmation_resent_after_restart() {
1649 telemetry_subscribers::init_for_testing();
1650
1651 let mut setup = DkgTestSetup::new(false).await;
1652 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1653 setup
1654 .distribute_messages_and_advance(&dkg_messages, 0)
1655 .await;
1656 let original_confirmations = setup.collect_confirmations().await;
1657 assert_eq!(setup.num_validators, original_confirmations.len());
1658
1659 let recovered_count = setup.recover_validator_randomness_managers().await;
1660 assert_eq!(setup.num_validators, recovered_count);
1661 for randomness_manager in &mut setup.randomness_managers {
1662 randomness_manager.start_dkg().await.unwrap();
1663 }
1664
1665 let resent_confirmations = setup.collect_confirmations().await;
1666 assert_eq!(setup.num_validators, resent_confirmations.len());
1667 assert_eq!(original_confirmations, resent_confirmations);
1669 setup
1670 .distribute_confirmations_and_advance(&resent_confirmations, 0)
1671 .await;
1672 for rm in &setup.randomness_managers {
1673 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1674 }
1675 }
1676
1677 #[sim_test]
1678 async fn test_dkg_status_for_commit_round() {
1679 telemetry_subscribers::init_for_testing();
1680
1681 let mut setup = DkgTestSetup::new(false).await;
1682 let timeout: Round = setup.epoch_stores[0]
1683 .protocol_config()
1684 .random_beacon_dkg_timeout_round()
1685 .into();
1686 let rm = &mut setup.randomness_managers[0];
1687 assert_eq!(DkgStatus::Pending, rm.dkg_status_for_commit_round(timeout));
1688 assert_eq!(
1689 DkgStatus::TimedOut,
1690 rm.dkg_status_for_commit_round(timeout + 1)
1691 );
1692 rm.dkg_output.set(None).unwrap();
1693 assert_eq!(DkgStatus::TimedOut, rm.dkg_status_for_commit_round(0));
1694 }
1696
1697 #[tokio::test]
1698 async fn test_dkg_recovers_processed_messages_after_restart() {
1699 telemetry_subscribers::init_for_testing();
1700
1701 let mut setup = DkgTestSetup::new(false).await;
1702 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1703 let processed_before_restart = 1;
1704 assert!(processed_before_restart > 0);
1705 assert!(processed_before_restart < setup.num_validators);
1706 let pre_restart_messages: Vec<_> = dkg_messages
1707 .iter()
1708 .cloned()
1709 .enumerate()
1710 .take(processed_before_restart)
1711 .collect();
1712 let post_restart_messages: Vec<_> = dkg_messages
1713 .iter()
1714 .cloned()
1715 .enumerate()
1716 .skip(processed_before_restart)
1717 .collect();
1718
1719 setup
1720 .distribute_indexed_messages_and_advance(&pre_restart_messages, 0)
1721 .await;
1722
1723 for rm in &setup.randomness_managers {
1724 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1725 assert_eq!(processed_before_restart, rm.processed_messages.len());
1726 assert!(!rm.used_messages.initialized());
1727 }
1728
1729 let recovered_count = setup.recover_validator_randomness_managers().await;
1730 assert_eq!(setup.num_validators, recovered_count);
1731 for rm in &setup.randomness_managers {
1732 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1733 assert_eq!(processed_before_restart, rm.processed_messages.len());
1734 assert!(!rm.used_messages.initialized());
1735 }
1736
1737 setup
1738 .distribute_indexed_messages_and_advance(&post_restart_messages, 0)
1739 .await;
1740
1741 for rm in &setup.randomness_managers {
1742 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1743 assert_eq!(setup.num_validators, rm.processed_messages.len());
1744 assert!(rm.used_messages.initialized());
1745 }
1746
1747 setup.collect_and_distribute_confirmations(0).await;
1748
1749 for rm in &setup.randomness_managers {
1750 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1751 }
1752 }
1753
1754 #[tokio::test]
1757 async fn test_dkg_observer() {
1758 telemetry_subscribers::init_for_testing();
1759
1760 let mut setup = DkgTestSetup::new(true).await;
1761 let observer_idx = setup.randomness_managers.len() - 1;
1762
1763 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1764 setup
1765 .distribute_messages_and_advance(&dkg_messages, 0)
1766 .await;
1767
1768 for rm in &setup.randomness_managers {
1769 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1770 }
1771
1772 setup.collect_and_distribute_confirmations(0).await;
1773
1774 for rm in &setup.randomness_managers {
1775 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1776 }
1777
1778 let observer_output = setup.randomness_managers[observer_idx]
1780 .dkg_output()
1781 .expect("observer should have DKG output");
1782 let validator_output = setup.randomness_managers[0]
1783 .dkg_output()
1784 .expect("validator should have DKG output");
1785 assert_eq!(observer_output.vss_pk, validator_output.vss_pk);
1786 assert!(observer_output.shares.is_none());
1787
1788 for rm in &setup.randomness_managers[..setup.num_validators] {
1789 let output = rm.dkg_output().expect("validator should have DKG output");
1790 assert!(output.shares.is_some());
1791 }
1792 }
1793
1794 fn build_dkg_nodes(
1796 network_config: &sui_swarm_config::network_config::NetworkConfig,
1797 ) -> (nodes::Nodes<EncG>, u16) {
1798 let dkg_nodes: Vec<_> = network_config
1799 .validator_configs
1800 .iter()
1801 .enumerate()
1802 .map(|(i, v)| {
1803 let pk = bls12381::G2Element::from_byte_array(
1804 v.protocol_key_pair()
1805 .public()
1806 .as_bytes()
1807 .try_into()
1808 .expect("key length should match"),
1809 )
1810 .expect("should work to convert BLS key to G2Element");
1811 nodes::Node::<EncG> {
1812 id: i as u16,
1813 pk: fastcrypto_threshold::ecies_v1::PublicKey::from(pk),
1814 weight: 1,
1815 }
1816 })
1817 .collect();
1818 let num_nodes = dkg_nodes.len();
1819 let t = num_nodes.div_ceil(3) as u16;
1821 nodes::Nodes::new(dkg_nodes).map(|n| (n, t)).unwrap()
1822 }
1823
1824 #[test]
1825 fn test_dkg_role_try_new_party() {
1826 let network_config =
1827 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1828 .committee_size(NonZeroUsize::new(4).unwrap())
1829 .with_reference_gas_price(500)
1830 .build();
1831
1832 let (nodes, t) = build_dkg_nodes(&network_config);
1833 let random_oracle =
1834 fastcrypto_threshold::random_oracle::RandomOracle::new("test_dkg_role_party");
1835
1836 let role = DkgRole::try_new(
1837 Some(network_config.validator_configs[0].protocol_key_pair()),
1838 nodes,
1839 t,
1840 random_oracle,
1841 );
1842 assert!(role.is_some());
1843 assert!(role.unwrap().is_party());
1844 }
1845
1846 #[sim_test]
1847 async fn test_randomness_manager_crash_recovery_v1() {
1848 telemetry_subscribers::init_for_testing();
1849
1850 let network_config =
1851 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1852 .committee_size(NonZeroUsize::new(4).unwrap())
1853 .with_reference_gas_price(500)
1854 .build();
1855
1856 let mut protocol_config =
1857 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1858 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1859
1860 let validator = &network_config.validator_configs[0];
1861 let state = TestAuthorityBuilder::new()
1862 .with_protocol_config(protocol_config.clone())
1863 .with_genesis_and_keypair(&network_config.genesis, validator.protocol_key_pair())
1864 .build()
1865 .await;
1866 let epoch_store = state.epoch_store_for_testing();
1867
1868 let dkg_nodes = nodes::Nodes::new(
1869 RandomnessManager::randomness_dkg_info_from_committee(epoch_store.committee())
1870 .into_iter()
1871 .map(|(id, _, pk, stake)| nodes::Node::<bls12381::G2Element> {
1872 id,
1873 pk,
1874 weight: stake.try_into().unwrap(),
1875 })
1876 .collect(),
1877 )
1878 .unwrap();
1879 let threshold = epoch_store
1880 .committee()
1881 .validity_threshold()
1882 .try_into()
1883 .unwrap();
1884 let party_id = epoch_store
1885 .committee()
1886 .authority_index(&epoch_store.name)
1887 .unwrap()
1888 .try_into()
1889 .unwrap();
1890 let expected_dkg_output = mocked_dkg::generate_mocked_output::<
1891 bls12381::G2Element,
1892 bls12381::G2Element,
1893 >(dkg_nodes, threshold, 0, party_id);
1894 let expected_dkg_output_bytes =
1895 bcs::to_bytes(&expected_dkg_output).expect("DKG output serialization should not fail");
1896 let expected_public_key = expected_dkg_output.vss_pk.c0();
1897
1898 let tables = epoch_store.tables();
1899 tables
1900 .dkg_output_v2
1901 .insert(&SINGLETON_KEY, &Some(expected_dkg_output))
1902 .unwrap();
1903 tables
1904 .randomness_next_round
1905 .insert(&SINGLETON_KEY, &RandomnessRound(3))
1906 .unwrap();
1907 tables
1908 .randomness_highest_completed_round
1909 .insert(&SINGLETON_KEY, &RandomnessRound(1))
1910 .unwrap();
1911
1912 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1913 Arc::new(MockConsensusClient::new()),
1914 CheckpointStore::new_for_tests(),
1915 epoch_store.name,
1916 100_000,
1917 100_000,
1918 ConsensusAdapterMetrics::new_test(),
1919 Arc::new(tokio::sync::Notify::new()),
1920 ));
1921 let recovered_receiver_handle = RandomnessRoundReceiverHandle::new_for_testing();
1922 assert!(recovered_receiver_handle.public_key_for_testing().is_none());
1923
1924 let recovered_randomness_manager = RandomnessManager::try_new(
1925 Arc::downgrade(&epoch_store),
1926 Box::new(consensus_adapter.clone()),
1927 sui_network::randomness::Handle::new_stub(),
1928 Some(validator.protocol_key_pair()),
1929 recovered_receiver_handle.clone(),
1930 )
1931 .await
1932 .unwrap();
1933
1934 assert_eq!(
1935 DkgStatus::Successful,
1936 recovered_randomness_manager.dkg_status()
1937 );
1938 let recovered_dkg_output = recovered_randomness_manager
1939 .dkg_output
1940 .get()
1941 .expect("recovered DKG output should be initialized")
1942 .as_ref()
1943 .expect("recovered DKG should be successful");
1944 assert_eq!(
1945 expected_dkg_output_bytes,
1946 bcs::to_bytes(recovered_dkg_output).expect("DKG output serialization should not fail")
1947 );
1948 assert_eq!(
1949 RandomnessRound(3),
1950 recovered_randomness_manager.next_randomness_round
1951 );
1952 assert_eq!(
1953 Some(RandomnessRound(1)),
1954 *recovered_randomness_manager.highest_completed_round.lock()
1955 );
1956 let recovered_public_key = recovered_receiver_handle
1957 .public_key_for_testing()
1958 .expect("public key should be restored on recovery");
1959 assert_eq!(
1960 expected_public_key.to_byte_array(),
1961 recovered_public_key.to_byte_array()
1962 );
1963 }
1964
1965 #[test]
1966 fn test_dkg_role_try_new_observer() {
1967 let network_config =
1968 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1969 .committee_size(NonZeroUsize::new(4).unwrap())
1970 .with_reference_gas_price(500)
1971 .build();
1972
1973 let (nodes, t) = build_dkg_nodes(&network_config);
1974 let random_oracle =
1975 fastcrypto_threshold::random_oracle::RandomOracle::new("test_dkg_role_observer");
1976
1977 let role = DkgRole::try_new(None, nodes, t, random_oracle);
1978 assert!(role.is_some());
1979 assert!(role.unwrap().is_observer());
1980 }
1981}