1use self::{auth::AllowedPeersUpdatable, metrics::Metrics};
5use anemo::PeerId;
6use anyhow::Result;
7use fastcrypto::groups::bls12381;
8use fastcrypto_tbls::{
9 dkg_v1,
10 nodes::PartyId,
11 tbls::ThresholdBls,
12 types::{ShareIndex, ThresholdBls12381MinSig},
13};
14use mysten_common::ZipDebugEqIteratorExt;
15use mysten_metrics::spawn_monitored_task;
16use mysten_network::anemo_ext::NetworkExt;
17use serde::{Deserialize, Serialize};
18use std::{
19 collections::{HashMap, HashSet, btree_map::BTreeMap},
20 ops::Bound,
21 sync::Arc,
22 time::{self, Duration},
23};
24use sui_config::p2p::RandomnessConfig;
25use sui_macros::fail_point_if;
26use sui_types::{
27 base_types::AuthorityName,
28 committee::EpochId,
29 crypto::{RandomnessPartialSignature, RandomnessRound, RandomnessSignature},
30};
31use tokio::sync::{
32 OnceCell, {mpsc, oneshot},
33};
34use tracing::{debug, error, info, instrument, warn};
35
36mod auth;
37mod builder;
38#[allow(clippy::result_large_err)]
39mod generated {
40 include!(concat!(env!("OUT_DIR"), "/sui.Randomness.rs"));
41}
42mod metrics;
43mod server;
44#[cfg(test)]
45mod tests;
46
47pub use builder::{Builder, UnstartedRandomness};
48pub use generated::{
49 randomness_client::RandomnessClient,
50 randomness_server::{Randomness, RandomnessServer},
51};
52
53#[derive(Clone, Debug, Serialize, Deserialize)]
54pub struct SendSignaturesRequest {
55 epoch: EpochId,
56 round: RandomnessRound,
57 partial_sigs: Vec<Vec<u8>>,
61 sig: Option<RandomnessSignature>,
64}
65
66#[derive(Clone, Debug)]
71pub struct Handle {
72 sender: mpsc::Sender<RandomnessMessage>,
73}
74
75impl Handle {
76 pub fn update_epoch(
79 &self,
80 new_epoch: EpochId,
81 authority_info: HashMap<AuthorityName, (PeerId, PartyId)>,
82 dkg_output: dkg_v1::Output<bls12381::G2Element, bls12381::G2Element>,
83 aggregation_threshold: u16,
84 recovered_last_completed_round: Option<RandomnessRound>, ) {
86 self.sender
87 .try_send(RandomnessMessage::UpdateEpoch(
88 new_epoch,
89 authority_info,
90 dkg_output,
91 aggregation_threshold,
92 recovered_last_completed_round,
93 ))
94 .expect("RandomnessEventLoop mailbox should not overflow or be closed")
95 }
96
97 pub fn send_partial_signatures(&self, epoch: EpochId, round: RandomnessRound) {
99 self.sender
100 .try_send(RandomnessMessage::SendPartialSignatures(epoch, round))
101 .expect("RandomnessEventLoop mailbox should not overflow or be closed")
102 }
103
104 pub fn complete_round(&self, epoch: EpochId, round: RandomnessRound) {
106 self.sender
107 .try_send(RandomnessMessage::CompleteRound(epoch, round))
108 .expect("RandomnessEventLoop mailbox should not overflow or be closed")
109 }
110
111 pub fn admin_get_partial_signatures(
114 &self,
115 round: RandomnessRound,
116 tx: oneshot::Sender<Vec<u8>>,
117 ) {
118 self.sender
119 .try_send(RandomnessMessage::AdminGetPartialSignatures(round, tx))
120 .expect("RandomnessEventLoop mailbox should not overflow or be closed")
121 }
122
123 pub fn admin_inject_partial_signatures(
126 &self,
127 authority_name: AuthorityName,
128 round: RandomnessRound,
129 sigs: Vec<RandomnessPartialSignature>,
130 result_channel: oneshot::Sender<Result<()>>,
131 ) {
132 self.sender
133 .try_send(RandomnessMessage::AdminInjectPartialSignatures(
134 authority_name,
135 round,
136 sigs,
137 result_channel,
138 ))
139 .expect("RandomnessEventLoop mailbox should not overflow or be closed")
140 }
141
142 pub fn admin_inject_full_signature(
145 &self,
146 round: RandomnessRound,
147 sig: RandomnessSignature,
148 result_channel: oneshot::Sender<Result<()>>,
149 ) {
150 self.sender
151 .try_send(RandomnessMessage::AdminInjectFullSignature(
152 round,
153 sig,
154 result_channel,
155 ))
156 .expect("RandomnessEventLoop mailbox should not overflow or be closed")
157 }
158
159 pub fn new_stub() -> Self {
161 let (sender, mut receiver) = mpsc::channel(100);
162 tokio::spawn(async move {
164 loop {
165 tokio::select! {
166 m = receiver.recv() => {
167 if m.is_none() {
168 break;
169 }
170 },
171 }
172 }
173 });
174 Self { sender }
175 }
176}
177
178#[derive(Debug)]
179enum RandomnessMessage {
180 UpdateEpoch(
181 EpochId,
182 HashMap<AuthorityName, (PeerId, PartyId)>,
183 dkg_v1::Output<bls12381::G2Element, bls12381::G2Element>,
184 u16, Option<RandomnessRound>, ),
187 SendPartialSignatures(EpochId, RandomnessRound),
188 CompleteRound(EpochId, RandomnessRound),
189 ReceiveSignatures(
190 PeerId,
191 EpochId,
192 RandomnessRound,
193 Vec<Vec<u8>>,
194 Option<RandomnessSignature>,
195 ),
196 MaybeIgnoreByzantinePeer(EpochId, PeerId),
197 AdminGetPartialSignatures(RandomnessRound, oneshot::Sender<Vec<u8>>),
198 AdminInjectPartialSignatures(
199 AuthorityName,
200 RandomnessRound,
201 Vec<RandomnessPartialSignature>,
202 oneshot::Sender<Result<()>>,
203 ),
204 AdminInjectFullSignature(
205 RandomnessRound,
206 RandomnessSignature,
207 oneshot::Sender<Result<()>>,
208 ),
209}
210
211struct RandomnessEventLoop {
212 name: AuthorityName,
213 config: RandomnessConfig,
214 mailbox: mpsc::Receiver<RandomnessMessage>,
215 mailbox_sender: mpsc::WeakSender<RandomnessMessage>,
216 network: anemo::Network,
217 allowed_peers: AllowedPeersUpdatable,
218 allowed_peers_set: HashSet<PeerId>,
219 metrics: Metrics,
220 randomness_tx: mpsc::Sender<(EpochId, RandomnessRound, Vec<u8>)>,
221
222 epoch: EpochId,
223 authority_info: Arc<HashMap<AuthorityName, (PeerId, PartyId)>>,
224 peer_share_ids: Option<HashMap<PeerId, Vec<ShareIndex>>>,
225 blocked_share_id_count: usize,
226 dkg_output: Option<dkg_v1::Output<bls12381::G2Element, bls12381::G2Element>>,
227 aggregation_threshold: u16,
228 highest_requested_round: BTreeMap<EpochId, RandomnessRound>,
229 send_tasks: BTreeMap<
230 RandomnessRound,
231 (
232 tokio::task::JoinHandle<()>,
233 Arc<OnceCell<RandomnessSignature>>,
234 ),
235 >,
236 round_request_time: BTreeMap<(EpochId, RandomnessRound), time::Instant>,
237 future_epoch_partial_sigs: BTreeMap<(EpochId, RandomnessRound, PeerId), Vec<Vec<u8>>>,
238 received_partial_sigs: BTreeMap<(RandomnessRound, PeerId), Vec<RandomnessPartialSignature>>,
239 completed_sigs: BTreeMap<RandomnessRound, RandomnessSignature>,
240 highest_completed_round: BTreeMap<EpochId, RandomnessRound>,
241}
242
243impl RandomnessEventLoop {
244 pub async fn start(mut self) {
245 info!("Randomness network event loop started");
246
247 loop {
248 tokio::select! {
249 maybe_message = self.mailbox.recv() => {
250 if let Some(message) = maybe_message {
253 self.handle_message(message);
254 } else {
255 break;
256 }
257 },
258 }
259 }
260
261 info!("Randomness network event loop ended");
262 }
263
264 fn handle_message(&mut self, message: RandomnessMessage) {
265 match message {
266 RandomnessMessage::UpdateEpoch(
267 epoch,
268 authority_info,
269 dkg_output,
270 aggregation_threshold,
271 recovered_highest_completed_round,
272 ) => {
273 if let Err(e) = self.update_epoch(
274 epoch,
275 authority_info,
276 dkg_output,
277 aggregation_threshold,
278 recovered_highest_completed_round,
279 ) {
280 error!("BUG: failed to update epoch in RandomnessEventLoop: {e:?}");
281 }
282 }
283 RandomnessMessage::SendPartialSignatures(epoch, round) => {
284 self.send_partial_signatures(epoch, round)
285 }
286 RandomnessMessage::CompleteRound(epoch, round) => self.complete_round(epoch, round),
287 RandomnessMessage::ReceiveSignatures(peer_id, epoch, round, partial_sigs, sig) => {
288 if let Some(sig) = sig {
289 self.receive_full_signature(peer_id, epoch, round, sig)
290 } else {
291 self.receive_partial_signatures(peer_id, epoch, round, partial_sigs)
292 }
293 }
294 RandomnessMessage::MaybeIgnoreByzantinePeer(epoch, peer_id) => {
295 self.maybe_ignore_byzantine_peer(epoch, peer_id)
296 }
297 RandomnessMessage::AdminGetPartialSignatures(round, tx) => {
298 self.admin_get_partial_signatures(round, tx)
299 }
300 RandomnessMessage::AdminInjectPartialSignatures(
301 authority_name,
302 round,
303 sigs,
304 result_channel,
305 ) => {
306 let _ = result_channel.send(self.admin_inject_partial_signatures(
307 authority_name,
308 round,
309 sigs,
310 ));
311 }
312 RandomnessMessage::AdminInjectFullSignature(round, sig, result_channel) => {
313 let _ = result_channel.send(self.admin_inject_full_signature(round, sig));
314 }
315 }
316 }
317
318 #[instrument(level = "debug", skip_all, fields(?new_epoch))]
319 fn update_epoch(
320 &mut self,
321 new_epoch: EpochId,
322 authority_info: HashMap<AuthorityName, (PeerId, PartyId)>,
323 dkg_output: dkg_v1::Output<bls12381::G2Element, bls12381::G2Element>,
324 aggregation_threshold: u16,
325 recovered_highest_completed_round: Option<RandomnessRound>,
326 ) -> Result<()> {
327 assert!(self.dkg_output.is_none() || new_epoch > self.epoch);
328
329 debug!("updating randomness network loop to new epoch");
330
331 self.peer_share_ids = Some(authority_info.iter().try_fold(
332 HashMap::new(),
333 |mut acc, (_name, (peer_id, party_id))| -> Result<_> {
334 let ids = dkg_output
335 .nodes
336 .share_ids_of(*party_id)
337 .expect("party_id should be valid");
338 acc.insert(*peer_id, ids);
339 Ok(acc)
340 },
341 )?);
342 self.allowed_peers_set = authority_info
343 .values()
344 .map(|(peer_id, _)| *peer_id)
345 .collect();
346 self.allowed_peers
347 .update(Arc::new(self.allowed_peers_set.clone()));
348 self.epoch = new_epoch;
349 self.authority_info = Arc::new(authority_info);
350 self.dkg_output = Some(dkg_output);
351 self.aggregation_threshold = aggregation_threshold;
352 if let Some(round) = recovered_highest_completed_round {
353 self.highest_completed_round
354 .entry(new_epoch)
355 .and_modify(|r| *r = std::cmp::max(*r, round))
356 .or_insert(round);
357 }
358 for (_, (task, _)) in std::mem::take(&mut self.send_tasks) {
359 task.abort();
360 }
361 self.metrics.set_epoch(new_epoch);
362
363 self.highest_requested_round = self.highest_requested_round.split_off(&new_epoch);
365 self.round_request_time = self
366 .round_request_time
367 .split_off(&(new_epoch, RandomnessRound(0)));
368 self.received_partial_sigs.clear();
369 self.completed_sigs.clear();
370 self.highest_completed_round = self.highest_completed_round.split_off(&new_epoch);
371
372 self.maybe_start_pending_tasks();
374
375 for ((epoch, round, peer_id), sig_bytes) in
379 std::mem::take(&mut self.future_epoch_partial_sigs)
380 {
381 self.receive_partial_signatures(peer_id, epoch, round, sig_bytes);
383 }
384 let rounds_to_aggregate: Vec<_> =
385 self.received_partial_sigs.keys().map(|(r, _)| *r).collect();
386 for round in rounds_to_aggregate {
387 self.maybe_aggregate_partial_signatures(new_epoch, round);
388 }
389
390 Ok(())
391 }
392
393 #[instrument(level = "debug", skip_all, fields(?epoch, ?round))]
394 fn send_partial_signatures(&mut self, epoch: EpochId, round: RandomnessRound) {
395 if epoch < self.epoch {
396 error!(
397 "BUG: skipping sending partial sigs, we are already up to epoch {}",
398 self.epoch
399 );
400 debug_assert!(
401 false,
402 "skipping sending partial sigs, we are already up to higher epoch"
403 );
404 return;
405 }
406 if epoch == self.epoch
407 && let Some(highest_completed_round) = self.highest_completed_round.get(&epoch)
408 && round <= *highest_completed_round
409 {
410 info!("skipping sending partial sigs, we already have completed this round");
411 return;
412 }
413
414 self.highest_requested_round
415 .entry(epoch)
416 .and_modify(|r| *r = std::cmp::max(*r, round))
417 .or_insert(round);
418 self.round_request_time
419 .insert((epoch, round), time::Instant::now());
420 self.maybe_start_pending_tasks();
421 }
422
423 #[instrument(level = "debug", skip_all, fields(?epoch, ?round))]
424 fn complete_round(&mut self, epoch: EpochId, round: RandomnessRound) {
425 debug!("completing randomness round");
426 let new_highest_round = *self
427 .highest_completed_round
428 .entry(epoch)
429 .and_modify(|r| *r = std::cmp::max(*r, round))
430 .or_insert(round);
431 if round != new_highest_round {
432 return;
435 }
436
437 self.round_request_time = self.round_request_time.split_off(&(epoch, round + 1));
438
439 if epoch == self.epoch {
440 self.remove_partial_sigs_in_range((
441 Bound::Included((RandomnessRound(0), PeerId([0; 32]))),
442 Bound::Excluded((round + 1, PeerId([0; 32]))),
443 ));
444 self.completed_sigs = self.completed_sigs.split_off(&(round + 1));
445 for (_, (task, _)) in self.send_tasks.iter().take_while(|(r, _)| **r <= round) {
446 task.abort();
447 }
448 self.send_tasks = self.send_tasks.split_off(&(round + 1));
449 self.maybe_start_pending_tasks();
450 }
451
452 self.update_rounds_pending_metric();
453 }
454
455 #[instrument(level = "debug", skip_all, fields(?peer_id, ?epoch, ?round))]
456 fn receive_partial_signatures(
457 &mut self,
458 peer_id: PeerId,
459 epoch: EpochId,
460 round: RandomnessRound,
461 sig_bytes: Vec<Vec<u8>>,
462 ) {
463 if epoch < self.epoch {
465 debug!(
466 "skipping received partial sigs, we are already up to epoch {}",
467 self.epoch
468 );
469 return;
470 }
471 if epoch > self.epoch + 1 {
472 debug!(
473 "skipping received partial sigs, we are still on epoch {}",
474 self.epoch
475 );
476 return;
477 }
478 if epoch == self.epoch && self.completed_sigs.contains_key(&round) {
479 debug!("skipping received partial sigs, we already have completed this sig");
480 return;
481 }
482 let highest_completed_round = self.highest_completed_round.get(&epoch).copied();
483 if let Some(highest_completed_round) = &highest_completed_round
484 && *highest_completed_round >= round
485 {
486 debug!("skipping received partial sigs, we already have completed this round");
487 return;
488 }
489
490 if epoch != self.epoch || self.peer_share_ids.is_none() {
493 if round.0 >= self.config.max_partial_sigs_rounds_ahead() {
494 debug!("skipping received partial sigs for future epoch, round too far ahead",);
495 return;
496 }
497
498 debug!("saving partial sigs from future epoch for later use");
499 self.future_epoch_partial_sigs
500 .insert((epoch, round, peer_id), sig_bytes);
501 return;
502 }
503
504 let peer_share_ids = self.peer_share_ids.as_ref().expect("checked above");
506 let expected_share_ids = if let Some(expected_share_ids) = peer_share_ids.get(&peer_id) {
507 expected_share_ids
508 } else {
509 debug!("received partial sigs from unknown peer");
510 return;
511 };
512 if sig_bytes.len() != expected_share_ids.len() as usize {
513 warn!(
514 "received partial sigs with wrong share ids count: expected {}, got {}",
515 expected_share_ids.len(),
516 sig_bytes.len(),
517 );
518 return;
519 }
520
521 let last_completed_signature = self.completed_sigs.last_key_value().map(|(r, _)| *r);
524 let last_completed_round = std::cmp::max(last_completed_signature, highest_completed_round)
525 .unwrap_or(RandomnessRound(0));
526 if round.0
527 >= last_completed_round
528 .0
529 .saturating_add(self.config.max_partial_sigs_rounds_ahead())
530 {
531 debug!(
532 "skipping received partial sigs, most recent round we completed was only {last_completed_round}",
533 );
534 return;
535 }
536
537 let partial_sigs =
539 match sig_bytes
540 .iter()
541 .try_fold(Vec::new(), |mut acc, bytes| -> Result<_> {
542 let sig: RandomnessPartialSignature = bcs::from_bytes(bytes)?;
543 acc.push(sig);
544 Ok(acc)
545 }) {
546 Ok(partial_sigs) => partial_sigs,
547 Err(e) => {
548 warn!("failed to deserialize partial sigs: {e:?}");
549 return;
550 }
551 };
552 let received_share_ids = partial_sigs.iter().map(|s| s.index);
555 if received_share_ids
556 .zip_debug_eq(expected_share_ids.iter())
557 .any(|(a, b)| a != *b)
558 {
559 let received_share_ids = partial_sigs.iter().map(|s| s.index).collect::<Vec<_>>();
560 warn!(
561 "received partial sigs with wrong share ids: expected {expected_share_ids:?}, received {received_share_ids:?}"
562 );
563 return;
564 }
565
566 debug!("recording received partial signatures");
568 self.received_partial_sigs
569 .insert((round, peer_id), partial_sigs);
570
571 self.maybe_aggregate_partial_signatures(epoch, round);
572 }
573
574 #[instrument(level = "debug", skip_all, fields(?epoch, ?round))]
575 fn maybe_aggregate_partial_signatures(&mut self, epoch: EpochId, round: RandomnessRound) {
576 if let Some(highest_completed_round) = self.highest_completed_round.get(&epoch)
577 && round <= *highest_completed_round
578 {
579 info!("skipping aggregation for already-completed round");
580 return;
581 }
582
583 let highest_requested_round = self.highest_requested_round.get(&epoch);
584 if highest_requested_round.is_none() || round > *highest_requested_round.unwrap() {
585 debug!(
590 "waiting to aggregate randomness partial signatures until local consensus catches up"
591 );
592 return;
593 }
594
595 if epoch != self.epoch {
596 debug!(
597 "waiting to aggregate randomness partial signatures until DKG completes for epoch"
598 );
599 return;
600 }
601
602 if self.completed_sigs.contains_key(&round) {
603 info!("skipping aggregation for already-completed signature");
604 return;
605 }
606
607 let vss_pk = {
608 let Some(dkg_output) = &self.dkg_output else {
609 debug!("called maybe_aggregate_partial_signatures before DKG completed");
610 return;
611 };
612 &dkg_output.vss_pk
613 };
614
615 let sig_bounds = (
616 Bound::Included((round, PeerId([0; 32]))),
617 Bound::Excluded((round + 1, PeerId([0; 32]))),
618 );
619
620 let sig_range = self
622 .received_partial_sigs
623 .range(sig_bounds)
624 .flat_map(|(_, sigs)| sigs);
625 let mut sig =
626 match ThresholdBls12381MinSig::aggregate(self.aggregation_threshold, sig_range) {
627 Ok(sig) => sig,
628 Err(fastcrypto::error::FastCryptoError::NotEnoughInputs) => return, Err(e) => {
630 error!("error while aggregating randomness partial signatures: {e:?}");
631 return;
632 }
633 };
634
635 if ThresholdBls12381MinSig::verify(&vss_pk.c0(), &round.signature_message(), &sig).is_err()
637 {
638 self.received_partial_sigs
642 .retain(|&(r, peer_id), partial_sigs| {
643 if round != r {
644 return true;
645 }
646 if ThresholdBls12381MinSig::partial_verify_batch(
647 vss_pk,
648 &round.signature_message(),
649 partial_sigs.iter(),
650 &mut rand::thread_rng(),
651 )
652 .is_err()
653 {
654 warn!(
655 "received invalid partial signatures from possibly-Byzantine peer {peer_id}"
656 );
657 if let Some(sender) = self.mailbox_sender.upgrade() {
658 sender.try_send(RandomnessMessage::MaybeIgnoreByzantinePeer(
659 epoch,
660 peer_id,
661 ))
662 .expect("RandomnessEventLoop mailbox should not overflow or be closed");
663 }
664 return false;
665 }
666 true
667 });
668 let sig_range = self
669 .received_partial_sigs
670 .range(sig_bounds)
671 .flat_map(|(_, sigs)| sigs);
672 sig = match ThresholdBls12381MinSig::aggregate(self.aggregation_threshold, sig_range) {
673 Ok(sig) => sig,
674 Err(fastcrypto::error::FastCryptoError::NotEnoughInputs) => return, Err(e) => {
676 error!("error while aggregating randomness partial signatures: {e:?}");
677 return;
678 }
679 };
680 if let Err(e) =
681 ThresholdBls12381MinSig::verify(&vss_pk.c0(), &round.signature_message(), &sig)
682 {
683 error!(
684 "error while verifying randomness partial signatures after removing invalid partials: {e:?}"
685 );
686 debug_assert!(
687 false,
688 "error while verifying randomness partial signatures after removing invalid partials"
689 );
690 return;
691 }
692 }
693
694 debug!("successfully generated randomness full signature");
695 self.process_valid_full_signature(epoch, round, sig);
696 }
697
698 #[instrument(level = "debug", skip_all, fields(?peer_id, ?epoch, ?round))]
699 fn receive_full_signature(
700 &mut self,
701 peer_id: PeerId,
702 epoch: EpochId,
703 round: RandomnessRound,
704 sig: RandomnessSignature,
705 ) {
706 let vss_pk = {
707 let Some(dkg_output) = &self.dkg_output else {
708 debug!("called receive_full_signature before DKG completed");
709 return;
710 };
711 &dkg_output.vss_pk
712 };
713
714 if epoch != self.epoch {
716 debug!("skipping received full sig, we are on epoch {}", self.epoch);
717 return;
718 }
719 if self.completed_sigs.contains_key(&round) {
720 debug!("skipping received full sigs, we already have completed this sig");
721 return;
722 }
723 let highest_completed_round = self.highest_completed_round.get(&epoch).copied();
724 if let Some(highest_completed_round) = &highest_completed_round
725 && *highest_completed_round >= round
726 {
727 debug!("skipping received full sig, we already have completed this round");
728 return;
729 }
730
731 let highest_requested_round = self.highest_requested_round.get(&epoch);
732 if highest_requested_round.is_none() || round > *highest_requested_round.unwrap() {
733 debug!(
735 "skipping received full signature, local consensus is not caught up to its round"
736 );
737 return;
738 }
739
740 if let Err(e) =
741 ThresholdBls12381MinSig::verify(&vss_pk.c0(), &round.signature_message(), &sig)
742 {
743 info!("received invalid full signature from peer {peer_id}: {e:?}");
744 if let Some(sender) = self.mailbox_sender.upgrade() {
745 sender
746 .try_send(RandomnessMessage::MaybeIgnoreByzantinePeer(epoch, peer_id))
747 .expect("RandomnessEventLoop mailbox should not overflow or be closed");
748 }
749 return;
750 }
751
752 debug!("received valid randomness full signature");
753 self.process_valid_full_signature(epoch, round, sig);
754 }
755
756 fn process_valid_full_signature(
757 &mut self,
758 epoch: EpochId,
759 round: RandomnessRound,
760 sig: RandomnessSignature,
761 ) {
762 assert_eq!(epoch, self.epoch);
763
764 if let Some((_, full_sig_cell)) = self.send_tasks.get(&round) {
765 full_sig_cell
766 .set(sig)
767 .expect("full signature should never be processed twice");
768 }
769 self.completed_sigs.insert(round, sig);
770 self.remove_partial_sigs_in_range((
771 Bound::Included((round, PeerId([0; 32]))),
772 Bound::Excluded((round + 1, PeerId([0; 32]))),
773 ));
774 self.metrics.record_completed_round(round);
775 if let Some(start_time) = self.round_request_time.get(&(epoch, round))
776 && let Some(metric) = self.metrics.round_generation_latency_metric()
777 {
778 metric.observe(start_time.elapsed().as_secs_f64());
779 }
780
781 let sig_bytes = bcs::to_bytes(&sig).expect("signature serialization should not fail");
782 if let Err(e) = self.randomness_tx.try_send((epoch, round, sig_bytes)) {
783 match e {
784 mpsc::error::TrySendError::Closed(_) => {
786 info!("dropping completed randomness round {round}: receiver channel closed");
787 }
788 mpsc::error::TrySendError::Full(_) => {
790 panic!("RandomnessRoundReceiver mailbox should not overflow");
791 }
792 }
793 }
794 }
795
796 fn maybe_ignore_byzantine_peer(&mut self, epoch: EpochId, peer_id: PeerId) {
797 if epoch != self.epoch {
798 return; }
800 let Some(dkg_output) = &self.dkg_output else {
801 return; };
803 if !self.allowed_peers_set.contains(&peer_id) {
804 return; }
806 let Some(peer_share_ids) = &self.peer_share_ids else {
807 return; };
809 let Some(peer_shares) = peer_share_ids.get(&peer_id) else {
810 warn!("can't ignore unknown byzantine peer {peer_id:?}");
811 return;
812 };
813 let max_ignored_shares = (self.config.max_ignored_peer_weight_factor()
814 * (dkg_output.nodes.total_weight() as f64)) as usize;
815 if self.blocked_share_id_count + peer_shares.len() > max_ignored_shares {
816 warn!(
817 "ignoring byzantine peer {peer_id:?} with {} shares would exceed max ignored peer weight {max_ignored_shares}",
818 peer_shares.len()
819 );
820 return;
821 }
822
823 warn!(
824 "ignoring byzantine peer {peer_id:?} with {} shares",
825 peer_shares.len()
826 );
827 self.blocked_share_id_count += peer_shares.len();
828 self.allowed_peers_set.remove(&peer_id);
829 self.allowed_peers
830 .update(Arc::new(self.allowed_peers_set.clone()));
831 self.metrics.inc_num_ignored_byzantine_peers();
832 }
833
834 fn maybe_start_pending_tasks(&mut self) {
835 let dkg_output = if let Some(dkg_output) = &self.dkg_output {
836 dkg_output
837 } else {
838 return; };
840 let shares = if let Some(shares) = &dkg_output.shares {
841 shares
842 } else {
843 return; };
845 let highest_requested_round =
846 if let Some(highest_requested_round) = self.highest_requested_round.get(&self.epoch) {
847 highest_requested_round
848 } else {
849 return; };
851 let start_round = std::cmp::max(
854 if let Some(highest_completed_round) = self.highest_completed_round.get(&self.epoch) {
855 highest_completed_round.checked_add(1).unwrap()
856 } else {
857 RandomnessRound(0)
858 },
859 self.send_tasks
860 .last_key_value()
861 .map(|(r, _)| r.checked_add(1).unwrap())
862 .unwrap_or(RandomnessRound(0)),
863 );
864
865 let mut rounds_to_aggregate = Vec::new();
866 for round in start_round.0..=highest_requested_round.0 {
867 let round = RandomnessRound(round);
868
869 if self.send_tasks.len() >= self.config.max_partial_sigs_concurrent_sends() {
870 break; }
872
873 let full_sig_cell = Arc::new(OnceCell::new());
874 self.send_tasks.entry(round).or_insert_with(|| {
875 let name = self.name;
876 let network = self.network.clone();
877 let retry_interval = self.config.partial_signature_retry_interval();
878 let metrics = self.metrics.clone();
879 let authority_info = self.authority_info.clone();
880 let epoch = self.epoch;
881 let partial_sigs = ThresholdBls12381MinSig::partial_sign_batch(
882 shares.iter(),
883 &round.signature_message(),
884 );
885 let full_sig_cell_clone = full_sig_cell.clone();
886
887 if !self.completed_sigs.contains_key(&round) {
889 self.received_partial_sigs
890 .insert((round, self.network.peer_id()), partial_sigs.clone());
891 rounds_to_aggregate.push((epoch, round));
892 }
893
894 debug!("sending partial sigs for epoch {epoch}, round {round}");
895 (
896 spawn_monitored_task!(RandomnessEventLoop::send_signatures_task(
897 name,
898 network,
899 retry_interval,
900 metrics,
901 authority_info,
902 epoch,
903 round,
904 partial_sigs,
905 full_sig_cell_clone,
906 )),
907 full_sig_cell,
908 )
909 });
910 }
911
912 self.update_rounds_pending_metric();
913
914 for (epoch, round) in rounds_to_aggregate {
917 self.maybe_aggregate_partial_signatures(epoch, round);
918 }
919 }
920
921 #[allow(clippy::type_complexity)]
922 fn remove_partial_sigs_in_range(
923 &mut self,
924 range: (
925 Bound<(RandomnessRound, PeerId)>,
926 Bound<(RandomnessRound, PeerId)>,
927 ),
928 ) {
929 let keys_to_remove: Vec<_> = self
930 .received_partial_sigs
931 .range(range)
932 .map(|(key, _)| *key)
933 .collect();
934 for key in keys_to_remove {
935 self.received_partial_sigs.remove(&key);
937 }
938 }
939
940 async fn send_signatures_task(
941 name: AuthorityName,
942 network: anemo::Network,
943 retry_interval: Duration,
944 metrics: Metrics,
945 authority_info: Arc<HashMap<AuthorityName, (PeerId, PartyId)>>,
946 epoch: EpochId,
947 round: RandomnessRound,
948 partial_sigs: Vec<RandomnessPartialSignature>,
949 full_sig: Arc<OnceCell<RandomnessSignature>>,
950 ) {
951 #[allow(unused_mut)]
953 let mut fail_point_skip_sending = false;
954 fail_point_if!("rb-send-partial-signatures", || {
955 fail_point_skip_sending = true;
956 });
957 if fail_point_skip_sending {
958 warn!("skipping sending partial sigs due to simtest fail point");
959 return;
960 }
961
962 let _metrics_guard = metrics
963 .round_observation_latency_metric()
964 .map(|metric| metric.start_timer());
965
966 let peers: HashMap<_, _> = authority_info
967 .iter()
968 .map(|(name, (peer_id, _party_id))| (name, network.waiting_peer(*peer_id)))
969 .collect();
970 let partial_sigs: Vec<_> = partial_sigs
971 .iter()
972 .map(|sig| bcs::to_bytes(sig).expect("message serialization should not fail"))
973 .collect();
974
975 loop {
976 let mut requests = Vec::new();
977 for (peer_name, peer) in &peers {
978 if name == **peer_name {
979 continue; }
981 let mut client = RandomnessClient::new(peer.clone());
982 const SEND_PARTIAL_SIGNATURES_TIMEOUT: Duration = Duration::from_secs(10);
983 let full_sig = full_sig.get().cloned();
984 let request = anemo::Request::new(SendSignaturesRequest {
985 epoch,
986 round,
987 partial_sigs: if full_sig.is_none() {
988 partial_sigs.clone()
989 } else {
990 Vec::new()
991 },
992 sig: full_sig,
993 })
994 .with_timeout(SEND_PARTIAL_SIGNATURES_TIMEOUT);
995 requests.push(async move {
996 let result = client.send_signatures(request).await;
997 if let Err(_error) = result {
998 debug!("failed to send partial signatures to {peer_name}");
1000 }
1001 });
1002 }
1003
1004 futures::future::join_all(requests).await;
1006
1007 tokio::time::sleep(retry_interval).await;
1009 }
1010 }
1011
1012 fn update_rounds_pending_metric(&self) {
1013 let highest_requested_round = self
1014 .highest_requested_round
1015 .get(&self.epoch)
1016 .map(|r| r.0)
1017 .unwrap_or(0);
1018 let highest_completed_round = self
1019 .highest_completed_round
1020 .get(&self.epoch)
1021 .map(|r| r.0)
1022 .unwrap_or(0);
1023 let num_rounds_pending =
1024 highest_requested_round.saturating_sub(highest_completed_round) as i64;
1025 let prev_value = self.metrics.num_rounds_pending().unwrap_or_default();
1026 if num_rounds_pending / 100 > prev_value / 100 {
1027 warn!(
1028 "RandomnessEventLoop randomness generation backlog: over {} rounds are pending (oldest is {:?})",
1030 (num_rounds_pending / 100) * 100,
1031 highest_completed_round + 1,
1032 );
1033 }
1034 self.metrics.set_num_rounds_pending(num_rounds_pending);
1035 }
1036
1037 fn admin_get_partial_signatures(&self, round: RandomnessRound, tx: oneshot::Sender<Vec<u8>>) {
1038 let shares = if let Some(shares) = self.dkg_output.as_ref().and_then(|d| d.shares.as_ref())
1039 {
1040 shares
1041 } else {
1042 let _ = tx.send(Vec::new()); return;
1044 };
1045
1046 let partial_sigs =
1047 ThresholdBls12381MinSig::partial_sign_batch(shares.iter(), &round.signature_message());
1048 let _ = tx.send(bcs::to_bytes(&partial_sigs).expect("serialization should not fail"));
1050 }
1051
1052 fn admin_inject_partial_signatures(
1053 &mut self,
1054 authority_name: AuthorityName,
1055 round: RandomnessRound,
1056 sigs: Vec<RandomnessPartialSignature>,
1057 ) -> Result<()> {
1058 let peer_id = self
1059 .authority_info
1060 .get(&authority_name)
1061 .map(|(peer_id, _)| *peer_id)
1062 .ok_or(anyhow::anyhow!("unknown AuthorityName {authority_name:?}"))?;
1063 self.received_partial_sigs.insert((round, peer_id), sigs);
1064 self.maybe_aggregate_partial_signatures(self.epoch, round);
1065 Ok(())
1066 }
1067
1068 fn admin_inject_full_signature(
1069 &mut self,
1070 round: RandomnessRound,
1071 sig: RandomnessSignature,
1072 ) -> Result<()> {
1073 let vss_pk = {
1074 let Some(dkg_output) = &self.dkg_output else {
1075 return Err(anyhow::anyhow!(
1076 "called admin_inject_full_signature before DKG completed"
1077 ));
1078 };
1079 &dkg_output.vss_pk
1080 };
1081
1082 ThresholdBls12381MinSig::verify(&vss_pk.c0(), &round.signature_message(), &sig)
1083 .map_err(|e| anyhow::anyhow!("invalid full signature: {e:?}"))?;
1084
1085 self.process_valid_full_signature(self.epoch, round, sig);
1086 Ok(())
1087 }
1088}