hotmint_crypto/
aggregate.rs1use ruc::*;
2
3use hotmint_types::crypto::AggregateSignature;
4use hotmint_types::validator::ValidatorSet;
5use hotmint_types::vote::Vote;
6
7pub fn aggregate_votes(vs: &ValidatorSet, votes: &[Vote]) -> Result<AggregateSignature> {
9 let mut agg = AggregateSignature::new(vs.validator_count());
10 for vote in votes {
11 let idx = vs
12 .index_of(vote.validator)
13 .c(d!("unknown validator in vote"))?;
14 agg.add(idx, vote.signature.clone()).c(d!())?;
15 }
16 Ok(agg)
17}
18
19pub fn has_quorum(vs: &ValidatorSet, agg: &AggregateSignature) -> bool {
21 let mut power = 0u64;
22 let validators = vs.validators();
23 for (i, signed) in agg.signers.iter().enumerate() {
24 if *signed && let Some(vi) = validators.get(i) {
25 power += vi.power;
26 }
27 }
28 power >= vs.quorum_threshold()
29}
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34 use crate::Ed25519Signer;
35 use hotmint_types::epoch::EpochNumber;
36 use hotmint_types::validator::{ValidatorId, ValidatorInfo};
37 use hotmint_types::view::ViewNumber;
38 use hotmint_types::vote::VoteType;
39 use hotmint_types::{BlockHash, Signer};
40
41 const TEST_CHAIN: [u8; 32] = [0u8; 32];
42
43 fn make_env() -> (ValidatorSet, Vec<Ed25519Signer>) {
44 let signers: Vec<Ed25519Signer> = (0..4)
45 .map(|i| Ed25519Signer::generate(ValidatorId(i)))
46 .collect();
47 let infos: Vec<ValidatorInfo> = signers
48 .iter()
49 .map(|s| ValidatorInfo {
50 id: s.validator_id(),
51 public_key: s.public_key(),
52 power: 1,
53 })
54 .collect();
55 (ValidatorSet::new(infos), signers)
56 }
57
58 #[test]
59 fn test_aggregate_votes_basic() {
60 let (vs, signers) = make_env();
61 let hash = BlockHash([1u8; 32]);
62 let view = ViewNumber(1);
63 let votes: Vec<Vote> = signers
64 .iter()
65 .take(3)
66 .map(|s| {
67 let bytes =
68 Vote::signing_bytes(&TEST_CHAIN, EpochNumber(0), view, &hash, VoteType::Vote);
69 Vote {
70 block_hash: hash,
71 view,
72 validator: s.validator_id(),
73 signature: s.sign(&bytes),
74 vote_type: VoteType::Vote,
75 extension: None,
76 }
77 })
78 .collect();
79
80 let agg = aggregate_votes(&vs, &votes).unwrap();
81 assert_eq!(agg.count(), 3);
82 assert!(has_quorum(&vs, &agg));
83 }
84
85 #[test]
86 fn test_no_quorum_with_too_few_votes() {
87 let (vs, signers) = make_env();
88 let hash = BlockHash([2u8; 32]);
89 let view = ViewNumber(1);
90 let votes: Vec<Vote> = signers
91 .iter()
92 .take(2)
93 .map(|s| {
94 let bytes =
95 Vote::signing_bytes(&TEST_CHAIN, EpochNumber(0), view, &hash, VoteType::Vote);
96 Vote {
97 block_hash: hash,
98 view,
99 validator: s.validator_id(),
100 signature: s.sign(&bytes),
101 vote_type: VoteType::Vote,
102 extension: None,
103 }
104 })
105 .collect();
106
107 let agg = aggregate_votes(&vs, &votes).unwrap();
108 assert_eq!(agg.count(), 2);
109 assert!(!has_quorum(&vs, &agg));
110 }
111
112 #[test]
113 fn test_aggregate_unknown_validator() {
114 let (vs, _) = make_env();
115 let unknown_signer = Ed25519Signer::generate(ValidatorId(99));
116 let hash = BlockHash([3u8; 32]);
117 let bytes = Vote::signing_bytes(
118 &TEST_CHAIN,
119 EpochNumber(0),
120 ViewNumber(1),
121 &hash,
122 VoteType::Vote,
123 );
124 let vote = Vote {
125 block_hash: hash,
126 view: ViewNumber(1),
127 validator: ValidatorId(99),
128 signature: unknown_signer.sign(&bytes),
129 vote_type: VoteType::Vote,
130 extension: None,
131 };
132 assert!(aggregate_votes(&vs, &[vote]).is_err());
133 }
134}