use crate::{ simplex::{ scheme::Scheme, types::{Attributable, Finalize, Notarize, Nullify, Proposal, Subject, Vote}, }, types::Participant, }; use commonware_cryptography::{certificate::Verification, Digest}; use commonware_parallel::Strategy; use commonware_utils::ordered::Set; use rand::rngs::StdRng; use rand_core::{CryptoRng, SeedableRng}; use std::{mem, sync::Arc}; use tracing::{Instrument as _, Span}; /// Runs a CPU-bound job through [Strategy::spawn], entering `span` on the worker thread and /// instrumenting the awaited future so the offloaded work stays attributed to the caller's trace. pub(super) async fn offload
(span: Span, strategy: &P, job: F) -> T
where
P: Strategy,
F: FnOnce(P) -> T + Send + 'static,
T: Send + 'static,
{
let worker_span = span.clone();
strategy
.spawn(move |strategy| worker_span.in_scope(|| job(strategy)))
.instrument(span)
.await
}
/// `Verifier` is a utility for tracking and verifying consensus messages.
///
/// For schemes where [`Verifier::is_batchable()`](commonware_cryptography::certificate::Verifier::is_batchable)
/// returns `true` (such as [ed25519], [bls12381_multisig] and [bls12381_threshold]), this struct collects
/// messages and defers verification until enough messages exist to potentially reach a quorum, enabling
/// efficient batch verification. For schemes where `is_batchable()` returns `false` (such as [secp256r1]),
/// signatures are verified eagerly as they arrive since there is no batching benefit.
///
/// To avoid unnecessary verification, it also tracks the number of already verified messages (ensuring
/// we no longer attempt to verify messages after a quorum of valid messages have already been verified).
///
/// Once polled, async verification moves the pending batch and accumulated verified votes into
/// the worker. Do not cancel an in-flight verification unless the verifier will also be discarded.
///
/// [ed25519]: crate::simplex::scheme::ed25519
/// [bls12381_multisig]: crate::simplex::scheme::bls12381_multisig
/// [bls12381_threshold]: crate::simplex::scheme::bls12381_threshold
/// [secp256r1]: crate::simplex::scheme::secp256r1
pub struct Verifier,
/// Required quorum size.
quorum: usize,
/// Current leader index.
leader: Option {
/// Creates a new `Verifier`.
///
/// # Arguments
///
/// * `scheme` - Scheme handle used to verify and aggregate votes.
/// * `quorum` - Number of votes (2f+1) required to reach a quorum.
pub fn new(scheme: impl Into {
Arc::clone(&self.scheme)
}
/// Takes verified notarize votes once they reach quorum.
pub(super) fn take_verified_notarizes(&mut self) -> Option, verified: bool) {
match msg {
Vote::Notarize(notarize) => {
if let Some(ref leader_proposal) = self.leader_proposal {
// If leader proposal is set and the message is not for it, drop it
if leader_proposal != ¬arize.proposal {
return;
}
} else if let Some(leader) = self.leader {
// If leader is set but leader proposal is not, set it
if leader == notarize.signer() {
// Set the leader proposal
self.set_leader_proposal(notarize.proposal.clone());
}
}
// If we've made it this far, add the notarize
if verified {
self.verified_notarizes.push(notarize);
} else {
self.pending_notarizes.push(notarize);
}
}
Vote::Nullify(nullify) => {
if verified {
self.verified_nullifies.push(nullify);
} else {
self.pending_nullifies.push(nullify);
}
}
Vote::Finalize(finalize) => {
// If leader proposal is set and the message is not for it, drop it
if let Some(ref leader_proposal) = self.leader_proposal {
if leader_proposal != &finalize.proposal {
return;
}
}
// If we've made it this far, add the finalize
if verified {
self.verified_finalizes.push(finalize);
} else {
self.pending_finalizes.push(finalize);
}
}
}
}
/// Sets the leader for the current consensus view.
///
/// If a notarize vote from the leader has already been received, this will
/// also set the leader's proposal, filtering out any pending votes for other
/// proposals.
pub fn set_leader(&mut self, leader: Participant) {
assert!(self.leader.is_none());
self.leader = Some(leader);
// If we already have the leader's vote, set the leader proposal
let Some(notarize) = self
.pending_notarizes
.iter()
.chain(&self.verified_notarizes)
.find(|n| n.signer() == leader)
else {
return;
};
self.set_leader_proposal(notarize.proposal.clone());
}
/// Verifies a batch of pending [Vote::Notarize] messages.
///
/// It uses `S::verify_attestations` for efficient batch verification, run as one CPU-bound job
/// submitted through [Strategy::spawn] so a parallel strategy hosts it on its own pool
/// instead of occupying the calling task.
///
/// # Arguments
///
/// * `rng` - Randomness source used by schemes that require batching randomness.
///
/// # Returns
///
/// A tuple containing the number of votes processed and the signer indices for whom
/// verification failed.
pub async fn verify_notarizes {
let proposal = Proposal::new(round, parent_view, sample_digest(payload_val));
Notarize::sign(scheme, proposal).unwrap()
}
// Helper to create a Nullify message for any signing scheme
fn create_nullify {
Nullify::sign:: {
let proposal = Proposal::new(round, parent_view, sample_digest(payload_val));
Finalize::sign(scheme, proposal).unwrap()
}
fn add_notarize(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let notarize1 = create_notarize(&schemes[0], round, View::new(0), 1);
let notarize2 = create_notarize(&schemes[1], round, View::new(0), 1);
let notarize_diff = create_notarize(&schemes[2], round, View::new(0), 2);
verifier.add(Vote::Notarize(notarize1.clone()), false);
assert_eq!(verifier.pending_notarizes.len(), 1);
assert_eq!(verifier.verified_notarizes.len(), 0);
verifier.add(Vote::Notarize(notarize1.clone()), true);
assert_eq!(verifier.pending_notarizes.len(), 1);
assert_eq!(verifier.verified_notarizes.len(), 1);
verifier.set_leader(notarize1.signer());
assert!(verifier.leader_proposal.is_some());
assert_eq!(
verifier.leader_proposal.as_ref().unwrap(),
¬arize1.proposal
);
assert_eq!(verifier.pending_notarizes.len(), 1);
verifier.add(Vote::Notarize(notarize2), false);
assert_eq!(verifier.pending_notarizes.len(), 2);
verifier.add(Vote::Notarize(notarize_diff), false);
assert_eq!(verifier.pending_notarizes.len(), 2);
let mut verifier2 = Verifier::::new(schemes[0].clone(), quorum);
let round2 = Round::new(Epoch::new(0), View::new(2));
let notarize_non_leader = create_notarize(&schemes[1], round2, View::new(1), 3);
let notarize_leader = create_notarize(&schemes[0], round2, View::new(1), 3);
verifier2.set_leader(notarize_leader.signer());
verifier2.add(Vote::Notarize(notarize_non_leader), false);
assert!(verifier2.leader_proposal.is_none());
assert_eq!(verifier2.pending_notarizes.len(), 1);
verifier2.add(Vote::Notarize(notarize_leader.clone()), false);
assert!(verifier2.leader_proposal.is_some());
assert_eq!(
verifier2.leader_proposal.as_ref().unwrap(),
¬arize_leader.proposal
);
assert_eq!(verifier2.pending_notarizes.len(), 2);
}
#[test]
fn test_add_notarize() {
add_notarize(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let leader_notarize = create_notarize(&schemes[0], round, View::new(0), 1);
let other_notarize = create_notarize(&schemes[1], round, View::new(0), 1);
verifier.add(Vote::Notarize(other_notarize), false);
assert_eq!(verifier.pending_notarizes.len(), 1);
let leader = leader_notarize.signer();
verifier.set_leader(leader);
assert_eq!(verifier.leader, Some(leader));
assert!(verifier.leader_proposal.is_none());
assert_eq!(verifier.pending_notarizes.len(), 1);
verifier.add(Vote::Notarize(leader_notarize.clone()), false);
assert!(verifier.leader_proposal.is_some());
assert_eq!(
verifier.leader_proposal.as_ref().unwrap(),
&leader_notarize.proposal
);
assert_eq!(verifier.pending_notarizes.len(), 2);
let mut verifier2 = Verifier::::new(schemes[0].clone(), quorum);
verifier2.add(Vote::Notarize(leader_notarize.clone()), true);
verifier2.set_leader(leader);
assert_eq!(
verifier2.leader_proposal.as_ref().unwrap(),
&leader_notarize.proposal
);
}
#[test]
fn test_set_leader() {
set_leader(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let notarizes: Vec<_> = schemes
.iter()
.map(|scheme| create_notarize(scheme, round, View::new(0), 1))
.collect();
assert!(!verifier.ready_notarizes());
verifier.set_leader(notarizes[0].signer());
verifier.add(Vote::Notarize(notarizes[0].clone()), false);
// Non-batchable schemes verify immediately when pending votes exist
assert_eq!(!verifier.ready_notarizes(), S::is_batchable());
assert_eq!(verifier.pending_notarizes.len(), 1);
verifier.add(Vote::Notarize(notarizes[1].clone()), false);
assert_eq!(!verifier.ready_notarizes(), S::is_batchable());
verifier.add(Vote::Notarize(notarizes[2].clone()), false);
assert_eq!(!verifier.ready_notarizes(), S::is_batchable());
verifier.add(Vote::Notarize(notarizes[3].clone()), false);
assert!(verifier.ready_notarizes());
assert_eq!(verifier.pending_notarizes.len(), 4);
let (batch, failed_bulk) = verifier.verify_notarizes(&mut rng, &Sequential).await;
assert_eq!(batch, 4);
assert!(failed_bulk.is_empty());
assert_eq!(verifier.verified_notarizes.len(), 4);
assert!(verifier.pending_notarizes.is_empty());
assert!(!verifier.ready_notarizes());
let mut verifier2 = Verifier::::new(schemes[0].clone(), quorum);
let round2 = Round::new(Epoch::new(0), View::new(2));
let leader_vote = create_notarize(&schemes[0], round2, View::new(1), 10);
let mut faulty_vote = create_notarize(&schemes[1], round2, View::new(1), 10);
verifier2.set_leader(leader_vote.signer());
verifier2.add(Vote::Notarize(leader_vote.clone()), false);
faulty_vote.attestation.signer = Participant::from_usize(schemes.len() + 10);
verifier2.add(Vote::Notarize(faulty_vote.clone()), false);
for scheme in schemes.iter().skip(2).take(quorum as usize - 2) {
verifier2.add(
Vote::Notarize(create_notarize(scheme, round2, View::new(1), 10)),
false,
);
}
assert!(verifier2.ready_notarizes());
let (batch, failed_second) = verifier2.verify_notarizes(&mut rng, &Sequential).await;
assert_eq!(batch, quorum as usize);
assert!(verifier2
.verified_notarizes
.iter()
.any(|notarize| notarize == &leader_vote));
assert_eq!(failed_second, vec![faulty_vote.signer()]);
}
#[test_async]
async fn test_ready_and_verify_notarizes() {
ready_and_verify_notarizes(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let nullify = create_nullify(&schemes[0], round);
verifier.add(Vote::Nullify(nullify.clone()), false);
assert_eq!(verifier.pending_nullifies.len(), 1);
assert_eq!(verifier.verified_nullifies.len(), 0);
verifier.add(Vote::Nullify(nullify), true);
assert_eq!(verifier.pending_nullifies.len(), 1);
assert_eq!(verifier.verified_nullifies.len(), 1);
}
#[test]
fn test_add_nullify() {
add_nullify(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let nullifies: Vec<_> = schemes
.iter()
.map(|scheme| create_nullify(scheme, round))
.collect();
verifier.add(Vote::Nullify(nullifies[0].clone()), true);
assert_eq!(verifier.verified_nullifies.len(), 1);
verifier.add(Vote::Nullify(nullifies[1].clone()), false);
// Non-batchable schemes verify immediately when pending votes exist
assert_eq!(!verifier.ready_nullifies(), S::is_batchable());
verifier.add(Vote::Nullify(nullifies[2].clone()), false);
assert_eq!(!verifier.ready_nullifies(), S::is_batchable());
verifier.add(Vote::Nullify(nullifies[3].clone()), false);
assert!(verifier.ready_nullifies());
assert_eq!(verifier.pending_nullifies.len(), 3);
let (batch, failed) = verifier.verify_nullifies(&mut rng, &Sequential).await;
assert_eq!(batch, 3);
assert!(failed.is_empty());
assert_eq!(verifier.verified_nullifies.len(), 4);
assert!(verifier.pending_nullifies.is_empty());
assert!(!verifier.ready_nullifies());
}
#[test_async]
async fn test_ready_and_verify_nullifies() {
ready_and_verify_nullifies(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let finalize_a = create_finalize(&schemes[0], round, View::new(0), 1);
let finalize_b = create_finalize(&schemes[1], round, View::new(0), 2);
verifier.add(Vote::Finalize(finalize_b.clone()), false);
assert_eq!(verifier.pending_finalizes.len(), 1);
assert_eq!(verifier.verified_finalizes.len(), 0);
verifier.add(Vote::Finalize(finalize_a.clone()), false);
assert_eq!(verifier.pending_finalizes.len(), 2);
verifier.set_leader(finalize_a.signer());
assert!(verifier.leader_proposal.is_none());
verifier.set_leader_proposal(finalize_a.proposal.clone());
assert_eq!(verifier.pending_finalizes.len(), 1);
assert_eq!(verifier.pending_finalizes[0], finalize_a);
assert_eq!(verifier.verified_finalizes.len(), 0);
verifier.add(Vote::Finalize(finalize_a), true);
assert_eq!(verifier.pending_finalizes.len(), 1);
assert_eq!(verifier.verified_finalizes.len(), 1);
verifier.add(Vote::Finalize(finalize_b), false);
assert_eq!(verifier.pending_finalizes.len(), 1);
assert_eq!(verifier.verified_finalizes.len(), 1);
}
#[test]
fn test_add_finalize() {
add_finalize(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let finalizes: Vec<_> = schemes
.iter()
.map(|scheme| create_finalize(scheme, round, View::new(0), 1))
.collect();
assert!(!verifier.ready_finalizes());
verifier.set_leader(finalizes[0].signer());
verifier.set_leader_proposal(finalizes[0].proposal.clone());
verifier.add(Vote::Finalize(finalizes[0].clone()), true);
assert_eq!(verifier.verified_finalizes.len(), 1);
assert!(verifier.pending_finalizes.is_empty());
verifier.add(Vote::Finalize(finalizes[1].clone()), false);
// Non-batchable schemes verify immediately when pending votes exist
assert_eq!(!verifier.ready_finalizes(), S::is_batchable());
verifier.add(Vote::Finalize(finalizes[2].clone()), false);
assert_eq!(!verifier.ready_finalizes(), S::is_batchable());
verifier.add(Vote::Finalize(finalizes[3].clone()), false);
assert!(verifier.ready_finalizes());
let (batch, failed) = verifier.verify_finalizes(&mut rng, &Sequential).await;
assert_eq!(batch, 3);
assert!(failed.is_empty());
assert_eq!(verifier.verified_finalizes.len(), 4);
assert!(verifier.pending_finalizes.is_empty());
assert!(!verifier.ready_finalizes());
}
#[test_async]
async fn test_ready_and_verify_finalizes() {
ready_and_verify_finalizes(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 3);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let proposal_a = Proposal::new(round, View::new(0), sample_digest(10));
let proposal_b = Proposal::new(round, View::new(0), sample_digest(20));
let notarize_a = Notarize::sign(&schemes[0], proposal_a.clone()).unwrap();
let notarize_b = Notarize::sign(&schemes[1], proposal_b.clone()).unwrap();
let finalize_a = Finalize::sign(&schemes[0], proposal_a.clone()).unwrap();
let finalize_b = Finalize::sign(&schemes[1], proposal_b).unwrap();
verifier.add(Vote::Notarize(notarize_a.clone()), false);
verifier.add(Vote::Notarize(notarize_b.clone()), false);
verifier.add(Vote::Notarize(notarize_a.clone()), true);
verifier.add(Vote::Notarize(notarize_b), true);
verifier.add(Vote::Finalize(finalize_a.clone()), false);
verifier.add(Vote::Finalize(finalize_b.clone()), false);
verifier.add(Vote::Finalize(finalize_a), true);
verifier.add(Vote::Finalize(finalize_b), true);
assert_eq!(verifier.pending_notarizes.len(), 2);
assert_eq!(verifier.verified_notarizes.len(), 2);
assert_eq!(verifier.pending_finalizes.len(), 2);
assert_eq!(verifier.verified_finalizes.len(), 2);
verifier.set_leader(notarize_a.signer());
assert_eq!(verifier.pending_notarizes.len(), 1);
assert_eq!(verifier.pending_notarizes[0].proposal, proposal_a);
assert_eq!(verifier.verified_notarizes.len(), 1);
assert_eq!(verifier.verified_notarizes[0].proposal, proposal_a);
assert_eq!(verifier.pending_finalizes.len(), 1);
assert_eq!(verifier.pending_finalizes[0].proposal, proposal_a);
assert_eq!(verifier.verified_finalizes.len(), 1);
assert_eq!(verifier.verified_finalizes[0].proposal, proposal_a);
}
#[test]
fn test_leader_proposal_filters_messages() {
leader_proposal_filters_messages(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 3);
let mut verifier = Verifier::::new(schemes[0].clone(), 3);
verifier.set_leader(Participant::new(0));
verifier.set_leader(Participant::new(1));
}
#[test]
#[should_panic(expected = "self.leader.is_none()")]
fn test_set_leader_twice_panics_bls_threshold_minsig() {
set_leader_twice_panics(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let leader_vote = create_notarize(&schemes[0], round, View::new(0), 1);
verifier.set_leader(leader_vote.signer());
verifier.add(Vote::Notarize(leader_vote), false);
// Non-batchable schemes verify immediately when pending votes exist
assert_eq!(
!verifier.ready_notarizes(),
S::is_batchable(),
"Batchable schemes wait for quorum, non-batchable verify immediately"
);
for scheme in schemes.iter().skip(1).take(quorum as usize - 1) {
verifier.add(
Vote::Notarize(create_notarize(scheme, round, View::new(0), 1)),
false,
);
}
assert!(verifier.ready_notarizes(), "Should be ready at quorum");
let (batch, _) = verifier.verify_notarizes(&mut rng, &Sequential).await;
assert_eq!(batch, quorum as usize);
assert!(!verifier.ready_notarizes());
}
#[test_async]
async fn test_notarizes_wait_for_quorum() {
notarizes_wait_for_quorum(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 3);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let notarizes: Vec<_> = schemes
.iter()
.take(quorum as usize)
.map(|scheme| create_notarize(scheme, round, View::new(0), 1))
.collect();
for vote in notarizes.iter() {
verifier.add(Vote::Notarize(vote.clone()), false);
}
assert!(
!verifier.ready_notarizes(),
"Should not be ready without leader/proposal set"
);
verifier.set_leader(notarizes[0].signer());
assert!(
verifier.ready_notarizes(),
"Should be ready once leader is set"
);
}
#[test]
fn test_ready_notarizes_without_leader_or_proposal() {
ready_notarizes_without_leader(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 3);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let finalizes: Vec<_> = schemes
.iter()
.take(quorum as usize)
.map(|scheme| create_finalize(scheme, round, View::new(0), 1))
.collect();
for finalize in finalizes.iter() {
verifier.add(Vote::Finalize(finalize.clone()), false);
}
assert!(
!verifier.ready_finalizes(),
"Should not be ready without leader/proposal set"
);
verifier.set_leader(finalizes[0].signer());
assert!(
!verifier.ready_finalizes(),
"Should not be ready without leader_proposal set"
);
}
#[test]
fn test_ready_finalizes_without_leader_or_proposal() {
ready_finalizes_without_leader(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 3);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let leader_proposal = Proposal::new(round, View::new(0), sample_digest(1));
verifier.set_leader_proposal(leader_proposal);
assert!(verifier.pending_notarizes.is_empty());
assert!(!verifier.ready_notarizes());
}
#[test]
fn test_verify_notarizes_empty_pending_when_forced() {
verify_notarizes_empty(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 3);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
assert!(verifier.pending_nullifies.is_empty());
assert!(!verifier.ready_nullifies());
let (batch, failed) = verifier.verify_nullifies(&mut rng, &Sequential).await;
assert_eq!(batch, 0);
assert!(failed.is_empty());
assert_eq!(verifier.verified_nullifies.len(), 0);
}
#[test_async]
async fn test_verify_nullifies_empty_pending() {
verify_nullifies_empty(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 3);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
verifier.set_leader(Participant::new(0));
assert!(verifier.pending_finalizes.is_empty());
assert!(!verifier.ready_finalizes());
let (batch, failed) = verifier.verify_finalizes(&mut rng, &Sequential).await;
assert_eq!(batch, 0);
assert!(failed.is_empty());
assert_eq!(verifier.verified_finalizes.len(), 0);
}
#[test_async]
async fn test_verify_finalizes_empty_pending() {
verify_finalizes_empty(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let leader_vote = create_notarize(&schemes[0], round, View::new(0), 1);
verifier.set_leader(leader_vote.signer());
verifier.add(Vote::Notarize(leader_vote), true);
assert_eq!(verifier.verified_notarizes.len(), 1);
for (i, scheme) in schemes.iter().enumerate().skip(1).take(quorum as usize - 1) {
let is_last = i == quorum as usize - 1;
verifier.add(
Vote::Notarize(create_notarize(scheme, round, View::new(0), 1)),
false,
);
if is_last {
assert!(
verifier.ready_notarizes(),
"Should be ready at exact quorum"
);
} else if S::is_batchable() {
// Batchable schemes wait for quorum
assert!(!verifier.ready_notarizes());
} else {
// Non-batchable schemes verify immediately when pending votes exist
assert!(verifier.ready_notarizes());
}
}
let (batch, failed) = verifier.verify_notarizes(&mut rng, &Sequential).await;
assert_eq!(batch, quorum as usize - 1);
assert!(failed.is_empty());
assert_eq!(verifier.verified_notarizes.len(), quorum as usize);
assert!(!verifier.ready_notarizes());
}
#[test_async]
async fn test_ready_notarizes_exact_quorum() {
ready_notarizes_exact_quorum(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
verifier.add(Vote::Nullify(create_nullify(&schemes[0], round)), true);
assert_eq!(verifier.verified_nullifies.len(), 1);
let pending_schemes: Vec<_> = schemes.iter().take(quorum as usize).skip(1).collect();
for (i, scheme) in pending_schemes.iter().enumerate() {
let is_last = i == pending_schemes.len() - 1;
verifier.add(Vote::Nullify(create_nullify(scheme, round)), false);
if is_last {
assert!(verifier.ready_nullifies());
} else if S::is_batchable() {
// Batchable schemes wait for quorum
assert!(!verifier.ready_nullifies());
} else {
// Non-batchable schemes verify immediately when pending votes exist
assert!(verifier.ready_nullifies());
}
}
}
#[test]
fn test_ready_nullifies_exact_quorum() {
ready_nullifies_exact_quorum(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
let leader_finalize = create_finalize(&schemes[0], round, View::new(0), 1);
verifier.set_leader(leader_finalize.signer());
verifier.set_leader_proposal(leader_finalize.proposal.clone());
verifier.add(Vote::Finalize(leader_finalize), true);
assert_eq!(verifier.verified_finalizes.len(), 1);
let pending_schemes: Vec<_> = schemes.iter().take(quorum as usize).skip(1).collect();
for (i, scheme) in pending_schemes.iter().enumerate() {
let is_last = i == pending_schemes.len() - 1;
verifier.add(
Vote::Finalize(create_finalize(scheme, round, View::new(0), 1)),
false,
);
if is_last {
assert!(verifier.ready_finalizes());
} else if S::is_batchable() {
// Batchable schemes wait for quorum
assert!(!verifier.ready_finalizes());
} else {
// Non-batchable schemes verify immediately when pending votes exist
assert!(verifier.ready_finalizes());
}
}
}
#[test]
fn test_ready_finalizes_exact_quorum() {
ready_finalizes_exact_quorum(bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
assert!(
schemes.len() > quorum as usize,
"test requires more validators than the quorum"
);
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
// Pre-load the leader vote as if it had already been processed.
let leader_vote = create_notarize(&schemes[0], round, View::new(0), 1);
verifier.set_leader(leader_vote.signer());
verifier.add(Vote::Notarize(leader_vote), false);
// Mark enough verified notarizes to satisfy the quorum outright.
for scheme in schemes.iter().take(quorum as usize) {
verifier.add(
Vote::Notarize(create_notarize(scheme, round, View::new(0), 1)),
true,
);
}
assert_eq!(verifier.verified_notarizes.len(), quorum as usize);
assert!(
!verifier.ready_notarizes(),
"Should not be ready if quorum already met by verified messages"
);
// Additional pending votes must not flip readiness in this situation.
let extra_vote = create_notarize(&schemes[quorum as usize], round, View::new(0), 1);
verifier.add(Vote::Notarize(extra_vote), false);
assert!(
!verifier.ready_notarizes(),
"Should not be ready if quorum already met by verified messages"
);
}
#[test]
fn test_ready_notarizes_quorum_already_met_by_verified() {
ready_notarizes_quorum_already_met_by_verified(
bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
assert!(
schemes.len() > quorum as usize,
"test requires more validators than the quorum"
);
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
// First mark a quorum's worth of verified nullifies.
for scheme in schemes.iter().take(quorum as usize) {
verifier.add(Vote::Nullify(create_nullify(scheme, round)), true);
}
assert_eq!(verifier.verified_nullifies.len(), quorum as usize);
assert!(
!verifier.ready_nullifies(),
"Should not be ready if quorum already met by verified messages"
);
// Pending messages alone cannot transition the batch to ready.
let extra_nullify = create_nullify(&schemes[quorum as usize], round);
verifier.add(Vote::Nullify(extra_nullify), false);
assert!(
!verifier.ready_nullifies(),
"Should not be ready if quorum already met by verified messages"
);
}
#[test]
fn test_ready_nullifies_quorum_already_met_by_verified() {
ready_nullifies_quorum_already_met_by_verified(
bls12381_threshold_vrf::fixture::(mut fixture: F)
where
S: Scheme,
{
let mut rng = test_rng();
let Fixture { schemes, .. } = fixture(&mut rng, NAMESPACE, 5);
let quorum = N3f1::quorum(schemes.len());
assert!(
schemes.len() > quorum as usize,
"test requires more validators than the quorum"
);
let mut verifier = Verifier::::new(schemes[0].clone(), quorum);
let round = Round::new(Epoch::new(0), View::new(1));
// Prime the leader state so the quorum is already satisfied by verified finalizes.
let leader_finalize = create_finalize(&schemes[0], round, View::new(0), 1);
verifier.set_leader(leader_finalize.signer());
verifier.set_leader_proposal(leader_finalize.proposal);
// Feed exactly the number of verified finalizes required to hit the quorum.
for scheme in schemes.iter().take(quorum as usize) {
verifier.add(
Vote::Finalize(create_finalize(scheme, round, View::new(0), 1)),
true,
);
}
assert_eq!(verifier.verified_finalizes.len(), quorum as usize);
assert!(
!verifier.ready_finalizes(),
"Should not be ready if quorum already met by verified messages"
);
// Ensure additional pending finalizes do not incorrectly trigger readiness.
let extra_finalize = create_finalize(&schemes[quorum as usize], round, View::new(0), 1);
verifier.add(Vote::Finalize(extra_finalize), false);
assert!(
!verifier.ready_finalizes(),
"Should not be ready if quorum already met by verified messages"
);
}
#[test]
fn test_ready_finalizes_quorum_already_met_by_verified() {
ready_finalizes_quorum_already_met_by_verified(
bls12381_threshold_vrf::fixture::