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 sync::mpsc::{self, Receiver},
20};
21
22pub(crate) use accumulate::CheckedAccumulator;
23use alloy::primitives::U256;
24use hotshot_types::{
25 data::{EpochNumber, ViewNumber},
26 epoch_membership::{EpochMembership, EpochMembershipCoordinator},
27 message::UpgradeLock,
28 simple_certificate::{LightClientStateUpdateCertificateV2, QuorumCertificate2},
29 simple_vote::{HasEpoch, QuorumVote2, SimpleVote, Voteable},
30 traits::{node_implementation::NodeType, signature_key::StakeTableEntryType},
31 vote::{Certificate, HasViewNumber, LightClientStateUpdateVoteAccumulator, Vote},
32};
33use tokio_util::task::JoinMap;
34use tracing::{error, info, warn};
35
36use crate::{cert_verifier::ValidCert, message::Vote1};
37
38pub trait Ballot {
44 type Signer;
45
46 fn view(&self) -> ViewNumber;
47 fn epoch(&self) -> Option<EpochNumber>;
48 fn signer(&self) -> Self::Signer;
49}
50
51pub trait Tally<T: NodeType> {
53 type Vote: Send + 'static;
54 type Output: Send + 'static;
55
56 fn tally(
57 r: Receiver<Self::Vote>,
58 m: EpochMembership<T>,
59 l: UpgradeLock<T>,
60 ) -> Option<Self::Output>;
61}
62
63impl<T: NodeType, D> Ballot for SimpleVote<T, D>
64where
65 D: Voteable<T> + HasEpoch + 'static,
66{
67 type Signer = T::SignatureKey;
68
69 fn view(&self) -> ViewNumber {
70 self.view_number()
71 }
72
73 fn epoch(&self) -> Option<EpochNumber> {
74 HasEpoch::epoch(self)
75 }
76
77 fn signer(&self) -> Self::Signer {
78 self.signing_key()
79 }
80}
81
82impl<T: NodeType> Ballot for Vote1<T> {
83 type Signer = T::SignatureKey;
84
85 fn view(&self) -> ViewNumber {
86 self.vote.view_number()
87 }
88
89 fn epoch(&self) -> Option<EpochNumber> {
90 HasEpoch::epoch(&self.vote)
91 }
92
93 fn signer(&self) -> Self::Signer {
94 self.vote.signing_key()
95 }
96}
97
98#[allow(clippy::type_complexity)]
100pub struct SimpleTally<T, V, C>(PhantomData<fn() -> (T, V, C)>);
101
102impl<T, V, C> Tally<T> for SimpleTally<T, V, C>
103where
104 T: NodeType,
105 V: Vote<T> + Send + 'static,
106 C: Certificate<T, V::Commitment, Voteable = V::Commitment> + HasEpoch + Send + 'static,
107{
108 type Vote = V;
109 type Output = ValidCert<C>;
110
111 fn tally(r: Receiver<V>, m: EpochMembership<T>, l: UpgradeLock<T>) -> Option<Self::Output> {
112 let mut a = CheckedAccumulator::<T, V, C>::new(m, l);
113 while let Ok(v) = r.recv() {
114 if let Some(c) = a.add(v) {
115 if let Some(e) = c.epoch() {
116 return Some(ValidCert::new(c, e));
117 } else {
118 warn!(cert = type_name::<C>(), "certificate has no epoch number");
119 break;
120 }
121 }
122 }
123 None
124 }
125}
126
127pub type EpochRootCerts<T> = (
129 ValidCert<QuorumCertificate2<T>>,
130 LightClientStateUpdateCertificateV2<T>,
131);
132
133pub struct EpochRootTally<T>(PhantomData<fn() -> T>);
135
136impl<T: NodeType> Tally<T> for EpochRootTally<T> {
137 type Vote = Vote1<T>;
138 type Output = EpochRootCerts<T>;
139
140 fn tally(
141 rx: mpsc::Receiver<Vote1<T>>,
142 mem: EpochMembership<T>,
143 lock: UpgradeLock<T>,
144 ) -> Option<Self::Output> {
145 let mut quorum_accu = CheckedAccumulator::<T, QuorumVote2<T>, QuorumCertificate2<T>>::new(
146 mem.clone(),
147 lock.clone(),
148 );
149
150 let mut state_accu = LightClientStateUpdateVoteAccumulator::<T> {
151 vote_outcomes: HashMap::new(),
152 upgrade_lock: lock,
153 };
154
155 let mut quorum_cert = None;
156 let mut state_cert = None;
157
158 while let Ok(vote1) = rx.recv() {
159 let Some(state_vote) = vote1.state_vote else {
160 error!(view = %vote1.vote.view_number(), "epoch-root vote1 without state vote");
161 continue;
162 };
163 let bls_key = vote1.vote.signing_key();
164
165 if quorum_cert.is_none() {
166 quorum_cert = quorum_accu.add(vote1.vote);
167 }
168
169 if state_cert.is_none() {
173 state_cert = state_accu.accumulate(&bls_key, &state_vote, &mem);
174 }
175
176 if let (Some(q), Some(s)) = (&quorum_cert, &state_cert) {
177 info!(view = %q.view_number(), epoch = %s.epoch, "epoch-root certificates formed");
178 if let Some(e) = q.epoch() {
179 return Some((ValidCert::new(q.clone(), e), s.clone()));
180 } else {
181 warn!(
182 cert = type_name_of_val(q),
183 "certificate has no epoch number"
184 );
185 break;
186 }
187 }
188 }
189
190 None
191 }
192}
193
194pub struct VoteCollector<T: NodeType, S: Tally<T>> {
196 accumulators: JoinMap<ViewNumber, Option<S::Output>>,
198
199 ballot_boxes: BTreeMap<ViewNumber, mpsc::Sender<S::Vote>>,
201
202 pending: BTreeMap<ViewNumber, HashMap<T::SignatureKey, S::Vote>>,
204
205 completed: BTreeSet<ViewNumber>,
207
208 signers: BTreeMap<ViewNumber, HashSet<T::SignatureKey>>,
210
211 lower_bound: ViewNumber,
213
214 membership: EpochMembershipCoordinator<T>,
215
216 upgrade_lock: UpgradeLock<T>,
217}
218
219impl<T: NodeType, S: Tally<T>> VoteCollector<T, S>
220where
221 <S as Tally<T>>::Vote: Ballot<Signer = T::SignatureKey>,
222{
223 pub fn new(mc: EpochMembershipCoordinator<T>, lock: UpgradeLock<T>) -> Self {
224 Self {
225 accumulators: JoinMap::new(),
226 ballot_boxes: BTreeMap::new(),
227 pending: BTreeMap::new(),
228 signers: BTreeMap::new(),
229 completed: BTreeSet::new(),
230 membership: mc,
231 upgrade_lock: lock,
232 lower_bound: ViewNumber::genesis(),
233 }
234 }
235
236 pub async fn next(&mut self) -> Option<S::Output> {
237 loop {
238 match self.accumulators.join_next().await {
239 Some((view, Ok(Some(cert)))) => {
240 self.ballot_boxes.remove(&view);
241 if view >= self.lower_bound {
242 self.completed.insert(view);
243 return Some(cert);
244 }
245 },
246 Some((_, Ok(None))) => {},
247 Some((view, Err(err))) => {
248 if err.is_panic() {
249 error!(%view, %err, "vote collection task panic");
250 }
251 self.ballot_boxes.remove(&view);
252 },
253 None => return None,
254 }
255 }
256 }
257
258 pub fn accumulate_vote(&mut self, vote: S::Vote) {
259 let view = vote.view();
260
261 if view < self.lower_bound || self.completed.contains(&view) {
262 return;
263 }
264
265 let Some(membership) = self.resolve_membership(&vote) else {
266 if vote.epoch().is_some() {
270 self.pending
271 .entry(view)
272 .or_default()
273 .insert(vote.signer(), vote);
274 }
275 return;
276 };
277
278 if !self.signers.entry(view).or_default().insert(vote.signer()) {
280 return;
281 }
282
283 if let Some(tx) = self.ballot_boxes.get(&view) {
284 let _ = tx.send(vote);
285 return;
286 }
287
288 let (tx, rx) = mpsc::channel();
289
290 let _ = tx.send(vote);
291 self.ballot_boxes.insert(view, tx);
292
293 let lock = self.upgrade_lock.clone();
294 self.accumulators
295 .spawn_blocking(view, move || S::tally(rx, membership, lock));
296 }
297
298 pub fn retry_pending_votes(&mut self) {
299 for vote in mem::take(&mut self.pending)
300 .into_values()
301 .flat_map(|votes| votes.into_values())
302 {
303 self.accumulate_vote(vote)
304 }
305 }
306
307 pub fn gc(&mut self, view: ViewNumber) {
308 self.ballot_boxes = self.ballot_boxes.split_off(&view);
309 self.completed = self.completed.split_off(&view);
310 self.pending = self.pending.split_off(&view);
311 self.signers = self.signers.split_off(&view);
312 self.lower_bound = view;
313 }
314
315 fn resolve_membership(&mut self, vote: &S::Vote) -> Option<EpochMembership<T>> {
316 let epoch = vote.epoch()?;
317 self.membership.membership_for_epoch(Some(epoch)).ok()
318 }
319}
320
321impl<T, V, C> VoteCollector<T, SimpleTally<T, V, C>>
322where
323 T: NodeType,
324 V: Vote<T> + Send + 'static,
325 C: Certificate<T, V::Commitment, Voteable = V::Commitment> + HasEpoch + Send + 'static,
326{
327 pub fn stats(&self, view: ViewNumber, epoch: EpochNumber) -> Option<VoteStats> {
335 let signers = self.signers.get(&view)?;
336 if signers.is_empty() {
337 return None;
338 }
339 let membership = self.membership.membership_for_epoch(Some(epoch)).ok()?;
340 let threshold = C::threshold(&membership);
341 let mut stake = U256::ZERO;
342 for signer in signers {
343 if let Some(peer) = C::stake_table_entry(&membership, signer) {
344 stake += peer.stake_table_entry.stake();
345 }
346 }
347 Some(VoteStats { stake, threshold })
348 }
349}
350
351#[derive(Clone, Copy, Debug)]
356pub struct VoteStats {
357 pub stake: U256,
358 pub threshold: U256,
359}
360
361#[cfg(test)]
362mod tests {
363 use std::{fmt::Debug, time::Duration};
364
365 use committable::Committable;
366 use hotshot::types::BLSPubKey;
367 use hotshot_example_types::node_types::TestTypes;
368 use hotshot_testing::{node_stake::TestNodeStakes, test_builder::gen_node_lists};
369 use hotshot_types::{
370 data::{EpochNumber, ViewNumber},
371 epoch_membership::EpochMembership,
372 simple_vote::{
373 HasEpoch, QuorumData2, QuorumVote2, SimpleVote, VersionedVoteData, Vote2Data,
374 },
375 stake_table::StakeTableEntries,
376 traits::{node_implementation::NodeType, signature_key::SignatureKey},
377 vote::{Certificate, HasViewNumber, Vote},
378 };
379 use tokio::{sync::mpsc, time::timeout};
380
381 use super::{Ballot, SimpleTally, VoteCollector};
382 use crate::{
383 helpers::test_upgrade_lock,
384 message::{Certificate1, Certificate2, Vote2},
385 tests::common::utils::mock_membership,
386 };
387
388 const NUM_NODES: u64 = 10;
390 const THRESHOLD: u64 = 7;
392
393 const CERT_TIMEOUT: Duration = Duration::from_millis(100);
395 const NO_CERT_TIMEOUT: Duration = Duration::from_millis(500);
397
398 fn make_quorum_vote(
400 node_index: u64,
401 view: ViewNumber,
402 epoch: EpochNumber,
403 ) -> QuorumVote2<TestTypes> {
404 let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
405 let data = QuorumData2 {
406 leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
407 .u64(42)
408 .finalize(),
409 epoch: Some(epoch),
410 block_number: Some(1),
411 };
412 SimpleVote::create_signed_vote(data, view, &pub_key, &priv_key, &test_upgrade_lock())
413 .expect("Failed to sign vote")
414 }
415
416 fn vote_2_data() -> Vote2Data<TestTypes> {
417 Vote2Data {
418 leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
419 .u64(42)
420 .finalize(),
421 epoch: EpochNumber::genesis(),
422 block_number: 1,
423 }
424 }
425
426 fn make_vote2(node_index: u64, view: ViewNumber) -> Vote2<TestTypes> {
428 let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
429 let data = vote_2_data();
430 SimpleVote::create_signed_vote(data, view, &pub_key, &priv_key, &test_upgrade_lock())
431 .expect("Failed to sign vote")
432 }
433
434 fn make_invalid_vote2(node_index: u64, view: ViewNumber) -> Vote2<TestTypes> {
436 let (pub_key, _) = BLSPubKey::generated_from_seed_indexed([0u8; 32], node_index);
437 let (_, wrong_priv_key) = BLSPubKey::generated_from_seed_indexed([1u8; 32], node_index);
439 let data = vote_2_data();
440 let commit =
441 VersionedVoteData::<TestTypes, _>::new(data.clone(), view, &test_upgrade_lock())
442 .unwrap()
443 .commit();
444 let bad_sig = BLSPubKey::sign(&wrong_priv_key, commit.as_ref()).unwrap();
445 SimpleVote {
446 signature: (pub_key, bad_sig),
447 data,
448 view_number: view,
449 }
450 }
451
452 fn setup_cert1_task() -> VoteCollector<
457 TestTypes,
458 SimpleTally<TestTypes, QuorumVote2<TestTypes>, Certificate1<TestTypes>>,
459 > {
460 setup_task::<QuorumVote2<TestTypes>, Certificate1<TestTypes>>()
461 }
462
463 fn setup_cert2_task()
464 -> VoteCollector<TestTypes, SimpleTally<TestTypes, Vote2<TestTypes>, Certificate2<TestTypes>>>
465 {
466 setup_task::<Vote2<TestTypes>, Certificate2<TestTypes>>()
467 }
468
469 fn setup_task<
471 V: Ballot<Signer = <TestTypes as NodeType>::SignatureKey>
472 + Vote<TestTypes>
473 + HasEpoch
474 + Send
475 + Sync
476 + 'static,
477 C: Certificate<TestTypes, V::Commitment, Voteable = V::Commitment>
478 + HasEpoch
479 + Send
480 + Sync
481 + 'static,
482 >() -> VoteCollector<TestTypes, SimpleTally<TestTypes, V, C>> {
483 let membership = mock_membership();
484 VoteCollector::new(membership, test_upgrade_lock())
485 }
486
487 async fn _collect_certs<T: std::fmt::Debug>(
489 cert_rx: &mut mpsc::Receiver<T>,
490 expected: usize,
491 ) -> Vec<T> {
492 let mut results = Vec::new();
493 for _ in 0..expected {
494 let cert = tokio::time::timeout(CERT_TIMEOUT, cert_rx.recv())
495 .await
496 .expect("Timed out waiting for certificate")
497 .expect("Cert channel closed unexpectedly");
498 results.push(cert);
499 }
500 results
501 }
502
503 async fn assert_no_certs<
505 V: Ballot<Signer = <TestTypes as NodeType>::SignatureKey>
506 + Vote<TestTypes>
507 + HasEpoch
508 + Send
509 + Sync
510 + 'static,
511 C: Certificate<TestTypes, V::Commitment, Voteable = V::Commitment>
512 + HasEpoch
513 + Debug
514 + Send
515 + Sync
516 + 'static,
517 >(
518 task: &mut VoteCollector<TestTypes, SimpleTally<TestTypes, V, C>>,
519 ) {
520 let result = tokio::time::timeout(NO_CERT_TIMEOUT, task.next()).await;
521 match result {
522 Err(_) => { },
523 Ok(None) => { },
524 Ok(Some(cert)) => panic!("Expected no certificate but got one: {cert:?}"),
525 }
526 }
527
528 fn verify_cert<C, D>(cert: &C, expected_data: &D, membership: &EpochMembership<TestTypes>)
531 where
532 D: Committable,
533 C: Certificate<TestTypes, D, Voteable = D>,
534 {
535 assert_eq!(
537 cert.data().commit(),
538 expected_data.commit(),
539 "Certificate data commitment does not match expected vote data"
540 );
541
542 let stake_table = C::stake_table(membership);
544 let stake_table_entries = StakeTableEntries::<TestTypes>::from(stake_table).0;
545 let threshold = C::threshold(membership);
546 cert.is_valid_cert(&stake_table_entries, threshold, &test_upgrade_lock())
547 .expect("Certificate signature validation failed");
548 }
549
550 #[tokio::test]
555 async fn test_cert1_single_view_happy_path() {
556 let mut task = setup_cert1_task();
557 let view = ViewNumber::new(1);
558 let epoch = EpochNumber::genesis();
559 let expected_data = QuorumData2 {
560 leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
561 .u64(42)
562 .finalize(),
563 epoch: Some(epoch),
564 block_number: Some(1),
565 };
566
567 for i in 0..THRESHOLD {
568 task.accumulate_vote(make_quorum_vote(i, view, epoch));
569 }
570
571 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
572 assert_eq!(cert.view_number(), view);
573
574 let membership = mock_membership();
575 let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
576 verify_cert(cert.cert(), &expected_data, &epoch_membership);
577 }
578
579 #[tokio::test]
582 async fn test_cert1_multiple_views_parallel() {
583 let mut task = setup_cert1_task();
584 let epoch = EpochNumber::genesis();
585 let expected_data = QuorumData2 {
586 leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
587 .u64(42)
588 .finalize(),
589 epoch: Some(epoch),
590 block_number: Some(1),
591 };
592
593 let views = [ViewNumber::new(1), ViewNumber::new(2), ViewNumber::new(3)];
594
595 for i in 0..THRESHOLD {
597 for &view in &views {
598 task.accumulate_vote(make_quorum_vote(i, view, epoch));
599 }
600 }
601 let mut certs = Vec::new();
602 for _ in 0..views.len() {
603 certs.push(timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap());
604 }
605 assert_eq!(
606 certs.len(),
607 views.len(),
608 "Expected one Certificate1 per view"
609 );
610 let mut cert_views: Vec<_> = certs.iter().map(|c| c.view_number()).collect();
611 cert_views.sort();
612 assert_eq!(cert_views, views.to_vec());
613
614 let membership = mock_membership();
615 let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
616 for cert in &certs {
617 verify_cert(cert.cert(), &expected_data, &epoch_membership);
618 }
619 }
620
621 #[tokio::test]
626 async fn test_cert2_single_view_happy_path() {
627 let mut task = setup_cert2_task();
628 let view = ViewNumber::new(1);
629 let epoch = EpochNumber::genesis();
630 let expected_data = vote_2_data();
631
632 for i in 0..THRESHOLD {
633 task.accumulate_vote(make_vote2(i, view));
634 }
635
636 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
637 assert_eq!(cert.view_number(), view);
638
639 let membership = mock_membership();
640 let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
641 verify_cert(cert.cert(), &expected_data, &epoch_membership);
642 }
643
644 #[tokio::test]
647 async fn test_cert2_multiple_views_parallel() {
648 let mut task = setup_cert2_task();
649 let epoch = EpochNumber::genesis();
650 let expected_data = vote_2_data();
651
652 let views = [ViewNumber::new(5), ViewNumber::new(6), ViewNumber::new(7)];
653
654 for i in 0..THRESHOLD {
655 for &view in &views {
656 task.accumulate_vote(make_vote2(i, view));
657 }
658 }
659
660 let mut certs = Vec::new();
661 for _ in 0..views.len() {
662 certs.push(timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap());
663 }
664 assert_eq!(
665 certs.len(),
666 views.len(),
667 "Expected one Certificate2 per view"
668 );
669 let mut cert_views: Vec<_> = certs.iter().map(|c| c.view_number()).collect();
670 cert_views.sort();
671 assert_eq!(cert_views, views.to_vec());
672
673 let membership = mock_membership();
674 let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
675 for cert in &certs {
676 verify_cert(cert.cert(), &expected_data, &epoch_membership);
677 }
678 }
679
680 #[tokio::test]
684 async fn test_cert1_below_threshold_no_certificate() {
685 let mut task = setup_cert1_task();
686 let view = ViewNumber::new(1);
687 let epoch = EpochNumber::genesis();
688
689 for i in 0..(THRESHOLD - 1) {
690 task.accumulate_vote(make_quorum_vote(i, view, epoch));
691 }
692
693 assert_no_certs(&mut task).await;
694 }
695
696 #[tokio::test]
698 async fn test_cert1_duplicate_votes_ignored() {
699 let mut task = setup_cert1_task();
700 let view = ViewNumber::new(1);
701 let epoch = EpochNumber::genesis();
702
703 for i in 0..6 {
705 task.accumulate_vote(make_quorum_vote(i, view, epoch));
706 }
707 for _ in 0..5 {
709 task.accumulate_vote(make_quorum_vote(0, view, epoch));
710 }
711
712 assert_no_certs(&mut task).await;
713 }
714
715 #[tokio::test]
719 async fn test_cert2_below_threshold_no_certificate() {
720 let mut task = setup_cert2_task();
721 let view = ViewNumber::new(1);
722
723 for i in 0..(THRESHOLD - 1) {
724 task.accumulate_vote(make_vote2(i, view));
725 }
726
727 assert_no_certs(&mut task).await;
728 }
729
730 #[tokio::test]
732 async fn test_cert2_duplicate_votes_ignored() {
733 let mut task = setup_cert2_task();
734 let view = ViewNumber::new(1);
735
736 for i in 0..6 {
738 task.accumulate_vote(make_vote2(i, view));
739 }
740 for _ in 0..5 {
742 task.accumulate_vote(make_vote2(0, view));
743 }
744
745 assert_no_certs(&mut task).await;
746 }
747
748 #[tokio::test]
750 async fn test_cert2_invalid_signature_rejected() {
751 let mut task = setup_cert2_task();
752 let view = ViewNumber::new(1);
753
754 for i in 0..6 {
756 task.accumulate_vote(make_vote2(i, view));
757 }
758 for i in 6..NUM_NODES {
760 task.accumulate_vote(make_invalid_vote2(i, view));
761 }
762
763 assert_no_certs(&mut task).await;
764 }
765
766 #[tokio::test]
768 async fn test_cert2_invalid_signature_recovery() {
769 let mut task = setup_cert2_task();
770 let view = ViewNumber::new(1);
771 let epoch = EpochNumber::genesis();
772
773 for i in 0..6 {
775 task.accumulate_vote(make_vote2(i, view));
776 }
777 for i in 6..8 {
779 task.accumulate_vote(make_invalid_vote2(i, view));
780 }
781 assert_no_certs(&mut task).await;
782
783 task.accumulate_vote(make_vote2(9, view));
784
785 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
786 assert_no_certs(&mut task).await;
787 let membership = mock_membership();
788 let epoch_membership = membership.membership_for_epoch(Some(epoch)).unwrap();
789 verify_cert(cert.cert(), &vote_2_data(), &epoch_membership);
790 }
791
792 #[tokio::test]
794 async fn test_cert2_channel_closed_early() {
795 let mut task = setup_cert2_task();
796 let view = ViewNumber::new(1);
797
798 for i in 0..3 {
799 task.accumulate_vote(make_vote2(i, view));
800 }
801 assert_no_certs(&mut task).await;
802 }
803
804 #[tokio::test]
808 async fn test_cert2_partial_views_only_complete_one_certifies() {
809 let mut task = setup_cert2_task();
810
811 let complete_view = ViewNumber::new(1);
812 let partial_view = ViewNumber::new(2);
813
814 for i in 0..THRESHOLD {
816 task.accumulate_vote(make_vote2(i, complete_view));
817 }
818
819 for i in 0..3 {
821 task.accumulate_vote(make_vote2(i, partial_view));
822 }
823
824 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
826 assert_no_certs(&mut task).await;
827 assert_eq!(cert.view_number(), complete_view);
828 }
829
830 #[tokio::test]
832 async fn test_cert2_extra_votes_after_threshold_no_duplicate_cert() {
833 let mut task = setup_cert2_task();
834 let view = ViewNumber::new(1);
835
836 for i in 0..NUM_NODES {
838 task.accumulate_vote(make_vote2(i, view));
839 }
840
841 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
843 assert_eq!(cert.view_number(), view);
844
845 assert_no_certs(&mut task).await;
847 }
848
849 #[tokio::test]
851 async fn test_cert2_conflicting_data_same_view_no_certificate() {
852 let mut task = setup_cert2_task();
853 let view = ViewNumber::new(1);
854
855 for i in 0..6 {
857 task.accumulate_vote(make_vote2(i, view));
858 }
859
860 for i in 6..NUM_NODES {
862 let (pub_key, priv_key) = BLSPubKey::generated_from_seed_indexed([0u8; 32], i);
863 let data = Vote2Data {
864 leaf_commit: committable::RawCommitmentBuilder::new("FakeLeaf")
865 .u64(1000)
867 .finalize(),
868 epoch: EpochNumber::genesis(),
869 block_number: 1,
870 };
871 let vote = SimpleVote::create_signed_vote(
872 data,
873 view,
874 &pub_key,
875 &priv_key,
876 &test_upgrade_lock(),
877 )
878 .expect("Failed to sign vote");
879 task.accumulate_vote(vote);
880 }
881 assert_no_certs(&mut task).await;
882 }
883
884 fn setup_cert1_task_with_removed_node() -> VoteCollector<
887 TestTypes,
888 SimpleTally<TestTypes, QuorumVote2<TestTypes>, Certificate1<TestTypes>>,
889 > {
890 let membership = mock_membership();
891 let committee = gen_node_lists::<TestTypes>(9, 9, &TestNodeStakes::default()).0;
892 membership
893 .membership()
894 .add_quorum_committee(EpochNumber::new(3), committee);
895 membership
896 .membership()
897 .register_epoch(EpochNumber::new(3), [0u8; 32]);
898 VoteCollector::new(membership, test_upgrade_lock())
899 }
900
901 #[tokio::test]
904 async fn test_vote_from_removed_validator_ignored() {
905 let mut task = setup_cert1_task_with_removed_node();
906 let view = ViewNumber::new(21);
907 let epoch = EpochNumber::new(3);
908
909 for i in 0..6 {
910 task.accumulate_vote(make_quorum_vote(i, view, epoch));
911 }
912 task.accumulate_vote(make_quorum_vote(9, view, epoch));
913 assert_no_certs(&mut task).await;
914
915 task.accumulate_vote(make_quorum_vote(6, view, epoch));
916 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
917 assert_eq!(cert.view_number(), view);
918 }
919
920 #[tokio::test]
923 async fn test_vote_counts_in_epoch_before_removal() {
924 let mut task = setup_cert1_task_with_removed_node();
925 let view = ViewNumber::new(11);
926 let epoch = EpochNumber::new(2);
927
928 for i in 0..6 {
929 task.accumulate_vote(make_quorum_vote(i, view, epoch));
930 }
931 task.accumulate_vote(make_quorum_vote(9, view, epoch));
932 let cert = timeout(CERT_TIMEOUT, task.next()).await.unwrap().unwrap();
933 assert_eq!(cert.view_number(), view);
934 }
935}