Skip to main content

sui_core/
randomness_round_receiver.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::BTreeSet;
5use std::sync::Arc;
6use std::time::Duration;
7
8use parking_lot::Mutex;
9
10use fastcrypto::groups::bls12381;
11use fastcrypto_threshold::{tbls::ThresholdBls, types::ThresholdBls12381MinSig};
12use mysten_common::debug_fatal_no_invariant;
13use mysten_common::fatal;
14use mysten_metrics::spawn_monitored_task;
15use serde::{Deserialize, Serialize};
16use sui_macros::fail_point_async;
17use sui_types::committee::EpochId;
18use sui_types::crypto::RandomnessRound;
19use sui_types::crypto::RandomnessSignature;
20use sui_types::effects::TransactionEffectsAPI;
21use sui_types::executable_transaction::VerifiedExecutableTransaction;
22use sui_types::execution_status::ExecutionStatus;
23use sui_types::transaction::{TransactionKey, VerifiedTransaction};
24use tokio::sync::{broadcast, mpsc, watch};
25use tokio::task::JoinHandle;
26use tracing::{debug, info, instrument, warn};
27
28use crate::authority::AuthorityState;
29
30/// A randomness signature for a specific epoch and round, broadcast to observer peers.
31#[derive(Debug, Serialize, Deserialize, Clone)]
32pub struct RandomnessSignatureMessage {
33    pub epoch: EpochId,
34    pub round: RandomnessRound,
35    pub signature_bytes: Vec<u8>,
36}
37
38const SIGNATURES_BROADCAST_CAPACITY: usize = 1000;
39const CONSENSUS_SIGNATURES_CHANNEL_SIZE: usize = 1000;
40const EXECUTED_ROUNDS_CACHE_CAPACITY: usize = 1000;
41
42pub struct RandomnessRoundReceiverHandle {
43    consensus_signatures_tx: mysten_metrics::monitored_mpsc::Sender<bytes::Bytes>,
44    vss_pk_tx: watch::Sender<Option<bls12381::G2Element>>,
45    signatures_broadcast: broadcast::Sender<bytes::Bytes>,
46    #[cfg(test)]
47    executed_consensus_rounds: Arc<Mutex<BTreeSet<(EpochId, RandomnessRound)>>>,
48    _task_handle: JoinHandle<()>,
49}
50
51impl RandomnessRoundReceiverHandle {
52    /// Sets the VSS public key for signature verification. Called by
53    /// `RandomnessManager::advance_dkg` when DKG completes.
54    pub fn set_public_key(&self, vss_pk: bls12381::G2Element) {
55        self.vss_pk_tx.send(Some(vss_pk)).ok();
56    }
57
58    /// Clears the VSS public key. Called at epoch start before the new DKG
59    /// completes. While the key is `None`, incoming randomness round signatues from consensus
60    /// accumulate in the bounded channel giving the opportunity to buffer them until DKG completes.
61    pub fn clear_public_key(&self) {
62        self.vss_pk_tx.send(None).ok();
63    }
64}
65
66impl RandomnessRoundReceiverHandle {
67    pub fn new_for_testing() -> Arc<Self> {
68        let (consensus_signatures_tx, _) =
69            mysten_metrics::monitored_mpsc::channel("test_auxiliary", 1);
70        let (vss_pk_tx, vss_pk_rx) = watch::channel(None);
71        let (signatures_broadcast, _) = broadcast::channel(1);
72        Arc::new(Self {
73            consensus_signatures_tx,
74            vss_pk_tx,
75            signatures_broadcast,
76            #[cfg(test)]
77            executed_consensus_rounds: Arc::new(Mutex::new(BTreeSet::new())),
78            _task_handle: tokio::spawn(async move {
79                let _vss_pk_rx = vss_pk_rx;
80                futures::future::pending::<()>().await;
81            }),
82        })
83    }
84
85    #[cfg(test)]
86    pub(crate) fn public_key_for_testing(&self) -> Option<bls12381::G2Element> {
87        let rx = self.vss_pk_tx.subscribe();
88        *rx.borrow()
89    }
90
91    #[cfg(test)]
92    pub(crate) fn mark_round_executed(&self, epoch: EpochId, round: RandomnessRound) {
93        self.executed_consensus_rounds.lock().insert((epoch, round));
94    }
95}
96
97impl consensus_core::RandomnessSignatureHandler for RandomnessRoundReceiverHandle {
98    fn handle_randomness_signature(&self, data: bytes::Bytes) {
99        if let Err(e) = self.consensus_signatures_tx.try_send(data) {
100            warn!(
101                "RandomnessRoundReceiverHandle: failed to forward randomness round signature: {e}"
102            );
103        }
104    }
105
106    fn subscribe_randomness_signatures(&self) -> broadcast::Receiver<bytes::Bytes> {
107        self.signatures_broadcast.subscribe()
108    }
109}
110
111pub struct RandomnessRoundReceiver {
112    authority_state: Arc<AuthorityState>,
113    randomness_rx: mpsc::Receiver<(EpochId, RandomnessRound, Vec<u8>)>,
114    consensus_signatures_rx: mysten_metrics::monitored_mpsc::Receiver<bytes::Bytes>,
115    vss_pk_rx: watch::Receiver<Option<bls12381::G2Element>>,
116    /// Best-effort broadcast of verified signatures. Primarily used for propagating the
117    /// signatures via consensus to non-committee peers (observers) syncing their state via consensus
118    /// from a read-only capacity.
119    signatures_broadcast: broadcast::Sender<bytes::Bytes>,
120    /// Tracks rounds whose randomness transaction executed successfully via the consensus relay
121    /// path. Prevents rebroadcast loops in observer-to-observer topologies (although in practice is not anticipated having such)
122    /// while still allowing retries for rounds that failed execution.
123    executed_consensus_rounds: Arc<Mutex<BTreeSet<(EpochId, RandomnessRound)>>>,
124}
125
126impl RandomnessRoundReceiver {
127    /// Spawns the receiver loop and returns the shared handle.
128    pub fn spawn(
129        authority_state: Arc<AuthorityState>,
130        randomness_rx: mpsc::Receiver<(EpochId, RandomnessRound, Vec<u8>)>,
131    ) -> Arc<RandomnessRoundReceiverHandle> {
132        let (signatures_broadcast, _) =
133            broadcast::channel::<bytes::Bytes>(SIGNATURES_BROADCAST_CAPACITY);
134
135        let (consensus_signatures_tx, consensus_signatures_rx) =
136            mysten_metrics::monitored_mpsc::channel(
137                "consensus_randomness_round_signatures",
138                CONSENSUS_SIGNATURES_CHANNEL_SIZE,
139            );
140        let (vss_pk_tx, vss_pk_rx) = watch::channel(None);
141        let executed_consensus_rounds = Arc::new(Mutex::new(BTreeSet::new()));
142
143        let rrr = RandomnessRoundReceiver {
144            authority_state,
145            randomness_rx,
146            consensus_signatures_rx,
147            vss_pk_rx,
148            signatures_broadcast: signatures_broadcast.clone(),
149            executed_consensus_rounds: executed_consensus_rounds.clone(),
150        };
151        let task_handle = spawn_monitored_task!(rrr.run());
152
153        Arc::new(RandomnessRoundReceiverHandle {
154            consensus_signatures_tx,
155            vss_pk_tx,
156            signatures_broadcast,
157            #[cfg(test)]
158            executed_consensus_rounds,
159            _task_handle: task_handle,
160        })
161    }
162
163    async fn run(mut self) {
164        info!("RandomnessRoundReceiver event loop started");
165
166        loop {
167            let vss_pk = *self.vss_pk_rx.borrow_and_update();
168            // A key change takes priority over queued signatures. `vss_pk` was read before this
169            // select, so a signature that is already queued when the key is cleared must wait for
170            // the next iteration instead of being verified against the stale key.
171            tokio::select! {
172                biased;
173                Ok(()) = self.vss_pk_rx.changed() => {},
174                maybe_recv = self.randomness_rx.recv() => {
175                    if let Some((epoch, round, bytes)) = maybe_recv {
176                        self.handle_new_randomness(epoch, round, bytes).await;
177                    } else {
178                        break;
179                    }
180                },
181                Some(data) = self.consensus_signatures_rx.recv(), if vss_pk.is_some() => {
182                    self.handle_consensus_randomness_signature(vss_pk.unwrap(), &data).await;
183                },
184            }
185        }
186
187        info!("RandomnessRoundReceiver event loop ended");
188    }
189
190    /// Signatures that are propagated via consensus state sync. This is meant to be used from nodes that are not part of the committee
191    /// and are using consensus sync additionally to sync state.
192    async fn handle_consensus_randomness_signature(
193        &mut self,
194        vss_pk: bls12381::G2Element,
195        data: &bytes::Bytes,
196    ) {
197        let msg: RandomnessSignatureMessage = match bcs::from_bytes(data) {
198            Ok(msg) => msg,
199            Err(e) => {
200                warn!("RandomnessRoundReceiver: failed to deserialize round signature: {e}");
201                return;
202            }
203        };
204
205        let sig: RandomnessSignature = match bcs::from_bytes(&msg.signature_bytes) {
206            Ok(sig) => sig,
207            Err(e) => {
208                warn!(
209                    "RandomnessRoundReceiver: failed to deserialize signature \
210                     for epoch {} round {}: {e}",
211                    msg.epoch, msg.round
212                );
213                return;
214            }
215        };
216
217        if let Err(e) =
218            ThresholdBls12381MinSig::verify(&vss_pk, &msg.round.signature_message(), &sig)
219        {
220            warn!(
221                "RandomnessRoundReceiver: invalid auxiliary signature \
222                 for epoch {} round {}: {e}",
223                msg.epoch, msg.round
224            );
225            return;
226        }
227
228        if self
229            .executed_consensus_rounds
230            .lock()
231            .contains(&(msg.epoch, msg.round))
232        {
233            info!(
234                "RandomnessRoundReceiver: dropping already-executed auxiliary signature for epoch {} round {}",
235                msg.epoch, msg.round
236            );
237            return;
238        }
239
240        debug!(
241            "RandomnessRoundReceiver: verified auxiliary signature for epoch {} round {}",
242            msg.epoch, msg.round
243        );
244
245        self.handle_new_randomness(msg.epoch, msg.round, msg.signature_bytes)
246            .await;
247    }
248
249    #[instrument(level = "debug", skip_all, fields(?epoch, ?round))]
250    async fn handle_new_randomness(&self, epoch: EpochId, round: RandomnessRound, bytes: Vec<u8>) {
251        fail_point_async!("randomness-delay");
252
253        let epoch_store = self.authority_state.load_epoch_store_one_call_per_task();
254        if epoch_store.epoch() != epoch {
255            warn!(
256                "dropping randomness for epoch {epoch}, round {round}, because we are in epoch {}",
257                epoch_store.epoch()
258            );
259            return;
260        }
261        // Broadcast signature to connected observer peers so they can create the same
262        // transaction. This enables observer-to-observer relay chains.
263        let msg = RandomnessSignatureMessage {
264            epoch,
265            round,
266            signature_bytes: bytes.clone(),
267        };
268        match bcs::to_bytes(&msg) {
269            Ok(encoded) => {
270                let _ = self.signatures_broadcast.send(bytes::Bytes::from(encoded));
271            }
272            Err(err) => {
273                warn!("serialisation of RandomnessSignatureMessage failed: {err}");
274            }
275        }
276
277        let key = TransactionKey::RandomnessRound(epoch, round);
278        let transaction = VerifiedTransaction::new_randomness_state_update(
279            epoch,
280            round,
281            bytes,
282            epoch_store
283                .epoch_start_config()
284                .randomness_obj_initial_shared_version()
285                .expect("randomness state obj must exist"),
286        );
287        debug!(
288            "created randomness state update transaction with digest: {:?}",
289            transaction.digest()
290        );
291        let transaction = VerifiedExecutableTransaction::new_system(transaction, epoch);
292        let digest = *transaction.digest();
293
294        // Randomness state updates contain the full bls signature for the random round,
295        // which cannot necessarily be reconstructed again later. Therefore we must immediately
296        // persist this transaction. If we crash before its outputs are committed, this
297        // ensures we will be able to re-execute it.
298        self.authority_state
299            .get_cache_commit()
300            .persist_transaction(&transaction);
301
302        // Notify the scheduler that the transaction key now has a known digest
303        epoch_store.insert_tx_key(key, digest);
304
305        let authority_state = self.authority_state.clone();
306        let executed_consensus_rounds = self.executed_consensus_rounds.clone();
307        spawn_monitored_task!(async move {
308            // Wait for transaction execution in a separate task, to avoid deadlock in case of
309            // out-of-order randomness generation. (Each RandomnessStateUpdate depends on the
310            // output of the RandomnessStateUpdate from the previous round.)
311            //
312            // We set a very long timeout so that in case this gets stuck for some reason, the
313            // validator will eventually crash rather than continuing in a zombie mode.
314            const RANDOMNESS_STATE_UPDATE_EXECUTION_TIMEOUT: Duration = Duration::from_secs(300);
315            let result = tokio::time::timeout(
316                RANDOMNESS_STATE_UPDATE_EXECUTION_TIMEOUT,
317                authority_state
318                    .get_transaction_cache_reader()
319                    .notify_read_executed_effects(
320                        "RandomnessRoundReceiver::notify_read_executed_effects_first",
321                        &[digest],
322                    ),
323            )
324            .await;
325            let mut effects = match result {
326                Ok(result) => result,
327                Err(_) => {
328                    // Crash on randomness update execution timeout in debug builds.
329                    debug_fatal_no_invariant!(
330                        "randomness state update transaction execution timed out at epoch {epoch}, round {round}"
331                    );
332                    // Continue waiting as long as necessary in non-debug builds.
333                    authority_state
334                        .get_transaction_cache_reader()
335                        .notify_read_executed_effects(
336                            "RandomnessRoundReceiver::notify_read_executed_effects_second",
337                            &[digest],
338                        )
339                        .await
340                }
341            };
342
343            let effects = effects.pop().expect("should return effects");
344            if *effects.status() != ExecutionStatus::Success {
345                fatal!(
346                    "failed to execute randomness state update transaction at epoch {epoch}, round {round}: {effects:?}"
347                );
348            }
349            debug!(
350                "successfully executed randomness state update transaction at epoch {epoch}, round {round}"
351            );
352
353            let cache = &mut *executed_consensus_rounds.lock();
354            cache.insert((epoch, round));
355            while cache.len() > EXECUTED_ROUNDS_CACHE_CAPACITY {
356                cache.pop_first();
357            }
358        });
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365    use crate::authority::test_authority_builder::TestAuthorityBuilder;
366    use consensus_core::RandomnessSignatureHandler;
367    use fastcrypto::groups::{GroupElement, HashToGroupElement, bls12381};
368    use sui_macros::sim_test;
369
370    fn generate_keypair() -> (bls12381::Scalar, bls12381::G2Element) {
371        let sk = bls12381::Scalar::generator();
372        let pk = bls12381::G2Element::generator() * sk;
373        (sk, pk)
374    }
375
376    fn sign(sk: &bls12381::Scalar, msg: &[u8]) -> RandomnessSignature {
377        bls12381::G1Element::hash_to_group_element(msg) * sk
378    }
379
380    fn build_message(
381        epoch: EpochId,
382        round: RandomnessRound,
383        sig: &RandomnessSignature,
384    ) -> bytes::Bytes {
385        let msg = RandomnessSignatureMessage {
386            epoch,
387            round,
388            signature_bytes: bcs::to_bytes(sig).unwrap(),
389        };
390        bytes::Bytes::from(bcs::to_bytes(&msg).unwrap())
391    }
392
393    #[sim_test]
394    async fn test_invalid_signature_not_broadcast() {
395        let (_sk, pk) = generate_keypair();
396        let state = TestAuthorityBuilder::new().build().await;
397        let epoch = state.epoch_store_for_testing().epoch();
398
399        let (_randomness_tx, randomness_rx) = mpsc::channel(1);
400        let handle = RandomnessRoundReceiver::spawn(state, randomness_rx);
401        let mut sig_rx = handle.subscribe_randomness_signatures();
402
403        handle.set_public_key(pk);
404        tokio::task::yield_now().await;
405
406        let wrong_sk = bls12381::Scalar::generator() + bls12381::Scalar::generator();
407        let round = RandomnessRound(1);
408        let bad_sig = sign(&wrong_sk, &round.signature_message());
409        handle.handle_randomness_signature(build_message(epoch, round, &bad_sig));
410
411        let result =
412            tokio::time::timeout(std::time::Duration::from_millis(200), sig_rx.recv()).await;
413        assert!(result.is_err(), "should have timed out (no broadcast)");
414    }
415
416    #[sim_test]
417    async fn test_malformed_data_not_broadcast() {
418        let (_sk, pk) = generate_keypair();
419        let state = TestAuthorityBuilder::new().build().await;
420
421        let (_randomness_tx, randomness_rx) = mpsc::channel(1);
422        let handle = RandomnessRoundReceiver::spawn(state, randomness_rx);
423        let mut sig_rx = handle.subscribe_randomness_signatures();
424
425        handle.set_public_key(pk);
426        tokio::task::yield_now().await;
427
428        handle.handle_randomness_signature(bytes::Bytes::from(vec![0u8; 32]));
429
430        let result =
431            tokio::time::timeout(std::time::Duration::from_millis(200), sig_rx.recv()).await;
432        assert!(result.is_err(), "should have timed out (no broadcast)");
433    }
434
435    #[sim_test]
436    async fn test_signatures_buffer_until_dkg_completes() {
437        let (sk, pk) = generate_keypair();
438        let state = TestAuthorityBuilder::new().build().await;
439        let epoch = state.epoch_store_for_testing().epoch();
440
441        let (_randomness_tx, randomness_rx) = mpsc::channel(1);
442        let handle = RandomnessRoundReceiver::spawn(state, randomness_rx);
443        let mut sig_rx = handle.subscribe_randomness_signatures();
444
445        // Send before DKG completes — should buffer.
446        let round = RandomnessRound(1);
447        let sig = sign(&sk, &round.signature_message());
448        handle.handle_randomness_signature(build_message(epoch, round, &sig));
449
450        let result =
451            tokio::time::timeout(std::time::Duration::from_millis(200), sig_rx.recv()).await;
452        assert!(result.is_err(), "should have timed out (DKG not complete)");
453
454        // Now complete DKG — buffered message should be processed.
455        handle.set_public_key(pk);
456
457        let received = tokio::time::timeout(std::time::Duration::from_secs(5), sig_rx.recv())
458            .await
459            .expect("timed out")
460            .expect("channel closed");
461
462        let decoded: RandomnessSignatureMessage = bcs::from_bytes(&received).unwrap();
463        assert_eq!(decoded.epoch, epoch);
464        assert_eq!(decoded.round, round);
465    }
466
467    #[sim_test]
468    async fn test_executed_consensus_signature_not_rebroadcast() {
469        let (sk, pk) = generate_keypair();
470        let state = TestAuthorityBuilder::new().build().await;
471        let epoch = state.epoch_store_for_testing().epoch();
472
473        let (_randomness_tx, randomness_rx) = mpsc::channel(1);
474        let handle = RandomnessRoundReceiver::spawn(state, randomness_rx);
475        let mut sig_rx = handle.subscribe_randomness_signatures();
476
477        handle.set_public_key(pk);
478        tokio::task::yield_now().await;
479
480        let round = RandomnessRound(1);
481        let sig = sign(&sk, &round.signature_message());
482        let msg = build_message(epoch, round, &sig);
483
484        // Simulate that round 1 has already been executed successfully.
485        handle.mark_round_executed(epoch, round);
486
487        handle.handle_randomness_signature(msg);
488
489        let result =
490            tokio::time::timeout(std::time::Duration::from_millis(200), sig_rx.recv()).await;
491        assert!(
492            result.is_err(),
493            "already-executed round should not be rebroadcast"
494        );
495    }
496
497    #[sim_test]
498    async fn test_clear_public_key_pauses_processing() {
499        let (sk, pk) = generate_keypair();
500        let state = TestAuthorityBuilder::new().build().await;
501        let epoch = state.epoch_store_for_testing().epoch();
502
503        let (_randomness_tx, randomness_rx) = mpsc::channel(1);
504        let handle = RandomnessRoundReceiver::spawn(state, randomness_rx);
505        let mut sig_rx = handle.subscribe_randomness_signatures();
506
507        // Set key, verify a signature flows through.
508        handle.set_public_key(pk);
509        tokio::task::yield_now().await;
510
511        let round1 = RandomnessRound(1);
512        let sig1 = sign(&sk, &round1.signature_message());
513        handle.handle_randomness_signature(build_message(epoch, round1, &sig1));
514
515        tokio::time::timeout(std::time::Duration::from_secs(5), sig_rx.recv())
516            .await
517            .expect("timed out")
518            .expect("channel closed");
519
520        // Clear key — should pause.
521        handle.clear_public_key();
522        // Yield a few times so the receiver loop picks up the cleared key.
523        for _ in 0..5 {
524            tokio::task::yield_now().await;
525        }
526
527        let round2 = RandomnessRound(2);
528        let sig2 = sign(&sk, &round2.signature_message());
529        handle.handle_randomness_signature(build_message(epoch, round2, &sig2));
530
531        let result =
532            tokio::time::timeout(std::time::Duration::from_millis(200), sig_rx.recv()).await;
533        assert!(result.is_err(), "should have timed out (key cleared)");
534
535        // Re-set key — buffered message should be processed.
536        handle.set_public_key(pk);
537
538        let received = tokio::time::timeout(std::time::Duration::from_secs(5), sig_rx.recv())
539            .await
540            .expect("timed out")
541            .expect("channel closed");
542
543        let decoded: RandomnessSignatureMessage = bcs::from_bytes(&received).unwrap();
544        assert_eq!(decoded.round, round2);
545    }
546}