1use core::time::Duration;
4
5use cometbft::{
6 block::Height, chain::Id as ChainId, crypto::Sha256, hash::Hash, merkle::MerkleHash,
7};
8
9use crate::{
10 errors::VerificationError,
11 operations::{CommitValidator, VotingPowerCalculator},
12 prelude::*,
13 types::{Header, SignedHeader, Time, TrustThreshold, ValidatorSet},
14};
15
16#[cfg(feature = "rust-crypto")]
19#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
20pub struct ProdPredicates;
21
22#[cfg(feature = "rust-crypto")]
23impl VerificationPredicates for ProdPredicates {
24 type Sha256 = cometbft::crypto::default::Sha256;
25}
26
27pub trait VerificationPredicates: Send + Sync {
34 type Sha256: MerkleHash + Sha256 + Default;
36
37 fn validator_sets_match(
40 &self,
41 validators: &ValidatorSet,
42 header_validators_hash: Hash,
43 ) -> Result<(), VerificationError> {
44 let validators_hash = validators.hash_with::<Self::Sha256>();
45 if header_validators_hash == validators_hash {
46 Ok(())
47 } else {
48 Err(VerificationError::invalid_validator_set(
49 header_validators_hash,
50 validators_hash,
51 ))
52 }
53 }
54
55 fn next_validators_match(
57 &self,
58 next_validators: &ValidatorSet,
59 header_next_validators_hash: Hash,
60 ) -> Result<(), VerificationError> {
61 let next_validators_hash = next_validators.hash_with::<Self::Sha256>();
62 if header_next_validators_hash == next_validators_hash {
63 Ok(())
64 } else {
65 Err(VerificationError::invalid_next_validator_set(
66 header_next_validators_hash,
67 next_validators_hash,
68 ))
69 }
70 }
71
72 fn header_matches_commit(
74 &self,
75 header: &Header,
76 commit_hash: Hash,
77 ) -> Result<(), VerificationError> {
78 let header_hash = header.hash_with::<Self::Sha256>();
79 if header_hash == commit_hash {
80 Ok(())
81 } else {
82 Err(VerificationError::invalid_commit_value(
83 header_hash,
84 commit_hash,
85 ))
86 }
87 }
88
89 fn valid_commit(
91 &self,
92 signed_header: &SignedHeader,
93 validators: &ValidatorSet,
94 commit_validator: &dyn CommitValidator,
95 ) -> Result<(), VerificationError> {
96 commit_validator.validate(signed_header, validators)?;
97 commit_validator.validate_full(signed_header, validators)?;
98
99 Ok(())
100 }
101
102 fn is_within_trust_period(
104 &self,
105 trusted_header_time: Time,
106 trusting_period: Duration,
107 now: Time,
108 ) -> Result<(), VerificationError> {
109 let expires_at =
110 (trusted_header_time + trusting_period).map_err(VerificationError::cometbft)?;
111
112 if expires_at > now {
113 Ok(())
114 } else {
115 Err(VerificationError::not_within_trust_period(expires_at, now))
116 }
117 }
118
119 fn is_header_from_past(
121 &self,
122 untrusted_header_time: Time,
123 clock_drift: Duration,
124 now: Time,
125 ) -> Result<(), VerificationError> {
126 let drifted = (now + clock_drift).map_err(VerificationError::cometbft)?;
127
128 if untrusted_header_time < drifted {
129 Ok(())
130 } else {
131 Err(VerificationError::header_from_the_future(
132 untrusted_header_time,
133 now,
134 clock_drift,
135 ))
136 }
137 }
138
139 fn is_monotonic_bft_time(
141 &self,
142 untrusted_header_time: Time,
143 trusted_header_time: Time,
144 ) -> Result<(), VerificationError> {
145 if untrusted_header_time > trusted_header_time {
146 Ok(())
147 } else {
148 Err(VerificationError::non_monotonic_bft_time(
149 untrusted_header_time,
150 trusted_header_time,
151 ))
152 }
153 }
154
155 fn is_monotonic_height(
157 &self,
158 untrusted_height: Height,
159 trusted_height: Height,
160 ) -> Result<(), VerificationError> {
161 if untrusted_height > trusted_height {
162 Ok(())
163 } else {
164 Err(VerificationError::non_increasing_height(
165 untrusted_height,
166 trusted_height.increment(),
167 ))
168 }
169 }
170
171 fn is_matching_chain_id(
173 &self,
174 untrusted_chain_id: &ChainId,
175 trusted_chain_id: &ChainId,
176 ) -> Result<(), VerificationError> {
177 if untrusted_chain_id == trusted_chain_id {
178 Ok(())
179 } else {
180 Err(VerificationError::chain_id_mismatch(
181 untrusted_chain_id.to_string(),
182 trusted_chain_id.to_string(),
183 ))
184 }
185 }
186
187 fn has_sufficient_validators_overlap(
190 &self,
191 untrusted_sh: &SignedHeader,
192 trusted_validators: &ValidatorSet,
193 trust_threshold: &TrustThreshold,
194 calculator: &dyn VotingPowerCalculator,
195 ) -> Result<(), VerificationError> {
196 calculator.check_enough_trust(untrusted_sh, trusted_validators, *trust_threshold)?;
197 Ok(())
198 }
199
200 fn has_sufficient_signers_overlap(
203 &self,
204 untrusted_sh: &SignedHeader,
205 untrusted_validators: &ValidatorSet,
206 calculator: &dyn VotingPowerCalculator,
207 ) -> Result<(), VerificationError> {
208 calculator.check_signers_overlap(untrusted_sh, untrusted_validators)?;
209 Ok(())
210 }
211
212 fn valid_next_validator_set(
215 &self,
216 untrusted_validators_hash: Hash,
217 trusted_next_validators_hash: Hash,
218 ) -> Result<(), VerificationError> {
219 if trusted_next_validators_hash == untrusted_validators_hash {
220 Ok(())
221 } else {
222 Err(VerificationError::invalid_next_validator_set(
223 untrusted_validators_hash,
224 trusted_next_validators_hash,
225 ))
226 }
227 }
228}
229
230#[cfg(all(test, feature = "rust-crypto"))]
231mod tests {
232 use core::{convert::TryInto, time::Duration};
233
234 use cometbft::{block::CommitSig, validator::Set};
235 use cometbft_testgen::{
236 light_block::{LightBlock as TestgenLightBlock, TmLightBlock},
237 Commit, Generator, Header, Validator, ValidatorSet,
238 };
239 use time::OffsetDateTime;
240
241 use crate::{
242 errors::{VerificationError, VerificationErrorDetail},
243 operations::{ProdCommitValidator, ProdVotingPowerCalculator, VotingPowerTally},
244 predicates::{ProdPredicates, VerificationPredicates},
245 prelude::*,
246 types::{LightBlock, TrustThreshold},
247 };
248
249 impl From<TmLightBlock> for LightBlock {
250 fn from(lb: TmLightBlock) -> Self {
251 LightBlock {
252 signed_header: lb.signed_header,
253 validators: lb.validators,
254 next_validators: lb.next_validators,
255 provider: lb.provider,
256 }
257 }
258 }
259
260 #[test]
261 fn test_is_monotonic_bft_time() {
262 let val = vec![Validator::new("val-1")];
263 let header_one = Header::new(&val).generate().unwrap();
264 let header_two = Header::new(&val).generate().unwrap();
265
266 let vp = ProdPredicates;
267
268 let result_ok = vp.is_monotonic_bft_time(header_two.time, header_one.time);
270 assert!(result_ok.is_ok());
271
272 let result_err = vp.is_monotonic_bft_time(header_one.time, header_two.time);
274 match result_err {
275 Err(VerificationError(VerificationErrorDetail::NonMonotonicBftTime(e), _)) => {
276 assert_eq!(e.header_bft_time, header_one.time);
277 assert_eq!(e.trusted_header_bft_time, header_two.time);
278 },
279 _ => panic!("expected NonMonotonicBftTime error"),
280 }
281 }
282
283 #[test]
284 fn test_is_monotonic_height() {
285 let val = vec![Validator::new("val-1")];
286 let header_one = Header::new(&val).generate().unwrap();
287 let header_two = Header::new(&val).height(2).generate().unwrap();
288
289 let vp = ProdPredicates;
290
291 let result_ok = vp.is_monotonic_height(header_two.height, header_one.height);
293 assert!(result_ok.is_ok());
294
295 let result_err = vp.is_monotonic_height(header_one.height, header_two.height);
297
298 match result_err {
299 Err(VerificationError(VerificationErrorDetail::NonIncreasingHeight(e), _)) => {
300 assert_eq!(e.got, header_one.height);
301 assert_eq!(e.expected, header_two.height.increment());
302 },
303 _ => panic!("expected NonIncreasingHeight error"),
304 }
305 }
306
307 #[test]
308 fn test_is_matching_chain_id() {
309 let val = vec![Validator::new("val-1")];
310 let header_one = Header::new(&val).chain_id("chaina-1").generate().unwrap();
311 let header_two = Header::new(&val).chain_id("chainb-1").generate().unwrap();
312
313 let vp = ProdPredicates;
314
315 let result_ok = vp.is_matching_chain_id(&header_one.chain_id, &header_one.chain_id);
317 assert!(result_ok.is_ok());
318
319 let result_err = vp.is_matching_chain_id(&header_one.chain_id, &header_two.chain_id);
321
322 match result_err {
323 Err(VerificationError(VerificationErrorDetail::ChainIdMismatch(e), _)) => {
324 assert_eq!(e.got, header_one.chain_id.to_string());
325 assert_eq!(e.expected, header_two.chain_id.to_string());
326 },
327 _ => panic!("expected ChainIdMismatch error"),
328 }
329 }
330
331 #[test]
332 fn test_is_within_trust_period() {
333 let val = Validator::new("val-1");
334 let header = Header::new(&[val]).generate().unwrap();
335
336 let vp = ProdPredicates;
337
338 let mut trusting_period = Duration::new(1000, 0);
340 let now = OffsetDateTime::now_utc().try_into().unwrap();
341
342 let result_ok = vp.is_within_trust_period(header.time, trusting_period, now);
343 assert!(result_ok.is_ok());
344
345 trusting_period = Duration::new(0, 1);
347
348 let result_err = vp.is_within_trust_period(header.time, trusting_period, now);
349
350 let expires_at = (header.time + trusting_period).unwrap();
351 match result_err {
352 Err(VerificationError(VerificationErrorDetail::NotWithinTrustPeriod(e), _)) => {
353 assert_eq!(e.expires_at, expires_at);
354 assert_eq!(e.now, now);
355 },
356 _ => panic!("expected NotWithinTrustPeriod error"),
357 }
358 }
359
360 #[test]
361 fn test_is_header_from_past() {
362 let val = Validator::new("val-1");
363 let header = Header::new(&[val]).generate().unwrap();
364
365 let vp = ProdPredicates;
366 let one_second = Duration::new(1, 0);
367
368 let now = OffsetDateTime::now_utc().try_into().unwrap();
369
370 let result_ok = vp.is_header_from_past(header.time, one_second, now);
372
373 assert!(result_ok.is_ok());
374
375 let now = (now - one_second * 15).unwrap();
377 let result_err = vp.is_header_from_past(header.time, one_second, now);
378
379 match result_err {
380 Err(VerificationError(VerificationErrorDetail::HeaderFromTheFuture(e), _)) => {
381 assert_eq!(e.header_time, header.time);
382 assert_eq!(e.now, now);
383 },
384 _ => panic!("expected HeaderFromTheFuture error"),
385 }
386 }
387
388 #[test]
389 fn test_validator_sets_match() {
391 let mut light_block: LightBlock =
392 TestgenLightBlock::new_default(1).generate().unwrap().into();
393
394 let bad_validator_set = ValidatorSet::new(vec!["bad-val"]).generate().unwrap();
395
396 let vp = ProdPredicates;
397
398 let val_sets_match_ok = vp.validator_sets_match(
401 &light_block.validators,
402 light_block.signed_header.header.validators_hash,
403 );
404
405 assert!(val_sets_match_ok.is_ok());
406
407 let next_val_sets_match_ok = vp.next_validators_match(
409 &light_block.next_validators,
410 light_block.signed_header.header.next_validators_hash,
411 );
412
413 assert!(next_val_sets_match_ok.is_ok());
414
415 light_block.validators = bad_validator_set.clone();
418
419 let val_sets_match_err = vp.validator_sets_match(
420 &light_block.validators,
421 light_block.signed_header.header.validators_hash,
422 );
423
424 match val_sets_match_err {
425 Err(VerificationError(VerificationErrorDetail::InvalidValidatorSet(e), _)) => {
426 assert_eq!(
427 e.header_validators_hash,
428 light_block.signed_header.header.validators_hash
429 );
430 assert_eq!(e.validators_hash, light_block.validators.hash());
431 },
432 _ => panic!("expected InvalidValidatorSet error"),
433 }
434
435 light_block.next_validators = bad_validator_set;
437 let next_val_sets_match_err = vp.next_validators_match(
438 &light_block.next_validators,
439 light_block.signed_header.header.next_validators_hash,
440 );
441
442 match next_val_sets_match_err {
443 Err(VerificationError(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => {
444 assert_eq!(
445 e.header_next_validators_hash,
446 light_block.signed_header.header.next_validators_hash
447 );
448 assert_eq!(e.next_validators_hash, light_block.next_validators.hash());
449 },
450 _ => panic!("expected InvalidNextValidatorSet error"),
451 }
452 }
453
454 #[test]
455 fn test_header_matches_commit() {
456 let mut signed_header = TestgenLightBlock::new_default(1)
457 .generate()
458 .unwrap()
459 .signed_header;
460
461 let vp = ProdPredicates;
462
463 let result_ok =
465 vp.header_matches_commit(&signed_header.header, signed_header.commit.block_id.hash);
466
467 assert!(result_ok.is_ok());
468
469 signed_header.commit.block_id.hash =
471 "15F15EF50BDE2018F4B129A827F90C18222C757770C8295EB8EE7BF50E761BC0"
472 .parse()
473 .unwrap();
474 let result_err =
475 vp.header_matches_commit(&signed_header.header, signed_header.commit.block_id.hash);
476
477 let header_hash = signed_header.header.hash();
479
480 match result_err {
481 Err(VerificationError(VerificationErrorDetail::InvalidCommitValue(e), _)) => {
482 assert_eq!(e.header_hash, header_hash);
483 assert_eq!(e.commit_hash, signed_header.commit.block_id.hash);
484 },
485 _ => panic!("expected InvalidCommitValue error"),
486 }
487 }
488
489 #[test]
490 fn test_valid_commit() {
491 let light_block: LightBlock = TestgenLightBlock::new_default(1).generate().unwrap().into();
492
493 let mut signed_header = light_block.signed_header;
494 let val_set = light_block.validators;
495
496 let vp = ProdPredicates;
497 let commit_validator = ProdCommitValidator;
498
499 let mut result_ok = vp.valid_commit(&signed_header, &val_set, &commit_validator);
502
503 assert!(result_ok.is_ok());
504
505 let signatures = signed_header.commit.signatures.clone();
507 signed_header.commit.signatures = vec![];
508
509 let mut result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator);
510
511 match result_err {
512 Err(VerificationError(VerificationErrorDetail::NoSignatureForCommit(_), _)) => {},
513 _ => panic!("expected ImplementationSpecific error"),
514 }
515
516 let mut bad_sigs = vec![signatures.clone().swap_remove(1)];
519 signed_header.commit.signatures = bad_sigs.clone();
520
521 result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator);
522
523 match result_err {
524 Err(VerificationError(VerificationErrorDetail::MismatchPreCommitLength(e), _)) => {
525 assert_eq!(e.pre_commit_length, signed_header.commit.signatures.len());
526 assert_eq!(e.validator_length, val_set.validators().len());
527 },
528 _ => panic!("expected MismatchPreCommitLength error"),
529 }
530
531 bad_sigs.push(CommitSig::BlockIdFlagAbsent);
533 signed_header.commit.signatures = bad_sigs;
534 result_ok = vp.valid_commit(&signed_header, &val_set, &commit_validator);
535 assert!(result_ok.is_ok());
536
537 let mut bad_vals = val_set.validators().clone();
539 bad_vals.pop();
540 bad_vals.push(
541 Validator::new("bad-val")
542 .generate()
543 .expect("Failed to generate validator"),
544 );
545 let val_set_with_faulty_signer = Set::without_proposer(bad_vals);
546
547 signed_header.commit.signatures = signatures;
549
550 result_err = vp.valid_commit(
551 &signed_header,
552 &val_set_with_faulty_signer,
553 &commit_validator,
554 );
555
556 match result_err {
557 Err(VerificationError(VerificationErrorDetail::FaultySigner(e), _)) => {
558 assert_eq!(
559 e.signer,
560 signed_header
561 .commit
562 .signatures
563 .iter()
564 .last()
565 .unwrap()
566 .validator_address()
567 .unwrap()
568 );
569
570 assert_eq!(e.validator_set, val_set_with_faulty_signer);
571 },
572 _ => panic!("expected FaultySigner error"),
573 }
574 }
575
576 #[test]
577 fn test_valid_next_validator_set() {
578 let test_lb1 = TestgenLightBlock::new_default(1);
579 let light_block1: LightBlock = test_lb1.generate().unwrap().into();
580
581 let light_block2: LightBlock = test_lb1.next().generate().unwrap().into();
582
583 let vp = ProdPredicates;
584
585 let result_ok = vp.valid_next_validator_set(
588 light_block1.signed_header.header.validators_hash,
589 light_block2.signed_header.header.next_validators_hash,
590 );
591
592 assert!(result_ok.is_ok());
593
594 let vals = &[Validator::new("new-1"), Validator::new("new-2")];
596 let header = Header::new(vals);
597 let commit = Commit::new(header.clone(), 1);
598
599 let light_block3: LightBlock = TestgenLightBlock::new(header, commit)
600 .generate()
601 .unwrap()
602 .into();
603
604 let result_err = vp.valid_next_validator_set(
605 light_block3.signed_header.header.validators_hash,
606 light_block2.signed_header.header.next_validators_hash,
607 );
608
609 match result_err {
610 Err(VerificationError(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => {
611 assert_eq!(
612 e.header_next_validators_hash,
613 light_block3.signed_header.header.validators_hash
614 );
615 assert_eq!(
616 e.next_validators_hash,
617 light_block2.signed_header.header.next_validators_hash
618 );
619 },
620 _ => panic!("expected InvalidNextValidatorSet error"),
621 }
622 }
623
624 #[test]
625 fn test_has_sufficient_validators_overlap() {
626 let light_block: LightBlock = TestgenLightBlock::new_default(1).generate().unwrap().into();
627 let val_set = light_block.validators;
628 let signed_header = light_block.signed_header;
629
630 let vp = ProdPredicates;
631 let mut trust_threshold = TrustThreshold::new(1, 3).expect("Cannot make trust threshold");
632 let voting_power_calculator = ProdVotingPowerCalculator::default();
633
634 let result_ok = vp.has_sufficient_validators_overlap(
637 &signed_header,
638 &val_set,
639 &trust_threshold,
640 &voting_power_calculator,
641 );
642
643 assert!(result_ok.is_ok());
644
645 let mut vals = val_set.validators().clone();
647 vals.push(
648 Validator::new("extra-val")
649 .voting_power(100)
650 .generate()
651 .unwrap(),
652 );
653 let bad_valset = Set::without_proposer(vals);
654
655 trust_threshold = TrustThreshold::new(2, 3).expect("Cannot make trust threshold");
656
657 let result_err = vp.has_sufficient_validators_overlap(
658 &signed_header,
659 &bad_valset,
660 &trust_threshold,
661 &voting_power_calculator,
662 );
663
664 match result_err {
665 Err(VerificationError(VerificationErrorDetail::NotEnoughTrust(e), _)) => {
666 assert_eq!(
667 e.tally,
668 VotingPowerTally {
669 total: 200,
670 tallied: 100,
671 trust_threshold,
672 }
673 );
674 },
675 _ => panic!("expected NotEnoughTrust error"),
676 }
677 }
678
679 #[test]
680 fn test_has_sufficient_signers_overlap() {
681 let mut light_block: LightBlock =
682 TestgenLightBlock::new_default(2).generate().unwrap().into();
683
684 let vp = ProdPredicates;
685 let voting_power_calculator = ProdVotingPowerCalculator::default();
686
687 let result_ok = vp.has_sufficient_signers_overlap(
690 &light_block.signed_header,
691 &light_block.validators,
692 &voting_power_calculator,
693 );
694
695 assert!(result_ok.is_ok());
696
697 light_block.signed_header.commit.signatures.pop();
699
700 let result_err = vp.has_sufficient_signers_overlap(
701 &light_block.signed_header,
702 &light_block.validators,
703 &voting_power_calculator,
704 );
705
706 let trust_threshold = TrustThreshold::TWO_THIRDS;
707
708 match result_err {
709 Err(VerificationError(VerificationErrorDetail::InsufficientSignersOverlap(e), _)) => {
710 assert_eq!(
711 e.tally,
712 VotingPowerTally {
713 total: 100,
714 tallied: 50,
715 trust_threshold,
716 }
717 );
718 },
719 _ => panic!("expected InsufficientSignersOverlap error"),
720 }
721 }
722}