1use serde::{Deserialize, Serialize};
4
5use crate::{
6 errors::{ErrorExt, VerificationError, VerificationErrorDetail},
7 operations::{voting_power::VotingPowerTally, CommitValidator, VotingPowerCalculator},
8 options::Options,
9 predicates::VerificationPredicates,
10 types::{Time, TrustedBlockState, UntrustedBlockState},
11};
12
13#[cfg(feature = "rust-crypto")]
14use crate::{
15 operations::{ProdCommitValidator, ProdVotingPowerCalculator},
16 predicates::ProdPredicates,
17};
18
19#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
22pub enum Verdict {
23 Success,
25 NotEnoughTrust(VotingPowerTally),
28 Invalid(VerificationErrorDetail),
30}
31
32impl From<Result<(), VerificationError>> for Verdict {
33 fn from(result: Result<(), VerificationError>) -> Self {
34 match result {
35 Ok(()) => Self::Success,
36 Err(VerificationError(e, _)) => match e.not_enough_trust() {
37 Some(tally) => Self::NotEnoughTrust(tally),
38 _ => Self::Invalid(e),
39 },
40 }
41 }
42}
43
44pub trait Verifier: Send + Sync {
54 fn verify_update_header(
56 &self,
57 untrusted: UntrustedBlockState<'_>,
58 trusted: TrustedBlockState<'_>,
59 options: &Options,
60 now: Time,
61 ) -> Verdict;
62
63 fn verify_misbehaviour_header(
68 &self,
69 untrusted: UntrustedBlockState<'_>,
70 trusted: TrustedBlockState<'_>,
71 options: &Options,
72 now: Time,
73 ) -> Verdict;
74}
75
76macro_rules! verdict {
77 ($e:expr) => {{
78 let result = $e;
79 if result.is_err() {
80 return result.into();
81 }
82 }};
83}
84
85macro_rules! ensure_verdict_success {
86 ($e:expr) => {{
87 let verdict = $e;
88 if !matches!(verdict, Verdict::Success) {
89 return verdict;
90 }
91 }};
92}
93
94#[derive(Debug, Clone, Default, PartialEq, Eq)]
97pub struct PredicateVerifier<P, C, V> {
98 predicates: P,
99 voting_power_calculator: C,
100 commit_validator: V,
101}
102
103impl<P, C, V> PredicateVerifier<P, C, V>
104where
105 P: VerificationPredicates,
106 C: VotingPowerCalculator,
107 V: CommitValidator,
108{
109 pub fn new(predicates: P, voting_power_calculator: C, commit_validator: V) -> Self {
111 Self {
112 predicates,
113 voting_power_calculator,
114 commit_validator,
115 }
116 }
117
118 pub fn verify_validator_sets(&self, untrusted: &UntrustedBlockState<'_>) -> Verdict {
120 verdict!(self.predicates.validator_sets_match(
122 untrusted.validators,
123 untrusted.signed_header.header.validators_hash,
124 ));
125
126 if let Some(untrusted_next_validators) = untrusted.next_validators {
128 verdict!(self.predicates.next_validators_match(
129 untrusted_next_validators,
130 untrusted.signed_header.header.next_validators_hash,
131 ));
132 }
133
134 verdict!(self.predicates.header_matches_commit(
136 &untrusted.signed_header.header,
137 untrusted.signed_header.commit.block_id.hash,
138 ));
139
140 verdict!(self.predicates.valid_commit(
142 untrusted.signed_header,
143 untrusted.validators,
144 &self.commit_validator,
145 ));
146
147 Verdict::Success
148 }
149
150 pub fn validate_against_trusted(
153 &self,
154 untrusted: &UntrustedBlockState<'_>,
155 trusted: &TrustedBlockState<'_>,
156 options: &Options,
157 now: Time,
158 ) -> Verdict {
159 verdict!(self.predicates.is_within_trust_period(
161 trusted.header_time,
162 options.trusting_period,
163 now,
164 ));
165
166 verdict!(self
168 .predicates
169 .is_monotonic_bft_time(untrusted.signed_header.header.time, trusted.header_time));
170
171 verdict!(self
173 .predicates
174 .is_matching_chain_id(&untrusted.signed_header.header.chain_id, trusted.chain_id));
175
176 let trusted_next_height = trusted.height.increment();
177
178 if untrusted.height() == trusted_next_height {
179 verdict!(self.predicates.valid_next_validator_set(
182 untrusted.signed_header.header.validators_hash,
183 trusted.next_validators_hash,
184 ));
185 } else {
186 verdict!(self
189 .predicates
190 .is_monotonic_height(untrusted.signed_header.header.height, trusted.height));
191 }
192
193 Verdict::Success
194 }
195
196 pub fn check_header_is_from_past(
198 &self,
199 untrusted: &UntrustedBlockState<'_>,
200 options: &Options,
201 now: Time,
202 ) -> Verdict {
203 verdict!(self.predicates.is_header_from_past(
204 untrusted.signed_header.header.time,
205 options.clock_drift,
206 now,
207 ));
208
209 Verdict::Success
210 }
211
212 pub fn verify_commit(&self, untrusted: &UntrustedBlockState<'_>) -> Verdict {
217 verdict!(self.predicates.has_sufficient_signers_overlap(
218 untrusted.signed_header,
219 untrusted.validators,
220 &self.voting_power_calculator,
221 ));
222
223 Verdict::Success
224 }
225
226 pub fn verify_commit_against_trusted(
230 &self,
231 untrusted: &UntrustedBlockState<'_>,
232 trusted: &TrustedBlockState<'_>,
233 options: &Options,
234 ) -> Verdict {
235 let trusted_next_height = trusted.height.increment();
239 let need_both = untrusted.height() != trusted_next_height;
240
241 let result = if need_both {
242 self.predicates
243 .has_sufficient_validators_and_signers_overlap(
244 untrusted.signed_header,
245 trusted.next_validators,
246 &options.trust_threshold,
247 untrusted.validators,
248 &self.voting_power_calculator,
249 )
250 } else {
251 self.predicates.has_sufficient_signers_overlap(
252 untrusted.signed_header,
253 untrusted.validators,
254 &self.voting_power_calculator,
255 )
256 };
257 verdict!(result);
258 Verdict::Success
259 }
260}
261
262impl<P, C, V> Verifier for PredicateVerifier<P, C, V>
263where
264 P: VerificationPredicates,
265 C: VotingPowerCalculator,
266 V: CommitValidator,
267{
268 fn verify_update_header(
296 &self,
297 untrusted: UntrustedBlockState<'_>,
298 trusted: TrustedBlockState<'_>,
299 options: &Options,
300 now: Time,
301 ) -> Verdict {
302 ensure_verdict_success!(self.verify_validator_sets(&untrusted));
303 ensure_verdict_success!(self.validate_against_trusted(&untrusted, &trusted, options, now));
304 ensure_verdict_success!(self.check_header_is_from_past(&untrusted, options, now));
305 ensure_verdict_success!(self.verify_commit_against_trusted(&untrusted, &trusted, options));
306
307 Verdict::Success
308 }
309
310 fn verify_misbehaviour_header(
314 &self,
315 untrusted: UntrustedBlockState<'_>,
316 trusted: TrustedBlockState<'_>,
317 options: &Options,
318 now: Time,
319 ) -> Verdict {
320 ensure_verdict_success!(self.verify_validator_sets(&untrusted));
321 ensure_verdict_success!(self.validate_against_trusted(&untrusted, &trusted, options, now));
322 ensure_verdict_success!(self.verify_commit_against_trusted(&untrusted, &trusted, options));
323 Verdict::Success
324 }
325}
326
327#[cfg(feature = "rust-crypto")]
328pub type ProdVerifier =
330 PredicateVerifier<ProdPredicates, ProdVotingPowerCalculator, ProdCommitValidator>;
331
332#[cfg(test)]
333mod tests {
334 use alloc::{borrow::ToOwned, string::ToString};
335 use core::{ops::Sub, time::Duration};
336
337 use tendermint::Time;
338 use tendermint_testgen::{light_block::LightBlock as TestgenLightBlock, Generator};
339
340 use crate::{
341 errors::VerificationErrorDetail, options::Options, types::LightBlock, ProdVerifier,
342 Verdict, Verifier,
343 };
344
345 #[allow(dead_code)]
346 #[cfg(feature = "rust-crypto")]
347 #[derive(Clone, Debug, PartialEq, Eq)]
348 struct ProdVerifierSupportsCommonDerivedTraits {
349 verifier: ProdVerifier,
350 }
351
352 #[test]
353 fn test_verification_failure_on_chain_id_mismatch() {
354 let now = Time::now();
355
356 let light_block_1: LightBlock = TestgenLightBlock::new_default_with_time_and_chain_id(
359 "chain-1".to_owned(),
360 now.sub(Duration::from_secs(20)).unwrap(),
361 1u64,
362 )
363 .generate()
364 .unwrap()
365 .into();
366
367 let light_block_2: LightBlock = TestgenLightBlock::new_default_with_time_and_chain_id(
370 "forged-chain".to_owned(),
371 now.sub(Duration::from_secs(10)).unwrap(),
372 2u64,
373 )
374 .generate()
375 .unwrap()
376 .into();
377
378 let vp = ProdVerifier::default();
379 let opt = Options {
380 trust_threshold: Default::default(),
381 trusting_period: Duration::from_secs(60),
382 clock_drift: Default::default(),
383 };
384
385 let verdict = vp.verify_update_header(
386 light_block_2.as_untrusted_state(),
387 light_block_1.as_trusted_state(),
388 &opt,
389 Time::now(),
390 );
391
392 match verdict {
393 Verdict::Invalid(VerificationErrorDetail::ChainIdMismatch(e)) => {
394 let chain_id_1 = light_block_1.signed_header.header.chain_id;
395 let chain_id_2 = light_block_2.signed_header.header.chain_id;
396 assert_eq!(e.got, chain_id_2.to_string());
397 assert_eq!(e.expected, chain_id_1.to_string());
398 },
399 v => panic!("expected ChainIdMismatch error, got: {:?}", v),
400 }
401 }
402
403 #[test]
404 #[cfg(feature = "rust-crypto")]
405 fn test_successful_verify_maliciousupdate_header() {
406 use tendermint::block::CommitSig;
407 use tendermint_testgen::{Header, Validator};
408
409 let now = Time::now();
410
411 let options = Options {
413 trust_threshold: Default::default(), trusting_period: Duration::from_secs(60), clock_drift: Duration::from_secs(5), };
417
418 let verifier = ProdVerifier::default();
420
421 let validators = [
423 Validator::new("EVIL").voting_power(51),
424 Validator::new("GOOD").voting_power(50),
425 ];
426
427 let header = Header::new(&validators.clone())
428 .height(1u64)
429 .chain_id("test-chain")
430 .next_validators(&validators)
431 .time(now.sub(Duration::from_secs(20)).unwrap());
432
433 let trusted_block: LightBlock = TestgenLightBlock::new_default_with_header(header)
434 .generate()
435 .unwrap()
436 .into();
437
438 let header2 = Header::new(&validators)
443 .height(2u64)
444 .chain_id("test-chain")
445 .next_validators(&validators)
446 .time(now.sub(Duration::from_secs(10)).unwrap());
447
448 let mut untrusted_block: LightBlock = TestgenLightBlock::new_default_with_header(header2)
449 .generate()
450 .unwrap()
451 .into();
452 untrusted_block.signed_header.commit.signatures[1] = CommitSig::BlockIdFlagAbsent;
453
454 let verdict = verifier.verify_update_header(
455 untrusted_block.as_untrusted_state(),
456 trusted_block.as_trusted_state(),
457 &options,
458 now,
459 );
460
461 assert_ne!(verdict, Verdict::Success, "Verification should fail");
462
463 untrusted_block.validators.validators[1].address =
467 untrusted_block.validators.validators[0].address;
468
469 let verdict = verifier.verify_update_header(
470 untrusted_block.as_untrusted_state(),
471 trusted_block.as_trusted_state(),
472 &options,
473 now,
474 );
475
476 match verdict {
478 Verdict::Invalid(VerificationErrorDetail::DuplicateValidator(e)) => {
479 assert_eq!(e.address, untrusted_block.validators.validators[0].address);
480 },
481 v => panic!("expected DuplicateValidator error, got: {:?}", v),
482 }
483
484 let serialized = serde_json::to_string(&untrusted_block).unwrap();
487 let deserialized: serde_json::Error =
488 serde_json::from_str::<LightBlock>(&serialized).unwrap_err();
489 assert!(deserialized
490 .to_string()
491 .contains("invalid validator address"),);
492 }
493}