1use core::time::Duration;
4
5use tendermint::{
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 = tendermint::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::tendermint)?;
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::tendermint)?;
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_and_signers_overlap(
206 &self,
207 untrusted_sh: &SignedHeader,
208 trusted_validators: &ValidatorSet,
209 trust_threshold: &TrustThreshold,
210 untrusted_validators: &ValidatorSet,
211 calculator: &dyn VotingPowerCalculator,
212 ) -> Result<(), VerificationError> {
213 calculator.check_enough_trust_and_signers(
214 untrusted_sh,
215 trusted_validators,
216 *trust_threshold,
217 untrusted_validators,
218 )?;
219 Ok(())
220 }
221
222 fn has_sufficient_signers_overlap(
225 &self,
226 untrusted_sh: &SignedHeader,
227 untrusted_validators: &ValidatorSet,
228 calculator: &dyn VotingPowerCalculator,
229 ) -> Result<(), VerificationError> {
230 calculator.check_signers_overlap(untrusted_sh, untrusted_validators)?;
231 Ok(())
232 }
233
234 fn valid_next_validator_set(
237 &self,
238 untrusted_validators_hash: Hash,
239 trusted_next_validators_hash: Hash,
240 ) -> Result<(), VerificationError> {
241 if trusted_next_validators_hash == untrusted_validators_hash {
242 Ok(())
243 } else {
244 Err(VerificationError::invalid_next_validator_set(
245 untrusted_validators_hash,
246 trusted_next_validators_hash,
247 ))
248 }
249 }
250}
251
252#[cfg(all(test, feature = "rust-crypto"))]
253mod tests {
254 use core::time::Duration;
255
256 use tendermint::{block::CommitSig, validator::Set};
257 use tendermint_testgen::{
258 light_block::{LightBlock as TestgenLightBlock, TmLightBlock},
259 Commit, Generator, Header, Validator, ValidatorSet,
260 };
261 use time::OffsetDateTime;
262
263 use crate::{
264 errors::{VerificationError, VerificationErrorDetail},
265 operations::{ProdCommitValidator, ProdVotingPowerCalculator, VotingPowerTally},
266 predicates::{ProdPredicates, VerificationPredicates},
267 prelude::*,
268 types::{LightBlock, TrustThreshold},
269 };
270
271 impl From<TmLightBlock> for LightBlock {
272 fn from(lb: TmLightBlock) -> Self {
273 LightBlock {
274 signed_header: lb.signed_header,
275 validators: lb.validators,
276 next_validators: lb.next_validators,
277 provider: lb.provider,
278 }
279 }
280 }
281
282 #[test]
283 fn test_is_monotonic_bft_time() {
284 let val = vec![Validator::new("val-1")];
285 let header_one = Header::new(&val).generate().unwrap();
286 let header_two = Header::new(&val).generate().unwrap();
287
288 let vp = ProdPredicates;
289
290 let result_ok = vp.is_monotonic_bft_time(header_two.time, header_one.time);
292 assert!(result_ok.is_ok());
293
294 let result_err = vp.is_monotonic_bft_time(header_one.time, header_two.time);
296 match result_err {
297 Err(VerificationError(VerificationErrorDetail::NonMonotonicBftTime(e), _)) => {
298 assert_eq!(e.header_bft_time, header_one.time);
299 assert_eq!(e.trusted_header_bft_time, header_two.time);
300 },
301 _ => panic!("expected NonMonotonicBftTime error"),
302 }
303 }
304
305 #[test]
306 fn test_is_monotonic_height() {
307 let val = vec![Validator::new("val-1")];
308 let header_one = Header::new(&val).generate().unwrap();
309 let header_two = Header::new(&val).height(2).generate().unwrap();
310
311 let vp = ProdPredicates;
312
313 let result_ok = vp.is_monotonic_height(header_two.height, header_one.height);
315 assert!(result_ok.is_ok());
316
317 let result_err = vp.is_monotonic_height(header_one.height, header_two.height);
319
320 match result_err {
321 Err(VerificationError(VerificationErrorDetail::NonIncreasingHeight(e), _)) => {
322 assert_eq!(e.got, header_one.height);
323 assert_eq!(e.expected, header_two.height.increment());
324 },
325 _ => panic!("expected NonIncreasingHeight error"),
326 }
327 }
328
329 #[test]
330 fn test_is_matching_chain_id() {
331 let val = vec![Validator::new("val-1")];
332 let header_one = Header::new(&val).chain_id("chaina-1").generate().unwrap();
333 let header_two = Header::new(&val).chain_id("chainb-1").generate().unwrap();
334
335 let vp = ProdPredicates;
336
337 let result_ok = vp.is_matching_chain_id(&header_one.chain_id, &header_one.chain_id);
339 assert!(result_ok.is_ok());
340
341 let result_err = vp.is_matching_chain_id(&header_one.chain_id, &header_two.chain_id);
343
344 match result_err {
345 Err(VerificationError(VerificationErrorDetail::ChainIdMismatch(e), _)) => {
346 assert_eq!(e.got, header_one.chain_id.to_string());
347 assert_eq!(e.expected, header_two.chain_id.to_string());
348 },
349 _ => panic!("expected ChainIdMismatch error"),
350 }
351 }
352
353 #[test]
354 fn test_is_within_trust_period() {
355 let val = Validator::new("val-1");
356 let header = Header::new(&[val]).generate().unwrap();
357
358 let vp = ProdPredicates;
359
360 let mut trusting_period = Duration::new(1000, 0);
362 let now = OffsetDateTime::now_utc().try_into().unwrap();
363
364 let result_ok = vp.is_within_trust_period(header.time, trusting_period, now);
365 assert!(result_ok.is_ok());
366
367 trusting_period = Duration::new(0, 1);
369
370 let result_err = vp.is_within_trust_period(header.time, trusting_period, now);
371
372 let expires_at = (header.time + trusting_period).unwrap();
373 match result_err {
374 Err(VerificationError(VerificationErrorDetail::NotWithinTrustPeriod(e), _)) => {
375 assert_eq!(e.expires_at, expires_at);
376 assert_eq!(e.now, now);
377 },
378 _ => panic!("expected NotWithinTrustPeriod error"),
379 }
380 }
381
382 #[test]
383 fn test_is_header_from_past() {
384 let val = Validator::new("val-1");
385 let header = Header::new(&[val]).generate().unwrap();
386
387 let vp = ProdPredicates;
388 let one_second = Duration::new(1, 0);
389
390 let now = OffsetDateTime::now_utc().try_into().unwrap();
391
392 let result_ok = vp.is_header_from_past(header.time, one_second, now);
394
395 assert!(result_ok.is_ok());
396
397 let now = (now - one_second * 15).unwrap();
399 let result_err = vp.is_header_from_past(header.time, one_second, now);
400
401 match result_err {
402 Err(VerificationError(VerificationErrorDetail::HeaderFromTheFuture(e), _)) => {
403 assert_eq!(e.header_time, header.time);
404 assert_eq!(e.now, now);
405 },
406 _ => panic!("expected HeaderFromTheFuture error"),
407 }
408 }
409
410 #[test]
411 fn test_validator_sets_match() {
413 let mut light_block: LightBlock =
414 TestgenLightBlock::new_default(1).generate().unwrap().into();
415
416 let bad_validator_set = ValidatorSet::new(vec!["bad-val"]).generate().unwrap();
417
418 let vp = ProdPredicates;
419
420 let val_sets_match_ok = vp.validator_sets_match(
423 &light_block.validators,
424 light_block.signed_header.header.validators_hash,
425 );
426
427 assert!(val_sets_match_ok.is_ok());
428
429 let next_val_sets_match_ok = vp.next_validators_match(
431 &light_block.next_validators,
432 light_block.signed_header.header.next_validators_hash,
433 );
434
435 assert!(next_val_sets_match_ok.is_ok());
436
437 light_block.validators = bad_validator_set.clone();
440
441 let val_sets_match_err = vp.validator_sets_match(
442 &light_block.validators,
443 light_block.signed_header.header.validators_hash,
444 );
445
446 match val_sets_match_err {
447 Err(VerificationError(VerificationErrorDetail::InvalidValidatorSet(e), _)) => {
448 assert_eq!(
449 e.header_validators_hash,
450 light_block.signed_header.header.validators_hash
451 );
452 assert_eq!(e.validators_hash, light_block.validators.hash());
453 },
454 _ => panic!("expected InvalidValidatorSet error"),
455 }
456
457 light_block.next_validators = bad_validator_set;
459 let next_val_sets_match_err = vp.next_validators_match(
460 &light_block.next_validators,
461 light_block.signed_header.header.next_validators_hash,
462 );
463
464 match next_val_sets_match_err {
465 Err(VerificationError(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => {
466 assert_eq!(
467 e.header_next_validators_hash,
468 light_block.signed_header.header.next_validators_hash
469 );
470 assert_eq!(e.next_validators_hash, light_block.next_validators.hash());
471 },
472 _ => panic!("expected InvalidNextValidatorSet error"),
473 }
474 }
475
476 #[test]
477 fn test_header_matches_commit() {
478 let mut signed_header = TestgenLightBlock::new_default(1)
479 .generate()
480 .unwrap()
481 .signed_header;
482
483 let vp = ProdPredicates;
484
485 let result_ok =
487 vp.header_matches_commit(&signed_header.header, signed_header.commit.block_id.hash);
488
489 assert!(result_ok.is_ok());
490
491 signed_header.commit.block_id.hash =
493 "15F15EF50BDE2018F4B129A827F90C18222C757770C8295EB8EE7BF50E761BC0"
494 .parse()
495 .unwrap();
496 let result_err =
497 vp.header_matches_commit(&signed_header.header, signed_header.commit.block_id.hash);
498
499 let header_hash = signed_header.header.hash();
501
502 match result_err {
503 Err(VerificationError(VerificationErrorDetail::InvalidCommitValue(e), _)) => {
504 assert_eq!(e.header_hash, header_hash);
505 assert_eq!(e.commit_hash, signed_header.commit.block_id.hash);
506 },
507 _ => panic!("expected InvalidCommitValue error"),
508 }
509 }
510
511 #[test]
512 fn test_valid_commit() {
513 let light_block: LightBlock = TestgenLightBlock::new_default(1).generate().unwrap().into();
514
515 let mut signed_header = light_block.signed_header;
516 let val_set = light_block.validators;
517
518 let vp = ProdPredicates;
519 let commit_validator = ProdCommitValidator;
520
521 let mut result_ok = vp.valid_commit(&signed_header, &val_set, &commit_validator);
524
525 assert!(result_ok.is_ok());
526
527 let signatures = signed_header.commit.signatures.clone();
529 signed_header.commit.signatures = vec![];
530
531 let mut result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator);
532
533 match result_err {
534 Err(VerificationError(VerificationErrorDetail::NoSignatureForCommit(_), _)) => {},
535 _ => panic!("expected ImplementationSpecific error"),
536 }
537
538 let mut bad_sigs = vec![signatures.clone().swap_remove(1)];
541 signed_header.commit.signatures.clone_from(&bad_sigs);
542
543 result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator);
544
545 match result_err {
546 Err(VerificationError(VerificationErrorDetail::MismatchPreCommitLength(e), _)) => {
547 assert_eq!(e.pre_commit_length, signed_header.commit.signatures.len());
548 assert_eq!(e.validator_length, val_set.validators().len());
549 },
550 _ => panic!("expected MismatchPreCommitLength error"),
551 }
552
553 bad_sigs.push(CommitSig::BlockIdFlagAbsent);
555 signed_header.commit.signatures = bad_sigs;
556 result_ok = vp.valid_commit(&signed_header, &val_set, &commit_validator);
557 assert!(result_ok.is_ok());
558
559 let mut bad_vals = val_set.validators().clone();
561 bad_vals.pop();
562 bad_vals.push(
563 Validator::new("bad-val")
564 .generate()
565 .expect("Failed to generate validator"),
566 );
567 let val_set_with_faulty_signer = Set::without_proposer(bad_vals);
568
569 signed_header.commit.signatures = signatures;
571
572 result_err = vp.valid_commit(
573 &signed_header,
574 &val_set_with_faulty_signer,
575 &commit_validator,
576 );
577
578 match result_err {
579 Err(VerificationError(VerificationErrorDetail::FaultySigner(e), _)) => {
580 assert_eq!(
581 e.signer,
582 signed_header
583 .commit
584 .signatures
585 .iter()
586 .last()
587 .unwrap()
588 .validator_address()
589 .unwrap()
590 );
591
592 assert_eq!(e.validator_set, val_set_with_faulty_signer);
593 },
594 _ => panic!("expected FaultySigner error"),
595 }
596 }
597
598 #[test]
599 fn test_valid_next_validator_set() {
600 let test_lb1 = TestgenLightBlock::new_default(1);
601 let light_block1: LightBlock = test_lb1.generate().unwrap().into();
602
603 let light_block2: LightBlock = test_lb1.next().generate().unwrap().into();
604
605 let vp = ProdPredicates;
606
607 let result_ok = vp.valid_next_validator_set(
610 light_block1.signed_header.header.validators_hash,
611 light_block2.signed_header.header.next_validators_hash,
612 );
613
614 assert!(result_ok.is_ok());
615
616 let vals = &[Validator::new("new-1"), Validator::new("new-2")];
618 let header = Header::new(vals);
619 let commit = Commit::new(header.clone(), 1);
620
621 let light_block3: LightBlock = TestgenLightBlock::new(header, commit)
622 .generate()
623 .unwrap()
624 .into();
625
626 let result_err = vp.valid_next_validator_set(
627 light_block3.signed_header.header.validators_hash,
628 light_block2.signed_header.header.next_validators_hash,
629 );
630
631 match result_err {
632 Err(VerificationError(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => {
633 assert_eq!(
634 e.header_next_validators_hash,
635 light_block3.signed_header.header.validators_hash
636 );
637 assert_eq!(
638 e.next_validators_hash,
639 light_block2.signed_header.header.next_validators_hash
640 );
641 },
642 _ => panic!("expected InvalidNextValidatorSet error"),
643 }
644 }
645
646 #[test]
647 fn test_has_sufficient_validators_and_signers_overlap() {
648 let light_block: LightBlock = TestgenLightBlock::new_default(1).generate().unwrap().into();
649 let val_set = light_block.validators;
650 let signed_header = light_block.signed_header;
651
652 let vp = ProdPredicates;
653 let voting_power_calculator = ProdVotingPowerCalculator::default();
654
655 vp.has_sufficient_validators_and_signers_overlap(
658 &signed_header,
659 &val_set,
660 &TrustThreshold::TWO_THIRDS,
661 &val_set,
662 &voting_power_calculator,
663 )
664 .unwrap();
665
666 let mut vals = val_set.validators().clone();
668 vals.push(
669 Validator::new("extra-val")
670 .voting_power(100)
671 .generate()
672 .unwrap(),
673 );
674 let bad_valset = Set::without_proposer(vals);
675
676 let result = vp.has_sufficient_validators_and_signers_overlap(
677 &signed_header,
678 &bad_valset,
679 &TrustThreshold::TWO_THIRDS,
680 &val_set,
681 &voting_power_calculator,
682 );
683
684 let expected_tally = VotingPowerTally {
685 total: 200,
686 tallied: 100,
687 trust_threshold: TrustThreshold::TWO_THIRDS,
688 };
689 match result {
690 Err(VerificationError(VerificationErrorDetail::NotEnoughTrust(e), _)) => {
691 assert_eq!(expected_tally, e.tally)
692 },
693 _ => panic!("expected NotEnoughTrust error, got: {result:?}"),
694 }
695
696 let result = vp.has_sufficient_validators_and_signers_overlap(
698 &signed_header,
699 &val_set,
700 &TrustThreshold::TWO_THIRDS,
701 &bad_valset,
702 &voting_power_calculator,
703 );
704 match result {
705 Err(VerificationError(VerificationErrorDetail::InsufficientSignersOverlap(e), _)) => {
706 assert_eq!(expected_tally, e.tally)
707 },
708 _ => panic!("expected InsufficientSignersOverlap error, got: {result:?}"),
709 }
710 }
711
712 #[test]
713 fn test_has_sufficient_signers_overlap() {
714 let mut light_block: LightBlock =
715 TestgenLightBlock::new_default(2).generate().unwrap().into();
716
717 let vp = ProdPredicates;
718 let voting_power_calculator = ProdVotingPowerCalculator::default();
719
720 let result_ok = vp.has_sufficient_signers_overlap(
723 &light_block.signed_header,
724 &light_block.validators,
725 &voting_power_calculator,
726 );
727
728 assert!(result_ok.is_ok());
729
730 light_block.signed_header.commit.signatures.pop();
732
733 let result_err = vp.has_sufficient_signers_overlap(
734 &light_block.signed_header,
735 &light_block.validators,
736 &voting_power_calculator,
737 );
738
739 let trust_threshold = TrustThreshold::TWO_THIRDS;
740
741 match result_err {
742 Err(VerificationError(VerificationErrorDetail::InsufficientSignersOverlap(e), _)) => {
743 assert_eq!(
744 e.tally,
745 VotingPowerTally {
746 total: 100,
747 tallied: 50,
748 trust_threshold,
749 }
750 );
751 },
752 _ => panic!("expected InsufficientSignersOverlap error"),
753 }
754 }
755}