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 qc.block_hash != header.hash {
79 return Err(eg!(
80 "QC block_hash mismatch: expected {}, got {}",
81 header.hash,
82 qc.block_hash
83 ));
84 }
85
86 if !has_quorum(&self.trusted_validator_set, &qc.aggregate_signature) {
88 return Err(eg!("QC does not have quorum"));
89 }
90
91 let signing_bytes = Vote::signing_bytes(
93 &self.chain_id_hash,
94 qc.epoch,
95 qc.view,
96 &qc.block_hash,
97 VoteType::Vote,
98 );
99 if !verifier.verify_aggregate(
100 &self.trusted_validator_set,
101 &signing_bytes,
102 &qc.aggregate_signature,
103 ) {
104 return Err(eg!("QC aggregate signature verification failed"));
105 }
106
107 Ok(())
108 }
109
110 pub fn update_validator_set(&mut self, new_vs: ValidatorSet, new_height: Height) {
112 self.trusted_validator_set = new_vs;
113 self.trusted_height = new_height;
114 }
115
116 pub fn trusted_height(&self) -> Height {
118 self.trusted_height
119 }
120
121 pub fn trusted_validator_set(&self) -> &ValidatorSet {
123 &self.trusted_validator_set
124 }
125
126 pub fn verify_state_proof(
135 app_hash: &[u8; 32],
136 expected_key: &[u8],
137 proof: &vsdb::MptProof,
138 ) -> ruc::Result<bool> {
139 vsdb::MptCalc::verify_proof(app_hash, expected_key, proof)
140 .map_err(|e| ruc::eg!(format!("MPT proof verification failed: {e}")))
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use hotmint_crypto::Ed25519Signer;
148 use hotmint_crypto::Ed25519Verifier;
149 use hotmint_crypto::aggregate::aggregate_votes;
150 use hotmint_types::crypto::Signer;
151 use hotmint_types::epoch::EpochNumber;
152 use hotmint_types::validator::ValidatorInfo;
153
154 const TEST_CHAIN: [u8; 32] = [0u8; 32];
155
156 fn make_env() -> (ValidatorSet, Vec<Ed25519Signer>) {
157 let signers: Vec<Ed25519Signer> = (0..4)
158 .map(|i| Ed25519Signer::generate(ValidatorId(i)))
159 .collect();
160 let infos: Vec<ValidatorInfo> = signers
161 .iter()
162 .map(|s| ValidatorInfo {
163 id: s.validator_id(),
164 public_key: s.public_key(),
165 power: 1,
166 })
167 .collect();
168 (ValidatorSet::new(infos), signers)
169 }
170
171 fn make_header(height: u64, hash: BlockHash) -> BlockHeader {
172 BlockHeader {
173 height: Height(height),
174 parent_hash: BlockHash::GENESIS,
175 view: ViewNumber(height),
176 proposer: ValidatorId(0),
177 timestamp: 0,
178 app_hash: BlockHash::GENESIS,
179 hash,
180 }
181 }
182
183 fn make_qc(
184 signers: &[Ed25519Signer],
185 vs: &ValidatorSet,
186 block_hash: BlockHash,
187 view: ViewNumber,
188 count: usize,
189 ) -> QuorumCertificate {
190 let epoch = EpochNumber(0);
191 let votes: Vec<hotmint_types::vote::Vote> = signers
192 .iter()
193 .take(count)
194 .map(|s| {
195 let bytes =
196 Vote::signing_bytes(&TEST_CHAIN, epoch, view, &block_hash, VoteType::Vote);
197 hotmint_types::vote::Vote {
198 block_hash,
199 view,
200 validator: s.validator_id(),
201 signature: s.sign(&bytes),
202 vote_type: VoteType::Vote,
203 extension: None,
204 }
205 })
206 .collect();
207 let agg = aggregate_votes(vs, &votes).unwrap();
208 QuorumCertificate {
209 block_hash,
210 view,
211 aggregate_signature: agg,
212 epoch,
213 }
214 }
215
216 #[test]
217 fn test_valid_qc_passes_verification() {
218 let (vs, signers) = make_env();
219 let hash = BlockHash([1u8; 32]);
220 let header = make_header(1, hash);
221 let qc = make_qc(&signers, &vs, hash, ViewNumber(1), 3);
222 let verifier = Ed25519Verifier;
223 let client = LightClient::new(vs, Height(0), TEST_CHAIN);
224
225 assert!(client.verify_header(&header, &qc, &verifier).is_ok());
226 }
227
228 #[test]
229 fn test_wrong_block_hash_fails() {
230 let (vs, signers) = make_env();
231 let hash = BlockHash([1u8; 32]);
232 let wrong_hash = BlockHash([2u8; 32]);
233 let header = make_header(1, hash);
234 let qc = make_qc(&signers, &vs, wrong_hash, ViewNumber(1), 3);
236 let verifier = Ed25519Verifier;
237 let client = LightClient::new(vs, Height(0), TEST_CHAIN);
238
239 let err = client.verify_header(&header, &qc, &verifier);
240 assert!(err.is_err());
241 assert!(
242 err.unwrap_err().to_string().contains("block_hash mismatch"),
243 "expected block_hash mismatch error"
244 );
245 }
246
247 #[test]
248 fn test_no_quorum_fails() {
249 let (vs, signers) = make_env();
250 let hash = BlockHash([1u8; 32]);
251 let header = make_header(1, hash);
252 let qc = make_qc(&signers, &vs, hash, ViewNumber(1), 2);
254 let verifier = Ed25519Verifier;
255 let client = LightClient::new(vs, Height(0), TEST_CHAIN);
256
257 let err = client.verify_header(&header, &qc, &verifier);
258 assert!(err.is_err());
259 assert!(
260 err.unwrap_err().to_string().contains("quorum"),
261 "expected quorum error"
262 );
263 }
264
265 #[test]
266 fn test_update_validator_set() {
267 let (vs, _signers) = make_env();
268 let mut client = LightClient::new(vs.clone(), Height(0), TEST_CHAIN);
269 assert_eq!(client.trusted_height(), Height(0));
270
271 let new_signers: Vec<Ed25519Signer> = (10..14)
272 .map(|i| Ed25519Signer::generate(ValidatorId(i)))
273 .collect();
274 let new_infos: Vec<ValidatorInfo> = new_signers
275 .iter()
276 .map(|s| ValidatorInfo {
277 id: s.validator_id(),
278 public_key: s.public_key(),
279 power: 1,
280 })
281 .collect();
282 let new_vs = ValidatorSet::new(new_infos);
283
284 client.update_validator_set(new_vs, Height(100));
285 assert_eq!(client.trusted_height(), Height(100));
286 assert_eq!(client.trusted_validator_set().validator_count(), 4);
287 }
288}