1use std::{
6 collections::{BTreeMap, HashMap},
7 sync::{Arc, Weak},
8 time::Instant,
9};
10
11use anemo::PeerId;
12use fastcrypto::{
13 encoding::{Encoding, Hex},
14 error::{FastCryptoError, FastCryptoResult},
15 groups::bls12381,
16 serde_helpers::ToFromByteArray,
17 traits::{KeyPair, ToFromBytes},
18};
19use fastcrypto_tbls::{dkg_v1, dkg_v1::Output, nodes, nodes::PartyId};
20use futures::{StreamExt, stream::FuturesUnordered};
21use iota_common::{debug_fatal, fatal};
22use iota_macros::{fail_point, fail_point_if};
23use iota_network::randomness;
24use iota_sdk_types::RandomnessRound;
25use iota_types::{
26 base_types::{AuthorityName, CommitRound},
27 committee::{Committee, EpochId, StakeUnit},
28 crypto::{AuthorityKeyPair, RandomnessSignature},
29 error::{IotaError, IotaResult},
30 iota_system_state::epoch_start_iota_system_state::EpochStartSystemStateTrait,
31 messages_consensus::{ConsensusTransaction, VersionedDkgConfirmation, VersionedDkgMessage},
32};
33use parking_lot::Mutex;
34use rand08::{
35 SeedableRng,
36 rngs::{OsRng, StdRng},
37};
38use serde::{Deserialize, Serialize};
39use tokio::{sync::OnceCell, task::JoinHandle};
40use tracing::{debug, error, info, warn};
41use typed_store::Map;
42
43use crate::{
44 authority::{
45 authority_per_epoch_store::{
46 AuthorityPerEpochStore, consensus_quarantine::ConsensusCommitOutput,
47 },
48 epoch_start_configuration::EpochStartConfigTrait,
49 },
50 consensus_adapter::SubmitToConsensus,
51};
52
53pub type CommitTimestampMs = u64;
55
56type PkG = bls12381::G2Element;
57type EncG = bls12381::G2Element;
58
59pub const SINGLETON_KEY: u64 = 0;
60
61#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
64pub enum VersionedProcessedMessage {
65 V1(dkg_v1::ProcessedMessage<PkG, EncG>),
66}
67
68impl VersionedProcessedMessage {
69 pub fn sender(&self) -> PartyId {
70 match self {
71 VersionedProcessedMessage::V1(msg) => msg.message.sender,
72 }
73 }
74
75 pub fn unwrap_v1(self) -> dkg_v1::ProcessedMessage<PkG, EncG> {
76 match self {
77 VersionedProcessedMessage::V1(msg) => msg,
78 }
79 }
80
81 pub fn process(
82 party: Arc<dkg_v1::Party<PkG, EncG>>,
83 message: VersionedDkgMessage,
84 ) -> FastCryptoResult<VersionedProcessedMessage> {
85 let processed = party.process_message(message.unwrap_v1(), &mut rand08::thread_rng())?;
88 Ok(VersionedProcessedMessage::V1(processed))
89 }
90
91 pub fn merge(
92 party: Arc<dkg_v1::Party<PkG, EncG>>,
93 messages: Vec<Self>,
94 ) -> FastCryptoResult<(VersionedDkgConfirmation, VersionedUsedProcessedMessages)> {
95 let (conf, msgs) = party.merge(
98 &messages
99 .into_iter()
100 .map(|vm| vm.unwrap_v1())
101 .collect::<Vec<_>>(),
102 )?;
103 Ok((
104 VersionedDkgConfirmation::V1(conf),
105 VersionedUsedProcessedMessages::V1(msgs),
106 ))
107 }
108}
109
110#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
111pub enum VersionedUsedProcessedMessages {
112 V1(dkg_v1::UsedProcessedMessages<PkG, EncG>),
113}
114
115impl VersionedUsedProcessedMessages {
116 fn complete_dkg<'a, Iter: Iterator<Item = &'a VersionedDkgConfirmation>>(
117 &self,
118 party: Arc<dkg_v1::Party<PkG, EncG>>,
119 confirmations: Iter,
120 ) -> FastCryptoResult<Output<PkG, EncG>> {
121 let rng = &mut StdRng::from_rng(OsRng).expect("RNG construction should not fail");
124 let VersionedUsedProcessedMessages::V1(msg) = self;
125 party.complete(
126 msg,
127 &confirmations
128 .map(|vm| vm.unwrap_v1())
129 .cloned()
130 .collect::<Vec<_>>(),
131 rng,
132 )
133 }
134}
135
136#[derive(Debug)]
139pub(crate) enum AdvanceDkgError {
140 Iota(IotaError),
141 RuntimeShutdown,
145}
146
147impl From<IotaError> for AdvanceDkgError {
148 fn from(e: IotaError) -> Self {
149 Self::Iota(e)
150 }
151}
152
153pub struct RandomnessManager {
173 epoch_store: Weak<AuthorityPerEpochStore>,
174 epoch: EpochId,
175 consensus_adapter: Box<dyn SubmitToConsensus>,
176 network_handle: randomness::Handle,
177 authority_info: HashMap<AuthorityName, (PeerId, PartyId)>,
178
179 dkg_start_time: OnceCell<Instant>,
181 party: Arc<dkg_v1::Party<PkG, EncG>>,
182 enqueued_messages: BTreeMap<PartyId, JoinHandle<Option<VersionedProcessedMessage>>>,
183 processed_messages: BTreeMap<PartyId, VersionedProcessedMessage>,
184 used_messages: OnceCell<VersionedUsedProcessedMessages>,
185 confirmations: BTreeMap<PartyId, VersionedDkgConfirmation>,
186 dkg_output: OnceCell<Option<dkg_v1::Output<PkG, EncG>>>,
187 dkg_timeout_reported: bool,
188
189 next_randomness_round: RandomnessRound,
191 highest_completed_round: Arc<Mutex<Option<RandomnessRound>>>,
192}
193
194impl RandomnessManager {
195 pub async fn try_new(
198 epoch_store_weak: Weak<AuthorityPerEpochStore>,
199 consensus_adapter: Box<dyn SubmitToConsensus>,
200 network_handle: randomness::Handle,
201 authority_key_pair: &AuthorityKeyPair,
202 ) -> IotaResult<Self> {
203 let epoch_store = epoch_store_weak
204 .upgrade()
205 .ok_or_else(|| IotaError::Unknown("AuthorityPerEpochStore already gone".to_string()))?;
206 let tables = epoch_store.tables()?;
207 let protocol_config = epoch_store.protocol_config();
208
209 let name: AuthorityName = authority_key_pair.public().into();
210 let committee = epoch_store.committee();
211 let info = RandomnessManager::randomness_dkg_info_from_committee(committee);
212 if tracing::enabled!(tracing::Level::DEBUG) {
213 for (id, name, pk, stake) in info.iter().filter(|(id, _, _, _)| *id < 3) {
215 let pk_bytes = pk.as_element().to_byte_array();
216 debug!(
217 "random beacon: DKG info: id={id}, stake={stake}, name={name}, pk={pk_bytes:x?}"
218 );
219 }
220 }
221 let authority_ids: HashMap<_, _> =
222 info.iter().map(|(id, name, _, _)| (*name, *id)).collect();
223 let authority_peer_ids = epoch_store
224 .epoch_start_config()
225 .epoch_start_state()
226 .get_authority_names_to_peer_ids();
227 let authority_info: HashMap<AuthorityName, (PeerId, PartyId)> = authority_ids
228 .into_iter()
229 .map(|(name, id)| {
230 authority_peer_ids
231 .get(&name)
232 .map(|peer_id| (name, (*peer_id, id)))
233 })
234 .collect::<Option<_>>()
235 .ok_or_else(|| {
236 IotaError::Unknown("authority name missing from peer_ids".to_string())
237 })?;
238 let nodes = info
239 .iter()
240 .map(|(id, _, pk, stake)| {
241 Some(nodes::Node::<EncG> {
242 id: *id,
243 pk: pk.clone(),
244 weight: (*stake).try_into().ok()?,
245 })
246 })
247 .collect::<Option<Vec<_>>>()
248 .ok_or_else(|| IotaError::Unknown("validator stake does not fit in u16".to_string()))?;
249 let validity_threshold: u16 = committee.validity_threshold().try_into().map_err(|_| {
250 IotaError::Unknown(format!(
251 "committee validity threshold {} does not fit in u16",
252 committee.validity_threshold()
253 ))
254 })?;
255 let reduction_lower_bound: u16 = protocol_config
256 .random_beacon_reduction_lower_bound()
257 .try_into()
258 .map_err(|_| {
259 IotaError::Unknown(format!(
260 "random beacon reduction lower bound {} does not fit in u16",
261 protocol_config.random_beacon_reduction_lower_bound()
262 ))
263 })?;
264 let (nodes, t) = nodes::Nodes::new_reduced(
265 nodes,
266 validity_threshold,
267 protocol_config.random_beacon_reduction_allowed_delta(),
268 reduction_lower_bound,
269 )
270 .map_err(|err| IotaError::Unknown(format!("error while initializing Nodes: {err:?}")))?;
271 let total_weight = nodes.total_weight();
272 let num_nodes = nodes.num_nodes();
273 let prefix_str = format!(
274 "dkg {} {}",
275 Hex::encode(epoch_store.get_chain_identifier().as_bytes()),
276 committee.epoch()
277 );
278 let randomness_private_key = bls12381::Scalar::from_byte_array(
279 authority_key_pair
280 .copy()
281 .private()
282 .as_bytes()
283 .try_into()
284 .expect("key length should match"),
285 )
286 .expect("should work to convert BLS key to Scalar");
287 let party = dkg_v1::Party::<PkG, EncG>::new(
288 fastcrypto_tbls::ecies_v1::PrivateKey::<bls12381::G2Element>::from(
289 randomness_private_key,
290 ),
291 nodes,
292 t,
293 fastcrypto_tbls::random_oracle::RandomOracle::new(prefix_str.as_str()),
294 &mut rand08::thread_rng(),
295 )
296 .map_err(|err| IotaError::Unknown(format!("error while initializing Party: {err:?}")))?;
297 info!(
298 "random beacon: state initialized with authority={name}, total_weight={total_weight}, t={t}, num_nodes={num_nodes}, oracle initial_prefix={prefix_str:?}",
299 );
300
301 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(0);
305 epoch_store
306 .metrics
307 .epoch_random_beacon_dkg_completed_after_timeout
308 .set(0);
309 epoch_store
310 .metrics
311 .epoch_random_beacon_dkg_num_shares
312 .set(0);
313
314 let highest_completed_round = tables
316 .randomness_highest_completed_round
317 .get(&SINGLETON_KEY)
318 .expect("typed_store should not fail");
319 let mut rm = RandomnessManager {
320 epoch_store: epoch_store_weak,
321 epoch: committee.epoch(),
322 consensus_adapter,
323 network_handle: network_handle.clone(),
324 authority_info,
325 dkg_start_time: OnceCell::new(),
326 party: Arc::new(party),
327 enqueued_messages: BTreeMap::new(),
328 processed_messages: BTreeMap::new(),
329 used_messages: OnceCell::new(),
330 confirmations: BTreeMap::new(),
331 dkg_output: OnceCell::new(),
332 dkg_timeout_reported: false,
333 next_randomness_round: RandomnessRound::new(0),
334 highest_completed_round: Arc::new(Mutex::new(highest_completed_round)),
335 };
336 let dkg_output = match tables
340 .dkg_output_v2
341 .get(&SINGLETON_KEY)
342 .expect("typed_store should not fail")
343 {
344 Some(persisted) => Some(persisted),
345 None => tables
346 .dkg_output
347 .get(&SINGLETON_KEY)
348 .expect("typed_store should not fail")
349 .map(Some),
350 };
351 match dkg_output {
352 Some(Some(dkg_output)) => {
353 info!(
354 "random beacon: loaded existing DKG output for epoch {}",
355 committee.epoch()
356 );
357 epoch_store
358 .metrics
359 .epoch_random_beacon_dkg_num_shares
360 .set(dkg_output.shares.as_ref().map_or(0, |shares| shares.len()) as i64);
361 rm.dkg_output
362 .set(Some(dkg_output.clone()))
363 .expect("setting new OnceCell should succeed");
364 network_handle.update_epoch(
365 committee.epoch(),
366 rm.authority_info.clone(),
367 dkg_output,
368 rm.party.t(),
369 highest_completed_round,
370 );
371 }
372 Some(None) => {
373 error!(
377 "random beacon: loaded failed DKG for epoch {}. Randomness disabled for this epoch. All randomness-using transactions will fail.",
378 committee.epoch()
379 );
380 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
381 rm.dkg_output
382 .set(None)
383 .expect("setting new OnceCell should succeed");
384 }
385 None => {
386 info!(
387 "random beacon: no existing DKG output found for epoch {}",
388 committee.epoch()
389 );
390
391 assert!(
393 epoch_store.protocol_config().dkg_version() > 0,
394 "BUG: DKG version 0 is deprecated"
395 );
396 rm.processed_messages.extend(
397 tables
398 .dkg_processed_messages
399 .safe_iter()
400 .map(|result| result.expect("typed_store should not fail")),
401 );
402 if let Some(used_messages) = tables
403 .dkg_used_messages
404 .get(&SINGLETON_KEY)
405 .expect("typed_store should not fail")
406 {
407 rm.used_messages
408 .set(used_messages)
409 .expect("setting new OnceCell should succeed");
410 }
411 rm.confirmations.extend(
412 tables
413 .dkg_confirmations
414 .safe_iter()
415 .map(|result| result.expect("typed_store should not fail")),
416 );
417 }
418 }
419
420 rm.next_randomness_round = tables
425 .randomness_next_round
426 .get(&SINGLETON_KEY)
427 .expect("typed_store should not fail")
428 .unwrap_or(RandomnessRound::new(0));
429 info!(
430 "random beacon: starting from next_randomness_round={}",
431 rm.next_randomness_round
432 );
433 let first_incomplete_round = highest_completed_round
434 .map(|r| r + 1)
435 .unwrap_or(RandomnessRound::new(0));
436 if first_incomplete_round < rm.next_randomness_round {
437 info!(
438 "random beacon: resuming generation for randomness rounds from {first_incomplete_round} to {}",
439 rm.next_randomness_round - 1,
440 );
441 for r in first_incomplete_round.value()..rm.next_randomness_round.value() {
442 network_handle.send_partial_signatures(committee.epoch(), RandomnessRound::new(r));
443 }
444 }
445
446 Ok(rm)
447 }
448
449 pub async fn start_dkg(&mut self) -> IotaResult {
451 if self.used_messages.initialized() || self.dkg_output.initialized() {
452 return Ok(());
454 }
455
456 let _ = self.dkg_start_time.set(Instant::now());
457
458 let epoch_store = self.epoch_store()?;
459 let dkg_version = epoch_store.protocol_config().dkg_version();
460 info!("random beacon: starting DKG, version {dkg_version}");
461
462 let msg = match VersionedDkgMessage::create(dkg_version, self.party.clone()) {
463 Ok(msg) => msg,
464 Err(FastCryptoError::IgnoredMessage) => {
465 info!(
466 "random beacon: no DKG Message for party id={} (zero weight)",
467 self.party.id
468 );
469 return Ok(());
470 }
471 Err(e) => {
472 error!("random beacon: error while creating a DKG Message: {e:?}");
473 return Ok(());
474 }
475 };
476
477 info!("random beacon: created {msg:?} with dkg version {dkg_version}");
478 let transaction = ConsensusTransaction::new_randomness_dkg_message(epoch_store.name, &msg);
479
480 #[cfg_attr(not(any(msim, fail_points)), expect(unused_mut))]
481 let mut fail_point_skip_sending = false;
482 fail_point_if!("rb-dkg", || {
483 fail_point_skip_sending = true;
485 });
486 if !fail_point_skip_sending {
487 self.consensus_adapter
488 .submit_to_consensus(&[transaction], &epoch_store)?;
489 }
490
491 epoch_store
492 .metrics
493 .epoch_random_beacon_dkg_message_time_ms
494 .set(
495 self.dkg_start_time
496 .get()
497 .unwrap() .elapsed()
499 .as_millis() as i64,
500 );
501 Ok(())
502 }
503
504 pub(crate) async fn advance_dkg(
508 &mut self,
509 consensus_output: &mut ConsensusCommitOutput,
510 round: CommitRound,
511 ) -> Result<(), AdvanceDkgError> {
512 let epoch_store = self.epoch_store()?;
513
514 if !self.dkg_output.initialized() && !self.used_messages.initialized() {
516 let mut handles: FuturesUnordered<_> = std::mem::take(&mut self.enqueued_messages)
518 .into_values()
519 .collect();
520 while let Some(res) = handles.next().await {
521 let processed = match res {
522 Ok(processed) => processed,
523 Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()),
524 Err(_) => return Err(AdvanceDkgError::RuntimeShutdown),
527 };
528 if let Some(processed) = processed {
529 self.processed_messages
530 .insert(processed.sender(), processed.clone());
531 consensus_output.insert_dkg_processed_message(processed);
532 }
533 }
534
535 match VersionedProcessedMessage::merge(
537 self.party.clone(),
538 self.processed_messages
539 .values()
540 .cloned()
541 .collect::<Vec<_>>(),
542 ) {
543 Ok((conf, used_msgs)) => {
544 info!(
545 "random beacon: sending DKG Confirmation with {} complaints",
546 conf.num_of_complaints()
547 );
548 if self.used_messages.set(used_msgs.clone()).is_err() {
549 error!("BUG: used_messages should only ever be set once");
550 }
551 consensus_output.insert_dkg_used_messages(used_msgs);
552
553 let transaction = ConsensusTransaction::new_randomness_dkg_confirmation(
554 epoch_store.name,
555 &conf,
556 );
557
558 #[cfg_attr(not(any(msim, fail_points)), expect(unused_mut))]
559 let mut fail_point_skip_sending = false;
560 fail_point_if!("rb-dkg", || {
561 fail_point_skip_sending = true;
563 });
564 if !fail_point_skip_sending {
565 self.consensus_adapter
566 .submit_to_consensus(&[transaction], &epoch_store)?;
567 }
568
569 let elapsed = self.dkg_start_time.get().map(|t| t.elapsed().as_millis());
570 if let Some(elapsed) = elapsed {
571 epoch_store
572 .metrics
573 .epoch_random_beacon_dkg_confirmation_time_ms
574 .set(elapsed as i64);
575 }
576 }
577 Err(FastCryptoError::NotEnoughInputs) => (), Err(e) => debug!("random beacon: error while merging DKG Messages: {e:?}"),
579 }
580 }
581
582 if !self.dkg_output.initialized() && self.used_messages.initialized() {
584 match self
585 .used_messages
586 .get()
587 .expect("checked above that `used_messages` is initialized")
588 .complete_dkg(self.party.clone(), self.confirmations.values())
589 {
590 Ok(output) => {
591 let num_shares = output.shares.as_ref().map_or(0, |shares| shares.len());
592 let epoch_elapsed = epoch_store.epoch_open_time.elapsed().as_millis();
593 let elapsed = self.dkg_start_time.get().map(|t| t.elapsed().as_millis());
594 let completed_after_timeout =
595 Self::is_past_dkg_timeout_round(round, epoch_store.protocol_config());
596 info!(
597 "random beacon: DKG complete in {epoch_elapsed}ms since epoch start, {elapsed:?}ms since DKG start, with {num_shares} shares for this node, completed_after_timeout={completed_after_timeout}"
598 );
599 epoch_store
600 .metrics
601 .epoch_random_beacon_dkg_num_shares
602 .set(num_shares as i64);
603 epoch_store
604 .metrics
605 .epoch_random_beacon_dkg_epoch_start_completion_time_ms
606 .set(epoch_elapsed as i64);
607 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(0);
608 epoch_store
609 .metrics
610 .epoch_random_beacon_dkg_completed_after_timeout
611 .set(i64::from(completed_after_timeout));
612 if completed_after_timeout {
613 fail_point!("rb-dkg-completed-after-timeout");
614 }
615 if let Some(elapsed) = elapsed {
616 epoch_store
617 .metrics
618 .epoch_random_beacon_dkg_completion_time_ms
619 .set(elapsed as i64);
620 }
621 self.dkg_output
622 .set(Some(output.clone()))
623 .expect("checked above that `dkg_output` is uninitialized");
624 self.network_handle.update_epoch(
625 epoch_store.committee().epoch(),
626 self.authority_info.clone(),
627 output.clone(),
628 self.party.t(),
629 None,
630 );
631 consensus_output.set_dkg_output(Some(output));
632 }
633 Err(FastCryptoError::NotEnoughInputs) => (), Err(e) => {
648 fatal!(
649 "random beacon: error while processing DKG Confirmations: {e:?}. A \
650 restart replays the same input. Restore the epoch database or \
651 state-sync before rejoining; if other validators stopped too, the \
652 cause is shared and needs a fix"
653 )
654 }
655 }
656 }
657
658 if !epoch_store
660 .protocol_config()
661 .allow_dkg_completion_after_timeout()
662 && !self.dkg_output.initialized()
663 && Self::is_past_dkg_timeout_round(round, epoch_store.protocol_config())
664 {
665 error!(
666 "random beacon: DKG timed out. Randomness disabled for this epoch. All randomness-using transactions will fail."
667 );
668 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
669 self.dkg_output
670 .set(None)
671 .expect("checked above that `dkg_output` is uninitialized");
672 consensus_output.set_dkg_output(None);
676 }
677
678 Ok(())
679 }
680
681 pub fn add_message(
683 &mut self,
684 authority: &AuthorityName,
685 msg: VersionedDkgMessage,
686 ) -> IotaResult {
687 let dkg_version = self.epoch_store()?.protocol_config().dkg_version();
691 if !msg.is_valid_version(dkg_version) {
692 warn!("ignoring DKG Message from authority {authority:?} with unsupported version");
693 return Ok(());
694 }
695
696 if self.used_messages.initialized() || self.dkg_output.initialized() {
697 return Ok(());
699 }
700 let Some((_, party_id)) = self.authority_info.get(authority) else {
701 debug_fatal!(
702 "random beacon: received DKG Message from unknown authority: {authority:?}"
703 );
704 return Ok(());
705 };
706 if *party_id != msg.sender() {
707 warn!(
708 "ignoring equivocating DKG Message from authority {authority:?} pretending to be PartyId {party_id}"
709 );
710 return Ok(());
711 }
712 if self.enqueued_messages.contains_key(&msg.sender())
713 || self.processed_messages.contains_key(&msg.sender())
714 {
715 info!("ignoring duplicate DKG Message from authority {authority:?}");
716 return Ok(());
717 }
718
719 let party = self.party.clone();
720 self.enqueued_messages.insert(
723 msg.sender(),
724 tokio::task::spawn_blocking(move || {
725 match VersionedProcessedMessage::process(party, msg) {
726 Ok(processed) => Some(processed),
727 Err(err) => {
728 debug!("random beacon: error while processing DKG Message: {err:?}");
729 None
730 }
731 }
732 }),
733 );
734 Ok(())
735 }
736
737 pub(crate) fn add_confirmation(
739 &mut self,
740 output: &mut ConsensusCommitOutput,
741 authority: &AuthorityName,
742 conf: VersionedDkgConfirmation,
743 ) -> IotaResult {
744 let dkg_version = self.epoch_store()?.protocol_config().dkg_version();
748 if !conf.is_valid_version(dkg_version) {
749 warn!(
750 "ignoring DKG Confirmation from authority {authority:?} with unsupported version"
751 );
752 return Ok(());
753 }
754
755 if self.dkg_output.initialized() {
756 return Ok(());
758 }
759 let Some((_, party_id)) = self.authority_info.get(authority) else {
760 error!(
761 "random beacon: received DKG Confirmation from unknown authority: {authority:?}"
762 );
763 return Ok(());
764 };
765 if *party_id != conf.sender() {
766 warn!(
767 "ignoring equivocating DKG Confirmation from authority {authority:?} pretending to be PartyId {party_id}"
768 );
769 return Ok(());
770 }
771 self.confirmations.insert(conf.sender(), conf.clone());
772 output.insert_dkg_confirmation(conf);
773 Ok(())
774 }
775
776 pub(crate) fn reserve_next_randomness(
783 &mut self,
784 commit_timestamp: CommitTimestampMs,
785 output: &mut ConsensusCommitOutput,
786 ) -> IotaResult<Option<RandomnessRound>> {
787 let epoch_store = self.epoch_store()?;
788
789 let last_round_timestamp = epoch_store
790 .get_randomness_last_round_timestamp()
791 .expect("read should not fail");
792
793 if let Some(last_round_timestamp) = last_round_timestamp {
794 if commit_timestamp - last_round_timestamp
795 < epoch_store
796 .protocol_config()
797 .random_beacon_min_round_interval_ms()
798 {
799 return Ok(None);
800 }
801 }
802
803 let randomness_round = self.next_randomness_round;
804 self.next_randomness_round = self
805 .next_randomness_round
806 .checked_add(1)
807 .ok_or_else(|| IotaError::Unknown("RandomnessRound overflow".to_string()))?;
808
809 output.reserve_next_randomness_round(self.next_randomness_round, commit_timestamp);
810
811 Ok(Some(randomness_round))
812 }
813
814 pub fn generate_randomness(&self, epoch: EpochId, randomness_round: RandomnessRound) {
816 self.network_handle
817 .send_partial_signatures(epoch, randomness_round);
818 }
819
820 pub fn dkg_status(&self) -> DkgStatus {
821 match self.dkg_output.get() {
822 Some(Some(_)) => DkgStatus::Successful,
823 Some(None) => DkgStatus::TimedOut,
824 None => DkgStatus::Pending,
825 }
826 }
827
828 fn is_past_dkg_timeout_round(
832 round: CommitRound,
833 protocol_config: &iota_protocol_config::ProtocolConfig,
834 ) -> bool {
835 round > protocol_config.random_beacon_dkg_timeout_round() as u64
836 }
837
838 pub fn dkg_status_for_commit_round(&mut self, round: CommitRound) -> IotaResult<DkgStatus> {
845 let status = self.dkg_status();
846 if status != DkgStatus::Pending {
847 return Ok(status);
848 }
849
850 let epoch_store = self.epoch_store()?;
851 if !epoch_store
852 .protocol_config()
853 .allow_dkg_completion_after_timeout()
854 || !Self::is_past_dkg_timeout_round(round, epoch_store.protocol_config())
855 {
856 return Ok(status);
857 }
858
859 if !self.dkg_timeout_reported {
860 error!(
861 "random beacon: DKG timed out; randomness-using transactions will be canceled unless/until DKG completes"
862 );
863 epoch_store.metrics.epoch_random_beacon_dkg_failed.set(1);
864 self.dkg_timeout_reported = true;
865 }
866 Ok(DkgStatus::TimedOut)
867 }
868
869 pub fn reporter(&self) -> RandomnessReporter {
872 RandomnessReporter {
873 epoch_store: self.epoch_store.clone(),
874 epoch: self.epoch,
875 network_handle: self.network_handle.clone(),
876 highest_completed_round: self.highest_completed_round.clone(),
877 }
878 }
879
880 fn epoch_store(&self) -> IotaResult<Arc<AuthorityPerEpochStore>> {
881 self.epoch_store
882 .upgrade()
883 .ok_or(IotaError::EpochEnded(self.epoch))
884 }
885
886 fn randomness_dkg_info_from_committee(
887 committee: &Committee,
888 ) -> Vec<(
889 u16,
890 AuthorityName,
891 fastcrypto_tbls::ecies_v1::PublicKey<bls12381::G2Element>,
892 StakeUnit,
893 )> {
894 committee
895 .members()
896 .map(|(name, stake)| {
897 let index: u16 = committee
898 .authority_index(name)
899 .expect("lookup of known committee member should succeed")
900 .try_into()
901 .expect("authority index should fit in u16");
902 let pk = bls12381::G2Element::from_byte_array(
903 committee
904 .public_key(name)
905 .expect("lookup of known committee member should succeed")
906 .as_bytes()
907 .try_into()
908 .expect("key length should match"),
909 )
910 .expect("should work to convert BLS key to G2Element");
911 (
912 index,
913 *name,
914 fastcrypto_tbls::ecies_v1::PublicKey::from(pk),
915 *stake,
916 )
917 })
918 .collect()
919 }
920}
921
922#[derive(Clone)]
925pub struct RandomnessReporter {
926 epoch_store: Weak<AuthorityPerEpochStore>,
927 epoch: EpochId,
928 network_handle: randomness::Handle,
929 highest_completed_round: Arc<Mutex<Option<RandomnessRound>>>,
930}
931
932impl RandomnessReporter {
933 pub fn notify_randomness_in_checkpoint(&self, round: RandomnessRound) -> IotaResult {
937 let epoch_store = self
938 .epoch_store
939 .upgrade()
940 .ok_or(IotaError::EpochEnded(self.epoch))?;
941 let mut highest_completed_round = self.highest_completed_round.lock();
942 if Some(round) > *highest_completed_round {
943 *highest_completed_round = Some(round);
944 epoch_store
945 .tables()?
946 .randomness_highest_completed_round
947 .insert(&SINGLETON_KEY, &round)?;
948 self.network_handle
949 .complete_round(epoch_store.committee().epoch(), round);
950 }
951 Ok(())
952 }
953
954 pub fn forward_randomness_from_checkpoint(&self, round: RandomnessRound, random_bytes: &[u8]) {
958 match bcs::from_bytes::<RandomnessSignature>(random_bytes) {
959 Ok(sig) => self
960 .network_handle
961 .receive_full_signature_from_checkpoint(self.epoch, round, sig),
962 Err(e) => error!(
963 "random beacon: cannot decode randomness for round {round} from a certified checkpoint: {e:?}"
964 ),
965 }
966 }
967}
968
969#[derive(Debug, Clone, Copy, PartialEq, Eq)]
970pub enum DkgStatus {
971 Pending,
972 TimedOut,
975 Successful,
976}
977
978#[cfg(test)]
979mod tests {
980 use std::num::NonZeroUsize;
981
982 use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
983 use iota_types::{crypto::AuthorityKeyPair, messages_consensus::ConsensusTransactionKind};
984 use starfish_core::{BlockRef, BlockStatus};
985 use tokio::sync::mpsc;
986
987 use crate::{
988 authority::{
989 authority_per_epoch_store::{
990 AuthorityPerEpochStore, ExecutionIndices, ExecutionIndicesWithStats,
991 },
992 test_authority_builder::TestAuthorityBuilder,
993 },
994 checkpoints::CheckpointStore,
995 consensus_adapter::{
996 ConnectionMonitorStatusForTests, ConsensusAdapter, ConsensusAdapterMetrics,
997 MockConsensusClient,
998 },
999 epoch::randomness::*,
1000 mock_consensus::with_block_status,
1001 };
1002
1003 #[tokio::test]
1004 async fn test_dkg_v1() {
1005 test_dkg(1).await;
1006 }
1007
1008 async fn test_dkg(version: u64) {
1009 telemetry_subscribers::init_for_testing();
1010
1011 let network_config =
1012 iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1013 .committee_size(NonZeroUsize::new(4).unwrap())
1014 .with_reference_gas_price(500)
1015 .build();
1016
1017 let mut protocol_config =
1018 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1019 protocol_config.set_random_beacon_dkg_version_for_testing(version);
1020
1021 let mut epoch_stores = Vec::new();
1022 let mut randomness_managers = Vec::new();
1023 let (tx_consensus, mut rx_consensus) = mpsc::channel(100);
1024
1025 for validator in network_config.validator_configs.iter() {
1026 let mut mock_consensus_client = MockConsensusClient::new();
1028 let tx_consensus = tx_consensus.clone();
1029 mock_consensus_client
1030 .expect_submit()
1031 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1032 tx_consensus.try_send(transactions.to_vec()).unwrap();
1033 true
1034 })
1035 .returning(|_, _| {
1036 Ok(with_block_status(BlockStatus::Sequenced(
1037 starfish_core::GenericTransactionRef::BlockRef(BlockRef::MIN),
1038 )))
1039 });
1040
1041 let state = TestAuthorityBuilder::new()
1042 .with_protocol_config(protocol_config.clone())
1043 .with_genesis_and_keypair(&network_config.genesis, validator.authority_key_pair())
1044 .build()
1045 .await;
1046 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1047 Arc::new(mock_consensus_client),
1048 CheckpointStore::new_for_tests(),
1049 state.name,
1050 Arc::new(ConnectionMonitorStatusForTests {}),
1051 100_000,
1052 100_000,
1053 None,
1054 None,
1055 ConsensusAdapterMetrics::new_test(),
1056 50,
1057 ));
1058 let epoch_store = state.epoch_store_for_testing();
1059 let randomness_manager = RandomnessManager::try_new(
1060 Arc::downgrade(&epoch_store),
1061 Box::new(consensus_adapter.clone()),
1062 iota_network::randomness::Handle::new_stub(),
1063 validator.authority_key_pair(),
1064 )
1065 .await
1066 .unwrap();
1067
1068 epoch_stores.push(epoch_store);
1069 randomness_managers.push(randomness_manager);
1070 }
1071
1072 let mut dkg_messages = Vec::new();
1074 for randomness_manager in randomness_managers.iter_mut() {
1075 randomness_manager.start_dkg().await.unwrap();
1076
1077 let mut dkg_message = rx_consensus.recv().await.unwrap();
1078 assert!(dkg_message.len() == 1);
1079 match dkg_message.remove(0).kind {
1080 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
1081 let msg: VersionedDkgMessage = bcs::from_bytes(&bytes)
1082 .expect("DKG message deserialization should not fail");
1083 dkg_messages.push(msg);
1084 }
1085 _ => panic!("wrong type of message sent"),
1086 }
1087 }
1088
1089 let shutdown_runtime = tokio::runtime::Builder::new_current_thread()
1092 .build()
1093 .unwrap();
1094 let shutdown_handle = shutdown_runtime.handle().clone();
1095 shutdown_runtime.shutdown_background();
1096 randomness_managers[0]
1097 .enqueued_messages
1098 .insert(0, shutdown_handle.spawn_blocking(|| None));
1099 let result = randomness_managers[0]
1100 .advance_dkg(&mut ConsensusCommitOutput::new(0, 0), 0)
1101 .await;
1102 assert!(matches!(result, Err(AdvanceDkgError::RuntimeShutdown)));
1103 assert!(randomness_managers[0].processed_messages.is_empty());
1104
1105 for i in 0..randomness_managers.len() {
1106 let mut output = ConsensusCommitOutput::new(0, 0);
1107 output.record_consensus_commit_stats(ExecutionIndicesWithStats {
1108 index: ExecutionIndices {
1109 last_committed_round: 0,
1110 ..Default::default()
1111 },
1112 ..Default::default()
1113 });
1114 for (j, dkg_message) in dkg_messages.iter().cloned().enumerate() {
1115 randomness_managers[i]
1116 .add_message(&epoch_stores[j].name, dkg_message)
1117 .unwrap();
1118 }
1119 randomness_managers[i]
1120 .advance_dkg(&mut output, 0)
1121 .await
1122 .unwrap();
1123 let mut batch = epoch_stores[i].db_batch_for_test();
1124 output.write_to_batch(&epoch_stores[i], &mut batch).unwrap();
1125 batch.write().unwrap();
1126 }
1127
1128 let mut dkg_confirmations = Vec::new();
1130 for _ in 0..randomness_managers.len() {
1131 let mut dkg_confirmation = rx_consensus.recv().await.unwrap();
1132 assert!(dkg_confirmation.len() == 1);
1133 match dkg_confirmation.remove(0).kind {
1134 ConsensusTransactionKind::RandomnessDkgConfirmation(_, bytes) => {
1135 let msg: VersionedDkgConfirmation = bcs::from_bytes(&bytes)
1136 .expect("DKG message deserialization should not fail");
1137 dkg_confirmations.push(msg);
1138 }
1139 _ => panic!("wrong type of message sent"),
1140 }
1141 }
1142 for i in 0..randomness_managers.len() {
1143 let mut output = ConsensusCommitOutput::new(0, 0);
1144 output.record_consensus_commit_stats(ExecutionIndicesWithStats {
1145 index: ExecutionIndices {
1146 last_committed_round: 1,
1147 ..Default::default()
1148 },
1149 ..Default::default()
1150 });
1151 for (j, dkg_confirmation) in dkg_confirmations.iter().cloned().enumerate() {
1152 randomness_managers[i]
1153 .add_confirmation(&mut output, &epoch_stores[j].name, dkg_confirmation)
1154 .unwrap();
1155 }
1156 randomness_managers[i]
1157 .advance_dkg(&mut output, 0)
1158 .await
1159 .unwrap();
1160 let mut batch = epoch_stores[i].db_batch_for_test();
1161 output.write_to_batch(&epoch_stores[i], &mut batch).unwrap();
1162 batch.write().unwrap();
1163 }
1164
1165 for randomness_manager in &randomness_managers {
1167 assert_eq!(DkgStatus::Successful, randomness_manager.dkg_status());
1168 }
1169 }
1170
1171 #[tokio::test]
1172 async fn test_dkg_expiration_v1() {
1173 test_dkg_expiration(1).await;
1174 }
1175
1176 async fn test_dkg_expiration(version: u64) {
1177 telemetry_subscribers::init_for_testing();
1178
1179 let network_config =
1180 iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1181 .committee_size(NonZeroUsize::new(4).unwrap())
1182 .with_reference_gas_price(500)
1183 .build();
1184
1185 let mut epoch_stores = Vec::new();
1186 let mut randomness_managers = Vec::new();
1187 let (tx_consensus, mut rx_consensus) = mpsc::channel(100);
1188
1189 let mut protocol_config =
1190 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1191 protocol_config.set_random_beacon_dkg_version_for_testing(version);
1192 protocol_config.set_allow_dkg_completion_after_timeout_for_testing(false);
1193
1194 for validator in network_config.validator_configs.iter() {
1195 let mut mock_consensus_client = MockConsensusClient::new();
1197 let tx_consensus = tx_consensus.clone();
1198 mock_consensus_client
1199 .expect_submit()
1200 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1201 tx_consensus.try_send(transactions.to_vec()).unwrap();
1202 true
1203 })
1204 .returning(|_, _| {
1205 Ok(with_block_status(starfish_core::BlockStatus::Sequenced(
1206 starfish_core::GenericTransactionRef::BlockRef(BlockRef::MIN),
1207 )))
1208 });
1209
1210 let state = TestAuthorityBuilder::new()
1211 .with_protocol_config(protocol_config.clone())
1212 .with_genesis_and_keypair(&network_config.genesis, validator.authority_key_pair())
1213 .build()
1214 .await;
1215 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1216 Arc::new(mock_consensus_client),
1217 CheckpointStore::new_for_tests(),
1218 state.name,
1219 Arc::new(ConnectionMonitorStatusForTests {}),
1220 100_000,
1221 100_000,
1222 None,
1223 None,
1224 ConsensusAdapterMetrics::new_test(),
1225 50,
1226 ));
1227 let epoch_store = state.epoch_store_for_testing();
1228 let randomness_manager = RandomnessManager::try_new(
1229 Arc::downgrade(&epoch_store),
1230 Box::new(consensus_adapter.clone()),
1231 iota_network::randomness::Handle::new_stub(),
1232 validator.authority_key_pair(),
1233 )
1234 .await
1235 .unwrap();
1236
1237 epoch_stores.push(epoch_store);
1238 randomness_managers.push(randomness_manager);
1239 }
1240
1241 let mut dkg_messages = Vec::new();
1243 for randomness_manager in randomness_managers.iter_mut() {
1244 randomness_manager.start_dkg().await.unwrap();
1245
1246 let mut dkg_message = rx_consensus.recv().await.unwrap();
1247 assert!(dkg_message.len() == 1);
1248 match dkg_message.remove(0).kind {
1249 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
1250 let msg: VersionedDkgMessage = bcs::from_bytes(&bytes)
1251 .expect("DKG message deserialization should not fail");
1252 dkg_messages.push(msg);
1253 }
1254 _ => panic!("wrong type of message sent"),
1255 }
1256 }
1257
1258 assert_eq!(
1261 DkgStatus::Pending,
1262 randomness_managers[0]
1263 .dkg_status_for_commit_round(u64::MAX)
1264 .unwrap()
1265 );
1266
1267 for i in 0..randomness_managers.len() {
1268 let mut output = ConsensusCommitOutput::new(0, 0);
1269 output.record_consensus_commit_stats(ExecutionIndicesWithStats {
1270 index: ExecutionIndices {
1271 last_committed_round: 0,
1272 ..Default::default()
1273 },
1274 ..Default::default()
1275 });
1276 for (j, dkg_message) in dkg_messages.iter().cloned().enumerate() {
1277 randomness_managers[i]
1278 .add_message(&epoch_stores[j].name, dkg_message)
1279 .unwrap();
1280 }
1281 randomness_managers[i]
1282 .advance_dkg(&mut output, u64::MAX)
1283 .await
1284 .unwrap();
1285 let mut batch = epoch_stores[i].db_batch_for_test();
1286 output.write_to_batch(&epoch_stores[i], &mut batch).unwrap();
1287 batch.write().unwrap();
1288 }
1289
1290 for randomness_manager in &randomness_managers {
1292 assert_eq!(DkgStatus::TimedOut, randomness_manager.dkg_status());
1293 }
1294
1295 for (epoch_store, validator) in epoch_stores
1302 .iter()
1303 .zip(network_config.validator_configs.iter())
1304 {
1305 let recovered = recover_randomness_manager(
1306 epoch_store,
1307 validator.authority_key_pair(),
1308 tx_consensus.clone(),
1309 )
1310 .await;
1311 assert_eq!(DkgStatus::TimedOut, recovered.dkg_status());
1312 }
1313 }
1314
1315 async fn recover_randomness_manager(
1322 epoch_store: &Arc<AuthorityPerEpochStore>,
1323 authority_key_pair: &AuthorityKeyPair,
1324 tx_consensus: mpsc::Sender<Vec<ConsensusTransaction>>,
1325 ) -> RandomnessManager {
1326 let mut mock_consensus_client = MockConsensusClient::new();
1327 mock_consensus_client
1328 .expect_submit()
1329 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1330 tx_consensus.try_send(transactions.to_vec()).unwrap();
1331 true
1332 })
1333 .returning(|_, _| {
1334 Ok(with_block_status(BlockStatus::Sequenced(
1335 starfish_core::GenericTransactionRef::BlockRef(BlockRef::MIN),
1336 )))
1337 });
1338 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1339 Arc::new(mock_consensus_client),
1340 CheckpointStore::new_for_tests(),
1341 epoch_store.name,
1342 Arc::new(ConnectionMonitorStatusForTests {}),
1343 100_000,
1344 100_000,
1345 None,
1346 None,
1347 ConsensusAdapterMetrics::new_test(),
1348 50,
1349 ));
1350 let randomness_manager = RandomnessManager::try_new(
1351 Arc::downgrade(epoch_store),
1352 Box::new(consensus_adapter.clone()),
1353 iota_network::randomness::Handle::new_stub(),
1354 authority_key_pair,
1355 )
1356 .await
1357 .unwrap();
1358 consensus_adapter.submit_recovered(epoch_store);
1359 randomness_manager
1360 }
1361
1362 #[tokio::test]
1370 async fn test_dkg_recovers_processed_messages_after_restart() {
1371 telemetry_subscribers::init_for_testing();
1372
1373 let network_config =
1374 iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1375 .committee_size(NonZeroUsize::new(4).unwrap())
1376 .with_reference_gas_price(500)
1377 .build();
1378
1379 let mut protocol_config =
1380 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1381 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1382
1383 let mut epoch_stores = Vec::new();
1384 let mut randomness_managers = Vec::new();
1385 let (tx_consensus, mut rx_consensus) = mpsc::channel(100);
1386
1387 for validator in network_config.validator_configs.iter() {
1388 let mut mock_consensus_client = MockConsensusClient::new();
1389 let tx_consensus = tx_consensus.clone();
1390 mock_consensus_client
1391 .expect_submit()
1392 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1393 tx_consensus.try_send(transactions.to_vec()).unwrap();
1394 true
1395 })
1396 .returning(|_, _| {
1397 Ok(with_block_status(BlockStatus::Sequenced(
1398 starfish_core::GenericTransactionRef::BlockRef(BlockRef::MIN),
1399 )))
1400 });
1401
1402 let state = TestAuthorityBuilder::new()
1403 .with_protocol_config(protocol_config.clone())
1404 .with_genesis_and_keypair(&network_config.genesis, validator.authority_key_pair())
1405 .build()
1406 .await;
1407 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1408 Arc::new(mock_consensus_client),
1409 CheckpointStore::new_for_tests(),
1410 state.name,
1411 Arc::new(ConnectionMonitorStatusForTests {}),
1412 100_000,
1413 100_000,
1414 None,
1415 None,
1416 ConsensusAdapterMetrics::new_test(),
1417 50,
1418 ));
1419 let epoch_store = state.epoch_store_for_testing();
1420 let randomness_manager = RandomnessManager::try_new(
1421 Arc::downgrade(&epoch_store),
1422 Box::new(consensus_adapter.clone()),
1423 iota_network::randomness::Handle::new_stub(),
1424 validator.authority_key_pair(),
1425 )
1426 .await
1427 .unwrap();
1428
1429 epoch_stores.push(epoch_store);
1430 randomness_managers.push(randomness_manager);
1431 }
1432
1433 let timeout = epoch_stores[0]
1434 .protocol_config()
1435 .random_beacon_dkg_timeout_round() as u64;
1436
1437 let mut dkg_messages = Vec::new();
1439 for randomness_manager in randomness_managers.iter_mut() {
1440 randomness_manager.start_dkg().await.unwrap();
1441
1442 let mut dkg_message = rx_consensus.recv().await.unwrap();
1443 assert!(dkg_message.len() == 1);
1444 match dkg_message.remove(0).kind {
1445 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
1446 let msg: VersionedDkgMessage = bcs::from_bytes(&bytes)
1447 .expect("DKG message deserialization should not fail");
1448 dkg_messages.push(msg);
1449 }
1450 _ => panic!("wrong type of message sent"),
1451 }
1452 }
1453 for i in 0..randomness_managers.len() {
1454 let mut output = ConsensusCommitOutput::new(0, 0);
1455 output.record_consensus_commit_stats(ExecutionIndicesWithStats {
1456 index: ExecutionIndices {
1457 last_committed_round: 0,
1458 ..Default::default()
1459 },
1460 ..Default::default()
1461 });
1462 for (j, dkg_message) in dkg_messages.iter().cloned().enumerate() {
1463 randomness_managers[i]
1464 .add_message(&epoch_stores[j].name, dkg_message)
1465 .unwrap();
1466 }
1467 randomness_managers[i]
1470 .advance_dkg(&mut output, timeout + 1)
1471 .await
1472 .unwrap();
1473 let mut batch = epoch_stores[i].db_batch_for_test();
1474 output.write_to_batch(&epoch_stores[i], &mut batch).unwrap();
1475 batch.write().unwrap();
1476 }
1477
1478 for randomness_manager in &randomness_managers {
1481 assert_eq!(DkgStatus::Pending, randomness_manager.dkg_status());
1482 assert_eq!(
1483 network_config.validator_configs.len(),
1484 randomness_manager.processed_messages.len()
1485 );
1486 assert!(randomness_manager.used_messages.initialized());
1487 }
1488 let dkg_confirmations =
1489 collect_dkg_confirmations(&mut rx_consensus, network_config.validator_configs.len())
1490 .await;
1491
1492 let mut recovered_managers = Vec::new();
1495 for (epoch_store, validator) in epoch_stores
1496 .iter()
1497 .zip(network_config.validator_configs.iter())
1498 {
1499 let mut recovered = recover_randomness_manager(
1500 epoch_store,
1501 validator.authority_key_pair(),
1502 tx_consensus.clone(),
1503 )
1504 .await;
1505 assert_eq!(DkgStatus::Pending, recovered.dkg_status());
1506 assert_eq!(
1507 network_config.validator_configs.len(),
1508 recovered.processed_messages.len()
1509 );
1510 assert!(recovered.used_messages.initialized());
1511 assert_eq!(
1512 DkgStatus::Pending,
1513 recovered.dkg_status_for_commit_round(timeout).unwrap()
1514 );
1515 assert_eq!(
1516 DkgStatus::TimedOut,
1517 recovered.dkg_status_for_commit_round(timeout + 1).unwrap()
1518 );
1519 recovered_managers.push(recovered);
1520 }
1521
1522 let mut resent_confirmations = tokio::time::timeout(
1525 std::time::Duration::from_secs(30),
1526 collect_dkg_confirmations(&mut rx_consensus, network_config.validator_configs.len()),
1527 )
1528 .await
1529 .expect("restart should send the pending DKG confirmations again");
1530 let mut sent_confirmations = dkg_confirmations.clone();
1531 resent_confirmations.sort_by_key(|conf| conf.sender());
1532 sent_confirmations.sort_by_key(|conf| conf.sender());
1533 assert_eq!(sent_confirmations, resent_confirmations);
1534
1535 for i in 0..recovered_managers.len() {
1537 let mut output = ConsensusCommitOutput::new(0, 0);
1538 output.record_consensus_commit_stats(ExecutionIndicesWithStats {
1539 index: ExecutionIndices {
1540 last_committed_round: 1,
1541 ..Default::default()
1542 },
1543 ..Default::default()
1544 });
1545 for (j, dkg_confirmation) in dkg_confirmations.iter().cloned().enumerate() {
1546 recovered_managers[i]
1547 .add_confirmation(&mut output, &epoch_stores[j].name, dkg_confirmation)
1548 .unwrap();
1549 }
1550 recovered_managers[i]
1551 .advance_dkg(&mut output, timeout + 2)
1552 .await
1553 .unwrap();
1554 let mut batch = epoch_stores[i].db_batch_for_test();
1555 output.write_to_batch(&epoch_stores[i], &mut batch).unwrap();
1556 batch.write().unwrap();
1557 }
1558 for recovered in &recovered_managers {
1559 assert_eq!(DkgStatus::Successful, recovered.dkg_status());
1560 }
1561 }
1562
1563 async fn collect_dkg_confirmations(
1566 rx_consensus: &mut mpsc::Receiver<Vec<ConsensusTransaction>>,
1567 num_validators: usize,
1568 ) -> Vec<VersionedDkgConfirmation> {
1569 let mut dkg_confirmations = Vec::new();
1570 while dkg_confirmations.len() < num_validators {
1571 let mut transactions = rx_consensus.recv().await.unwrap();
1572 assert!(transactions.len() == 1);
1573 match transactions.remove(0).kind {
1574 ConsensusTransactionKind::RandomnessDkgConfirmation(_, bytes) => {
1575 let msg: VersionedDkgConfirmation = bcs::from_bytes(&bytes)
1576 .expect("DKG confirmation deserialization should not fail");
1577 dkg_confirmations.push(msg);
1578 }
1579 ConsensusTransactionKind::RandomnessDkgMessage(..) => {}
1580 _ => panic!("wrong type of message sent"),
1581 }
1582 }
1583 dkg_confirmations
1584 }
1585
1586 #[tokio::test]
1587 async fn try_new_fails_when_reduction_lower_bound_exceeds_u16() {
1588 telemetry_subscribers::init_for_testing();
1589
1590 let network_config =
1591 iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1592 .committee_size(NonZeroUsize::new(4).unwrap())
1593 .with_reference_gas_price(500)
1594 .build();
1595
1596 let mut protocol_config =
1597 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1598 protocol_config
1599 .set_random_beacon_reduction_lower_bound_for_testing(u32::from(u16::MAX) + 1);
1600
1601 let validator = &network_config.validator_configs[0];
1602 let state = TestAuthorityBuilder::new()
1603 .with_protocol_config(protocol_config)
1604 .with_genesis_and_keypair(&network_config.genesis, validator.authority_key_pair())
1605 .build()
1606 .await;
1607 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1608 Arc::new(MockConsensusClient::new()),
1609 CheckpointStore::new_for_tests(),
1610 state.name,
1611 Arc::new(ConnectionMonitorStatusForTests {}),
1612 100_000,
1613 100_000,
1614 None,
1615 None,
1616 ConsensusAdapterMetrics::new_test(),
1617 50,
1618 ));
1619 let epoch_store = state.epoch_store_for_testing();
1620
1621 let randomness_manager = RandomnessManager::try_new(
1622 Arc::downgrade(&epoch_store),
1623 Box::new(consensus_adapter),
1624 iota_network::randomness::Handle::new_stub(),
1625 validator.authority_key_pair(),
1626 )
1627 .await;
1628
1629 let err = randomness_manager.err().expect("construction should fail");
1630 assert!(err.to_string().contains("reduction lower bound"));
1631 }
1632
1633 #[tokio::test]
1642 #[should_panic(expected = "error while processing DKG Confirmations")]
1643 async fn test_dkg_completion_error_is_fatal() {
1644 telemetry_subscribers::init_for_testing();
1645
1646 let network_config =
1647 iota_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
1648 .committee_size(NonZeroUsize::new(4).unwrap())
1649 .with_reference_gas_price(500)
1650 .build();
1651
1652 let mut protocol_config =
1653 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
1654 protocol_config.set_random_beacon_dkg_version_for_testing(1);
1655
1656 let mut epoch_stores = Vec::new();
1657 let mut randomness_managers = Vec::new();
1658 let (tx_consensus, mut rx_consensus) = mpsc::channel(100);
1659
1660 for validator in network_config.validator_configs.iter() {
1661 let mut mock_consensus_client = MockConsensusClient::new();
1662 let tx_consensus = tx_consensus.clone();
1663 mock_consensus_client
1664 .expect_submit()
1665 .withf(move |transactions: &[ConsensusTransaction], _epoch_store| {
1666 tx_consensus.try_send(transactions.to_vec()).unwrap();
1667 true
1668 })
1669 .returning(|_, _| {
1670 Ok(with_block_status(BlockStatus::Sequenced(
1671 starfish_core::GenericTransactionRef::BlockRef(BlockRef::MIN),
1672 )))
1673 });
1674
1675 let state = TestAuthorityBuilder::new()
1676 .with_protocol_config(protocol_config.clone())
1677 .with_genesis_and_keypair(&network_config.genesis, validator.authority_key_pair())
1678 .build()
1679 .await;
1680 let consensus_adapter = Arc::new(ConsensusAdapter::new(
1681 Arc::new(mock_consensus_client),
1682 CheckpointStore::new_for_tests(),
1683 state.name,
1684 Arc::new(ConnectionMonitorStatusForTests {}),
1685 100_000,
1686 100_000,
1687 None,
1688 None,
1689 ConsensusAdapterMetrics::new_test(),
1690 50,
1691 ));
1692 let epoch_store = state.epoch_store_for_testing();
1693 let randomness_manager = RandomnessManager::try_new(
1694 Arc::downgrade(&epoch_store),
1695 Box::new(consensus_adapter.clone()),
1696 iota_network::randomness::Handle::new_stub(),
1697 validator.authority_key_pair(),
1698 )
1699 .await
1700 .unwrap();
1701
1702 epoch_stores.push(epoch_store);
1703 randomness_managers.push(randomness_manager);
1704 }
1705
1706 let mut dkg_messages = Vec::new();
1708 for randomness_manager in randomness_managers.iter_mut() {
1709 randomness_manager.start_dkg().await.unwrap();
1710
1711 let mut dkg_message = rx_consensus.recv().await.unwrap();
1712 assert_eq!(dkg_message.len(), 1);
1713 match dkg_message.remove(0).kind {
1714 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
1715 let msg: VersionedDkgMessage = bcs::from_bytes(&bytes)
1716 .expect("DKG message deserialization should not fail");
1717 dkg_messages.push(msg);
1718 }
1719 _ => panic!("wrong type of message sent"),
1720 }
1721 }
1722
1723 let (faulty_manager, peer_managers) = randomness_managers.split_first_mut().unwrap();
1726 faulty_manager
1727 .used_messages
1728 .set(VersionedUsedProcessedMessages::V1(
1729 dkg_v1::UsedProcessedMessages(vec![]),
1730 ))
1731 .unwrap();
1732
1733 for randomness_manager in peer_managers.iter_mut() {
1735 let mut output = ConsensusCommitOutput::new(0, 0);
1736 for (j, dkg_message) in dkg_messages.iter().cloned().enumerate() {
1737 randomness_manager
1738 .add_message(&epoch_stores[j].name, dkg_message)
1739 .unwrap();
1740 }
1741 randomness_manager
1742 .advance_dkg(&mut output, 0)
1743 .await
1744 .unwrap();
1745 }
1746 let mut dkg_confirmations = Vec::new();
1747 for _ in 0..peer_managers.len() {
1748 let mut dkg_confirmation = rx_consensus.recv().await.unwrap();
1749 assert_eq!(dkg_confirmation.len(), 1);
1750 match dkg_confirmation.remove(0).kind {
1751 ConsensusTransactionKind::RandomnessDkgConfirmation(_, bytes) => {
1752 let msg: VersionedDkgConfirmation = bcs::from_bytes(&bytes)
1753 .expect("DKG confirmation deserialization should not fail");
1754 dkg_confirmations.push(msg);
1755 }
1756 _ => panic!("wrong type of message sent"),
1757 }
1758 }
1759
1760 let mut output = ConsensusCommitOutput::new(0, 0);
1763 for (dkg_confirmation, epoch_store) in dkg_confirmations.into_iter().zip(&epoch_stores[1..])
1764 {
1765 faulty_manager
1766 .add_confirmation(&mut output, &epoch_store.name, dkg_confirmation)
1767 .unwrap();
1768 }
1769 assert_eq!(DkgStatus::Pending, faulty_manager.dkg_status());
1770 let _ = faulty_manager.advance_dkg(&mut output, 0).await;
1771 }
1772}