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