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_if;
22use sui_network::randomness;
23use sui_types::base_types::AuthorityName;
24use sui_types::committee::{Committee, EpochId, StakeUnit};
25use sui_types::crypto::{AuthorityKeyPair, RandomnessRound};
26use sui_types::error::{SuiErrorKind, SuiResult};
27use sui_types::messages_consensus::{
28 ConsensusTransaction, Round, TimestampMs, VersionedDkgConfirmation, VersionedDkgMessage,
29};
30use sui_types::sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait;
31use tokio::sync::OnceCell;
32use tokio::task::JoinHandle;
33use tracing::{debug, error, info, warn};
34use typed_store::Map;
35
36use crate::authority::authority_per_epoch_store::{
37 AuthorityPerEpochStore, consensus_quarantine::ConsensusCommitOutput,
38};
39use crate::authority::epoch_start_configuration::EpochStartConfigTrait;
40use crate::consensus_adapter::SubmitToConsensus;
41use crate::randomness_round_receiver::RandomnessRoundReceiverHandle;
42
43type PkG = bls12381::G2Element;
44type EncG = bls12381::G2Element;
45
46pub const SINGLETON_KEY: u64 = 0;
47
48#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
51#[allow(clippy::large_enum_variant)]
52pub enum VersionedProcessedMessage {
53 V0(), V1(dkg_v1::ProcessedMessage<PkG, EncG>),
55}
56
57impl VersionedProcessedMessage {
58 pub fn sender(&self) -> PartyId {
59 match self {
60 VersionedProcessedMessage::V0() => {
61 panic!("BUG: invalid VersionedProcessedMessage version V0")
62 }
63 VersionedProcessedMessage::V1(msg) => msg.message.sender,
64 }
65 }
66
67 pub fn unwrap_v1(self) -> dkg_v1::ProcessedMessage<PkG, EncG> {
68 if let VersionedProcessedMessage::V1(msg) = self {
69 msg
70 } else {
71 panic!("BUG: expected message version is 1")
72 }
73 }
74
75 pub fn as_v1(&self) -> Option<&dkg_v1::ProcessedMessage<PkG, EncG>> {
76 if let VersionedProcessedMessage::V1(msg) = self {
77 Some(msg)
78 } else {
79 None
80 }
81 }
82}
83
84#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
85pub enum VersionedUsedProcessedMessages {
86 V0(), V1(dkg_v1::UsedProcessedMessages<PkG, EncG>),
88}
89
90impl VersionedUsedProcessedMessages {
91 pub fn as_v1(&self) -> Option<&dkg_v1::UsedProcessedMessages<PkG, EncG>> {
92 if let VersionedUsedProcessedMessages::V1(msg) = self {
93 Some(msg)
94 } else {
95 None
96 }
97 }
98}
99
100enum DkgRole {
102 Party(dkg_v1::Party<PkG, EncG>),
103 Observer(dkg_v1::Observer<PkG, EncG>),
104}
105
106impl DkgRole {
107 fn try_new(
116 authority_key_pair: Option<&AuthorityKeyPair>,
117 nodes: nodes::Nodes<EncG>,
118 t: u16,
119 random_oracle: fastcrypto_tbls::random_oracle::RandomOracle,
120 ) -> Option<Self> {
121 let total_weight = nodes.total_weight();
122 let num_nodes = nodes.num_nodes();
123
124 if let Some(authority_key_pair) = authority_key_pair {
125 let randomness_private_key = bls12381::Scalar::from_byte_array(
126 authority_key_pair
127 .copy()
128 .private()
129 .as_bytes()
130 .try_into()
131 .expect("key length should match"),
132 )
133 .expect("should work to convert BLS key to Scalar");
134 let party = match dkg_v1::Party::<PkG, EncG>::new(
135 fastcrypto_tbls::ecies_v1::PrivateKey::<bls12381::G2Element>::from(
136 randomness_private_key,
137 ),
138 nodes,
139 t,
140 random_oracle,
141 &mut rand::thread_rng(),
142 ) {
143 Ok(party) => party,
144 Err(err) => {
145 debug_fatal!("random beacon: error while initializing Party: {err:?}");
146 return None;
147 }
148 };
149 let name: AuthorityName = authority_key_pair.public().into();
150 info!(
151 "random beacon: Party initialized with authority={name}, total_weight={total_weight}, t={t}, num_nodes={num_nodes}",
152 );
153 Some(DkgRole::Party(party))
154 } else {
155 let observer = match dkg_v1::Observer::<PkG, EncG>::new(nodes, t, random_oracle) {
156 Ok(observer) => observer,
157 Err(err) => {
158 debug_fatal!("random beacon: error while initializing Observer: {err:?}");
159 return None;
160 }
161 };
162 info!(
163 "random beacon: Observer initialized with total_weight={total_weight}, t={t}, num_nodes={num_nodes}",
164 );
165 Some(DkgRole::Observer(observer))
166 }
167 }
168
169 fn is_party(&self) -> bool {
170 matches!(self, DkgRole::Party(_))
171 }
172
173 fn is_observer(&self) -> bool {
174 matches!(self, DkgRole::Observer(_))
175 }
176
177 fn process_message(
179 &self,
180 message: VersionedDkgMessage,
181 ) -> FastCryptoResult<VersionedProcessedMessage> {
182 match self {
183 DkgRole::Party(party) => {
184 let processed =
185 party.process_message(message.unwrap_v1(), &mut rand::thread_rng())?;
186 Ok(VersionedProcessedMessage::V1(processed))
187 }
188 DkgRole::Observer(observer) => {
189 let raw_msg = message.unwrap_v1();
190 observer.process_message(raw_msg.clone())?;
191 Ok(VersionedProcessedMessage::V1(dkg_v1::ProcessedMessage {
192 message: raw_msg,
193 shares: vec![],
194 complaint: None,
195 }))
196 }
197 }
198 }
199
200 fn merge_messages(
204 &self,
205 messages: Vec<VersionedProcessedMessage>,
206 ) -> FastCryptoResult<(
207 Option<VersionedDkgConfirmation>,
208 VersionedUsedProcessedMessages,
209 )> {
210 match self {
211 DkgRole::Party(party) => {
212 let (conf, msgs) = party.merge(
213 &messages
214 .into_iter()
215 .map(|vm| vm.unwrap_v1())
216 .collect::<Vec<_>>(),
217 )?;
218 Ok((
219 Some(VersionedDkgConfirmation::V1(conf)),
220 VersionedUsedProcessedMessages::V1(msgs),
221 ))
222 }
223 DkgRole::Observer(observer) => {
224 let raw_messages: Vec<_> = messages
225 .into_iter()
226 .map(|pm| pm.unwrap_v1().message)
227 .collect();
228 let used = observer.merge(raw_messages)?;
229 Ok((
230 None,
231 VersionedUsedProcessedMessages::V1(dkg_v1::UsedProcessedMessages(
232 used.into_iter()
233 .map(|m| dkg_v1::ProcessedMessage {
234 message: m,
235 shares: vec![],
236 complaint: None,
237 })
238 .collect(),
239 )),
240 ))
241 }
242 }
243 }
244
245 fn complete_dkg<'a>(
249 &self,
250 used_messages: &VersionedUsedProcessedMessages,
251 confirmations: impl Iterator<Item = &'a VersionedDkgConfirmation>,
252 ) -> FastCryptoResult<Output<PkG, EncG>> {
253 match self {
254 DkgRole::Party(party) => {
255 let rng = &mut StdRng::from_rng(OsRng).expect("RNG construction should not fail");
256 let msg = used_messages
257 .as_v1()
258 .expect("expected V1 used processed messages");
259 party.complete(
260 msg,
261 &confirmations
262 .map(|vm| vm.as_v1().expect("expected V1 confirmation"))
263 .cloned()
264 .collect::<Vec<_>>(),
265 rng,
266 )
267 }
268 DkgRole::Observer(observer) => {
269 let raw_messages: Vec<_> = used_messages
270 .as_v1()
271 .expect("expected V1 used processed messages")
272 .0
273 .iter()
274 .map(|pm| pm.message.clone())
275 .collect();
276 let confirmations: Vec<_> = confirmations
277 .map(|c| c.as_v1().expect("expected V1 confirmation").clone())
278 .collect();
279 observer.complete(&raw_messages, &confirmations)
280 }
281 }
282 }
283}
284
285pub struct RandomnessManager {
305 epoch_store: Weak<AuthorityPerEpochStore>,
306 epoch: EpochId,
307 consensus_adapter: Box<dyn SubmitToConsensus>,
308 network_handle: randomness::Handle,
309 authority_info: HashMap<AuthorityName, (PeerId, PartyId)>,
310
311 dkg_start_time: OnceCell<Instant>,
313 role: Arc<DkgRole>,
314 enqueued_messages: BTreeMap<PartyId, JoinHandle<Option<VersionedProcessedMessage>>>,
315 processed_messages: BTreeMap<PartyId, VersionedProcessedMessage>,
316 used_messages: OnceCell<VersionedUsedProcessedMessages>,
317 confirmations: BTreeMap<PartyId, VersionedDkgConfirmation>,
318 dkg_output: OnceCell<Option<dkg_v1::Output<PkG, EncG>>>,
319
320 next_randomness_round: RandomnessRound,
322 highest_completed_round: Arc<Mutex<Option<RandomnessRound>>>,
323
324 randomness_receiver_handle: Arc<RandomnessRoundReceiverHandle>,
325}
326
327impl RandomnessManager {
328 pub async fn try_new(
330 epoch_store_weak: Weak<AuthorityPerEpochStore>,
331 consensus_adapter: Box<dyn SubmitToConsensus>,
332 network_handle: randomness::Handle,
333 authority_key_pair: Option<&AuthorityKeyPair>,
334 randomness_receiver_handle: Arc<RandomnessRoundReceiverHandle>,
335 ) -> Option<Self> {
336 let epoch_store = match epoch_store_weak.upgrade() {
337 Some(epoch_store) => epoch_store,
338 None => {
339 error!(
340 "could not construct RandomnessManager: AuthorityPerEpochStore already gone"
341 );
342 return None;
343 }
344 };
345 let tables = match epoch_store.tables() {
346 Ok(tables) => tables,
347 Err(_) => {
348 error!(
349 "could not construct RandomnessManager: AuthorityPerEpochStore tables already gone"
350 );
351 return None;
352 }
353 };
354 let protocol_config = epoch_store.protocol_config();
355 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(0);
356 epoch_store
357 .metrics
358 .epoch_random_beacon_dkg_num_shares
359 .set(0);
360
361 let committee = epoch_store.committee();
362 let info = RandomnessManager::randomness_dkg_info_from_committee(committee);
363 if tracing::enabled!(tracing::Level::DEBUG) {
364 for (id, name, pk, stake) in info.iter().filter(|(id, _, _, _)| *id < 3) {
366 let pk_bytes = pk.as_element().to_byte_array();
367 debug!(
368 "random beacon: DKG info: id={id}, stake={stake}, name={name}, pk={pk_bytes:x?}"
369 );
370 }
371 }
372 let authority_ids: HashMap<_, _> =
373 info.iter().map(|(id, name, _, _)| (*name, *id)).collect();
374 let authority_peer_ids = epoch_store
375 .epoch_start_config()
376 .epoch_start_state()
377 .get_authority_names_to_peer_ids();
378 let authority_info = authority_ids
379 .into_iter()
380 .map(|(name, id)| {
381 let peer_id = *authority_peer_ids
382 .get(&name)
383 .expect("authority name should be in peer_ids");
384 (name, (peer_id, id))
385 })
386 .collect();
387 let nodes = info
388 .iter()
389 .map(|(id, _, pk, stake)| nodes::Node::<EncG> {
390 id: *id,
391 pk: pk.clone(),
392 weight: (*stake).try_into().expect("stake should fit in u16"),
393 })
394 .collect();
395 let (nodes, t) = match nodes::Nodes::new_reduced(
396 nodes,
397 committee
398 .validity_threshold()
399 .try_into()
400 .expect("validity threshold should fit in u16"),
401 protocol_config.random_beacon_reduction_allowed_delta(),
402 protocol_config
403 .random_beacon_reduction_lower_bound()
404 .try_into()
405 .expect("should fit u16"),
406 ) {
407 Ok((nodes, t)) => (nodes, t),
408 Err(err) => {
409 error!("random beacon: error while initializing Nodes: {err:?}");
410 return None;
411 }
412 };
413 let random_oracle = fastcrypto_tbls::random_oracle::RandomOracle::new(&format!(
414 "dkg {} {}",
415 Hex::encode(epoch_store.get_chain_identifier().as_bytes()),
416 committee.epoch()
417 ));
418
419 let role = Arc::new(DkgRole::try_new(
420 authority_key_pair,
421 nodes,
422 t,
423 random_oracle,
424 )?);
425
426 let highest_completed_round = tables
428 .randomness_highest_completed_round
429 .get(&SINGLETON_KEY)
430 .expect("typed_store should not fail");
431 let mut rm = RandomnessManager {
432 epoch_store: epoch_store_weak,
433 epoch: committee.epoch(),
434 consensus_adapter,
435 network_handle: network_handle.clone(),
436 authority_info,
437 dkg_start_time: OnceCell::new(),
438 role,
439 enqueued_messages: BTreeMap::new(),
440 processed_messages: BTreeMap::new(),
441 used_messages: OnceCell::new(),
442 confirmations: BTreeMap::new(),
443 dkg_output: OnceCell::new(),
444 next_randomness_round: RandomnessRound(0),
445 highest_completed_round: Arc::new(Mutex::new(highest_completed_round)),
446 randomness_receiver_handle,
447 };
448 let dkg_output = tables
449 .dkg_output_v2
450 .get(&SINGLETON_KEY)
451 .expect("typed_store should not fail");
452 match dkg_output {
453 Some(Some(dkg_output)) => {
454 info!(
455 "random beacon: loaded existing DKG output for epoch {}",
456 committee.epoch()
457 );
458 epoch_store
459 .metrics
460 .epoch_random_beacon_dkg_num_shares
461 .set(dkg_output.shares.as_ref().map_or(0, |shares| shares.len()) as i64);
462 rm.dkg_output
463 .set(Some(dkg_output.clone()))
464 .expect("setting new OnceCell should succeed");
465 rm.randomness_receiver_handle
468 .set_public_key(dkg_output.vss_pk.c0());
469
470 if let DkgRole::Party(party) = rm.role.as_ref() {
471 network_handle.update_epoch(
472 committee.epoch(),
473 rm.authority_info.clone(),
474 dkg_output,
475 party.t(),
476 highest_completed_round,
477 );
478 }
479 }
480 Some(None) => {
481 error!(
485 "random beacon: loaded failed DKG for epoch {}. Randomness disabled for this epoch. All randomness-using transactions will fail.",
486 committee.epoch()
487 );
488 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
489 rm.dkg_output
490 .set(None)
491 .expect("setting new OnceCell should succeed");
492 }
493 None => {
494 info!(
495 "random beacon: no existing DKG output found for epoch {}",
496 committee.epoch()
497 );
498
499 assert!(
501 epoch_store.protocol_config().dkg_version() > 0,
502 "BUG: DKG version 0 is deprecated"
503 );
504 rm.processed_messages.extend(
505 tables
506 .dkg_processed_messages_v2
507 .safe_iter()
508 .map(|result| result.expect("typed_store should not fail")),
509 );
510 if let Some(used_messages) = tables
511 .dkg_used_messages_v2
512 .get(&SINGLETON_KEY)
513 .expect("typed_store should not fail")
514 {
515 rm.used_messages
516 .set(used_messages.clone())
517 .expect("setting new OnceCell should succeed");
518 }
519 rm.confirmations.extend(
520 tables
521 .dkg_confirmations_v2
522 .safe_iter()
523 .map(|result| result.expect("typed_store should not fail")),
524 );
525 }
526 }
527
528 rm.next_randomness_round = tables
533 .randomness_next_round
534 .get(&SINGLETON_KEY)
535 .expect("typed_store should not fail")
536 .unwrap_or(RandomnessRound(0));
537 info!(
538 "random beacon: starting from next_randomness_round={}",
539 rm.next_randomness_round.0
540 );
541
542 if rm.role.is_party() {
544 let first_incomplete_round = highest_completed_round
545 .map(|r| r + 1)
546 .unwrap_or(RandomnessRound(0));
547 if first_incomplete_round < rm.next_randomness_round {
548 info!(
549 "random beacon: resuming generation for randomness rounds from {} to {}",
550 first_incomplete_round,
551 rm.next_randomness_round - 1,
552 );
553 for r in first_incomplete_round.0..rm.next_randomness_round.0 {
554 network_handle.send_partial_signatures(committee.epoch(), RandomnessRound(r));
555 }
556 }
557 }
558
559 Some(rm)
560 }
561
562 pub async fn start_dkg(&mut self) -> SuiResult {
565 let party = match self.role.as_ref() {
566 DkgRole::Observer(_) => {
567 info!("random beacon: observer started observing DKG");
568 return Ok(());
569 }
570 DkgRole::Party(party) => party,
571 };
572
573 if self.used_messages.initialized() || self.dkg_output.initialized() {
574 return Ok(());
576 }
577
578 let _ = self.dkg_start_time.set(Instant::now());
579
580 let epoch_store = self.epoch_store()?;
581 let dkg_version = epoch_store.protocol_config().dkg_version();
582 info!("random beacon: starting DKG, version {dkg_version}");
583
584 let msg = match VersionedDkgMessage::create(dkg_version, party) {
585 Ok(msg) => msg,
586 Err(FastCryptoError::IgnoredMessage) => {
587 info!(
588 "random beacon: no DKG Message for party id={} (zero weight)",
589 party.id
590 );
591 return Ok(());
592 }
593 Err(e) => {
594 error!("random beacon: error while creating a DKG Message: {e:?}");
595 return Ok(());
596 }
597 };
598
599 info!("random beacon: created {msg:?} with dkg version {dkg_version}");
600 let transaction = ConsensusTransaction::new_randomness_dkg_message(epoch_store.name, &msg);
601
602 #[allow(unused_mut)]
603 let mut fail_point_skip_sending = false;
604 fail_point_if!("rb-dkg", || {
605 fail_point_skip_sending = true;
607 });
608 if !fail_point_skip_sending {
609 self.consensus_adapter
610 .submit_to_consensus(&[transaction], &epoch_store)?;
611 }
612
613 epoch_store
614 .metrics
615 .epoch_random_beacon_dkg_message_time_ms
616 .set(
617 self.dkg_start_time
618 .get()
619 .unwrap() .elapsed()
621 .as_millis() as i64,
622 );
623 Ok(())
624 }
625
626 pub(crate) async fn advance_dkg(
629 &mut self,
630 consensus_output: &mut ConsensusCommitOutput,
631 round: Round,
632 ) -> SuiResult {
633 let epoch_store = self.epoch_store()?;
634
635 self.try_merge_messages(consensus_output, &epoch_store)
636 .await?;
637 self.try_complete_dkg(consensus_output, round, &epoch_store)?;
638
639 if !self.dkg_output.initialized()
641 && round
642 > epoch_store
643 .protocol_config()
644 .random_beacon_dkg_timeout_round()
645 .into()
646 {
647 error!(
648 "random beacon: DKG timed out. Randomness disabled for this epoch. All randomness-using transactions will fail."
649 );
650 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
651 self.dkg_output
652 .set(None)
653 .expect("checked above that `dkg_output` is uninitialized");
654 consensus_output.set_dkg_output(None);
655 }
656
657 Ok(())
658 }
659
660 async fn try_merge_messages(
663 &mut self,
664 consensus_output: &mut ConsensusCommitOutput,
665 epoch_store: &Arc<AuthorityPerEpochStore>,
666 ) -> SuiResult {
667 if self.dkg_output.initialized() || self.used_messages.initialized() {
668 return Ok(());
669 }
670
671 let mut handles: FuturesUnordered<_> = std::mem::take(&mut self.enqueued_messages)
673 .into_values()
674 .collect();
675 while let Some(res) = handles.next().await {
676 if let Ok(Some(processed)) = res {
677 self.processed_messages
678 .insert(processed.sender(), processed.clone());
679 consensus_output.insert_dkg_processed_message(processed);
680 }
681 }
682
683 let messages: Vec<_> = self.processed_messages.values().cloned().collect();
684
685 match self.role.merge_messages(messages) {
686 Ok((conf, used_msgs)) => {
687 if let Some(conf) = &conf {
688 info!(
689 "random beacon: sending DKG Confirmation with {} complaints",
690 conf.num_of_complaints()
691 );
692 } else {
693 info!(
694 "random beacon: observer merged {} DKG messages",
695 used_msgs
696 .as_v1()
697 .expect("expected V1 used processed messages")
698 .0
699 .len()
700 );
701 }
702 if self.used_messages.set(used_msgs.clone()).is_err() {
703 error!("BUG: used_messages should only ever be set once");
704 }
705 consensus_output.insert_dkg_used_messages(used_msgs);
706
707 if let Some(conf) = conf {
708 let transaction = ConsensusTransaction::new_randomness_dkg_confirmation(
709 epoch_store.name,
710 &conf,
711 );
712
713 #[allow(unused_mut)]
714 let mut fail_point_skip_sending = false;
715 fail_point_if!("rb-dkg", || {
716 fail_point_skip_sending = true;
718 });
719 if !fail_point_skip_sending {
720 self.consensus_adapter
721 .submit_to_consensus(&[transaction], epoch_store)?;
722 }
723
724 let elapsed = self.dkg_start_time.get().map(|t| t.elapsed().as_millis());
725 if let Some(elapsed) = elapsed {
726 epoch_store
727 .metrics
728 .epoch_random_beacon_dkg_confirmation_time_ms
729 .set(elapsed as i64);
730 }
731 }
732 }
733 Err(FastCryptoError::NotEnoughInputs) => (), Err(e) => debug!("random beacon: error while merging DKG Messages: {e:?}"),
735 }
736
737 Ok(())
738 }
739
740 fn try_complete_dkg(
744 &mut self,
745 consensus_output: &mut ConsensusCommitOutput,
746 round: Round,
747 epoch_store: &Arc<AuthorityPerEpochStore>,
748 ) -> SuiResult {
749 if !self.dkg_output.initialized() && self.used_messages.initialized() {
750 let used_messages = self
751 .used_messages
752 .get()
753 .expect("checked above that `used_messages` is initialized");
754 let complete_result = self
755 .role
756 .complete_dkg(used_messages, self.confirmations.values());
757
758 let epoch = epoch_store.committee().epoch();
759 let num_confirmations = self.confirmations.len();
760 let num_messages = self.processed_messages.len();
761
762 match complete_result {
763 Ok(output) => {
764 self.dkg_output
766 .set(Some(output.clone()))
767 .expect("checked above that `dkg_output` is uninitialized");
768 consensus_output.set_dkg_output(Some(output.clone()));
769
770 self.randomness_receiver_handle
771 .set_public_key(output.vss_pk.c0());
772
773 let epoch_elapsed = epoch_store.epoch_open_time.elapsed().as_millis();
774 epoch_store
775 .metrics
776 .epoch_random_beacon_dkg_epoch_start_completion_time_ms
777 .set(epoch_elapsed as i64);
778 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(0);
779
780 match self.role.as_ref() {
781 DkgRole::Party(party) => {
782 let num_shares =
783 output.shares.as_ref().map_or(0, |shares| shares.len());
784 let elapsed =
785 self.dkg_start_time.get().map(|t| t.elapsed().as_millis());
786 info!(
787 "random beacon: DKG complete for Party epoch={epoch} commit_round={round} \
788 num_messages={num_messages} num_confirmations={num_confirmations} \
789 num_shares={num_shares} epoch_elapsed_ms={epoch_elapsed} dkg_elapsed_ms={elapsed:?}"
790 );
791 epoch_store
792 .metrics
793 .epoch_random_beacon_dkg_num_shares
794 .set(num_shares as i64);
795
796 if let Some(elapsed) = elapsed {
797 epoch_store
798 .metrics
799 .epoch_random_beacon_dkg_completion_time_ms
800 .set(elapsed as i64);
801 }
802
803 self.network_handle.update_epoch(
804 epoch_store.committee().epoch(),
805 self.authority_info.clone(),
806 output,
807 party.t(),
808 None,
809 );
810 }
811 DkgRole::Observer(_) => {
812 info!(
813 "random beacon: DKG complete for Observer epoch={epoch} commit_round={round} \
814 num_messages={num_messages} num_confirmations={num_confirmations} \
815 epoch_elapsed_ms={epoch_elapsed}"
816 );
817 }
818 }
819 }
820 Err(FastCryptoError::NotEnoughInputs) => (), Err(e) => error!("random beacon: error while processing DKG Confirmations: {e:?}"),
822 }
823 }
824
825 Ok(())
826 }
827
828 pub fn add_message(
830 &mut self,
831 authority: &AuthorityName,
832 msg: VersionedDkgMessage,
833 ) -> SuiResult {
834 let dkg_version = self.epoch_store()?.protocol_config().dkg_version();
837 if !msg.is_valid_version(dkg_version) {
838 warn!("ignoring DKG Message from authority {authority:?} with unsupported version");
839 return Ok(());
840 }
841
842 if self.used_messages.initialized() || self.dkg_output.initialized() {
843 return Ok(());
845 }
846 let Some((_, party_id)) = self.authority_info.get(authority) else {
847 debug_fatal!(
848 "random beacon: received DKG Message from unknown authority: {authority:?}"
849 );
850 return Ok(());
851 };
852 if *party_id != msg.sender() {
853 warn!(
854 "ignoring equivocating DKG Message from authority {authority:?} pretending to be PartyId {party_id:?}"
855 );
856 return Ok(());
857 }
858 if self.enqueued_messages.contains_key(&msg.sender())
859 || self.processed_messages.contains_key(&msg.sender())
860 {
861 info!("ignoring duplicate DKG Message from authority {authority:?}");
862 return Ok(());
863 }
864
865 let sender = msg.sender();
866 let role = self.role.clone();
867 let handle = tokio::task::spawn_blocking(move || match role.process_message(msg) {
869 Ok(processed) => Some(processed),
870 Err(err) => {
871 debug!("random beacon: error while processing DKG Message: {err:?}");
872 None
873 }
874 });
875 self.enqueued_messages.insert(sender, handle);
876 Ok(())
877 }
878
879 pub(crate) fn add_confirmation(
881 &mut self,
882 output: &mut ConsensusCommitOutput,
883 authority: &AuthorityName,
884 conf: VersionedDkgConfirmation,
885 ) -> SuiResult {
886 let dkg_version = self.epoch_store()?.protocol_config().dkg_version();
889 if !conf.is_valid_version(dkg_version) {
890 warn!(
891 "ignoring DKG Confirmation from authority {authority:?} with unsupported version"
892 );
893 return Ok(());
894 }
895
896 if self.dkg_output.initialized() {
897 return Ok(());
899 }
900 let Some((_, party_id)) = self.authority_info.get(authority) else {
901 error!(
902 "random beacon: received DKG Confirmation from unknown authority: {authority:?}"
903 );
904 return Ok(());
905 };
906 if *party_id != conf.sender() {
907 warn!(
908 "ignoring equivocating DKG Confirmation from authority {authority:?} pretending to be PartyId {party_id:?}"
909 );
910 return Ok(());
911 }
912 self.confirmations.insert(conf.sender(), conf.clone());
913 output.insert_dkg_confirmation(conf);
914 Ok(())
915 }
916
917 pub(crate) fn reserve_next_randomness(
922 &mut self,
923 commit_timestamp: TimestampMs,
924 output: &mut ConsensusCommitOutput,
925 ) -> SuiResult<Option<RandomnessRound>> {
926 let epoch_store = self.epoch_store()?;
927
928 let last_round_timestamp = epoch_store
929 .get_randomness_last_round_timestamp()
930 .expect("read should not fail");
931
932 if let Some(last_round_timestamp) = last_round_timestamp
933 && commit_timestamp - last_round_timestamp
934 < epoch_store
935 .protocol_config()
936 .random_beacon_min_round_interval_ms()
937 {
938 return Ok(None);
939 }
940
941 let randomness_round = self.next_randomness_round;
942 self.next_randomness_round = self
943 .next_randomness_round
944 .checked_add(1)
945 .expect("RandomnessRound should not overflow");
946
947 output.reserve_next_randomness_round(self.next_randomness_round, commit_timestamp);
948
949 Ok(Some(randomness_round))
950 }
951
952 pub fn generate_randomness(&self, epoch: EpochId, randomness_round: RandomnessRound) {
954 if self.role.is_party() {
955 self.network_handle
956 .send_partial_signatures(epoch, randomness_round);
957 }
958 }
959
960 pub fn dkg_status(&self) -> DkgStatus {
961 match self.dkg_output.get() {
962 Some(Some(_)) => DkgStatus::Successful,
963 Some(None) => DkgStatus::Failed,
964 None => DkgStatus::Pending,
965 }
966 }
967
968 pub fn reporter(&self) -> Option<RandomnessReporter> {
971 if self.role.is_observer() {
972 return None;
973 }
974 Some(RandomnessReporter {
975 epoch_store: self.epoch_store.clone(),
976 epoch: self.epoch,
977 network_handle: self.network_handle.clone(),
978 highest_completed_round: self.highest_completed_round.clone(),
979 })
980 }
981
982 #[cfg(test)]
983 fn dkg_output(&self) -> Option<&dkg_v1::Output<PkG, EncG>> {
984 self.dkg_output.get().and_then(|o| o.as_ref())
985 }
986
987 fn epoch_store(&self) -> SuiResult<Arc<AuthorityPerEpochStore>> {
988 self.epoch_store
989 .upgrade()
990 .ok_or(SuiErrorKind::EpochEnded(self.epoch).into())
991 }
992
993 fn randomness_dkg_info_from_committee(
994 committee: &Committee,
995 ) -> Vec<(
996 u16,
997 AuthorityName,
998 fastcrypto_tbls::ecies_v1::PublicKey<bls12381::G2Element>,
999 StakeUnit,
1000 )> {
1001 committee
1002 .members()
1003 .map(|(name, stake)| {
1004 let index: u16 = committee
1005 .authority_index(name)
1006 .expect("lookup of known committee member should succeed")
1007 .try_into()
1008 .expect("authority index should fit in u16");
1009 let pk = bls12381::G2Element::from_byte_array(
1010 committee
1011 .public_key(name)
1012 .expect("lookup of known committee member should succeed")
1013 .as_bytes()
1014 .try_into()
1015 .expect("key length should match"),
1016 )
1017 .expect("should work to convert BLS key to G2Element");
1018 (
1019 index,
1020 *name,
1021 fastcrypto_tbls::ecies_v1::PublicKey::from(pk),
1022 *stake,
1023 )
1024 })
1025 .collect()
1026 }
1027}
1028
1029#[derive(Clone)]
1031pub struct RandomnessReporter {
1032 epoch_store: Weak<AuthorityPerEpochStore>,
1033 epoch: EpochId,
1034 network_handle: randomness::Handle,
1035 highest_completed_round: Arc<Mutex<Option<RandomnessRound>>>,
1036}
1037
1038impl RandomnessReporter {
1039 pub fn notify_randomness_in_checkpoint(&self, round: RandomnessRound) -> SuiResult {
1043 let epoch_store = self
1044 .epoch_store
1045 .upgrade()
1046 .ok_or(SuiErrorKind::EpochEnded(self.epoch))?;
1047 let mut highest_completed_round = self.highest_completed_round.lock();
1048 if Some(round) > *highest_completed_round {
1049 *highest_completed_round = Some(round);
1050 epoch_store
1051 .tables()?
1052 .randomness_highest_completed_round
1053 .insert(&SINGLETON_KEY, &round)?;
1054 self.network_handle
1055 .complete_round(epoch_store.committee().epoch(), round);
1056 }
1057 Ok(())
1058 }
1059}
1060
1061#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1062pub enum DkgStatus {
1063 Pending,
1064 Failed,
1065 Successful,
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070 use crate::{
1071 authority::{
1072 authority_per_epoch_store::{ExecutionIndices, ExecutionIndicesWithStatsV2},
1073 test_authority_builder::TestAuthorityBuilder,
1074 },
1075 checkpoints::CheckpointStore,
1076 consensus_adapter::{ConsensusAdapter, ConsensusAdapterMetrics, MockConsensusClient},
1077 epoch::randomness::*,
1078 mock_consensus::with_block_status,
1079 randomness_round_receiver::RandomnessRoundReceiverHandle,
1080 };
1081 use consensus_core::BlockStatus;
1082 use consensus_types::block::BlockRef;
1083 use fastcrypto::groups::bls12381;
1084 use fastcrypto::serde_helpers::ToFromByteArray;
1085 use fastcrypto_tbls::{mocked_dkg, nodes};
1086 use std::num::NonZeroUsize;
1087 use sui_protocol_config::ProtocolConfig;
1088 use sui_protocol_config::{Chain, ProtocolVersion};
1089 use sui_types::{base_types::AuthorityName, messages_consensus::ConsensusTransactionKind};
1090 use tokio::sync::mpsc;
1091 use typed_store::Map;
1092
1093 use arc_swap::Guard;
1094
1095 struct DkgTestSetup {
1098 network_config: sui_swarm_config::network_config::NetworkConfig,
1099 epoch_stores: Vec<Guard<Arc<AuthorityPerEpochStore>>>,
1100 randomness_managers: Vec<RandomnessManager>,
1101 tx_consensus: mpsc::Sender<Vec<ConsensusTransaction>>,
1102 rx_consensus: mpsc::Receiver<Vec<ConsensusTransaction>>,
1103 num_validators: usize,
1104 }
1105
1106 impl DkgTestSetup {
1107 async fn new(include_observer: bool) -> Self {
1108 let network_config =
1109 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1110 .committee_size(NonZeroUsize::new(4).unwrap())
1111 .with_reference_gas_price(500)
1112 .build();
1113
1114 let mut protocol_config =
1115 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1116 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1117
1118 let num_validators = network_config.validator_configs.len();
1119 let mut epoch_stores = Vec::new();
1120 let mut randomness_managers = Vec::new();
1121 let (tx_consensus, rx_consensus) = mpsc::channel(100);
1122
1123 for validator in network_config.validator_configs.iter() {
1124 let mut mock_consensus_client = MockConsensusClient::new();
1125 let tx_consensus = tx_consensus.clone();
1126 mock_consensus_client
1127 .expect_submit()
1128 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1129 tx_consensus.try_send(transactions.to_vec()).unwrap();
1130 true
1131 })
1132 .returning(|_, _| {
1133 Ok((
1134 Vec::new(),
1135 with_block_status(BlockStatus::Sequenced(BlockRef::MIN)),
1136 ))
1137 });
1138
1139 let state = TestAuthorityBuilder::new()
1140 .with_protocol_config(protocol_config.clone())
1141 .with_genesis_and_keypair(
1142 &network_config.genesis,
1143 validator.protocol_key_pair(),
1144 )
1145 .build()
1146 .await;
1147 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1148 Arc::new(mock_consensus_client),
1149 CheckpointStore::new_for_tests(),
1150 state.name,
1151 100_000,
1152 100_000,
1153 ConsensusAdapterMetrics::new_test(),
1154 Arc::new(tokio::sync::Notify::new()),
1155 ));
1156 let epoch_store = state.epoch_store_for_testing();
1157 let randomness_manager = RandomnessManager::try_new(
1158 Arc::downgrade(&epoch_store),
1159 Box::new(consensus_adapter.clone()),
1160 sui_network::randomness::Handle::new_stub(),
1161 Some(validator.protocol_key_pair()),
1162 RandomnessRoundReceiverHandle::new_for_testing(),
1163 )
1164 .await
1165 .unwrap();
1166
1167 epoch_stores.push(epoch_store);
1168 randomness_managers.push(randomness_manager);
1169 }
1170
1171 if include_observer {
1172 let observer_epoch_store = epoch_stores[0].clone();
1173 let mut mock_observer_consensus = MockConsensusClient::new();
1174 mock_observer_consensus
1175 .expect_submit()
1176 .returning(|_, _| panic!("observer should not submit to consensus"));
1177 let observer_adapter = Arc::new(ConsensusAdapter::new(
1178 Arc::new(mock_observer_consensus),
1179 CheckpointStore::new_for_tests(),
1180 observer_epoch_store.name,
1181 100_000,
1182 100_000,
1183 ConsensusAdapterMetrics::new_test(),
1184 Arc::new(tokio::sync::Notify::new()),
1185 ));
1186 let observer_manager = RandomnessManager::try_new(
1187 Arc::downgrade(&observer_epoch_store),
1188 Box::new(observer_adapter),
1189 sui_network::randomness::Handle::new_stub(),
1190 None,
1191 RandomnessRoundReceiverHandle::new_for_testing(),
1192 )
1193 .await
1194 .unwrap();
1195
1196 epoch_stores.push(observer_epoch_store.into());
1197 randomness_managers.push(observer_manager);
1198 }
1199
1200 Self {
1201 network_config,
1202 epoch_stores,
1203 randomness_managers,
1204 tx_consensus,
1205 rx_consensus,
1206 num_validators,
1207 }
1208 }
1209
1210 fn consensus_adapter(&self, authority: AuthorityName) -> Arc<ConsensusAdapter> {
1211 let mut mock_consensus_client = MockConsensusClient::new();
1212 let tx_consensus = self.tx_consensus.clone();
1213 mock_consensus_client
1214 .expect_submit()
1215 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1216 tx_consensus.try_send(transactions.to_vec()).unwrap();
1217 true
1218 })
1219 .returning(|_, _| {
1220 Ok((
1221 Vec::new(),
1222 with_block_status(BlockStatus::Sequenced(BlockRef::MIN)),
1223 ))
1224 });
1225
1226 Arc::new(ConsensusAdapter::new(
1227 Arc::new(mock_consensus_client),
1228 CheckpointStore::new_for_tests(),
1229 authority,
1230 100_000,
1231 100_000,
1232 ConsensusAdapterMetrics::new_test(),
1233 Arc::new(tokio::sync::Notify::new()),
1234 ))
1235 }
1236
1237 async fn recover_validator_randomness_managers(&mut self) -> usize {
1238 let mut recovered_randomness_managers = Vec::new();
1239 for (i, validator) in self
1240 .network_config
1241 .validator_configs
1242 .iter()
1243 .enumerate()
1244 .take(self.num_validators)
1245 {
1246 let consensus_adapter = self.consensus_adapter(self.epoch_stores[i].name);
1247 recovered_randomness_managers.push(
1248 RandomnessManager::try_new(
1249 Arc::downgrade(&self.epoch_stores[i]),
1250 Box::new(consensus_adapter),
1251 sui_network::randomness::Handle::new_stub(),
1252 Some(validator.protocol_key_pair()),
1253 RandomnessRoundReceiverHandle::new_for_testing(),
1254 )
1255 .await
1256 .unwrap(),
1257 );
1258 }
1259 let recovered_count = recovered_randomness_managers.len();
1260 self.randomness_managers = recovered_randomness_managers;
1261 recovered_count
1262 }
1263
1264 async fn start_dkg_and_collect_messages(&mut self) -> Vec<VersionedDkgMessage> {
1266 let mut dkg_messages = Vec::new();
1267 for randomness_manager in self.randomness_managers.iter_mut() {
1268 randomness_manager.start_dkg().await.unwrap();
1269 }
1270 for _ in 0..self.num_validators {
1271 let mut dkg_message = self.rx_consensus.recv().await.unwrap();
1272 assert!(dkg_message.len() == 1);
1273 match dkg_message.remove(0).kind {
1274 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
1275 let msg: VersionedDkgMessage = bcs::from_bytes(&bytes)
1276 .expect("DKG message deserialization should not fail");
1277 dkg_messages.push(msg);
1278 }
1279 _ => panic!("wrong type of message sent"),
1280 }
1281 }
1282 dkg_messages
1283 }
1284
1285 async fn distribute_messages_and_advance(
1287 &mut self,
1288 dkg_messages: &[VersionedDkgMessage],
1289 advance_round: Round,
1290 ) {
1291 let indexed_messages: Vec<_> = dkg_messages.iter().cloned().enumerate().collect();
1292 self.distribute_indexed_messages_and_advance(&indexed_messages, advance_round)
1293 .await;
1294 }
1295
1296 async fn distribute_indexed_messages_and_advance(
1297 &mut self,
1298 dkg_messages: &[(usize, VersionedDkgMessage)],
1299 advance_round: Round,
1300 ) {
1301 for i in 0..self.randomness_managers.len() {
1302 let mut output = ConsensusCommitOutput::new(0);
1303 output.record_consensus_commit_stats(ExecutionIndicesWithStatsV2 {
1304 index: ExecutionIndices {
1305 last_committed_round: 0,
1306 ..Default::default()
1307 },
1308 ..Default::default()
1309 });
1310 for (j, dkg_message) in dkg_messages.iter().cloned() {
1311 self.randomness_managers[i]
1312 .add_message(&self.epoch_stores[j].name, dkg_message)
1313 .unwrap();
1314 }
1315 self.randomness_managers[i]
1316 .advance_dkg(&mut output, advance_round)
1317 .await
1318 .unwrap();
1319 let mut batch = self.epoch_stores[i].db_batch_for_test();
1320 output
1321 .write_to_batch(&self.epoch_stores[i], &mut batch)
1322 .unwrap();
1323 batch.write().unwrap();
1324 }
1325 }
1326
1327 async fn collect_and_distribute_confirmations(&mut self) {
1329 let mut dkg_confirmations = Vec::new();
1330 for _ in 0..self.num_validators {
1331 let mut dkg_confirmation = self.rx_consensus.recv().await.unwrap();
1332 assert!(dkg_confirmation.len() == 1);
1333 match dkg_confirmation.remove(0).kind {
1334 ConsensusTransactionKind::RandomnessDkgConfirmation(_, bytes) => {
1335 let msg: VersionedDkgConfirmation = bcs::from_bytes(&bytes)
1336 .expect("DKG confirmation deserialization should not fail");
1337 dkg_confirmations.push(msg);
1338 }
1339 _ => panic!("wrong type of message sent"),
1340 }
1341 }
1342 for i in 0..self.randomness_managers.len() {
1343 let mut output = ConsensusCommitOutput::new(0);
1344 output.record_consensus_commit_stats(ExecutionIndicesWithStatsV2 {
1345 index: ExecutionIndices {
1346 last_committed_round: 1,
1347 ..Default::default()
1348 },
1349 ..Default::default()
1350 });
1351 for (j, dkg_confirmation) in dkg_confirmations.iter().cloned().enumerate() {
1352 self.randomness_managers[i]
1353 .add_confirmation(&mut output, &self.epoch_stores[j].name, dkg_confirmation)
1354 .unwrap();
1355 }
1356 self.randomness_managers[i]
1357 .advance_dkg(&mut output, 0)
1358 .await
1359 .unwrap();
1360 let mut batch = self.epoch_stores[i].db_batch_for_test();
1361 output
1362 .write_to_batch(&self.epoch_stores[i], &mut batch)
1363 .unwrap();
1364 batch.write().unwrap();
1365 }
1366 }
1367 }
1368
1369 #[tokio::test]
1370 async fn test_dkg() {
1371 telemetry_subscribers::init_for_testing();
1372
1373 let mut setup = DkgTestSetup::new(false).await;
1374 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1375 setup
1376 .distribute_messages_and_advance(&dkg_messages, 0)
1377 .await;
1378 setup.collect_and_distribute_confirmations().await;
1379
1380 for rm in &setup.randomness_managers {
1381 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1382 }
1383 }
1384
1385 #[tokio::test]
1386 async fn test_dkg_expiration() {
1387 telemetry_subscribers::init_for_testing();
1388
1389 let mut setup = DkgTestSetup::new(false).await;
1390 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1391 setup
1393 .distribute_messages_and_advance(&dkg_messages, u64::MAX)
1394 .await;
1395
1396 for rm in &setup.randomness_managers {
1397 assert_eq!(DkgStatus::Failed, rm.dkg_status());
1398 }
1399
1400 let recovered_count = setup.recover_validator_randomness_managers().await;
1401 assert_eq!(setup.num_validators, recovered_count);
1402
1403 for rm in &setup.randomness_managers {
1407 assert_eq!(DkgStatus::Failed, rm.dkg_status());
1408 }
1409 }
1410
1411 #[tokio::test]
1412 async fn test_dkg_recovers_processed_messages_after_restart() {
1413 telemetry_subscribers::init_for_testing();
1414
1415 let mut setup = DkgTestSetup::new(false).await;
1416 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1417 let processed_before_restart = 1;
1418 assert!(processed_before_restart > 0);
1419 assert!(processed_before_restart < setup.num_validators);
1420 let pre_restart_messages: Vec<_> = dkg_messages
1421 .iter()
1422 .cloned()
1423 .enumerate()
1424 .take(processed_before_restart)
1425 .collect();
1426 let post_restart_messages: Vec<_> = dkg_messages
1427 .iter()
1428 .cloned()
1429 .enumerate()
1430 .skip(processed_before_restart)
1431 .collect();
1432
1433 setup
1434 .distribute_indexed_messages_and_advance(&pre_restart_messages, 0)
1435 .await;
1436
1437 for rm in &setup.randomness_managers {
1438 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1439 assert_eq!(processed_before_restart, rm.processed_messages.len());
1440 assert!(!rm.used_messages.initialized());
1441 }
1442
1443 let recovered_count = setup.recover_validator_randomness_managers().await;
1444 assert_eq!(setup.num_validators, recovered_count);
1445 for rm in &setup.randomness_managers {
1446 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1447 assert_eq!(processed_before_restart, rm.processed_messages.len());
1448 assert!(!rm.used_messages.initialized());
1449 }
1450
1451 setup
1452 .distribute_indexed_messages_and_advance(&post_restart_messages, 0)
1453 .await;
1454
1455 for rm in &setup.randomness_managers {
1456 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1457 assert_eq!(setup.num_validators, rm.processed_messages.len());
1458 assert!(rm.used_messages.initialized());
1459 }
1460
1461 setup.collect_and_distribute_confirmations().await;
1462
1463 for rm in &setup.randomness_managers {
1464 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1465 }
1466 }
1467
1468 #[tokio::test]
1471 async fn test_dkg_observer() {
1472 telemetry_subscribers::init_for_testing();
1473
1474 let mut setup = DkgTestSetup::new(true).await;
1475 let observer_idx = setup.randomness_managers.len() - 1;
1476
1477 let dkg_messages = setup.start_dkg_and_collect_messages().await;
1478 setup
1479 .distribute_messages_and_advance(&dkg_messages, 0)
1480 .await;
1481
1482 for rm in &setup.randomness_managers {
1483 assert_eq!(DkgStatus::Pending, rm.dkg_status());
1484 }
1485
1486 setup.collect_and_distribute_confirmations().await;
1487
1488 for rm in &setup.randomness_managers {
1489 assert_eq!(DkgStatus::Successful, rm.dkg_status());
1490 }
1491
1492 let observer_output = setup.randomness_managers[observer_idx]
1494 .dkg_output()
1495 .expect("observer should have DKG output");
1496 let validator_output = setup.randomness_managers[0]
1497 .dkg_output()
1498 .expect("validator should have DKG output");
1499 assert_eq!(observer_output.vss_pk, validator_output.vss_pk);
1500 assert!(observer_output.shares.is_none());
1501
1502 for rm in &setup.randomness_managers[..setup.num_validators] {
1503 let output = rm.dkg_output().expect("validator should have DKG output");
1504 assert!(output.shares.is_some());
1505 }
1506 }
1507
1508 fn build_dkg_nodes(
1510 network_config: &sui_swarm_config::network_config::NetworkConfig,
1511 ) -> (nodes::Nodes<EncG>, u16) {
1512 let dkg_nodes: Vec<_> = network_config
1513 .validator_configs
1514 .iter()
1515 .enumerate()
1516 .map(|(i, v)| {
1517 let pk = bls12381::G2Element::from_byte_array(
1518 v.protocol_key_pair()
1519 .public()
1520 .as_bytes()
1521 .try_into()
1522 .expect("key length should match"),
1523 )
1524 .expect("should work to convert BLS key to G2Element");
1525 nodes::Node::<EncG> {
1526 id: i as u16,
1527 pk: fastcrypto_tbls::ecies_v1::PublicKey::from(pk),
1528 weight: 1,
1529 }
1530 })
1531 .collect();
1532 let num_nodes = dkg_nodes.len();
1533 let t = num_nodes.div_ceil(3) as u16;
1535 nodes::Nodes::new(dkg_nodes).map(|n| (n, t)).unwrap()
1536 }
1537
1538 #[test]
1539 fn test_dkg_role_try_new_party() {
1540 let network_config =
1541 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1542 .committee_size(NonZeroUsize::new(4).unwrap())
1543 .with_reference_gas_price(500)
1544 .build();
1545
1546 let (nodes, t) = build_dkg_nodes(&network_config);
1547 let random_oracle =
1548 fastcrypto_tbls::random_oracle::RandomOracle::new("test_dkg_role_party");
1549
1550 let role = DkgRole::try_new(
1551 Some(network_config.validator_configs[0].protocol_key_pair()),
1552 nodes,
1553 t,
1554 random_oracle,
1555 );
1556 assert!(role.is_some());
1557 assert!(role.unwrap().is_party());
1558 }
1559
1560 #[tokio::test]
1561 async fn test_randomness_manager_crash_recovery_v1() {
1562 telemetry_subscribers::init_for_testing();
1563
1564 let network_config =
1565 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1566 .committee_size(NonZeroUsize::new(4).unwrap())
1567 .with_reference_gas_price(500)
1568 .build();
1569
1570 let mut protocol_config =
1571 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1572 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1573
1574 let validator = &network_config.validator_configs[0];
1575 let state = TestAuthorityBuilder::new()
1576 .with_protocol_config(protocol_config.clone())
1577 .with_genesis_and_keypair(&network_config.genesis, validator.protocol_key_pair())
1578 .build()
1579 .await;
1580 let epoch_store = state.epoch_store_for_testing();
1581
1582 let dkg_nodes = nodes::Nodes::new(
1583 RandomnessManager::randomness_dkg_info_from_committee(epoch_store.committee())
1584 .into_iter()
1585 .map(|(id, _, pk, stake)| nodes::Node::<bls12381::G2Element> {
1586 id,
1587 pk,
1588 weight: stake.try_into().unwrap(),
1589 })
1590 .collect(),
1591 )
1592 .unwrap();
1593 let threshold = epoch_store
1594 .committee()
1595 .validity_threshold()
1596 .try_into()
1597 .unwrap();
1598 let party_id = epoch_store
1599 .committee()
1600 .authority_index(&epoch_store.name)
1601 .unwrap()
1602 .try_into()
1603 .unwrap();
1604 let expected_dkg_output = mocked_dkg::generate_mocked_output::<
1605 bls12381::G2Element,
1606 bls12381::G2Element,
1607 >(dkg_nodes, threshold, 0, party_id);
1608 let expected_dkg_output_bytes =
1609 bcs::to_bytes(&expected_dkg_output).expect("DKG output serialization should not fail");
1610 let expected_public_key = expected_dkg_output.vss_pk.c0();
1611
1612 let tables = epoch_store.tables().unwrap();
1613 tables
1614 .dkg_output_v2
1615 .insert(&SINGLETON_KEY, &Some(expected_dkg_output))
1616 .unwrap();
1617 tables
1618 .randomness_next_round
1619 .insert(&SINGLETON_KEY, &RandomnessRound(3))
1620 .unwrap();
1621 tables
1622 .randomness_highest_completed_round
1623 .insert(&SINGLETON_KEY, &RandomnessRound(1))
1624 .unwrap();
1625
1626 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1627 Arc::new(MockConsensusClient::new()),
1628 CheckpointStore::new_for_tests(),
1629 epoch_store.name,
1630 100_000,
1631 100_000,
1632 ConsensusAdapterMetrics::new_test(),
1633 Arc::new(tokio::sync::Notify::new()),
1634 ));
1635 let recovered_receiver_handle = RandomnessRoundReceiverHandle::new_for_testing();
1636 assert!(recovered_receiver_handle.public_key_for_testing().is_none());
1637
1638 let recovered_randomness_manager = RandomnessManager::try_new(
1639 Arc::downgrade(&epoch_store),
1640 Box::new(consensus_adapter.clone()),
1641 sui_network::randomness::Handle::new_stub(),
1642 Some(validator.protocol_key_pair()),
1643 recovered_receiver_handle.clone(),
1644 )
1645 .await
1646 .unwrap();
1647
1648 assert_eq!(
1649 DkgStatus::Successful,
1650 recovered_randomness_manager.dkg_status()
1651 );
1652 let recovered_dkg_output = recovered_randomness_manager
1653 .dkg_output
1654 .get()
1655 .expect("recovered DKG output should be initialized")
1656 .as_ref()
1657 .expect("recovered DKG should be successful");
1658 assert_eq!(
1659 expected_dkg_output_bytes,
1660 bcs::to_bytes(recovered_dkg_output).expect("DKG output serialization should not fail")
1661 );
1662 assert_eq!(
1663 RandomnessRound(3),
1664 recovered_randomness_manager.next_randomness_round
1665 );
1666 assert_eq!(
1667 Some(RandomnessRound(1)),
1668 *recovered_randomness_manager.highest_completed_round.lock()
1669 );
1670 let recovered_public_key = recovered_receiver_handle
1671 .public_key_for_testing()
1672 .expect("public key should be restored on recovery");
1673 assert_eq!(
1674 expected_public_key.to_byte_array(),
1675 recovered_public_key.to_byte_array()
1676 );
1677 }
1678
1679 #[test]
1680 fn test_dkg_role_try_new_observer() {
1681 let network_config =
1682 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1683 .committee_size(NonZeroUsize::new(4).unwrap())
1684 .with_reference_gas_price(500)
1685 .build();
1686
1687 let (nodes, t) = build_dkg_nodes(&network_config);
1688 let random_oracle =
1689 fastcrypto_tbls::random_oracle::RandomOracle::new("test_dkg_role_observer");
1690
1691 let role = DkgRole::try_new(None, nodes, t, random_oracle);
1692 assert!(role.is_some());
1693 assert!(role.unwrap().is_observer());
1694 }
1695}