Skip to main content

sui_network/randomness/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    // BCS-serialized `RandomnessPartialSignature` values. We store raw bytes here to enable
58    // defenses against too-large messages.
59    // The protocol requires the signatures to be ordered by share index (as provided by fastcrypto).
60    partial_sigs: Vec<Vec<u8>>,
61    // If peer already has a full signature available for the round, it's provided here in lieu
62    // of partial sigs.
63    sig: Option<RandomnessSignature>,
64}
65
66/// A handle to the Randomness network subsystem.
67///
68/// This handle can be cloned and shared. Once all copies of a Randomness system's Handle have been
69/// dropped, the Randomness system will be gracefully shutdown.
70#[derive(Clone, Debug)]
71pub struct Handle {
72    sender: mpsc::Sender<RandomnessMessage>,
73}
74
75impl Handle {
76    /// Transitions the Randomness system to a new epoch. Cancels all partial signature sends for
77    /// prior epochs.
78    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>, // set to None if not starting up mid-epoch
85    ) {
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    /// Begins transmitting partial signatures for the given epoch and round until completed.
98    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    /// Records the given round as complete, stopping any partial signature sends.
105    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    /// Admin interface handler: generates partial signatures for the given round at the
112    /// current epoch.
113    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    /// Admin interface handler: injects partial signatures for the given round at the
124    /// current epoch, skipping validity checks.
125    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    /// Admin interface handler: injects full signature for the given round at the
143    /// current epoch, skipping validity checks.
144    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    // For testing.
160    pub fn new_stub() -> Self {
161        let (sender, mut receiver) = mpsc::channel(100);
162        // Keep receiver open until all senders are closed.
163        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,                     // aggregation_threshold
185        Option<RandomnessRound>, // recovered_highest_completed_round
186    ),
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                    // Once all handles to our mailbox have been dropped this
251                    // will yield `None` and we can terminate the event loop.
252                    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        // Throw away info from old epochs.
364        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        // Start any pending tasks for the new epoch.
373        self.maybe_start_pending_tasks();
374
375        // Aggregate any sigs received early from the new epoch.
376        // (We can't call `maybe_aggregate_partial_signatures` directly while iterating,
377        // because it takes `&mut self`, so we store in a Vec first.)
378        for ((epoch, round, peer_id), sig_bytes) in
379            std::mem::take(&mut self.future_epoch_partial_sigs)
380        {
381            // We can fully validate these now that we have current epoch DKG output.
382            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            // This round completion came out of order, and we're already ahead. Nothing more
433            // to do in that case.
434            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        // Basic validity checks.
464        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 sigs are for a future epoch, we can't fully verify them without DKG output.
491        // Save them for later use.
492        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        // Verify shape of sigs matches what we expect for the peer.
505        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        // Accept partial signatures up to `max_partial_sigs_rounds_ahead` past the round of the
522        // last completed signature, or the highest completed round, whichever is greater.
523        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        // Deserialize the partial sigs.
538        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        // Verify we received the expected share IDs (to protect against a validator that sends
553        // valid signatures of other peers which will be successfully verified below).
554        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        // We passed all the checks, save the partial sigs.
567        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            // We have to wait here, because even if we have enough information from other nodes
586            // to complete the signature, local shared object versions are not set until consensus
587            // finishes processing the corresponding commit. This function will be called again
588            // after maybe_start_pending_tasks begins this round locally.
589            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        // If we have enough partial signatures, aggregate them.
621        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, // wait for more input
629                Err(e) => {
630                    error!("error while aggregating randomness partial signatures: {e:?}");
631                    return;
632                }
633            };
634
635        // Try to verify the aggregated signature all at once. (Should work in the happy path.)
636        if ThresholdBls12381MinSig::verify(&vss_pk.c0(), &round.signature_message(), &sig).is_err()
637        {
638            // If verifiation fails, some of the inputs must be invalid. We have to go through
639            // one-by-one to find which.
640            // TODO: add test for individual sig verification.
641            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, // wait for more input
675                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        // Basic validity checks.
715        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            // Wait for local consensus to catch up if necessary.
734            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                // Receiver is torn down during node shutdown; dropping the round is harmless.
785                mpsc::error::TrySendError::Closed(_) => {
786                    info!("dropping completed randomness round {round}: receiver channel closed");
787                }
788                // Mailbox capacity is huge (default 1M); a full mailbox means a real bug.
789                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; // make sure we're still on the same epoch
799        }
800        let Some(dkg_output) = &self.dkg_output else {
801            return; // can't ignore a peer if we haven't finished DKG
802        };
803        if !self.allowed_peers_set.contains(&peer_id) {
804            return; // peer is already disallowed
805        }
806        let Some(peer_share_ids) = &self.peer_share_ids else {
807            return; // can't ignore a peer if we haven't finished DKG
808        };
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; // wait for DKG
839        };
840        let shares = if let Some(shares) = &dkg_output.shares {
841            shares
842        } else {
843            return; // can't participate in randomness generation without shares
844        };
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; // no rounds to start
850            };
851        // Begin from the next round after the most recent one we've started (or, if none are running,
852        // after the highest completed round in the epoch).
853        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; // limit concurrent tasks
871            }
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                // Record own partial sigs.
888                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        // After starting a round, we have generated our own partial sigs. Check if that's
915        // enough for us to aggregate already.
916        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            // Have to remove keys one-by-one because BTreeMap does not support range-removal.
936            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        // For simtests, we may test not sending partial signatures.
952        #[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; // don't send partial sigs to self
980                }
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                        // TODO: add Display impl to anemo::rpc::Status, log it here
999                        debug!("failed to send partial signatures to {peer_name}");
1000                    }
1001                });
1002            }
1003
1004            // Process all requests.
1005            futures::future::join_all(requests).await;
1006
1007            // Keep retrying send to all peers until task is aborted via external message.
1008            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                // Recording multiples of 100 so tests can match on the log message.
1029                "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()); // no error handling needed if receiver is already dropped
1043            return;
1044        };
1045
1046        let partial_sigs =
1047            ThresholdBls12381MinSig::partial_sign_batch(shares.iter(), &round.signature_message());
1048        // no error handling needed if receiver is already dropped
1049        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}