1use ruc::*;
6
7use hotmint_crypto::has_quorum;
8use hotmint_types::block::{Block, BlockHash, Height};
9use hotmint_types::certificate::QuorumCertificate;
10use hotmint_types::crypto::Verifier;
11use hotmint_types::validator::{ValidatorId, ValidatorSet};
12use hotmint_types::view::ViewNumber;
13use hotmint_types::vote::{Vote, VoteType};
14
15#[derive(Debug, Clone)]
17pub struct BlockHeader {
18 pub height: Height,
19 pub parent_hash: BlockHash,
20 pub view: ViewNumber,
21 pub proposer: ValidatorId,
22 pub app_hash: BlockHash,
23 pub hash: BlockHash,
24}
25
26impl From<&Block> for BlockHeader {
27 fn from(block: &Block) -> Self {
28 Self {
29 height: block.height,
30 parent_hash: block.parent_hash,
31 view: block.view,
32 proposer: block.proposer,
33 app_hash: block.app_hash,
34 hash: block.hash,
35 }
36 }
37}
38
39pub struct LightClient {
41 trusted_validator_set: ValidatorSet,
42 trusted_height: Height,
43 chain_id_hash: [u8; 32],
44}
45
46impl LightClient {
47 pub fn new(
49 trusted_validator_set: ValidatorSet,
50 trusted_height: Height,
51 chain_id_hash: [u8; 32],
52 ) -> Self {
53 Self {
54 trusted_validator_set,
55 trusted_height,
56 chain_id_hash,
57 }
58 }
59
60 pub fn verify_header(
67 &self,
68 header: &BlockHeader,
69 qc: &QuorumCertificate,
70 verifier: &dyn Verifier,
71 ) -> Result<()> {
72 if qc.block_hash != header.hash {
74 return Err(eg!(
75 "QC block_hash mismatch: expected {}, got {}",
76 header.hash,
77 qc.block_hash
78 ));
79 }
80
81 if !has_quorum(&self.trusted_validator_set, &qc.aggregate_signature) {
83 return Err(eg!("QC does not have quorum"));
84 }
85
86 let signing_bytes = Vote::signing_bytes(
88 &self.chain_id_hash,
89 qc.epoch,
90 qc.view,
91 &qc.block_hash,
92 VoteType::Vote,
93 );
94 if !verifier.verify_aggregate(
95 &self.trusted_validator_set,
96 &signing_bytes,
97 &qc.aggregate_signature,
98 ) {
99 return Err(eg!("QC aggregate signature verification failed"));
100 }
101
102 Ok(())
103 }
104
105 pub fn update_validator_set(&mut self, new_vs: ValidatorSet, new_height: Height) {
107 self.trusted_validator_set = new_vs;
108 self.trusted_height = new_height;
109 }
110
111 pub fn trusted_height(&self) -> Height {
113 self.trusted_height
114 }
115
116 pub fn trusted_validator_set(&self) -> &ValidatorSet {
118 &self.trusted_validator_set
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use hotmint_crypto::Ed25519Signer;
126 use hotmint_crypto::Ed25519Verifier;
127 use hotmint_crypto::aggregate::aggregate_votes;
128 use hotmint_types::crypto::Signer;
129 use hotmint_types::epoch::EpochNumber;
130 use hotmint_types::validator::ValidatorInfo;
131
132 const TEST_CHAIN: [u8; 32] = [0u8; 32];
133
134 fn make_env() -> (ValidatorSet, Vec<Ed25519Signer>) {
135 let signers: Vec<Ed25519Signer> = (0..4)
136 .map(|i| Ed25519Signer::generate(ValidatorId(i)))
137 .collect();
138 let infos: Vec<ValidatorInfo> = signers
139 .iter()
140 .map(|s| ValidatorInfo {
141 id: s.validator_id(),
142 public_key: s.public_key(),
143 power: 1,
144 })
145 .collect();
146 (ValidatorSet::new(infos), signers)
147 }
148
149 fn make_header(height: u64, hash: BlockHash) -> BlockHeader {
150 BlockHeader {
151 height: Height(height),
152 parent_hash: BlockHash::GENESIS,
153 view: ViewNumber(height),
154 proposer: ValidatorId(0),
155 app_hash: BlockHash::GENESIS,
156 hash,
157 }
158 }
159
160 fn make_qc(
161 signers: &[Ed25519Signer],
162 vs: &ValidatorSet,
163 block_hash: BlockHash,
164 view: ViewNumber,
165 count: usize,
166 ) -> QuorumCertificate {
167 let epoch = EpochNumber(0);
168 let votes: Vec<hotmint_types::vote::Vote> = signers
169 .iter()
170 .take(count)
171 .map(|s| {
172 let bytes =
173 Vote::signing_bytes(&TEST_CHAIN, epoch, view, &block_hash, VoteType::Vote);
174 hotmint_types::vote::Vote {
175 block_hash,
176 view,
177 validator: s.validator_id(),
178 signature: s.sign(&bytes),
179 vote_type: VoteType::Vote,
180 extension: None,
181 }
182 })
183 .collect();
184 let agg = aggregate_votes(vs, &votes).unwrap();
185 QuorumCertificate {
186 block_hash,
187 view,
188 aggregate_signature: agg,
189 epoch,
190 }
191 }
192
193 #[test]
194 fn test_valid_qc_passes_verification() {
195 let (vs, signers) = make_env();
196 let hash = BlockHash([1u8; 32]);
197 let header = make_header(1, hash);
198 let qc = make_qc(&signers, &vs, hash, ViewNumber(1), 3);
199 let verifier = Ed25519Verifier;
200 let client = LightClient::new(vs, Height(0), TEST_CHAIN);
201
202 assert!(client.verify_header(&header, &qc, &verifier).is_ok());
203 }
204
205 #[test]
206 fn test_wrong_block_hash_fails() {
207 let (vs, signers) = make_env();
208 let hash = BlockHash([1u8; 32]);
209 let wrong_hash = BlockHash([2u8; 32]);
210 let header = make_header(1, hash);
211 let qc = make_qc(&signers, &vs, wrong_hash, ViewNumber(1), 3);
213 let verifier = Ed25519Verifier;
214 let client = LightClient::new(vs, Height(0), TEST_CHAIN);
215
216 let err = client.verify_header(&header, &qc, &verifier);
217 assert!(err.is_err());
218 assert!(
219 err.unwrap_err().to_string().contains("block_hash mismatch"),
220 "expected block_hash mismatch error"
221 );
222 }
223
224 #[test]
225 fn test_no_quorum_fails() {
226 let (vs, signers) = make_env();
227 let hash = BlockHash([1u8; 32]);
228 let header = make_header(1, hash);
229 let qc = make_qc(&signers, &vs, hash, ViewNumber(1), 2);
231 let verifier = Ed25519Verifier;
232 let client = LightClient::new(vs, Height(0), TEST_CHAIN);
233
234 let err = client.verify_header(&header, &qc, &verifier);
235 assert!(err.is_err());
236 assert!(
237 err.unwrap_err().to_string().contains("quorum"),
238 "expected quorum error"
239 );
240 }
241
242 #[test]
243 fn test_update_validator_set() {
244 let (vs, _signers) = make_env();
245 let mut client = LightClient::new(vs.clone(), Height(0), TEST_CHAIN);
246 assert_eq!(client.trusted_height(), Height(0));
247
248 let new_signers: Vec<Ed25519Signer> = (10..14)
249 .map(|i| Ed25519Signer::generate(ValidatorId(i)))
250 .collect();
251 let new_infos: Vec<ValidatorInfo> = new_signers
252 .iter()
253 .map(|s| ValidatorInfo {
254 id: s.validator_id(),
255 public_key: s.public_key(),
256 power: 1,
257 })
258 .collect();
259 let new_vs = ValidatorSet::new(new_infos);
260
261 client.update_validator_set(new_vs, Height(100));
262 assert_eq!(client.trusted_height(), Height(100));
263 assert_eq!(client.trusted_validator_set().validator_count(), 4);
264 }
265}