Skip to main content

hotshot_new_protocol/
vote.rs

1//! Per-view vote collection and certificate formation.
2//!
3//! Votes for views arrive concurrently, and each view is tallied on its own
4//! until it crosses a threshold and forms a certificate. [`VoteCollector`]
5//! owns the machinery common to every kind of vote, inspecting each vote only
6//! through the [`Ballot`] trait. It creates a task per view and voted epoch
7//! and routes votes to the appropriate task, drops duplicate and stale votes,
8//! buffers votes whose epoch is not yet resolved, and GCs decided views. How
9//! a task actually combines votes into an output is delegated to a pluggable
10//! [`Tally`] strategy.
11
12mod accumulate;
13
14use std::{
15    any::{type_name, type_name_of_val},
16    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
17    marker::PhantomData,
18    mem,
19    panic::resume_unwind,
20};
21
22pub(crate) use accumulate::{Cert, CheckedAccumulator};
23use alloy::primitives::U256;
24use committable::Committable;
25use hotshot_types::{
26    data::{EpochNumber, ViewNumber},
27    epoch_membership::{EpochMembership, EpochMembershipCoordinator},
28    message::UpgradeLock,
29    simple_certificate::{
30        LightClientStateUpdateCertificateV2, QuorumCertificate2, SuccessThreshold, Threshold,
31        TimeoutCertificate3, UpgradeThreshold,
32    },
33    simple_vote::{
34        HasEpoch, QuorumVote2, SimpleVote, TimeoutLock, UpgradeVote2, VersionedVoteData, Voteable,
35    },
36    stake_table::StakeTableEntries,
37    traits::{
38        node_implementation::NodeType,
39        signature_key::{SignatureKey, StakeTableEntryType},
40    },
41    vote::{Certificate, HasViewNumber, LightClientStateUpdateVoteAccumulator, Vote},
42};
43use hotshot_utils::anytrace;
44use tokio::{sync::mpsc, task::spawn_blocking};
45use tokio_util::task::JoinMap;
46use tracing::{error, info, warn};
47
48use crate::{
49    cert_verifier::{ValidCert, verify_signatures, verify_timeout3},
50    message::{Certificate1, TimeoutBallot, Vote1},
51};
52
53/// Information about a vote.
54///
55/// Used by [`VoteCollector`] to handle incoming votes, i.e. reject
56/// duplicate votes by the same signer, or stale votes for GCed views,
57/// before even tallying the vote.
58pub trait Ballot {
59    type Signer;
60
61    fn view(&self) -> ViewNumber;
62    fn epoch(&self) -> Option<EpochNumber>;
63    fn signer(&self) -> Self::Signer;
64
65    /// The epochs whose committees counting the vote reads, its own included.
66    ///
67    /// The vote waits until every one of them is known.
68    fn epochs(&self) -> impl Iterator<Item = EpochNumber> {
69        self.epoch().into_iter()
70    }
71}
72
73/// How to count votes to form a certificate.
74pub trait Tally<T: NodeType>: Send + 'static {
75    type Vote: Send + 'static;
76    type Output: Send + 'static;
77
78    fn new(m: EpochMembership<T>, l: UpgradeLock<T>) -> Self;
79
80    fn add(&mut self, vote: Self::Vote) -> Option<Self::Output>;
81
82    fn min_votes(&self) -> usize;
83
84    fn has_signer(m: &EpochMembership<T>, signer: &T::SignatureKey) -> bool;
85}
86
87impl<T: NodeType, D> Ballot for SimpleVote<T, D>
88where
89    D: Voteable<T> + HasEpoch + 'static,
90{
91    type Signer = T::SignatureKey;
92
93    fn view(&self) -> ViewNumber {
94        self.view_number()
95    }
96
97    fn epoch(&self) -> Option<EpochNumber> {
98        HasEpoch::epoch(self)
99    }
100
101    fn signer(&self) -> Self::Signer {
102        self.signing_key()
103    }
104}
105
106impl<T: NodeType> Ballot for Vote1<T> {
107    type Signer = T::SignatureKey;
108
109    fn view(&self) -> ViewNumber {
110        self.vote.view_number()
111    }
112
113    fn epoch(&self) -> Option<EpochNumber> {
114        HasEpoch::epoch(&self.vote)
115    }
116
117    fn signer(&self) -> Self::Signer {
118        self.vote.signing_key()
119    }
120}
121
122/// Accumulates votes into a single [`Certificate`] via a [`CheckedAccumulator`].
123pub struct SimpleTally<T, V, Th>
124where
125    T: NodeType,
126    V: Vote<T>,
127    Th: Threshold<T>,
128    Cert<T, V, Th>: Certificate<T, V::Commitment, Voteable = V::Commitment> + HasEpoch,
129{
130    accumulator: CheckedAccumulator<T, V, Th>,
131}
132
133impl<T, V, Th> Tally<T> for SimpleTally<T, V, Th>
134where
135    T: NodeType,
136    V: Vote<T> + Send + 'static,
137    V::Commitment: 'static,
138    Th: Threshold<T> + Send + 'static,
139    Cert<T, V, Th>:
140        Certificate<T, V::Commitment, Voteable = V::Commitment> + HasEpoch + Send + 'static,
141{
142    type Vote = V;
143    type Output = ValidCert<Cert<T, V, Th>>;
144
145    fn new(m: EpochMembership<T>, l: UpgradeLock<T>) -> Self {
146        Self {
147            accumulator: CheckedAccumulator::new(m, l),
148        }
149    }
150
151    fn min_votes(&self) -> usize {
152        self.accumulator.min_votes()
153    }
154
155    fn has_signer(m: &EpochMembership<T>, signer: &T::SignatureKey) -> bool {
156        Cert::<T, V, Th>::stake_table_entry(m, signer).is_some()
157    }
158
159    fn add(&mut self, vote: V) -> Option<Self::Output> {
160        let cert = self.accumulator.add(vote)?;
161        let Some(epoch) = cert.epoch() else {
162            warn!(
163                cert = type_name::<Cert<T, V, Th>>(),
164                "certificate has no epoch number"
165            );
166            return None;
167        };
168        Some(ValidCert::new(cert, epoch))
169    }
170}
171
172/// Accumulates [`UpgradeVote2`]s into an `UpgradeCertificate2`, under the
173/// epoch the votes bind.
174pub type UpgradeTally<T> = SimpleTally<T, UpgradeVote2<T>, UpgradeThreshold>;
175
176/// The quorum and light-client state certificates formed at an epoch-root view.
177pub type EpochRootCerts<T> = (
178    ValidCert<QuorumCertificate2<T>>,
179    LightClientStateUpdateCertificateV2<T>,
180);
181
182/// Accumulates epoch-root [`Vote1`]s into the (quorum, state) certificate pair.
183pub struct EpochRootTally<T: NodeType> {
184    membership: EpochMembership<T>,
185    quorum: CheckedAccumulator<T, QuorumVote2<T>, SuccessThreshold>,
186    state: LightClientStateUpdateVoteAccumulator<T>,
187    quorum_cert: Option<QuorumCertificate2<T>>,
188    state_cert: Option<LightClientStateUpdateCertificateV2<T>>,
189}
190
191impl<T: NodeType> Tally<T> for EpochRootTally<T> {
192    type Vote = Vote1<T>;
193    type Output = EpochRootCerts<T>;
194
195    fn new(m: EpochMembership<T>, l: UpgradeLock<T>) -> Self {
196        Self {
197            quorum: CheckedAccumulator::new(m.clone(), l.clone()),
198            state: LightClientStateUpdateVoteAccumulator {
199                vote_outcomes: HashMap::new(),
200                upgrade_lock: l,
201            },
202            membership: m,
203            quorum_cert: None,
204            state_cert: None,
205        }
206    }
207
208    fn min_votes(&self) -> usize {
209        self.quorum.min_votes()
210    }
211
212    fn has_signer(m: &EpochMembership<T>, signer: &T::SignatureKey) -> bool {
213        QuorumCertificate2::<T>::stake_table_entry(m, signer).is_some()
214    }
215
216    fn add(&mut self, vote1: Vote1<T>) -> Option<Self::Output> {
217        let Some(state_vote) = vote1.state_vote else {
218            error!(view = %vote1.vote.view_number(), "epoch-root vote1 without state vote");
219            return None;
220        };
221        let bls_key = vote1.vote.signing_key();
222
223        if self.quorum_cert.is_none() {
224            self.quorum_cert = self.quorum.add(vote1.vote);
225        }
226
227        // Unlike quorum votes, state votes are fully checked, including
228        // their signatures, by the accumulator, so the certificate does
229        // not need to be validated again.
230        if self.state_cert.is_none() {
231            self.state_cert = self
232                .state
233                .accumulate(&bls_key, &state_vote, &self.membership);
234        }
235
236        let (Some(q), Some(s)) = (&self.quorum_cert, &self.state_cert) else {
237            return None;
238        };
239        info!(view = %q.view_number(), epoch = %s.epoch, "epoch-root certificates formed");
240        let Some(e) = q.epoch() else {
241            warn!(
242                cert = type_name_of_val(q),
243                "certificate has no epoch number"
244            );
245            return None;
246        };
247        Some((ValidCert::new(q.clone(), e), s.clone()))
248    }
249}
250
251impl<T: NodeType> Ballot for TimeoutBallot<T> {
252    type Signer = T::SignatureKey;
253
254    fn view(&self) -> ViewNumber {
255        self.vote().view_number()
256    }
257
258    fn epoch(&self) -> Option<EpochNumber> {
259        Some(self.vote().data.epoch)
260    }
261
262    fn signer(&self) -> Self::Signer {
263        self.vote().signing_key()
264    }
265
266    /// Its own epoch, and its lock's if the lock is recent: the lock
267    /// certificate is verified against that epoch's committee.
268    fn epochs(&self) -> impl Iterator<Item = EpochNumber> {
269        std::iter::once(self.vote().data.epoch).chain(self.timeout_lock().verified_in())
270    }
271}
272
273/// Accumulates [`TimeoutBallot`]s into a [`TimeoutCertificate3`] once their
274/// signers' combined stake reaches the threshold `Th`.
275///
276/// Signers with different locks sign different data, so unlike a
277/// [`SimpleTally`] the stake is counted across locks. As in a
278/// [`CheckedAccumulator`], votes are taken unchecked until a certificate
279/// fails to verify, and are checked one by one from then on. A ballot's lock
280/// certificate is checked along with its signature: a vote naming a lock no
281/// quorum certified is not counted, so it cannot raise the certificate's lock.
282pub struct TimeoutTally<T: NodeType, Th> {
283    membership: EpochMembership<T>,
284    upgrade_lock: UpgradeLock<T>,
285    ballots: Vec<TimeoutBallot<T>>,
286    weight: U256,
287    verify_votes: bool,
288    /// Whether each lock certificate checked so far is signed by a quorum.
289    ///
290    /// Most voters name the same lock, so once votes are checked one by one,
291    /// each certificate's signatures are checked once rather than per vote.
292    checked_locks: HashMap<Certificate1<T>, bool>,
293    _threshold: PhantomData<fn() -> Th>,
294}
295
296impl<T: NodeType, Th: Threshold<T> + Send + 'static> Tally<T> for TimeoutTally<T, Th> {
297    type Vote = TimeoutBallot<T>;
298    type Output = ValidCert<TimeoutCertificate3<T>>;
299
300    fn new(m: EpochMembership<T>, l: UpgradeLock<T>) -> Self {
301        Self {
302            membership: m,
303            upgrade_lock: l,
304            ballots: Vec::new(),
305            weight: U256::ZERO,
306            verify_votes: false,
307            checked_locks: HashMap::new(),
308            _threshold: PhantomData,
309        }
310    }
311
312    fn min_votes(&self) -> usize {
313        let table: Vec<_> = self.membership.stake_table().collect();
314        let largest = table
315            .iter()
316            .map(|peer| peer.stake_table_entry.stake())
317            .max()
318            .unwrap_or_default();
319        if largest.is_zero() {
320            return 1;
321        }
322        let nodes = table.len().max(1);
323        let votes = Th::threshold(&self.membership).div_ceil(largest);
324        usize::try_from(votes).unwrap_or(nodes).clamp(1, nodes)
325    }
326
327    fn has_signer(m: &EpochMembership<T>, signer: &T::SignatureKey) -> bool {
328        m.stake(signer).is_some()
329    }
330
331    fn add(&mut self, ballot: TimeoutBallot<T>) -> Option<Self::Output> {
332        if self.verify_votes && !self.is_valid(&ballot) {
333            return None;
334        }
335        self.weight += self.stake(&ballot);
336        self.ballots.push(ballot);
337        if self.weight < Th::threshold(&self.membership) {
338            return None;
339        }
340        match self.assemble() {
341            Some(Ok(cert)) => Some(cert),
342            Some(Err(err)) if !self.verify_votes => {
343                warn!(%err, "invalid timeout certificate formed");
344                self.recover()
345            },
346            Some(Err(err)) => {
347                error!(%err, "timeout certificate of checked votes is invalid");
348                None
349            },
350            None => None,
351        }
352    }
353}
354
355impl<T: NodeType, Th: Threshold<T>> TimeoutTally<T, Th> {
356    /// The stake of the ballot's signer.
357    ///
358    /// The collector admits only signers in the stake table (`has_signer`),
359    /// so one without stake here is a bug, and counts for nothing.
360    fn stake(&self, ballot: &TimeoutBallot<T>) -> U256 {
361        let signer = ballot.vote().signing_key();
362        let Some(peer) = self.membership.stake(&signer) else {
363            warn!(
364                %signer,
365                view = %ballot.vote().view_number(),
366                "timeout vote signer is not in the stake table"
367            );
368            return U256::ZERO;
369        };
370        peer.stake_table_entry.stake()
371    }
372
373    /// The certificate of the ballots so far, checked.
374    fn assemble(&self) -> Option<anytrace::Result<ValidCert<TimeoutCertificate3<T>>>> {
375        let first = self.ballots.first()?;
376        let view = first.vote().view_number();
377        let epoch = first.vote().data.epoch;
378        let latest = self
379            .ballots
380            .iter()
381            .max_by_key(|ballot| ballot.vote().data.lock)
382            .expect("there is a first ballot");
383        let entries = StakeTableEntries::from_iter(self.membership.stake_table()).0;
384        let threshold = Th::threshold(&self.membership);
385        let votes = self.ballots.iter().map(|ballot| {
386            (
387                ballot.vote().data.lock,
388                ballot.vote().signing_key(),
389                ballot.vote().signature(),
390            )
391        });
392        let result =
393            TimeoutCertificate3::assemble(view, epoch, &entries, votes, latest.lock().cloned())
394                .and_then(|cert| {
395                    verify_timeout3(
396                        &cert,
397                        &entries,
398                        threshold,
399                        *self.membership.coordinator.epoch_height(),
400                        &self.upgrade_lock,
401                        &self.membership.coordinator,
402                    )?;
403                    Ok(cert)
404                })
405                .map(|cert| {
406                    info!(%view, %epoch, lock = ?cert.lock(), "timeout certificate formed");
407                    ValidCert::new(cert, epoch)
408                });
409        Some(result)
410    }
411
412    /// Drop the ballots that fail their checks and assemble the rest again.
413    fn recover(&mut self) -> Option<ValidCert<TimeoutCertificate3<T>>> {
414        self.verify_votes = true;
415        let ballots = mem::take(&mut self.ballots);
416        self.weight = U256::ZERO;
417        for ballot in ballots {
418            if self.is_valid(&ballot) {
419                self.weight += self.stake(&ballot);
420                self.ballots.push(ballot);
421            }
422        }
423        if self.weight < Th::threshold(&self.membership) {
424            return None;
425        }
426        match self.assemble()? {
427            Ok(cert) => Some(cert),
428            Err(err) => {
429                error!(%err, "timeout certificate of checked votes is invalid");
430                None
431            },
432        }
433    }
434
435    /// Check a ballot's signature and its lock certificate.
436    fn is_valid(&mut self, ballot: &TimeoutBallot<T>) -> bool {
437        let vote = ballot.vote();
438        let Ok(data) =
439            VersionedVoteData::new(vote.data.clone(), vote.view_number(), &self.upgrade_lock)
440        else {
441            return false;
442        };
443        if !vote
444            .signing_key()
445            .validate(&vote.signature(), data.commit().as_ref())
446        {
447            warn!(view = %vote.view_number(), signer = %vote.signing_key(), "invalid timeout vote");
448            return false;
449        }
450        let TimeoutLock::Recent(cert) = ballot.timeout_lock() else {
451            return true;
452        };
453        if !cert
454            .data
455            .is_well_formed(*self.membership.coordinator.epoch_height())
456        {
457            return false;
458        }
459        let valid = match self.checked_locks.get(cert) {
460            Some(valid) => *valid,
461            None => {
462                let Ok(membership) = self
463                    .membership
464                    .coordinator
465                    .membership_for_epoch(cert.data.epoch)
466                else {
467                    return false;
468                };
469                let entries = StakeTableEntries::from_iter(membership.stake_table()).0;
470                let valid = verify_signatures(
471                    cert,
472                    &entries,
473                    membership.success_threshold(),
474                    &self.upgrade_lock,
475                )
476                .is_ok();
477                self.checked_locks.insert(cert.clone(), valid);
478                valid
479            },
480        };
481        if !valid {
482            warn!(view = %vote.view_number(), signer = %vote.signing_key(), "timeout vote with an invalid lock certificate");
483        }
484        valid
485    }
486}
487
488async fn run_tally<T, S>(
489    mut rx: mpsc::UnboundedReceiver<S::Vote>,
490    mut tally: S,
491) -> Option<S::Output>
492where
493    T: NodeType,
494    S: Tally<T>,
495{
496    let cap = tally.min_votes().max(1);
497    let mut buf = Vec::with_capacity(cap);
498    let mut need = cap;
499
500    loop {
501        while buf.len() < need {
502            if rx.recv_many(&mut buf, cap).await == 0 {
503                return None;
504            }
505        }
506        need = 1;
507
508        let hop = spawn_blocking(move || {
509            let out = buf.drain(..).find_map(|v| tally.add(v));
510            (tally, buf, out)
511        });
512
513        match hop.await {
514            Ok((_, _, Some(o))) => return Some(o),
515            Ok((t, b, None)) => (tally, buf) = (t, b),
516            Err(e) if e.is_panic() => resume_unwind(e.into_panic()),
517            Err(_) => return None,
518        }
519    }
520}
521
522/// What a tally is keyed by: the view and the epoch.
523type Key = (ViewNumber, EpochNumber);
524
525/// Collects votes per view and epoch and forms certificate(s) using the strategy `S`.
526pub struct VoteCollector<T: NodeType, S: Tally<T>> {
527    /// Tasks collecting votes and forming certificates.
528    accumulators: JoinMap<Key, Option<S::Output>>,
529
530    /// Where callers submit their votes.
531    ballot_boxes: BTreeMap<Key, mpsc::UnboundedSender<S::Vote>>,
532
533    /// Votes for epochs we have yet to resolve, deduplicated by signer.
534    pending: BTreeMap<Key, HashMap<T::SignatureKey, S::Vote>>,
535
536    /// The view and epoch pairs that formed a valid certificate already.
537    completed: BTreeSet<Key>,
538
539    /// The signers per view and epoch.
540    signers: BTreeMap<Key, HashSet<T::SignatureKey>>,
541
542    /// The GC threshold.
543    lower_bound: ViewNumber,
544
545    membership: EpochMembershipCoordinator<T>,
546
547    upgrade_lock: UpgradeLock<T>,
548}
549
550impl<T: NodeType, S: Tally<T>> VoteCollector<T, S>
551where
552    <S as Tally<T>>::Vote: Ballot<Signer = T::SignatureKey>,
553{
554    pub fn new(mc: EpochMembershipCoordinator<T>, lock: UpgradeLock<T>) -> Self {
555        Self {
556            accumulators: JoinMap::new(),
557            ballot_boxes: BTreeMap::new(),
558            pending: BTreeMap::new(),
559            signers: BTreeMap::new(),
560            completed: BTreeSet::new(),
561            membership: mc,
562            upgrade_lock: lock,
563            lower_bound: ViewNumber::genesis(),
564        }
565    }
566
567    pub async fn next(&mut self) -> Option<S::Output> {
568        loop {
569            let (key, result) = self.accumulators.join_next().await?;
570            let (view, epoch) = key;
571            // However the task ended, it is gone from the `JoinMap` and will
572            // not be joined again. A sender left behind in `ballot_boxes`
573            // would keep taking votes for `key` that nothing ever reads.
574            self.ballot_boxes.remove(&key);
575            let cert = match result {
576                Ok(cert) => cert,
577                Err(err) => {
578                    if err.is_panic() {
579                        error!(%view, %epoch, %err, "vote collection task panic");
580                    }
581                    None
582                },
583            };
584            match cert {
585                Some(cert) if view >= self.lower_bound && self.completed.insert(key) => {
586                    return Some(cert);
587                },
588                // `key` was retired by `mark_completed` before its tally was
589                // joined. Nothing reopens it, so its signers stay for the
590                // timeout diagnostics.
591                _ if self.completed.contains(&key) => {},
592                // No certificate came out of `key`, so a later vote may open a
593                // new ballot box for it. Its accumulator starts empty, hence the
594                // signers this one saw have to go too, or every one of them is
595                // deduplicated away from a tally that never counted them.
596                _ => {
597                    self.signers.remove(&key);
598                },
599            }
600        }
601    }
602
603    /// Count `vote`, or hold it until the committees of its epochs are known.
604    ///
605    /// Returns the epoch whose committee a held vote waits for, so that the
606    /// caller can drive its catchup.
607    pub fn accumulate_vote(&mut self, vote: S::Vote) -> Option<EpochNumber> {
608        let view = vote.view();
609
610        let Some(epoch) = vote.epoch() else {
611            // A vote without an epoch names no committee, so it can never be tallied.
612            return None;
613        };
614
615        let key = (view, epoch);
616
617        if view < self.lower_bound || self.completed.contains(&key) {
618            return None;
619        }
620
621        // A stake table may still arrive by catchup, so keep the vote for
622        // `retry_pending_votes`.
623        let Ok(membership) = self.membership.membership_for_epoch(Some(epoch)) else {
624            self.pending
625                .entry(key)
626                .or_default()
627                .insert(vote.signer(), vote);
628            return Some(epoch);
629        };
630        let missing = vote
631            .epochs()
632            .find(|e| self.membership.membership_for_epoch(Some(*e)).is_err());
633        if let Some(missing) = missing {
634            self.pending
635                .entry(key)
636                .or_default()
637                .insert(vote.signer(), vote);
638            return Some(missing);
639        }
640
641        if !S::has_signer(&membership, &vote.signer()) {
642            return None;
643        }
644
645        // Check that we have not received a vote from this signer already.
646        if !self.signers.entry(key).or_default().insert(vote.signer()) {
647            return None;
648        }
649
650        if let Some(tx) = self.ballot_boxes.get(&key) {
651            let _ = tx.send(vote);
652            return None;
653        }
654
655        let (tx, rx) = mpsc::unbounded_channel();
656
657        let _ = tx.send(vote);
658        self.ballot_boxes.insert(key, tx);
659
660        let tally = S::new(membership, self.upgrade_lock.clone());
661        self.accumulators.spawn(key, run_tally::<T, S>(rx, tally));
662        None
663    }
664
665    /// Record that `view`'s certificate in `epoch` was obtained by other means,
666    /// e.g. from the network, so the tally for it stops.
667    ///
668    /// Drops the ballot box and buffered votes and aborts the tally. A tally
669    /// mid-way through a blocking hop finishes that hop and has its
670    /// certificate discarded by [`Self::next`]. `signers` is left in place so
671    /// the timeout diagnostics keep what the tally saw.
672    pub fn mark_completed(&mut self, view: ViewNumber, epoch: EpochNumber) {
673        if view < self.lower_bound {
674            return;
675        }
676        let key = (view, epoch);
677        self.completed.insert(key);
678        self.ballot_boxes.remove(&key);
679        self.pending.remove(&key);
680        self.accumulators.abort(&key);
681    }
682
683    pub fn retry_pending_votes(&mut self) {
684        for vote in mem::take(&mut self.pending)
685            .into_values()
686            .flat_map(|votes| votes.into_values())
687        {
688            self.accumulate_vote(vote);
689        }
690    }
691
692    pub fn gc(&mut self, view: ViewNumber) {
693        let floor = (view, EpochNumber::new(0));
694        self.ballot_boxes = self.ballot_boxes.split_off(&floor);
695        self.completed = self.completed.split_off(&floor);
696        self.pending = self.pending.split_off(&floor);
697        self.signers = self.signers.split_off(&floor);
698        self.lower_bound = view;
699    }
700}
701
702impl<T, V, Th> VoteCollector<T, SimpleTally<T, V, Th>>
703where
704    T: NodeType,
705    V: Vote<T> + Send + 'static,
706    V::Commitment: 'static,
707    Th: Threshold<T> + Send + 'static,
708    Cert<T, V, Th>:
709        Certificate<T, V::Commitment, Voteable = V::Commitment> + HasEpoch + Send + 'static,
710{
711    /// What every epoch that voted in `view` has collected.
712    ///
713    /// Each entry counts the unique signers routed to `view`'s tally under
714    /// that epoch and, where the epoch still resolves, sums their stake
715    /// against its cert threshold. Votes name their own epoch, so a view can
716    /// hold more than one entry; that means its votes are split across
717    /// committees and neither tally need reach its threshold. Looks up each
718    /// signer's stake on demand, only intended for rare paths like timeout
719    /// diagnostics.
720    ///
721    /// An epoch that has signers always yields an entry, so an empty result
722    /// means no vote was received for `view` and nothing weaker.
723    pub fn stats(&self, view: ViewNumber) -> Vec<(EpochNumber, VoteStats)> {
724        let epochs = (view, EpochNumber::new(0))..(view + 1, EpochNumber::new(0));
725        self.signers
726            .range(epochs)
727            .filter(|(_, signers)| !signers.is_empty())
728            .map(|(&(_, epoch), signers)| {
729                let stats = VoteStats {
730                    signers: signers.len(),
731                    stake: self.membership.membership_for_epoch(Some(epoch)).ok().map(
732                        |membership| {
733                            let stake = signers
734                                .iter()
735                                .filter_map(|signer| {
736                                    Cert::<T, V, Th>::stake_table_entry(&membership, signer)
737                                })
738                                .map(|peer| peer.stake_table_entry.stake())
739                                .sum();
740                            (stake, Cert::<T, V, Th>::threshold(&membership))
741                        },
742                    ),
743                };
744                (epoch, stats)
745            })
746            .collect()
747    }
748}
749
750/// What a single view and epoch has collected.
751///
752/// Used by diagnostics (e.g. timeout logging) to show how close a view came
753/// to forming a cert.
754#[derive(Clone, Copy, Debug)]
755pub struct VoteStats {
756    /// Unique signers routed to this view and epoch.
757    pub signers: usize,
758
759    /// Their combined stake and the epoch's cert threshold, in that order.
760    ///
761    /// Absent once the epoch's stake table is no longer available: the
762    /// signers were counted while it resolved, the stake cannot be.
763    pub stake: Option<(U256, U256)>,
764}
765
766#[cfg(test)]
767mod tests {
768    use std::{fmt::Debug, marker::PhantomData, time::Duration};
769
770    use committable::Committable;
771    use hotshot_example_types::{
772        node_stake::{TestNodeStakes, gen_node_lists},
773        node_types::TestTypes,
774    };
775    use hotshot_types::{
776        data::{EpochNumber, ViewNumber},
777        epoch_membership::EpochMembership,
778        message::UpgradeLock,
779        signature_key::BLSPubKey,
780        simple_certificate::{
781            Certificate1, SuccessThreshold, Threshold, TimeoutCertificate2, TimeoutCertificate3,
782            TimeoutEvidence, UpgradeCertificate,
783        },
784        simple_vote::{
785            HasEpoch, LockView, QuorumData2, QuorumVote2, SimpleVote, TimeoutData2, TimeoutData3,
786            TimeoutVote2, UpgradeProposalData, VersionedVoteData, Vote2Data,
787        },
788        stake_table::StakeTableEntries,
789        traits::{node_implementation::NodeType, signature_key::SignatureKey},
790        vote::{Certificate, HasViewNumber, Vote, VoteAccumulator},
791    };
792    use tokio::{sync::mpsc, time::timeout};
793    use versions::{NEW_PROTOCOL_VERSION, TIMEOUT_EPOCH_VERSION, Upgrade};
794
795    use super::{Ballot, Cert, SimpleTally, TimeoutTally, VoteCollector};
796    use crate::{
797        cert_verifier::verify_timeout3,
798        helpers::{test_timeout_epoch_lock, test_upgrade_lock},
799        message::{CatchupEvidence, TimeoutBallot, TimeoutVoteMessage3, UpgradeVoteMessage, Vote2},
800        tests::common::utils::{
801            TEST_DRB_RESULT, build_timeout_cert3_signed_by, build_timeout_cert3_with_lock,
802            mock_membership,
803        },
804    };
805
806    /// Number of test validators.
807    const NUM_NODES: u64 = 10;
808    /// Threshold for SuccessThreshold with 10 nodes of stake 1: (10*2)/3 + 1 = 7.
809    const THRESHOLD: u64 = 7;
810
811    /// How long to wait for expected certificates before failing.
812    const CERT_TIMEOUT: Duration = Duration::from_millis(100);
813    /// How long to wait for a timeout certificate that checks its votes one
814    /// by one, lock certificates included, which is slow on a loaded machine.
815    const RECOVERY_TIMEOUT: Duration = Duration::from_secs(10);
816    /// How long to wait to confirm no certificate is produced (failure tests).
817    const NO_CERT_TIMEOUT: Duration = Duration::from_millis(500);
818
819    /// Create a signed QuorumVote2 (used for Certificate1 accumulation).
820    fn make_quorum_vote(
821        node_index: u64,
822        view: ViewNumber,
823        epoch: EpochNumber,
824    ) -> QuorumVote2<TestTypes> {
825        let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
826        let data = QuorumData2 {
827            leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
828                .u64(42)
829                .finalize(),
830            epoch: Some(epoch),
831            block_number: Some(1),
832        };
833        SimpleVote::create_signed_vote(data, view, &pub_key, &priv_key, &test_upgrade_lock())
834            .expect("Failed to sign vote")
835    }
836
837    fn vote_2_data() -> Vote2Data<TestTypes> {
838        Vote2Data {
839            leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
840                .u64(42)
841                .finalize(),
842            epoch: EpochNumber::genesis(),
843            block_number: 1,
844        }
845    }
846
847    /// Create a signed Vote2 (used for Certificate2 accumulation).
848    fn make_vote2(node_index: u64, view: ViewNumber) -> Vote2<TestTypes> {
849        let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
850        let data = vote_2_data();
851        SimpleVote::create_signed_vote(data, view, &pub_key, &priv_key, &test_upgrade_lock())
852            .expect("Failed to sign vote")
853    }
854
855    /// Create a Vote2 with an invalid signature (signed by a different key than claimed).
856    fn make_invalid_vote2(node_index: u64, view: ViewNumber) -> Vote2<TestTypes> {
857        let (pub_key, _) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
858        // Sign with a completely different key
859        let (_, wrong_priv_key) = BLSPubKey::generated_from_seed_indexed([1u8; 32], node_index);
860        let data = vote_2_data();
861        let commit =
862            VersionedVoteData::<TestTypes, _>::new(data.clone(), view, &test_upgrade_lock())
863                .unwrap()
864                .commit();
865        let bad_sig = BLSPubKey::sign(&wrong_priv_key, commit.as_ref()).unwrap();
866        SimpleVote {
867            signature: (pub_key, bad_sig),
868            data,
869            view_number: view,
870        }
871    }
872
873    /// Spawn a VoteCollectionTask and return:
874    /// - vote sender
875    /// - cert notification channel (receives (view, cert) when a certificate is formed)
876    /// - task JoinHandle (abort this to clean up)
877    fn setup_cert1_task()
878    -> VoteCollector<TestTypes, SimpleTally<TestTypes, QuorumVote2<TestTypes>, SuccessThreshold>>
879    {
880        setup_task::<QuorumVote2<TestTypes>, SuccessThreshold>()
881    }
882
883    fn setup_cert2_task()
884    -> VoteCollector<TestTypes, SimpleTally<TestTypes, Vote2<TestTypes>, SuccessThreshold>> {
885        setup_task::<Vote2<TestTypes>, SuccessThreshold>()
886    }
887
888    /// Spawn a VoteCollectionTask for Certificate2.
889    fn setup_task<
890        V: Ballot<Signer = <TestTypes as NodeType>::SignatureKey>
891            + Vote<TestTypes>
892            + HasEpoch
893            + Send
894            + Sync
895            + 'static,
896        Th: Threshold<TestTypes> + Send + 'static,
897    >() -> VoteCollector<TestTypes, SimpleTally<TestTypes, V, Th>>
898    where
899        Cert<TestTypes, V, Th>: Certificate<TestTypes, V::Commitment, Voteable = V::Commitment>
900            + HasEpoch
901            + Send
902            + 'static,
903    {
904        let membership = mock_membership();
905        VoteCollector::new(membership, test_upgrade_lock())
906    }
907
908    /// Wait for exactly `expected` certificates, then abort the task.
909    async fn _collect_certs<T: std::fmt::Debug>(
910        cert_rx: &mut mpsc::Receiver<T>,
911        expected: usize,
912    ) -> Vec<T> {
913        let mut results = Vec::new();
914        for _ in 0..expected {
915            let cert = tokio::time::timeout(CERT_TIMEOUT, cert_rx.recv())
916                .await
917                .expect("Timed out waiting for certificate")
918                .expect("Cert channel closed unexpectedly");
919            results.push(cert);
920        }
921        results
922    }
923
924    /// Confirm no certificates are produced within the timeout.
925    ///
926    /// Requires the collector to have a tally running or one already
927    /// certified: `next` yields `None` the moment there is no task at all, so
928    /// without this a test that opened no tally would pass without waiting for
929    /// anything.
930    async fn assert_no_certs<
931        V: Ballot<Signer = <TestTypes as NodeType>::SignatureKey>
932            + Vote<TestTypes>
933            + HasEpoch
934            + Send
935            + Sync
936            + 'static,
937        Th: Threshold<TestTypes> + Send + 'static,
938    >(
939        task: &mut VoteCollector<TestTypes, SimpleTally<TestTypes, V, Th>>,
940    ) where
941        Cert<TestTypes, V, Th>: Certificate<TestTypes, V::Commitment, Voteable = V::Commitment>
942            + HasEpoch
943            + Debug
944            + Send
945            + 'static,
946    {
947        assert!(
948            !task.ballot_boxes.is_empty() || !task.completed.is_empty(),
949            "no tally has run, so there is nothing that could produce a certificate"
950        );
951        let result = tokio::time::timeout(NO_CERT_TIMEOUT, task.next()).await;
952        match result {
953            Err(_) => { /* timeout — good, no cert produced */ },
954            Ok(None) => { /* good, no cert produced */ },
955            Ok(Some(cert)) => panic!("Expected no certificate but got one: {cert:?}"),
956        }
957    }
958
959    /// Verify that a certificate's data commitment matches `expected_data` and that
960    /// the aggregate signature is valid against the stake table.
961    fn verify_cert<C, D>(cert: &C, expected_data: &D, membership: &EpochMembership<TestTypes>)
962    where
963        D: Committable,
964        C: Certificate<TestTypes, D, Voteable = D>,
965    {
966        // Data commitment must match the vote data that produced the cert.
967        assert_eq!(
968            cert.data().commit(),
969            expected_data.commit(),
970            "Certificate data commitment does not match expected vote data"
971        );
972
973        // Aggregate signature must be valid against the stake table.
974        let stake_table = C::stake_table(membership);
975        let stake_table_entries = StakeTableEntries::<TestTypes>::from(stake_table).0;
976        let threshold = C::threshold(membership);
977        cert.is_valid_cert(&stake_table_entries, threshold, &test_upgrade_lock())
978            .expect("Certificate signature validation failed");
979    }
980
981    // ==================== Certificate1 (QuorumVote2) happy path ====================
982
983    /// Sending enough QuorumVote2s for a single view produces a valid Certificate1
984    /// whose data commitment matches the votes.
985    #[tokio::test]
986    async fn test_cert1_single_view_happy_path() {
987        let mut task = setup_cert1_task();
988        let view = ViewNumber::new(1);
989        let epoch = EpochNumber::genesis();
990        let expected_data = QuorumData2 {
991            leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
992                .u64(42)
993                .finalize(),
994            epoch: Some(epoch),
995            block_number: Some(1),
996        };
997
998        for i in 0..THRESHOLD {
999            task.accumulate_vote(make_quorum_vote(i, view, epoch));
1000        }
1001
1002        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1003        assert_eq!(cert.view_number(), view);
1004
1005        let membership = mock_membership();
1006        let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
1007        verify_cert(cert.cert(), &expected_data, &epoch_membership);
1008    }
1009
1010    /// Sending votes for multiple views produces a valid certificate for each view,
1011    /// each with data commitment matching the votes.
1012    #[tokio::test]
1013    async fn test_cert1_multiple_views_parallel() {
1014        let mut task = setup_cert1_task();
1015        let epoch = EpochNumber::genesis();
1016        let expected_data = QuorumData2 {
1017            leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
1018                .u64(42)
1019                .finalize(),
1020            epoch: Some(epoch),
1021            block_number: Some(1),
1022        };
1023
1024        let views = [ViewNumber::new(1), ViewNumber::new(2), ViewNumber::new(3)];
1025
1026        // Interleave votes across views
1027        for i in 0..THRESHOLD {
1028            for &view in &views {
1029                task.accumulate_vote(make_quorum_vote(i, view, epoch));
1030            }
1031        }
1032        let mut certs = Vec::new();
1033        for _ in 0..views.len() {
1034            certs.push(timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap());
1035        }
1036        assert_eq!(
1037            certs.len(),
1038            views.len(),
1039            "Expected one Certificate1 per view"
1040        );
1041        let mut cert_views: Vec<_> = certs.iter().map(|c| c.view_number()).collect();
1042        cert_views.sort();
1043        assert_eq!(cert_views, views.to_vec());
1044
1045        let membership = mock_membership();
1046        let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
1047        for cert in &certs {
1048            verify_cert(cert.cert(), &expected_data, &epoch_membership);
1049        }
1050    }
1051
1052    // ==================== Certificate2 (Vote2) happy path ====================
1053
1054    /// Sending enough Vote2s for a single view produces a valid Certificate2
1055    /// whose data commitment matches the votes.
1056    #[tokio::test]
1057    async fn test_cert2_single_view_happy_path() {
1058        let mut task = setup_cert2_task();
1059        let view = ViewNumber::new(1);
1060        let epoch = EpochNumber::genesis();
1061        let expected_data = vote_2_data();
1062
1063        for i in 0..THRESHOLD {
1064            task.accumulate_vote(make_vote2(i, view));
1065        }
1066
1067        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1068        assert_eq!(cert.view_number(), view);
1069
1070        let membership = mock_membership();
1071        let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
1072        verify_cert(cert.cert(), &expected_data, &epoch_membership);
1073    }
1074
1075    /// Sending votes for multiple views in parallel produces valid certificates for each,
1076    /// each with data commitment matching the votes.
1077    #[tokio::test]
1078    async fn test_cert2_multiple_views_parallel() {
1079        let mut task = setup_cert2_task();
1080        let epoch = EpochNumber::genesis();
1081        let expected_data = vote_2_data();
1082
1083        let views = [ViewNumber::new(5), ViewNumber::new(6), ViewNumber::new(7)];
1084
1085        for i in 0..THRESHOLD {
1086            for &view in &views {
1087                task.accumulate_vote(make_vote2(i, view));
1088            }
1089        }
1090
1091        let mut certs = Vec::new();
1092        for _ in 0..views.len() {
1093            certs.push(timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap());
1094        }
1095        assert_eq!(
1096            certs.len(),
1097            views.len(),
1098            "Expected one Certificate2 per view"
1099        );
1100        let mut cert_views: Vec<_> = certs.iter().map(|c| c.view_number()).collect();
1101        cert_views.sort();
1102        assert_eq!(cert_views, views.to_vec());
1103
1104        let membership = mock_membership();
1105        let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
1106        for cert in &certs {
1107            verify_cert(cert.cert(), &expected_data, &epoch_membership);
1108        }
1109    }
1110
1111    // ==================== Certificate1 failure cases ====================
1112
1113    /// Fewer than threshold votes do not produce a certificate.
1114    #[tokio::test]
1115    async fn test_cert1_below_threshold_no_certificate() {
1116        let mut task = setup_cert1_task();
1117        let view = ViewNumber::new(1);
1118        let epoch = EpochNumber::genesis();
1119
1120        for i in 0..(THRESHOLD - 1) {
1121            task.accumulate_vote(make_quorum_vote(i, view, epoch));
1122        }
1123
1124        assert_no_certs(&mut task).await;
1125    }
1126
1127    /// Duplicate votes from the same signer do not count toward threshold.
1128    #[tokio::test]
1129    async fn test_cert1_duplicate_votes_ignored() {
1130        let mut task = setup_cert1_task();
1131        let view = ViewNumber::new(1);
1132        let epoch = EpochNumber::genesis();
1133
1134        // Send 6 unique votes (below threshold of 7)
1135        for i in 0..6 {
1136            task.accumulate_vote(make_quorum_vote(i, view, epoch));
1137        }
1138        // Send duplicates of node 0 — should not push us over threshold
1139        for _ in 0..5 {
1140            task.accumulate_vote(make_quorum_vote(0, view, epoch));
1141        }
1142
1143        assert_no_certs(&mut task).await;
1144    }
1145
1146    // ==================== Certificate2 failure cases ====================
1147
1148    /// Fewer than threshold Vote2s do not produce a Certificate2.
1149    #[tokio::test]
1150    async fn test_cert2_below_threshold_no_certificate() {
1151        let mut task = setup_cert2_task();
1152        let view = ViewNumber::new(1);
1153
1154        for i in 0..(THRESHOLD - 1) {
1155            task.accumulate_vote(make_vote2(i, view));
1156        }
1157
1158        assert_no_certs(&mut task).await;
1159    }
1160
1161    /// Duplicate Vote2s from the same signer do not count toward threshold.
1162    #[tokio::test]
1163    async fn test_cert2_duplicate_votes_ignored() {
1164        let mut task = setup_cert2_task();
1165        let view = ViewNumber::new(1);
1166
1167        // Send 6 unique votes (below threshold of 7)
1168        for i in 0..6 {
1169            task.accumulate_vote(make_vote2(i, view));
1170        }
1171        // Repeat node 0 votes — should not reach threshold
1172        for _ in 0..5 {
1173            task.accumulate_vote(make_vote2(0, view));
1174        }
1175
1176        assert_no_certs(&mut task).await;
1177    }
1178
1179    /// Votes with invalid signatures are rejected and do not count.
1180    #[tokio::test]
1181    async fn test_cert2_invalid_signature_rejected() {
1182        let mut task = setup_cert2_task();
1183        let view = ViewNumber::new(1);
1184
1185        // Send 6 valid votes (below threshold)
1186        for i in 0..6 {
1187            task.accumulate_vote(make_vote2(i, view));
1188        }
1189        // Send invalid-signature votes — should be rejected, not reaching threshold
1190        for i in 6..NUM_NODES {
1191            task.accumulate_vote(make_invalid_vote2(i, view));
1192        }
1193
1194        assert_no_certs(&mut task).await;
1195    }
1196
1197    /// Votes with invalid signatures are rejected and do not count.
1198    #[tokio::test]
1199    async fn test_cert2_invalid_signature_recovery() {
1200        let mut task = setup_cert2_task();
1201        let view = ViewNumber::new(1);
1202        let epoch = EpochNumber::genesis();
1203
1204        // Send 6 valid votes (below threshold)
1205        for i in 0..6 {
1206            task.accumulate_vote(make_vote2(i, view));
1207        }
1208        // Send invalid-signature votes — should be rejected, not reaching threshold
1209        for i in 6..8 {
1210            task.accumulate_vote(make_invalid_vote2(i, view));
1211        }
1212        assert_no_certs(&mut task).await;
1213
1214        task.accumulate_vote(make_vote2(9, view));
1215
1216        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1217        assert_no_certs(&mut task).await;
1218        let membership = mock_membership();
1219        let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
1220        verify_cert(cert.cert(), &vote_2_data(), &epoch_membership);
1221    }
1222
1223    /// A timeout certificate for the genesis view is signature-checked like any
1224    /// other.
1225    ///
1226    /// `is_valid_cert` passes every certificate at the genesis view, so a vote
1227    /// with a bad signature would end up in the certificate there, and the
1228    /// recovery that drops such votes would never run.
1229    #[tokio::test]
1230    async fn test_genesis_timeout_invalid_signature_recovery() {
1231        let lock = test_timeout_epoch_lock();
1232        let mut task = VoteCollector::<TestTypes, TimeoutTally<TestTypes, SuccessThreshold>>::new(
1233            mock_membership(),
1234            lock.clone(),
1235        );
1236        let view = ViewNumber::genesis();
1237        let epoch = EpochNumber::genesis();
1238        let vote = |node_index, signer_seed| {
1239            let (pub_key, _) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
1240            let (_, priv_key) = BLSPubKey::generated_from_seed_indexed(signer_seed, node_index);
1241            let data = TimeoutData3 {
1242                view,
1243                epoch,
1244                lock: None,
1245            };
1246            let commit = VersionedVoteData::<TestTypes, _>::new(data.clone(), view, &lock)
1247                .unwrap()
1248                .commit();
1249            TimeoutVoteMessage3 {
1250                signer: pub_key,
1251                signature: BLSPubKey::sign(&priv_key, commit.as_ref()).unwrap(),
1252                view,
1253                epoch,
1254                lock: None,
1255                evidence: None,
1256            }
1257            .ballot()
1258            .unwrap()
1259        };
1260
1261        for i in 0..6 {
1262            task.accumulate_vote(vote(i, [0u8; 32]));
1263        }
1264        for i in 6..8 {
1265            task.accumulate_vote(vote(i, [1u8; 32]));
1266        }
1267        // Six valid votes are one short; the two invalid ones are dropped.
1268        assert!(timeout(NO_CERT_TIMEOUT, task.next()).await.is_err());
1269
1270        task.accumulate_vote(vote(9, [0u8; 32]));
1271        let cert = timeout(RECOVERY_TIMEOUT, task.next())
1272            .await
1273            .unwrap()
1274            .unwrap();
1275        let membership = mock_membership().membership_for_epoch(Some(epoch)).unwrap();
1276        let entries = StakeTableEntries::<TestTypes>::from_iter(membership.stake_table()).0;
1277        cert.check_signatures(&entries, membership.success_threshold(), &lock)
1278            .expect("the certificate carries only valid signatures");
1279        assert!(cert.earlier.is_empty());
1280    }
1281
1282    /// Timeout votes naming different locks are counted together, and the
1283    /// certificate carries the latest of their locks.
1284    #[tokio::test]
1285    async fn timeout_votes_with_different_locks_form_one_certificate() {
1286        let lock = test_timeout_epoch_lock();
1287        let coordinator = mock_membership();
1288        let mut task = VoteCollector::<TestTypes, TimeoutTally<TestTypes, SuccessThreshold>>::new(
1289            coordinator.clone(),
1290            lock.clone(),
1291        );
1292        let epoch = EpochNumber::genesis();
1293        let membership = coordinator.membership_for_epoch(Some(epoch)).unwrap();
1294        let view = ViewNumber::new(5);
1295        let older = lock_cert(ViewNumber::new(2), epoch, &membership, &lock);
1296        let newer = lock_cert(ViewNumber::new(3), epoch, &membership, &lock);
1297        let ballot = |node_index, cert: &Option<Certificate1<TestTypes>>| {
1298            let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
1299            TimeoutBallot::sign(view, epoch, cert.clone(), &pub_key, &priv_key, &lock).unwrap()
1300        };
1301        for i in 0..3 {
1302            task.accumulate_vote(ballot(i, &None));
1303        }
1304        for i in 3..5 {
1305            task.accumulate_vote(ballot(i, &Some(older.clone())));
1306        }
1307        for i in 5..7 {
1308            task.accumulate_vote(ballot(i, &Some(newer.clone())));
1309        }
1310        let cert = timeout(RECOVERY_TIMEOUT, task.next())
1311            .await
1312            .unwrap()
1313            .unwrap();
1314        assert_eq!(cert.earlier.len(), 2);
1315        assert_eq!(cert.lock(), Some(LockView::of(&newer)));
1316        let entries = StakeTableEntries::<TestTypes>::from_iter(membership.stake_table()).0;
1317        cert.check_signatures(&entries, membership.success_threshold(), &lock)
1318            .expect("valid grouped certificate");
1319    }
1320
1321    /// A vote whose lock is of an epoch whose committee is not known yet waits,
1322    /// as one of an unknown epoch of its own does, and is counted once it is.
1323    #[tokio::test]
1324    async fn timeout_vote_with_a_lock_of_an_unknown_epoch_waits() {
1325        let lock = test_timeout_epoch_lock();
1326        let (lock_epoch, epoch) = (EpochNumber::new(3), EpochNumber::new(4));
1327        let known = mock_membership();
1328        for e in [lock_epoch, epoch] {
1329            known.membership().register_epoch(e, TEST_DRB_RESULT);
1330        }
1331        let lock_membership = known.membership_for_epoch(Some(lock_epoch)).unwrap();
1332        let cert = Some(lock_cert(
1333            ViewNumber::new(30),
1334            lock_epoch,
1335            &lock_membership,
1336            &lock,
1337        ));
1338
1339        let coordinator = mock_membership();
1340        coordinator
1341            .membership()
1342            .register_epoch(epoch, TEST_DRB_RESULT);
1343        let mut task = VoteCollector::<TestTypes, TimeoutTally<TestTypes, SuccessThreshold>>::new(
1344            coordinator.clone(),
1345            lock.clone(),
1346        );
1347        let view = ViewNumber::new(31);
1348        for i in 0..NUM_NODES {
1349            let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], i);
1350            let ballot =
1351                TimeoutBallot::sign(view, epoch, cert.clone(), &pub_key, &priv_key, &lock).unwrap();
1352            assert_eq!(task.accumulate_vote(ballot), Some(lock_epoch));
1353        }
1354        assert!(
1355            !matches!(
1356                timeout(Duration::from_millis(100), task.next()).await,
1357                Ok(Some(_))
1358            ),
1359            "nothing is counted yet"
1360        );
1361
1362        coordinator
1363            .membership()
1364            .register_epoch(lock_epoch, TEST_DRB_RESULT);
1365        task.retry_pending_votes();
1366        let formed = timeout(RECOVERY_TIMEOUT, task.next())
1367            .await
1368            .unwrap()
1369            .unwrap();
1370        assert_eq!(formed.lock(), cert.as_ref().map(LockView::of));
1371    }
1372
1373    /// A vote naming a lock its certificate does not show is not counted, so
1374    /// it cannot raise the certificate's lock.
1375    #[tokio::test]
1376    async fn timeout_vote_with_a_forged_lock_is_dropped() {
1377        let lock = test_timeout_epoch_lock();
1378        let coordinator = mock_membership();
1379        let mut task = VoteCollector::<TestTypes, TimeoutTally<TestTypes, SuccessThreshold>>::new(
1380            coordinator.clone(),
1381            lock.clone(),
1382        );
1383        let epoch = EpochNumber::genesis();
1384        let membership = coordinator.membership_for_epoch(Some(epoch)).unwrap();
1385        let view = ViewNumber::new(5);
1386        let honest = lock_cert(ViewNumber::new(2), epoch, &membership, &lock);
1387        // A "certificate" at a later view with no signatures at all.
1388        let mut forged = honest.clone();
1389        forged.view_number = ViewNumber::new(4);
1390        forged.signatures = None;
1391        let ballot = |node_index, cert: &Certificate1<TestTypes>| {
1392            let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
1393            TimeoutBallot::sign(view, epoch, Some(cert.clone()), &pub_key, &priv_key, &lock)
1394                .unwrap()
1395        };
1396        task.accumulate_vote(ballot(0, &forged));
1397        for i in 1..8 {
1398            task.accumulate_vote(ballot(i, &honest));
1399        }
1400        let cert = timeout(RECOVERY_TIMEOUT, task.next())
1401            .await
1402            .unwrap()
1403            .unwrap();
1404        assert_eq!(cert.lock(), Some(LockView::of(&honest)));
1405    }
1406
1407    /// No honest node holds a lock of an epoch later than its own, so a vote
1408    /// or certificate naming one is malformed. Rejecting it before the lock
1409    /// certificate is checked keeps a peer from starting catchups for epochs
1410    /// of its choosing.
1411    #[tokio::test]
1412    async fn timeout_lock_of_a_later_epoch_is_rejected() {
1413        let lock = test_timeout_epoch_lock();
1414        let epoch = EpochNumber::genesis();
1415        let membership = mock_membership().membership_for_epoch(Some(epoch)).unwrap();
1416        let view = ViewNumber::new(5);
1417        let later = lock_cert(ViewNumber::new(2), epoch + 1, &membership, &lock);
1418
1419        let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], 0);
1420        let ballot =
1421            TimeoutBallot::sign(view, epoch, Some(later.clone()), &pub_key, &priv_key, &lock)
1422                .unwrap();
1423        assert!(
1424            TimeoutVoteMessage3::new(ballot.clone(), None)
1425                .ballot()
1426                .is_err()
1427        );
1428
1429        let entries = StakeTableEntries::<TestTypes>::from_iter(membership.stake_table()).0;
1430        let vote = ballot.vote();
1431        let cert = TimeoutCertificate3::<TestTypes>::assemble(
1432            view,
1433            epoch,
1434            &entries,
1435            [(vote.data.lock, vote.signing_key(), vote.signature())],
1436            Some(later),
1437        )
1438        .unwrap();
1439        assert!(cert.check_lock().is_err());
1440    }
1441
1442    /// A lock more than one epoch older than its timeout certificate is not
1443    /// checked, so not even a forged one is refused; one of the epoch before
1444    /// is checked.
1445    #[tokio::test]
1446    async fn timeout_lock_older_than_the_epoch_before_is_not_checked() {
1447        let lock = test_timeout_epoch_lock();
1448        let coordinator = mock_membership();
1449        let epoch = EpochNumber::new(2);
1450        let membership = coordinator.membership_for_epoch(Some(epoch)).unwrap();
1451        let entries = StakeTableEntries::<TestTypes>::from_iter(membership.stake_table()).0;
1452        let with_forged_lock = |lock_epoch| {
1453            let mut cert = lock_cert(ViewNumber::new(2), lock_epoch, &membership, &lock);
1454            cert.signatures = None;
1455            build_timeout_cert3_with_lock(ViewNumber::new(5), epoch, Some(cert), &membership)
1456        };
1457        let verify = |tc| {
1458            verify_timeout3(
1459                &tc,
1460                &entries,
1461                membership.success_threshold(),
1462                *coordinator.epoch_height(),
1463                &lock,
1464                &coordinator,
1465            )
1466        };
1467        assert!(verify(with_forged_lock(EpochNumber::new(0))).is_ok());
1468        assert!(verify(with_forged_lock(EpochNumber::new(1))).is_err());
1469    }
1470
1471    /// A timeout vote message carries its lock once: evidence that is the lock
1472    /// is left out and restored from the lock on receipt, other evidence is
1473    /// kept.
1474    #[tokio::test]
1475    async fn timeout_vote_message_carries_its_lock_once() {
1476        let lock = test_timeout_epoch_lock();
1477        let epoch = EpochNumber::genesis();
1478        let membership = mock_membership().membership_for_epoch(Some(epoch)).unwrap();
1479        let own = lock_cert(ViewNumber::new(2), epoch, &membership, &lock);
1480        let other = lock_cert(ViewNumber::new(3), epoch, &membership, &lock);
1481        let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], 0);
1482        let ballot = TimeoutBallot::sign(
1483            ViewNumber::new(5),
1484            epoch,
1485            Some(own.clone()),
1486            &pub_key,
1487            &priv_key,
1488            &lock,
1489        )
1490        .unwrap();
1491
1492        let message =
1493            TimeoutVoteMessage3::new(ballot.clone(), Some(CatchupEvidence::Qc(own.clone())));
1494        assert!(message.evidence.is_none());
1495        assert_eq!(message.evidence(), Some(CatchupEvidence::Qc(own)));
1496
1497        let message = TimeoutVoteMessage3::new(ballot, Some(CatchupEvidence::Qc(other.clone())));
1498        assert_eq!(message.evidence(), Some(CatchupEvidence::Qc(other)));
1499    }
1500
1501    /// A timeout certificate refused for its signers is refused before any of
1502    /// its signatures is checked: too few signers with signatures that would
1503    /// not verify either are refused for their weight.
1504    #[tokio::test]
1505    async fn timeout_certificate_weight_is_checked_before_signatures() {
1506        let lock = test_timeout_epoch_lock();
1507        let epoch = EpochNumber::genesis();
1508        let membership = mock_membership().membership_for_epoch(Some(epoch)).unwrap();
1509        let entries = StakeTableEntries::<TestTypes>::from_iter(membership.stake_table()).0;
1510        let mut cert =
1511            build_timeout_cert3_signed_by(ViewNumber::new(5), epoch, None, &membership, &[0, 1, 2]);
1512        // The signatures are over view 5, so they do not verify for view 6.
1513        cert.view_number = ViewNumber::new(6);
1514        let err = cert
1515            .check_signatures(&entries, membership.success_threshold(), &lock)
1516            .expect_err("three signers are below the threshold");
1517        assert!(
1518            err.to_string().contains("below threshold"),
1519            "refused for its weight, not its signatures: {err}"
1520        );
1521    }
1522
1523    /// A `Certificate1` at `view` signed by every member.
1524    fn lock_cert(
1525        view: ViewNumber,
1526        epoch: EpochNumber,
1527        membership: &EpochMembership<TestTypes>,
1528        lock: &UpgradeLock<TestTypes>,
1529    ) -> Certificate1<TestTypes> {
1530        let mut accumulator =
1531            VoteAccumulator::<TestTypes, QuorumVote2<TestTypes>, Certificate1<TestTypes>>::new(
1532                lock.clone(),
1533            );
1534        for i in 0..NUM_NODES {
1535            let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], i);
1536            let data = QuorumData2 {
1537                leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
1538                    .u64(*view)
1539                    .finalize(),
1540                epoch: Some(epoch),
1541                block_number: Some(*view),
1542            };
1543            let vote = SimpleVote::create_signed_vote(data, view, &pub_key, &priv_key, lock)
1544                .expect("failed to sign quorum vote");
1545            if let Some(cert) = accumulator.accumulate(&vote, membership.clone()) {
1546                return cert;
1547            }
1548        }
1549        panic!("threshold reached without forming a certificate");
1550    }
1551
1552    /// Channel closed before threshold means no certificate is produced.
1553    #[tokio::test]
1554    async fn test_cert2_channel_closed_early() {
1555        let mut task = setup_cert2_task();
1556        let view = ViewNumber::new(1);
1557
1558        for i in 0..3 {
1559            task.accumulate_vote(make_vote2(i, view));
1560        }
1561        assert_no_certs(&mut task).await;
1562    }
1563
1564    // ==================== Mixed / advanced scenarios ====================
1565
1566    /// Only the view that reaches threshold gets a certificate; others don't.
1567    #[tokio::test]
1568    async fn test_cert2_partial_views_only_complete_one_certifies() {
1569        let mut task = setup_cert2_task();
1570
1571        let complete_view = ViewNumber::new(1);
1572        let partial_view = ViewNumber::new(2);
1573
1574        // Send threshold votes for the complete view
1575        for i in 0..THRESHOLD {
1576            task.accumulate_vote(make_vote2(i, complete_view));
1577        }
1578
1579        // Send fewer than threshold for the partial view
1580        for i in 0..3 {
1581            task.accumulate_vote(make_vote2(i, partial_view));
1582        }
1583
1584        // Wait for the one expected certificate
1585        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1586        assert_no_certs(&mut task).await;
1587        assert_eq!(cert.view_number(), complete_view);
1588    }
1589
1590    /// Extra votes beyond threshold for the same view do not produce a second certificate.
1591    #[tokio::test]
1592    async fn test_cert2_extra_votes_after_threshold_no_duplicate_cert() {
1593        let mut task = setup_cert2_task();
1594        let view = ViewNumber::new(1);
1595
1596        // Send all 10 votes (more than threshold of 7)
1597        for i in 0..NUM_NODES {
1598            task.accumulate_vote(make_vote2(i, view));
1599        }
1600
1601        // Should get exactly one cert, then confirm no more arrive
1602        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1603        assert_eq!(cert.view_number(), view);
1604
1605        // Confirm no second certificate
1606        assert_no_certs(&mut task).await;
1607    }
1608
1609    /// Votes for different data commitments on the same view do not combine.
1610    #[tokio::test]
1611    async fn test_cert2_conflicting_data_same_view_no_certificate() {
1612        let mut task = setup_cert2_task();
1613        let view = ViewNumber::new(1);
1614
1615        // Send 6 votes for one leaf commitment
1616        for i in 0..6 {
1617            task.accumulate_vote(make_vote2(i, view));
1618        }
1619
1620        // Send 4 votes for a different leaf commitment
1621        for i in 6..NUM_NODES {
1622            let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], i);
1623            let data = Vote2Data {
1624                leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
1625                    //different leaf commitment
1626                    .u64(1000)
1627                    .finalize(),
1628                epoch: EpochNumber::genesis(),
1629                block_number: 1,
1630            };
1631            let vote = SimpleVote::create_signed_vote(
1632                data,
1633                view,
1634                &pub_key,
1635                &priv_key,
1636                &test_upgrade_lock(),
1637            )
1638            .expect("Failed to sign vote");
1639            task.accumulate_vote(vote);
1640        }
1641        assert_no_certs(&mut task).await;
1642    }
1643
1644    /// Collector over a membership where node 9 is removed from the quorum
1645    /// committee starting at epoch 3 (9 members of stake 1, threshold 7).
1646    fn setup_cert1_task_with_removed_node()
1647    -> VoteCollector<TestTypes, SimpleTally<TestTypes, QuorumVote2<TestTypes>, SuccessThreshold>>
1648    {
1649        let membership = mock_membership();
1650        let committee = gen_node_lists::<TestTypes>(9, 9, &TestNodeStakes::default()).0;
1651        membership
1652            .membership()
1653            .add_quorum_committee(EpochNumber::new(3), committee);
1654        membership
1655            .membership()
1656            .register_epoch(EpochNumber::new(3), [0u8; 32]);
1657        VoteCollector::new(membership, test_upgrade_lock())
1658    }
1659
1660    /// A vote from a validator that was removed from the quorum committee in
1661    /// a later epoch does not count toward that epoch's certificate.
1662    #[tokio::test]
1663    async fn test_vote_from_removed_validator_ignored() {
1664        let mut task = setup_cert1_task_with_removed_node();
1665        let view = ViewNumber::new(21);
1666        let epoch = EpochNumber::new(3);
1667
1668        for i in 0..6 {
1669            task.accumulate_vote(make_quorum_vote(i, view, epoch));
1670        }
1671        task.accumulate_vote(make_quorum_vote(9, view, epoch));
1672        assert_no_certs(&mut task).await;
1673
1674        task.accumulate_vote(make_quorum_vote(6, view, epoch));
1675        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1676        assert_eq!(cert.view_number(), view);
1677    }
1678
1679    /// A vote's epoch is chosen by its sender and bound to nothing else in
1680    /// the vote, so the first vote of a view must not decide which committee
1681    /// the rest of it is counted against.
1682    ///
1683    /// Node 9 left the committee in epoch 3, so a view opened by a vote
1684    /// naming that epoch would drop node 9's epoch-2 vote and leave the
1685    /// remaining six short of the threshold.
1686    #[tokio::test]
1687    async fn test_forged_epoch_vote_does_not_pin_the_committee() {
1688        let mut task = setup_cert1_task_with_removed_node();
1689        let view = ViewNumber::new(11);
1690        let voted = EpochNumber::new(2);
1691        let forged = EpochNumber::new(3);
1692
1693        // Node 0 opens the view naming an epoch nobody voted in.
1694        task.accumulate_vote(make_quorum_vote(0, view, forged));
1695
1696        // Exactly the threshold votes in the epoch of the view, node 9 among
1697        // them.
1698        for i in 3..NUM_NODES {
1699            task.accumulate_vote(make_quorum_vote(i, view, voted));
1700        }
1701
1702        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1703        assert_eq!(cert.view_number(), view);
1704        assert_eq!(cert.epoch(), voted);
1705        assert_eq!(cert.data.epoch, Some(voted));
1706        // The forged epoch holds a single vote and certifies nothing.
1707        assert_no_certs(&mut task).await;
1708    }
1709
1710    /// Two epochs voting in one view are tallied apart, each against its own
1711    /// committee, and both reach a certificate.
1712    #[tokio::test]
1713    async fn test_votes_in_two_epochs_certify_separately() {
1714        let mut task = setup_cert1_task_with_removed_node();
1715        let view = ViewNumber::new(11);
1716        let epochs = [EpochNumber::new(2), EpochNumber::new(3)];
1717
1718        for &epoch in &epochs {
1719            for i in 0..THRESHOLD {
1720                task.accumulate_vote(make_quorum_vote(i, view, epoch));
1721            }
1722        }
1723
1724        let mut certified = Vec::new();
1725        for _ in 0..epochs.len() {
1726            let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1727            assert_eq!(cert.view_number(), view);
1728            certified.push(cert.epoch());
1729        }
1730        certified.sort();
1731        assert_eq!(certified, epochs.to_vec());
1732    }
1733
1734    /// GC drops every epoch of the views it collects, including epoch 0,
1735    /// which sorts below the epoch numbers a stake table can have, and keeps
1736    /// every epoch of the views it does not.
1737    #[tokio::test]
1738    async fn test_gc_clears_all_epochs_of_a_view() {
1739        let mut task = setup_cert1_task();
1740        let view = ViewNumber::new(1);
1741
1742        // A vote whose epoch resolves opens a ballot box; one whose epoch
1743        // never will is buffered instead.
1744        for v in [view, view + 1] {
1745            task.accumulate_vote(make_quorum_vote(0, v, EpochNumber::genesis()));
1746            task.accumulate_vote(make_quorum_vote(1, v, EpochNumber::new(0)));
1747        }
1748        assert_eq!(task.signers.len(), 2);
1749        assert_eq!(task.pending.len(), 2);
1750
1751        task.gc(view + 1);
1752
1753        let kept = [(view + 1, EpochNumber::genesis())];
1754        assert_eq!(task.ballot_boxes.keys().copied().collect::<Vec<_>>(), kept);
1755        assert_eq!(task.signers.keys().copied().collect::<Vec<_>>(), kept);
1756        assert_eq!(
1757            task.pending.keys().copied().collect::<Vec<_>>(),
1758            [(view + 1, EpochNumber::new(0))]
1759        );
1760        assert!(task.completed.is_empty());
1761    }
1762
1763    /// A view waiting for votes must not hold a thread from the blocking
1764    /// pool, which the process shares with VID and certificate verification.
1765    ///
1766    /// This runtime has a single blocking thread, so one tally parked in a
1767    /// receive loop would starve every other view.
1768    #[test]
1769    fn test_waiting_tallies_do_not_hold_blocking_threads() {
1770        let rt = tokio::runtime::Builder::new_multi_thread()
1771            .max_blocking_threads(1)
1772            .enable_all()
1773            .build()
1774            .expect("runtime");
1775
1776        rt.block_on(async {
1777            let mut task = setup_cert1_task();
1778            let epoch = EpochNumber::genesis();
1779
1780            // Open a tally for each of these views and leave every one of
1781            // them short of the threshold.
1782            for view in 1..=8 {
1783                task.accumulate_vote(make_quorum_vote(0, ViewNumber::new(view), epoch));
1784            }
1785
1786            let view = ViewNumber::new(9);
1787            for i in 0..THRESHOLD {
1788                task.accumulate_vote(make_quorum_vote(i, view, epoch));
1789            }
1790
1791            let cert = timeout(Duration::from_secs(5), task.next())
1792                .await
1793                .expect("a tally runs while every other view is still waiting")
1794                .expect("certificate");
1795            assert_eq!(cert.view_number(), view);
1796        });
1797    }
1798
1799    /// A vote from outside the epoch's committee can never be tallied, so it
1800    /// must not create collector state for its view and epoch either.
1801    #[tokio::test]
1802    async fn test_vote_from_outside_the_committee_opens_nothing() {
1803        let mut task = setup_cert1_task_with_removed_node();
1804        let view = ViewNumber::new(21);
1805
1806        // Node 9 left the committee in epoch 3.
1807        task.accumulate_vote(make_quorum_vote(9, view, EpochNumber::new(3)));
1808        assert!(task.ballot_boxes.is_empty());
1809        assert!(task.signers.is_empty());
1810
1811        // In epoch 2 it is still a member, and the same vote opens a tally.
1812        task.accumulate_vote(make_quorum_vote(9, view, EpochNumber::new(2)));
1813        assert_eq!(task.ballot_boxes.len(), 1);
1814    }
1815
1816    /// The same membership still counts node 9's vote in an epoch where it
1817    /// is a member: committee resolution is per-epoch.
1818    #[tokio::test]
1819    async fn test_vote_counts_in_epoch_before_removal() {
1820        let mut task = setup_cert1_task_with_removed_node();
1821        let view = ViewNumber::new(11);
1822        let epoch = EpochNumber::new(2);
1823
1824        for i in 0..6 {
1825            task.accumulate_vote(make_quorum_vote(i, view, epoch));
1826        }
1827        task.accumulate_vote(make_quorum_vote(9, view, epoch));
1828        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1829        assert_eq!(cert.view_number(), view);
1830    }
1831
1832    // ==================== mark_completed ====================
1833
1834    /// `mark_completed` discards a certificate the accumulator already formed.
1835    #[tokio::test]
1836    async fn test_mark_completed_discards_formed_cert() {
1837        let mut task = setup_cert2_task();
1838        let view = ViewNumber::new(1);
1839
1840        for i in 0..THRESHOLD {
1841            task.accumulate_vote(make_vote2(i, view));
1842        }
1843        task.mark_completed(view, EpochNumber::genesis());
1844        assert_no_certs(&mut task).await;
1845    }
1846
1847    /// Votes arriving after `mark_completed` are ignored.
1848    #[tokio::test]
1849    async fn test_mark_completed_ignores_later_votes() {
1850        let mut task = setup_cert2_task();
1851        let view = ViewNumber::new(1);
1852
1853        task.mark_completed(view, EpochNumber::genesis());
1854        for i in 0..THRESHOLD {
1855            task.accumulate_vote(make_vote2(i, view));
1856        }
1857        assert_no_certs(&mut task).await;
1858    }
1859
1860    /// `mark_completed` only affects the marked view.
1861    #[tokio::test]
1862    async fn test_mark_completed_leaves_other_views_alone() {
1863        let mut task = setup_cert2_task();
1864        let marked = ViewNumber::new(1);
1865        let live = ViewNumber::new(2);
1866
1867        task.mark_completed(marked, EpochNumber::genesis());
1868        for i in 0..THRESHOLD {
1869            task.accumulate_vote(make_vote2(i, marked));
1870            task.accumulate_vote(make_vote2(i, live));
1871        }
1872        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1873        assert_eq!(cert.view_number(), live);
1874        assert_no_certs(&mut task).await;
1875    }
1876
1877    /// `mark_completed` only affects the marked epoch: at a boundary the same
1878    /// view tallies under two committees, and a certificate from one says
1879    /// nothing about the other.
1880    #[tokio::test]
1881    async fn test_mark_completed_leaves_other_epochs_alone() {
1882        let mut task = setup_cert1_task();
1883        let view = ViewNumber::new(1);
1884        let (marked, live) = (EpochNumber::genesis(), EpochNumber::genesis() + 1);
1885
1886        task.mark_completed(view, marked);
1887        for i in 0..THRESHOLD {
1888            task.accumulate_vote(make_quorum_vote(i, view, marked));
1889            task.accumulate_vote(make_quorum_vote(i, view, live));
1890        }
1891        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1892        assert_eq!(cert.epoch(), live);
1893        assert_no_certs(&mut task).await;
1894    }
1895
1896    /// A signer that has voted under one epoch may vote again under the next.
1897    ///
1898    /// At a boundary the honest nodes are split across the two committees, so
1899    /// a tally keyed by view alone sees the same signer twice and drops the
1900    /// second vote. Neither side then reaches its threshold on its own and the
1901    /// view would have no certificate at all.
1902    #[tokio::test]
1903    async fn a_signer_may_vote_again_once_its_epoch_advances() {
1904        let mut task = setup_cert1_task();
1905        let view = ViewNumber::new(1);
1906        let (old, new) = (EpochNumber::genesis(), EpochNumber::genesis() + 1);
1907
1908        // Everyone votes under the outgoing committee first...
1909        for i in 0..THRESHOLD {
1910            task.accumulate_vote(make_quorum_vote(i, view, old));
1911        }
1912        // ...then the very same signers vote again under the incoming one.
1913        for i in 0..THRESHOLD {
1914            task.accumulate_vote(make_quorum_vote(i, view, new));
1915        }
1916
1917        let mut certified = Vec::new();
1918        for _ in 0..2 {
1919            let cert = timeout(CERT_TIMEOUT, task.next())
1920                .await
1921                .expect("a certificate for each epoch")
1922                .expect("the collector still has a tally running");
1923            assert_eq!(cert.view_number(), view);
1924            certified.push(cert.epoch());
1925        }
1926        certified.sort();
1927        assert_eq!(certified, vec![old, new]);
1928    }
1929
1930    // ==================== Timeout certificate epoch binding ====================
1931
1932    /// Collect a timeout certificate for `view` in `epoch`, in the form that
1933    /// does not bind the epoch.
1934    fn timeout_cert_v2(
1935        view: ViewNumber,
1936        epoch: EpochNumber,
1937        lock: &UpgradeLock<TestTypes>,
1938        membership: &EpochMembership<TestTypes>,
1939    ) -> TimeoutCertificate2<TestTypes> {
1940        let mut accumulator = VoteAccumulator::<
1941            TestTypes,
1942            TimeoutVote2<TestTypes>,
1943            TimeoutCertificate2<TestTypes>,
1944        >::new(lock.clone());
1945
1946        for i in 0..NUM_NODES {
1947            let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], i);
1948            let data = TimeoutData2 {
1949                view,
1950                epoch: Some(epoch),
1951            };
1952            let vote = SimpleVote::create_signed_vote(data, view, &pub_key, &priv_key, lock)
1953                .expect("failed to sign timeout vote");
1954            if let Some(cert) = accumulator.accumulate(&vote, membership.clone()) {
1955                return cert;
1956            }
1957        }
1958        panic!("threshold reached without forming a certificate");
1959    }
1960
1961    /// Collect a timeout certificate for `view` in `epoch`, in the form that
1962    /// binds the epoch.
1963    fn timeout_cert_v3(
1964        view: ViewNumber,
1965        epoch: EpochNumber,
1966        lock: &UpgradeLock<TestTypes>,
1967        membership: &EpochMembership<TestTypes>,
1968    ) -> TimeoutCertificate3<TestTypes> {
1969        let entries = StakeTableEntries::<TestTypes>::from_iter(membership.stake_table()).0;
1970        let votes = (0..NUM_NODES).map(|i| {
1971            let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], i);
1972            let data = TimeoutData3 {
1973                view,
1974                epoch,
1975                lock: None,
1976            };
1977            let vote = SimpleVote::create_signed_vote(data, view, &pub_key, &priv_key, lock)
1978                .expect("failed to sign timeout vote");
1979            (None, pub_key, vote.signature())
1980        });
1981        TimeoutCertificate3::assemble(view, epoch, &entries, votes.collect::<Vec<_>>(), None)
1982            .expect("assemble timeout certificate")
1983    }
1984
1985    fn verifies(
1986        cert: &TimeoutEvidence<TestTypes>,
1987        membership: &EpochMembership<TestTypes>,
1988        lock: &UpgradeLock<TestTypes>,
1989    ) -> bool {
1990        let entries = StakeTableEntries::<TestTypes>::from(
1991            TimeoutCertificate2::<TestTypes>::stake_table(membership),
1992        )
1993        .0;
1994        let threshold =
1995            <TimeoutCertificate2<TestTypes> as Certificate<_, _>>::threshold(membership);
1996        cert.is_valid_cert(&entries, threshold, lock).is_ok()
1997    }
1998
1999    /// An upgrade lock that puts [`TIMEOUT_EPOCH_VERSION`] in effect from
2000    /// `first_view` on. The signatures are never checked by
2001    /// `UpgradeLock::version`, so the certificate carries none.
2002    fn upgrading_lock(first_view: ViewNumber) -> UpgradeLock<TestTypes> {
2003        let data = UpgradeProposalData {
2004            old_version: NEW_PROTOCOL_VERSION,
2005            new_version: TIMEOUT_EPOCH_VERSION,
2006            decide_by: first_view,
2007            new_version_hash: Vec::new(),
2008            old_version_last_view: first_view - 1,
2009            new_version_first_view: first_view,
2010        };
2011        let commitment = data.commit();
2012        let cert =
2013            UpgradeCertificate::<TestTypes>::new(data, commitment, first_view, None, PhantomData);
2014        UpgradeLock::from_certificate(
2015            Upgrade::new(NEW_PROTOCOL_VERSION, TIMEOUT_EPOCH_VERSION),
2016            &Some(cert),
2017        )
2018    }
2019
2020    /// Relabelling the epoch of an epoch binding certificate must invalidate
2021    /// its signature. Every consumer picks the stake table to verify against
2022    /// and the committee to advance into from this field, so an unbound label
2023    /// lets a relaying node steer both.
2024    #[tokio::test]
2025    async fn relabelled_v3_cert_is_rejected() {
2026        let coordinator = mock_membership();
2027        let epoch = EpochNumber::genesis();
2028        let membership = coordinator.membership_for_epoch(Some(epoch)).unwrap();
2029        let lock = UpgradeLock::new(Upgrade::trivial(TIMEOUT_EPOCH_VERSION));
2030
2031        let mut cert = timeout_cert_v3(ViewNumber::new(1), epoch, &lock, &membership);
2032        assert!(
2033            verifies(&TimeoutEvidence::V3(cert.clone()), &membership, &lock),
2034            "freshly collected certificate must verify"
2035        );
2036
2037        cert.epoch = epoch + 1;
2038        assert!(
2039            !verifies(&TimeoutEvidence::V3(cert), &membership, &lock),
2040            "a certificate relabelled to another epoch must not verify"
2041        );
2042    }
2043
2044    /// The hole the new form closes, pinned: the old form's signature says
2045    /// nothing about the epoch, so relabelling one leaves it valid. It is
2046    /// therefore admissibility, checked below, that has to keep the old form out
2047    /// of a view that must bind its epoch.
2048    #[tokio::test]
2049    async fn relabelled_v2_cert_still_verifies_where_it_is_allowed() {
2050        // Tests behaviour before 0.7.
2051        crate::helpers::pin_test_version(versions::NEW_PROTOCOL_VERSION);
2052        let coordinator = mock_membership();
2053        let epoch = EpochNumber::genesis();
2054        let membership = coordinator.membership_for_epoch(Some(epoch)).unwrap();
2055        let lock = test_upgrade_lock();
2056
2057        let mut cert = timeout_cert_v2(ViewNumber::new(1), epoch, &lock, &membership);
2058        cert.data.epoch = Some(epoch + 1);
2059        assert!(
2060            verifies(&TimeoutEvidence::V2(cert), &membership, &lock),
2061            "the old form does not cover the epoch"
2062        );
2063    }
2064
2065    /// Neither form may stand in for the other: past the upgrade the old form
2066    /// is refused however well signed, and before it the new one is.
2067    #[tokio::test]
2068    async fn the_wrong_form_is_refused() {
2069        // Tests behaviour before 0.7.
2070        crate::helpers::pin_test_version(versions::NEW_PROTOCOL_VERSION);
2071        let coordinator = mock_membership();
2072        let epoch = EpochNumber::genesis();
2073        let membership = coordinator.membership_for_epoch(Some(epoch)).unwrap();
2074        let view = ViewNumber::new(1);
2075
2076        let bound = UpgradeLock::new(Upgrade::trivial(TIMEOUT_EPOCH_VERSION));
2077        let unbound = test_upgrade_lock();
2078
2079        let v2 = TimeoutEvidence::V2(timeout_cert_v2(view, epoch, &unbound, &membership));
2080        let v3 = TimeoutEvidence::V3(timeout_cert_v3(view, epoch, &bound, &membership));
2081
2082        assert!(!verifies(&v2, &membership, &bound), "old form past upgrade");
2083        assert!(
2084            !verifies(&v3, &membership, &unbound),
2085            "new form before upgrade"
2086        );
2087    }
2088
2089    /// Which form a view requires is keyed on the view the certificate
2090    /// justifies, not the one it certifies, so the form always matches the
2091    /// version of the block that carries it.
2092    #[tokio::test]
2093    async fn the_required_form_flips_at_the_boundary() {
2094        let coordinator = mock_membership();
2095        let epoch = EpochNumber::genesis();
2096        let membership = coordinator.membership_for_epoch(Some(epoch)).unwrap();
2097        let boundary = ViewNumber::new(10);
2098        let lock = upgrading_lock(boundary);
2099
2100        // Certifies the last old-version view, justifies the first new-version
2101        // proposal, so the epoch must be bound.
2102        let justifies_boundary = boundary - 1;
2103        assert!(
2104            verifies(
2105                &TimeoutEvidence::V3(timeout_cert_v3(
2106                    justifies_boundary,
2107                    epoch,
2108                    &lock,
2109                    &membership
2110                )),
2111                &membership,
2112                &lock,
2113            ),
2114            "the certificate justifying the first new-version proposal must bind its epoch"
2115        );
2116
2117        // One view earlier the proposal it justifies is still old-version.
2118        assert!(
2119            verifies(
2120                &TimeoutEvidence::V2(timeout_cert_v2(
2121                    justifies_boundary - 1,
2122                    epoch,
2123                    &lock,
2124                    &membership
2125                )),
2126                &membership,
2127                &lock,
2128            ),
2129            "before the boundary the old form is the admissible one"
2130        );
2131    }
2132
2133    // ==================== UpgradeTally ====================
2134
2135    /// Upgrade threshold for 10 nodes of stake 1: max((10*9)/10, 7) = 9.
2136    const UPGRADE_THRESHOLD: u64 = 9;
2137
2138    fn upgrade_data(view: ViewNumber) -> hotshot_types::simple_vote::UpgradeProposalData2 {
2139        crate::upgrade::expected_upgrade_data(
2140            &versions::Upgrade::new(versions::version(0, 6), versions::version(0, 7)),
2141            view,
2142            EpochNumber::genesis(),
2143        )
2144        .unwrap()
2145    }
2146
2147    fn make_upgrade_vote(node_index: u64, view: ViewNumber) -> UpgradeVoteMessage<TestTypes> {
2148        let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
2149        SimpleVote::create_signed_vote(
2150            upgrade_data(view),
2151            view,
2152            &pub_key,
2153            &priv_key,
2154            &test_upgrade_lock(),
2155        )
2156        .expect("Failed to sign vote")
2157    }
2158
2159    fn make_invalid_upgrade_vote(
2160        node_index: u64,
2161        view: ViewNumber,
2162    ) -> UpgradeVoteMessage<TestTypes> {
2163        let (pub_key, _) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
2164        let (_, wrong_priv_key) = BLSPubKey::generated_from_seed_indexed([1u8; 32], node_index);
2165        let data = upgrade_data(view);
2166        let commit =
2167            VersionedVoteData::<TestTypes, _>::new(data.clone(), view, &test_upgrade_lock())
2168                .unwrap()
2169                .commit();
2170        let bad_sig = BLSPubKey::sign(&wrong_priv_key, commit.as_ref()).unwrap();
2171        SimpleVote {
2172            signature: (pub_key, bad_sig),
2173            data,
2174            view_number: view,
2175        }
2176    }
2177
2178    fn setup_upgrade_task() -> VoteCollector<TestTypes, super::UpgradeTally<TestTypes>> {
2179        VoteCollector::new(mock_membership(), test_upgrade_lock())
2180    }
2181
2182    async fn assert_no_upgrade_cert(
2183        task: &mut VoteCollector<TestTypes, super::UpgradeTally<TestTypes>>,
2184    ) {
2185        match tokio::time::timeout(NO_CERT_TIMEOUT, task.next()).await {
2186            Err(_) | Ok(None) => {},
2187            Ok(Some(cert)) => panic!("Expected no upgrade certificate but got one: {cert:?}"),
2188        }
2189    }
2190
2191    /// An upgrade certificate forms at the (stricter) upgrade threshold, not
2192    /// at the quorum threshold.
2193    #[tokio::test]
2194    async fn test_upgrade_cert_at_upgrade_threshold() {
2195        let mut task = setup_upgrade_task();
2196        let view = ViewNumber::new(1);
2197
2198        for i in 0..UPGRADE_THRESHOLD - 1 {
2199            task.accumulate_vote(make_upgrade_vote(i, view));
2200        }
2201        assert_no_upgrade_cert(&mut task).await;
2202
2203        task.accumulate_vote(make_upgrade_vote(UPGRADE_THRESHOLD - 1, view));
2204        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
2205        assert_eq!(cert.view_number(), view);
2206        assert_eq!(cert.epoch(), EpochNumber::genesis());
2207        assert_eq!(cert.data, upgrade_data(view));
2208
2209        let membership = mock_membership();
2210        let epoch_membership = membership
2211            .membership_for_epoch(Some(EpochNumber::genesis()))
2212            .unwrap();
2213        verify_cert(cert.cert(), &upgrade_data(view), &epoch_membership);
2214    }
2215
2216    /// Duplicate upgrade votes by the same signer count once.
2217    #[tokio::test]
2218    async fn test_upgrade_cert_duplicate_votes_ignored() {
2219        let mut task = setup_upgrade_task();
2220        let view = ViewNumber::new(1);
2221
2222        for _ in 0..3 {
2223            for i in 0..UPGRADE_THRESHOLD - 1 {
2224                task.accumulate_vote(make_upgrade_vote(i, view));
2225            }
2226        }
2227        assert_no_upgrade_cert(&mut task).await;
2228    }
2229
2230    /// Invalid signatures do not contribute; the tally recovers.
2231    #[tokio::test]
2232    async fn test_upgrade_cert_invalid_signature_recovery() {
2233        let mut task = setup_upgrade_task();
2234        let view = ViewNumber::new(1);
2235
2236        for i in 0..UPGRADE_THRESHOLD - 1 {
2237            task.accumulate_vote(make_upgrade_vote(i, view));
2238        }
2239        task.accumulate_vote(make_invalid_upgrade_vote(UPGRADE_THRESHOLD - 1, view));
2240        assert_no_upgrade_cert(&mut task).await;
2241
2242        task.accumulate_vote(make_upgrade_vote(UPGRADE_THRESHOLD, view));
2243        let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
2244        assert_eq!(cert.view_number(), view);
2245    }
2246}