1mod 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
53pub 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 fn epochs(&self) -> impl Iterator<Item = EpochNumber> {
69 self.epoch().into_iter()
70 }
71}
72
73pub 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
122pub 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
172pub type UpgradeTally<T> = SimpleTally<T, UpgradeVote2<T>, UpgradeThreshold>;
175
176pub type EpochRootCerts<T> = (
178 ValidCert<QuorumCertificate2<T>>,
179 LightClientStateUpdateCertificateV2<T>,
180);
181
182pub 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 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 fn epochs(&self) -> impl Iterator<Item = EpochNumber> {
269 std::iter::once(self.vote().data.epoch).chain(self.timeout_lock().verified_in())
270 }
271}
272
273pub 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 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 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 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 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 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
522type Key = (ViewNumber, EpochNumber);
524
525pub struct VoteCollector<T: NodeType, S: Tally<T>> {
527 accumulators: JoinMap<Key, Option<S::Output>>,
529
530 ballot_boxes: BTreeMap<Key, mpsc::UnboundedSender<S::Vote>>,
532
533 pending: BTreeMap<Key, HashMap<T::SignatureKey, S::Vote>>,
535
536 completed: BTreeSet<Key>,
538
539 signers: BTreeMap<Key, HashSet<T::SignatureKey>>,
541
542 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 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 _ if self.completed.contains(&key) => {},
592 _ => {
597 self.signers.remove(&key);
598 },
599 }
600 }
601 }
602
603 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 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 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 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 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 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#[derive(Clone, Copy, Debug)]
755pub struct VoteStats {
756 pub signers: usize,
758
759 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 const NUM_NODES: u64 = 10;
808 const THRESHOLD: u64 = 7;
810
811 const CERT_TIMEOUT: Duration = Duration::from_millis(100);
813 const RECOVERY_TIMEOUT: Duration = Duration::from_secs(10);
816 const NO_CERT_TIMEOUT: Duration = Duration::from_millis(500);
818
819 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 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 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 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 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 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 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 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(_) => { },
954 Ok(None) => { },
955 Ok(Some(cert)) => panic!("Expected no certificate but got one: {cert:?}"),
956 }
957 }
958
959 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 assert_eq!(
968 cert.data().commit(),
969 expected_data.commit(),
970 "Certificate data commitment does not match expected vote data"
971 );
972
973 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 #[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 #[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 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 #[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 #[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 #[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 #[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 for i in 0..6 {
1136 task.accumulate_vote(make_quorum_vote(i, view, epoch));
1137 }
1138 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 #[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 #[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 for i in 0..6 {
1169 task.accumulate_vote(make_vote2(i, view));
1170 }
1171 for _ in 0..5 {
1173 task.accumulate_vote(make_vote2(0, view));
1174 }
1175
1176 assert_no_certs(&mut task).await;
1177 }
1178
1179 #[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 for i in 0..6 {
1187 task.accumulate_vote(make_vote2(i, view));
1188 }
1189 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 #[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 for i in 0..6 {
1206 task.accumulate_vote(make_vote2(i, view));
1207 }
1208 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 #[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 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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 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 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 #[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 #[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 for i in 0..THRESHOLD {
1576 task.accumulate_vote(make_vote2(i, complete_view));
1577 }
1578
1579 for i in 0..3 {
1581 task.accumulate_vote(make_vote2(i, partial_view));
1582 }
1583
1584 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 #[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 for i in 0..NUM_NODES {
1598 task.accumulate_vote(make_vote2(i, view));
1599 }
1600
1601 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
1603 assert_eq!(cert.view_number(), view);
1604
1605 assert_no_certs(&mut task).await;
1607 }
1608
1609 #[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 for i in 0..6 {
1617 task.accumulate_vote(make_vote2(i, view));
1618 }
1619
1620 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 .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 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 #[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 #[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 task.accumulate_vote(make_quorum_vote(0, view, forged));
1695
1696 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 assert_no_certs(&mut task).await;
1708 }
1709
1710 #[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 #[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 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 #[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 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 #[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 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 task.accumulate_vote(make_quorum_vote(9, view, EpochNumber::new(2)));
1813 assert_eq!(task.ballot_boxes.len(), 1);
1814 }
1815
1816 #[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 #[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 #[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 #[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 #[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 #[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 for i in 0..THRESHOLD {
1910 task.accumulate_vote(make_quorum_vote(i, view, old));
1911 }
1912 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 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 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 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 #[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 #[tokio::test]
2049 async fn relabelled_v2_cert_still_verifies_where_it_is_allowed() {
2050 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 #[tokio::test]
2068 async fn the_wrong_form_is_refused() {
2069 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 #[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 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 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 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 #[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 #[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 #[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}