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_tbls::{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_tbls::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_tbls::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_tbls::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_tbls::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_tbls::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_tbls::{mocked_dkg, nodes};
1210 use std::num::NonZeroUsize;
1211 use sui_protocol_config::ProtocolConfig;
1212 use sui_protocol_config::{Chain, ProtocolVersion};
1213 use sui_types::{base_types::AuthorityName, messages_consensus::ConsensusTransactionKind};
1214 use tokio::sync::mpsc;
1215 use typed_store::Map;
1216
1217 use arc_swap::Guard;
1218
1219 struct DkgTestSetup {
1222 network_config: sui_swarm_config::network_config::NetworkConfig,
1223 epoch_stores: Vec<Guard<Arc<AuthorityPerEpochStore>>>,
1224 randomness_managers: Vec<RandomnessManager>,
1225 tx_consensus: mpsc::Sender<Vec<ConsensusTransaction>>,
1226 rx_consensus: mpsc::Receiver<Vec<ConsensusTransaction>>,
1227 num_validators: usize,
1228 }
1229
1230 impl DkgTestSetup {
1231 async fn new(include_observer: bool) -> Self {
1232 let mut protocol_config =
1233 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1234 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1235 Self::new_with_protocol_config(include_observer, protocol_config).await
1236 }
1237
1238 async fn new_with_protocol_config(
1239 include_observer: bool,
1240 protocol_config: ProtocolConfig,
1241 ) -> Self {
1242 let network_config =
1243 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1244 .committee_size(NonZeroUsize::new(4).unwrap())
1245 .with_reference_gas_price(500)
1246 .build();
1247
1248 let num_validators = network_config.validator_configs.len();
1249 let mut epoch_stores = Vec::new();
1250 let mut randomness_managers = Vec::new();
1251 let (tx_consensus, rx_consensus) = mpsc::channel(100);
1252
1253 for validator in network_config.validator_configs.iter() {
1254 let mut mock_consensus_client = MockConsensusClient::new();
1255 let tx_consensus = tx_consensus.clone();
1256 mock_consensus_client
1257 .expect_submit()
1258 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1259 tx_consensus.try_send(transactions.to_vec()).unwrap();
1260 true
1261 })
1262 .returning(|_, _| {
1263 Ok((
1264 Vec::new(),
1265 with_block_status(BlockStatus::Sequenced(BlockRef::MIN)),
1266 ))
1267 });
1268
1269 let state = TestAuthorityBuilder::new()
1270 .with_protocol_config(protocol_config.clone())
1271 .with_genesis_and_keypair(
1272 &network_config.genesis,
1273 validator.protocol_key_pair(),
1274 )
1275 .build()
1276 .await;
1277 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1278 Arc::new(mock_consensus_client),
1279 CheckpointStore::new_for_tests(),
1280 state.name,
1281 100_000,
1282 100_000,
1283 ConsensusAdapterMetrics::new_test(),
1284 Arc::new(tokio::sync::Notify::new()),
1285 ));
1286 let epoch_store = state.epoch_store_for_testing();
1287 let randomness_manager = RandomnessManager::try_new(
1288 Arc::downgrade(&epoch_store),
1289 Box::new(consensus_adapter.clone()),
1290 sui_network::randomness::Handle::new_stub(),
1291 Some(validator.protocol_key_pair()),
1292 RandomnessRoundReceiverHandle::new_for_testing(),
1293 )
1294 .await
1295 .unwrap();
1296
1297 epoch_stores.push(epoch_store);
1298 randomness_managers.push(randomness_manager);
1299 }
1300
1301 if include_observer {
1302 let observer_epoch_store = epoch_stores[0].clone();
1303 let mut mock_observer_consensus = MockConsensusClient::new();
1304 mock_observer_consensus
1305 .expect_submit()
1306 .returning(|_, _| panic!("observer should not submit to consensus"));
1307 let observer_adapter = Arc::new(ConsensusAdapter::new(
1308 Arc::new(mock_observer_consensus),
1309 CheckpointStore::new_for_tests(),
1310 observer_epoch_store.name,
1311 100_000,
1312 100_000,
1313 ConsensusAdapterMetrics::new_test(),
1314 Arc::new(tokio::sync::Notify::new()),
1315 ));
1316 let observer_manager = RandomnessManager::try_new(
1317 Arc::downgrade(&observer_epoch_store),
1318 Box::new(observer_adapter),
1319 sui_network::randomness::Handle::new_stub(),
1320 None,
1321 RandomnessRoundReceiverHandle::new_for_testing(),
1322 )
1323 .await
1324 .unwrap();
1325
1326 epoch_stores.push(observer_epoch_store.into());
1327 randomness_managers.push(observer_manager);
1328 }
1329
1330 Self {
1331 network_config,
1332 epoch_stores,
1333 randomness_managers,
1334 tx_consensus,
1335 rx_consensus,
1336 num_validators,
1337 }
1338 }
1339
1340 fn consensus_adapter(&self, authority: AuthorityName) -> Arc<ConsensusAdapter> {
1341 let mut mock_consensus_client = MockConsensusClient::new();
1342 let tx_consensus = self.tx_consensus.clone();
1343 mock_consensus_client
1344 .expect_submit()
1345 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1346 tx_consensus.try_send(transactions.to_vec()).unwrap();
1347 true
1348 })
1349 .returning(|_, _| {
1350 Ok((
1351 Vec::new(),
1352 with_block_status(BlockStatus::Sequenced(BlockRef::MIN)),
1353 ))
1354 });
1355
1356 Arc::new(ConsensusAdapter::new(
1357 Arc::new(mock_consensus_client),
1358 CheckpointStore::new_for_tests(),
1359 authority,
1360 100_000,
1361 100_000,
1362 ConsensusAdapterMetrics::new_test(),
1363 Arc::new(tokio::sync::Notify::new()),
1364 ))
1365 }
1366
1367 async fn recover_validator_randomness_managers(&mut self) -> usize {
1368 let mut recovered_randomness_managers = Vec::new();
1369 for (i, validator) in self
1370 .network_config
1371 .validator_configs
1372 .iter()
1373 .enumerate()
1374 .take(self.num_validators)
1375 {
1376 let consensus_adapter = self.consensus_adapter(self.epoch_stores[i].name);
1377 recovered_randomness_managers.push(
1378 RandomnessManager::try_new(
1379 Arc::downgrade(&self.epoch_stores[i]),
1380 Box::new(consensus_adapter),
1381 sui_network::randomness::Handle::new_stub(),
1382 Some(validator.protocol_key_pair()),
1383 RandomnessRoundReceiverHandle::new_for_testing(),
1384 )
1385 .await
1386 .unwrap(),
1387 );
1388 }
1389 let recovered_count = recovered_randomness_managers.len();
1390 self.randomness_managers = recovered_randomness_managers;
1391 recovered_count
1392 }
1393
1394 async fn start_dkg_and_collect_messages(&mut self) -> Vec<VersionedDkgMessage> {
1396 let mut dkg_messages = Vec::new();
1397 for randomness_manager in self.randomness_managers.iter_mut() {
1398 randomness_manager.start_dkg().await.unwrap();
1399 }
1400 for _ in 0..self.num_validators {
1401 let mut dkg_message = self.rx_consensus.recv().await.unwrap();
1402 assert!(dkg_message.len() == 1);
1403 match dkg_message.remove(0).kind {
1404 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
1405 let msg: VersionedDkgMessage = bcs::from_bytes(&bytes)
1406 .expect("DKG message deserialization should not fail");
1407 dkg_messages.push(msg);
1408 }
1409 _ => panic!("wrong type of message sent"),
1410 }
1411 }
1412 dkg_messages
1413 }
1414
1415 async fn distribute_messages_and_advance(
1417 &mut self,
1418 dkg_messages: &[VersionedDkgMessage],
1419 advance_round: Round,
1420 ) {
1421 let indexed_messages: Vec<_> = dkg_messages.iter().cloned().enumerate().collect();
1422 self.distribute_indexed_messages_and_advance(&indexed_messages, advance_round)
1423 .await;
1424 }
1425
1426 async fn distribute_indexed_messages_and_advance(
1427 &mut self,
1428 dkg_messages: &[(usize, VersionedDkgMessage)],
1429 advance_round: Round,
1430 ) {
1431 for i in 0..self.randomness_managers.len() {
1432 let mut output = ConsensusCommitOutput::new(0);
1433 output.record_consensus_commit_stats(ExecutionIndicesWithStatsV2 {
1434 index: ExecutionIndices {
1435 last_committed_round: 0,
1436 ..Default::default()
1437 },
1438 ..Default::default()
1439 });
1440 for (j, dkg_message) in dkg_messages.iter().cloned() {
1441 self.randomness_managers[i]
1442 .add_message(&self.epoch_stores[j].name, dkg_message)
1443 .unwrap();
1444 }
1445 self.randomness_managers[i]
1446 .advance_dkg(&mut output, advance_round)
1447 .await
1448 .unwrap();
1449 let mut batch = self.epoch_stores[i].db_batch_for_test();
1450 output
1451 .write_to_batch(&self.epoch_stores[i], &mut batch)
1452 .unwrap();
1453 batch.write().unwrap();
1454 }
1455 }
1456
1457 async fn collect_confirmations(&mut self) -> Vec<VersionedDkgConfirmation> {
1458 let mut dkg_confirmations = Vec::new();
1459 for _ in 0..self.num_validators {
1460 let mut dkg_confirmation = self.rx_consensus.recv().await.unwrap();
1461 assert!(dkg_confirmation.len() == 1);
1462 match dkg_confirmation.remove(0).kind {
1463 ConsensusTransactionKind::RandomnessDkgConfirmation(_, bytes) => {
1464 let msg: VersionedDkgConfirmation = bcs::from_bytes(&bytes)
1465 .expect("DKG confirmation deserialization should not fail");
1466 dkg_confirmations.push(msg);
1467 }
1468 _ => panic!("wrong type of message sent"),
1469 }
1470 }
1471 dkg_confirmations
1472 }
1473
1474 async fn distribute_confirmations_and_advance(
1475 &mut self,
1476 dkg_confirmations: &[VersionedDkgConfirmation],
1477 advance_round: Round,
1478 ) {
1479 for i in 0..self.randomness_managers.len() {
1480 let mut output = ConsensusCommitOutput::new(0);
1481 output.record_consensus_commit_stats(ExecutionIndicesWithStatsV2 {
1482 index: ExecutionIndices {
1483 last_committed_round: 1,
1484 ..Default::default()
1485 },
1486 ..Default::default()
1487 });
1488 for (j, dkg_confirmation) in dkg_confirmations.iter().cloned().enumerate() {
1489 self.randomness_managers[i]
1490 .add_confirmation(&mut output, &self.epoch_stores[j].name, dkg_confirmation)
1491 .unwrap();
1492 }
1493 self.randomness_managers[i]
1494 .advance_dkg(&mut output, advance_round)
1495 .await
1496 .unwrap();
1497 let mut batch = self.epoch_stores[i].db_batch_for_test();
1498 output
1499 .write_to_batch(&self.epoch_stores[i], &mut batch)
1500 .unwrap();
1501 batch.write().unwrap();
1502 }
1503 }
1504
1505 async fn collect_and_distribute_confirmations(&mut self, advance_round: Round) {
1507 let dkg_confirmations = self.collect_confirmations().await;
1508 self.distribute_confirmations_and_advance(&dkg_confirmations, advance_round)
1509 .await;
1510 }
1511 }
1512
1513 #[tokio::test]
1514 async fn test_dkg() {
1515 telemetry_subscribers::init_for_testing();
1516
1517 let mut setup = DkgTestSetup::new(false).await;
1518 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1519 setup
1520 .distribute_messages_and_advance(&dkg_messages, 0)
1521 .await;
1522 setup.collect_and_distribute_confirmations(0).await;
1523
1524 for rm in &setup.randomness_managers {
1525 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1526 }
1527 }
1528
1529 #[tokio::test]
1530 async fn test_dkg_expiration() {
1531 telemetry_subscribers::init_for_testing();
1532
1533 let mut protocol_config =
1534 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1535 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1536 protocol_config.set_allow_dkg_completion_after_timeout_for_testing(false);
1537 let mut setup = DkgTestSetup::new_with_protocol_config(false, protocol_config).await;
1538 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1539
1540 assert_eq!(
1543 DkgStatus::Pending,
1544 setup.randomness_managers[0].dkg_status_for_commit_round(u64::MAX)
1545 );
1546
1547 setup
1549 .distribute_messages_and_advance(&dkg_messages, u64::MAX)
1550 .await;
1551
1552 for rm in &setup.randomness_managers {
1553 assert_eq!(DkgStatus::TimedOut, rm.dkg_status());
1554 }
1555
1556 let recovered_count = setup.recover_validator_randomness_managers().await;
1557 assert_eq!(setup.num_validators, recovered_count);
1558
1559 for rm in &setup.randomness_managers {
1563 assert_eq!(DkgStatus::TimedOut, rm.dkg_status());
1564 }
1565 }
1566
1567 #[tokio::test]
1568 async fn test_dkg_completes_after_timeout() {
1569 telemetry_subscribers::init_for_testing();
1570
1571 let mut setup = DkgTestSetup::new(false).await;
1572 let timeout: Round = setup.epoch_stores[0]
1573 .protocol_config()
1574 .random_beacon_dkg_timeout_round()
1575 .into();
1576 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1577 setup
1578 .distribute_messages_and_advance(&dkg_messages, timeout + 1)
1579 .await;
1580
1581 for rm in &mut setup.randomness_managers {
1582 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1583 assert_eq!(
1584 DkgStatus::TimedOut,
1585 rm.dkg_status_for_commit_round(timeout + 1)
1586 );
1587 }
1588
1589 setup
1590 .collect_and_distribute_confirmations(timeout + 2)
1591 .await;
1592 for rm in &setup.randomness_managers {
1593 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1594 }
1595 }
1596
1597 #[tokio::test]
1598 async fn test_dkg_timeout_then_restart_then_complete() {
1599 telemetry_subscribers::init_for_testing();
1600
1601 let mut setup = DkgTestSetup::new(false).await;
1602 let timeout: Round = setup.epoch_stores[0]
1603 .protocol_config()
1604 .random_beacon_dkg_timeout_round()
1605 .into();
1606 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1607 setup
1608 .distribute_messages_and_advance(&dkg_messages, timeout + 1)
1609 .await;
1610
1611 let recovered_count = setup.recover_validator_randomness_managers().await;
1612 assert_eq!(setup.num_validators, recovered_count);
1613 for rm in &mut setup.randomness_managers {
1614 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1615 assert!(rm.used_messages.initialized());
1616 assert_eq!(
1617 DkgStatus::TimedOut,
1618 rm.dkg_status_for_commit_round(timeout + 1)
1619 );
1620 }
1621
1622 setup
1623 .collect_and_distribute_confirmations(timeout + 2)
1624 .await;
1625 for rm in &setup.randomness_managers {
1626 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1627 }
1628 }
1629
1630 #[tokio::test]
1631 async fn test_dkg_confirmation_resent_after_restart() {
1632 telemetry_subscribers::init_for_testing();
1633
1634 let mut setup = DkgTestSetup::new(false).await;
1635 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1636 setup
1637 .distribute_messages_and_advance(&dkg_messages, 0)
1638 .await;
1639 let original_confirmations = setup.collect_confirmations().await;
1640 assert_eq!(setup.num_validators, original_confirmations.len());
1641
1642 let recovered_count = setup.recover_validator_randomness_managers().await;
1643 assert_eq!(setup.num_validators, recovered_count);
1644 for randomness_manager in &mut setup.randomness_managers {
1645 randomness_manager.start_dkg().await.unwrap();
1646 }
1647
1648 let resent_confirmations = setup.collect_confirmations().await;
1649 assert_eq!(setup.num_validators, resent_confirmations.len());
1650 assert_eq!(original_confirmations, resent_confirmations);
1652 setup
1653 .distribute_confirmations_and_advance(&resent_confirmations, 0)
1654 .await;
1655 for rm in &setup.randomness_managers {
1656 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1657 }
1658 }
1659
1660 #[tokio::test]
1661 async fn test_dkg_status_for_commit_round() {
1662 telemetry_subscribers::init_for_testing();
1663
1664 let mut setup = DkgTestSetup::new(false).await;
1665 let timeout: Round = setup.epoch_stores[0]
1666 .protocol_config()
1667 .random_beacon_dkg_timeout_round()
1668 .into();
1669 let rm = &mut setup.randomness_managers[0];
1670 assert_eq!(DkgStatus::Pending, rm.dkg_status_for_commit_round(timeout));
1671 assert_eq!(
1672 DkgStatus::TimedOut,
1673 rm.dkg_status_for_commit_round(timeout + 1)
1674 );
1675 rm.dkg_output.set(None).unwrap();
1676 assert_eq!(DkgStatus::TimedOut, rm.dkg_status_for_commit_round(0));
1677 }
1679
1680 #[tokio::test]
1681 async fn test_dkg_recovers_processed_messages_after_restart() {
1682 telemetry_subscribers::init_for_testing();
1683
1684 let mut setup = DkgTestSetup::new(false).await;
1685 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1686 let processed_before_restart = 1;
1687 assert!(processed_before_restart > 0);
1688 assert!(processed_before_restart < setup.num_validators);
1689 let pre_restart_messages: Vec<_> = dkg_messages
1690 .iter()
1691 .cloned()
1692 .enumerate()
1693 .take(processed_before_restart)
1694 .collect();
1695 let post_restart_messages: Vec<_> = dkg_messages
1696 .iter()
1697 .cloned()
1698 .enumerate()
1699 .skip(processed_before_restart)
1700 .collect();
1701
1702 setup
1703 .distribute_indexed_messages_and_advance(&pre_restart_messages, 0)
1704 .await;
1705
1706 for rm in &setup.randomness_managers {
1707 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1708 assert_eq!(processed_before_restart, rm.processed_messages.len());
1709 assert!(!rm.used_messages.initialized());
1710 }
1711
1712 let recovered_count = setup.recover_validator_randomness_managers().await;
1713 assert_eq!(setup.num_validators, recovered_count);
1714 for rm in &setup.randomness_managers {
1715 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1716 assert_eq!(processed_before_restart, rm.processed_messages.len());
1717 assert!(!rm.used_messages.initialized());
1718 }
1719
1720 setup
1721 .distribute_indexed_messages_and_advance(&post_restart_messages, 0)
1722 .await;
1723
1724 for rm in &setup.randomness_managers {
1725 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1726 assert_eq!(setup.num_validators, rm.processed_messages.len());
1727 assert!(rm.used_messages.initialized());
1728 }
1729
1730 setup.collect_and_distribute_confirmations(0).await;
1731
1732 for rm in &setup.randomness_managers {
1733 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1734 }
1735 }
1736
1737 #[tokio::test]
1740 async fn test_dkg_observer() {
1741 telemetry_subscribers::init_for_testing();
1742
1743 let mut setup = DkgTestSetup::new(true).await;
1744 let observer_idx = setup.randomness_managers.len() - 1;
1745
1746 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1747 setup
1748 .distribute_messages_and_advance(&dkg_messages, 0)
1749 .await;
1750
1751 for rm in &setup.randomness_managers {
1752 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1753 }
1754
1755 setup.collect_and_distribute_confirmations(0).await;
1756
1757 for rm in &setup.randomness_managers {
1758 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1759 }
1760
1761 let observer_output = setup.randomness_managers[observer_idx]
1763 .dkg_output()
1764 .expect("observer should have DKG output");
1765 let validator_output = setup.randomness_managers[0]
1766 .dkg_output()
1767 .expect("validator should have DKG output");
1768 assert_eq!(observer_output.vss_pk, validator_output.vss_pk);
1769 assert!(observer_output.shares.is_none());
1770
1771 for rm in &setup.randomness_managers[..setup.num_validators] {
1772 let output = rm.dkg_output().expect("validator should have DKG output");
1773 assert!(output.shares.is_some());
1774 }
1775 }
1776
1777 fn build_dkg_nodes(
1779 network_config: &sui_swarm_config::network_config::NetworkConfig,
1780 ) -> (nodes::Nodes<EncG>, u16) {
1781 let dkg_nodes: Vec<_> = network_config
1782 .validator_configs
1783 .iter()
1784 .enumerate()
1785 .map(|(i, v)| {
1786 let pk = bls12381::G2Element::from_byte_array(
1787 v.protocol_key_pair()
1788 .public()
1789 .as_bytes()
1790 .try_into()
1791 .expect("key length should match"),
1792 )
1793 .expect("should work to convert BLS key to G2Element");
1794 nodes::Node::<EncG> {
1795 id: i as u16,
1796 pk: fastcrypto_tbls::ecies_v1::PublicKey::from(pk),
1797 weight: 1,
1798 }
1799 })
1800 .collect();
1801 let num_nodes = dkg_nodes.len();
1802 let t = num_nodes.div_ceil(3) as u16;
1804 nodes::Nodes::new(dkg_nodes).map(|n| (n, t)).unwrap()
1805 }
1806
1807 #[test]
1808 fn test_dkg_role_try_new_party() {
1809 let network_config =
1810 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1811 .committee_size(NonZeroUsize::new(4).unwrap())
1812 .with_reference_gas_price(500)
1813 .build();
1814
1815 let (nodes, t) = build_dkg_nodes(&network_config);
1816 let random_oracle =
1817 fastcrypto_tbls::random_oracle::RandomOracle::new("test_dkg_role_party");
1818
1819 let role = DkgRole::try_new(
1820 Some(network_config.validator_configs[0].protocol_key_pair()),
1821 nodes,
1822 t,
1823 random_oracle,
1824 );
1825 assert!(role.is_some());
1826 assert!(role.unwrap().is_party());
1827 }
1828
1829 #[tokio::test]
1830 async fn test_randomness_manager_crash_recovery_v1() {
1831 telemetry_subscribers::init_for_testing();
1832
1833 let network_config =
1834 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1835 .committee_size(NonZeroUsize::new(4).unwrap())
1836 .with_reference_gas_price(500)
1837 .build();
1838
1839 let mut protocol_config =
1840 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1841 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1842
1843 let validator = &network_config.validator_configs[0];
1844 let state = TestAuthorityBuilder::new()
1845 .with_protocol_config(protocol_config.clone())
1846 .with_genesis_and_keypair(&network_config.genesis, validator.protocol_key_pair())
1847 .build()
1848 .await;
1849 let epoch_store = state.epoch_store_for_testing();
1850
1851 let dkg_nodes = nodes::Nodes::new(
1852 RandomnessManager::randomness_dkg_info_from_committee(epoch_store.committee())
1853 .into_iter()
1854 .map(|(id, _, pk, stake)| nodes::Node::<bls12381::G2Element> {
1855 id,
1856 pk,
1857 weight: stake.try_into().unwrap(),
1858 })
1859 .collect(),
1860 )
1861 .unwrap();
1862 let threshold = epoch_store
1863 .committee()
1864 .validity_threshold()
1865 .try_into()
1866 .unwrap();
1867 let party_id = epoch_store
1868 .committee()
1869 .authority_index(&epoch_store.name)
1870 .unwrap()
1871 .try_into()
1872 .unwrap();
1873 let expected_dkg_output = mocked_dkg::generate_mocked_output::<
1874 bls12381::G2Element,
1875 bls12381::G2Element,
1876 >(dkg_nodes, threshold, 0, party_id);
1877 let expected_dkg_output_bytes =
1878 bcs::to_bytes(&expected_dkg_output).expect("DKG output serialization should not fail");
1879 let expected_public_key = expected_dkg_output.vss_pk.c0();
1880
1881 let tables = epoch_store.tables();
1882 tables
1883 .dkg_output_v2
1884 .insert(&SINGLETON_KEY, &Some(expected_dkg_output))
1885 .unwrap();
1886 tables
1887 .randomness_next_round
1888 .insert(&SINGLETON_KEY, &RandomnessRound(3))
1889 .unwrap();
1890 tables
1891 .randomness_highest_completed_round
1892 .insert(&SINGLETON_KEY, &RandomnessRound(1))
1893 .unwrap();
1894
1895 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1896 Arc::new(MockConsensusClient::new()),
1897 CheckpointStore::new_for_tests(),
1898 epoch_store.name,
1899 100_000,
1900 100_000,
1901 ConsensusAdapterMetrics::new_test(),
1902 Arc::new(tokio::sync::Notify::new()),
1903 ));
1904 let recovered_receiver_handle = RandomnessRoundReceiverHandle::new_for_testing();
1905 assert!(recovered_receiver_handle.public_key_for_testing().is_none());
1906
1907 let recovered_randomness_manager = RandomnessManager::try_new(
1908 Arc::downgrade(&epoch_store),
1909 Box::new(consensus_adapter.clone()),
1910 sui_network::randomness::Handle::new_stub(),
1911 Some(validator.protocol_key_pair()),
1912 recovered_receiver_handle.clone(),
1913 )
1914 .await
1915 .unwrap();
1916
1917 assert_eq!(
1918 DkgStatus::Successful,
1919 recovered_randomness_manager.dkg_status()
1920 );
1921 let recovered_dkg_output = recovered_randomness_manager
1922 .dkg_output
1923 .get()
1924 .expect("recovered DKG output should be initialized")
1925 .as_ref()
1926 .expect("recovered DKG should be successful");
1927 assert_eq!(
1928 expected_dkg_output_bytes,
1929 bcs::to_bytes(recovered_dkg_output).expect("DKG output serialization should not fail")
1930 );
1931 assert_eq!(
1932 RandomnessRound(3),
1933 recovered_randomness_manager.next_randomness_round
1934 );
1935 assert_eq!(
1936 Some(RandomnessRound(1)),
1937 *recovered_randomness_manager.highest_completed_round.lock()
1938 );
1939 let recovered_public_key = recovered_receiver_handle
1940 .public_key_for_testing()
1941 .expect("public key should be restored on recovery");
1942 assert_eq!(
1943 expected_public_key.to_byte_array(),
1944 recovered_public_key.to_byte_array()
1945 );
1946 }
1947
1948 #[test]
1949 fn test_dkg_role_try_new_observer() {
1950 let network_config =
1951 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1952 .committee_size(NonZeroUsize::new(4).unwrap())
1953 .with_reference_gas_price(500)
1954 .build();
1955
1956 let (nodes, t) = build_dkg_nodes(&network_config);
1957 let random_oracle =
1958 fastcrypto_tbls::random_oracle::RandomOracle::new("test_dkg_role_observer");
1959
1960 let role = DkgRole::try_new(None, nodes, t, random_oracle);
1961 assert!(role.is_some());
1962 assert!(role.unwrap().is_observer());
1963 }
1964}