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 verify_commit(&self, untrusted: &UntrustedBlockState<'_>) -> Verdict {
152 verdict!(self.predicates.has_sufficient_signers_overlap(
153 untrusted.signed_header,
154 untrusted.validators,
155 &self.voting_power_calculator,
156 ));
157
158 Verdict::Success
159 }
160
161 pub fn validate_against_trusted(
164 &self,
165 untrusted: &UntrustedBlockState<'_>,
166 trusted: &TrustedBlockState<'_>,
167 options: &Options,
168 now: Time,
169 ) -> Verdict {
170 verdict!(self.predicates.is_within_trust_period(
172 trusted.header_time,
173 options.trusting_period,
174 now,
175 ));
176
177 verdict!(self
179 .predicates
180 .is_monotonic_bft_time(untrusted.signed_header.header.time, trusted.header_time));
181
182 verdict!(self
184 .predicates
185 .is_matching_chain_id(&untrusted.signed_header.header.chain_id, trusted.chain_id));
186
187 let trusted_next_height = trusted.height.increment();
188
189 if untrusted.height() == trusted_next_height {
190 verdict!(self.predicates.valid_next_validator_set(
193 untrusted.signed_header.header.validators_hash,
194 trusted.next_validators_hash,
195 ));
196 } else {
197 verdict!(self
200 .predicates
201 .is_monotonic_height(untrusted.signed_header.header.height, trusted.height));
202 }
203
204 Verdict::Success
205 }
206
207 pub fn check_header_is_from_past(
209 &self,
210 untrusted: &UntrustedBlockState<'_>,
211 options: &Options,
212 now: Time,
213 ) -> Verdict {
214 verdict!(self.predicates.is_header_from_past(
215 untrusted.signed_header.header.time,
216 options.clock_drift,
217 now,
218 ));
219
220 Verdict::Success
221 }
222
223 pub fn verify_commit_against_trusted(
226 &self,
227 untrusted: &UntrustedBlockState<'_>,
228 trusted: &TrustedBlockState<'_>,
229 options: &Options,
230 ) -> Verdict {
231 let trusted_next_height = trusted.height.increment();
232
233 if untrusted.height() != trusted_next_height {
234 verdict!(self.predicates.has_sufficient_validators_overlap(
237 untrusted.signed_header,
238 trusted.next_validators,
239 &options.trust_threshold,
240 &self.voting_power_calculator,
241 ));
242 }
243
244 Verdict::Success
245 }
246}
247
248impl<P, C, V> Verifier for PredicateVerifier<P, C, V>
249where
250 P: VerificationPredicates,
251 C: VotingPowerCalculator,
252 V: CommitValidator,
253{
254 fn verify_update_header(
282 &self,
283 untrusted: UntrustedBlockState<'_>,
284 trusted: TrustedBlockState<'_>,
285 options: &Options,
286 now: Time,
287 ) -> Verdict {
288 ensure_verdict_success!(self.verify_validator_sets(&untrusted));
289 ensure_verdict_success!(self.validate_against_trusted(&untrusted, &trusted, options, now));
290 ensure_verdict_success!(self.check_header_is_from_past(&untrusted, options, now));
291 ensure_verdict_success!(self.verify_commit_against_trusted(&untrusted, &trusted, options));
292 ensure_verdict_success!(self.verify_commit(&untrusted));
293
294 Verdict::Success
295 }
296
297 fn verify_misbehaviour_header(
301 &self,
302 untrusted: UntrustedBlockState<'_>,
303 trusted: TrustedBlockState<'_>,
304 options: &Options,
305 now: Time,
306 ) -> Verdict {
307 ensure_verdict_success!(self.verify_validator_sets(&untrusted));
308 ensure_verdict_success!(self.validate_against_trusted(&untrusted, &trusted, options, now));
309 ensure_verdict_success!(self.verify_commit_against_trusted(&untrusted, &trusted, options));
310 ensure_verdict_success!(self.verify_commit(&untrusted));
311 Verdict::Success
312 }
313}
314
315#[cfg(feature = "rust-crypto")]
316pub type ProdVerifier =
318 PredicateVerifier<ProdPredicates, ProdVotingPowerCalculator, ProdCommitValidator>;
319
320#[cfg(test)]
321mod tests {
322 use alloc::{borrow::ToOwned, string::ToString};
323 use core::{ops::Sub, time::Duration};
324
325 use cometbft::Time;
326 use cometbft_testgen::{light_block::LightBlock as TestgenLightBlock, Generator};
327
328 use crate::{
329 errors::VerificationErrorDetail, options::Options, types::LightBlock, ProdVerifier,
330 Verdict, Verifier,
331 };
332
333 #[cfg(feature = "rust-crypto")]
334 #[derive(Clone, Debug, PartialEq, Eq)]
335 struct ProdVerifierSupportsCommonDerivedTraits {
336 verifier: ProdVerifier,
337 }
338
339 #[test]
340 fn test_verification_failure_on_chain_id_mismatch() {
341 let now = Time::now();
342
343 let light_block_1: LightBlock = TestgenLightBlock::new_default_with_time_and_chain_id(
346 "chain-1".to_owned(),
347 now.sub(Duration::from_secs(20)).unwrap(),
348 1u64,
349 )
350 .generate()
351 .unwrap()
352 .into();
353
354 let light_block_2: LightBlock = TestgenLightBlock::new_default_with_time_and_chain_id(
357 "forged-chain".to_owned(),
358 now.sub(Duration::from_secs(10)).unwrap(),
359 2u64,
360 )
361 .generate()
362 .unwrap()
363 .into();
364
365 let vp = ProdVerifier::default();
366 let opt = Options {
367 trust_threshold: Default::default(),
368 trusting_period: Duration::from_secs(60),
369 clock_drift: Default::default(),
370 };
371
372 let verdict = vp.verify_update_header(
373 light_block_2.as_untrusted_state(),
374 light_block_1.as_trusted_state(),
375 &opt,
376 Time::now(),
377 );
378
379 match verdict {
380 Verdict::Invalid(VerificationErrorDetail::ChainIdMismatch(e)) => {
381 let chain_id_1 = light_block_1.signed_header.header.chain_id;
382 let chain_id_2 = light_block_2.signed_header.header.chain_id;
383 assert_eq!(e.got, chain_id_2.to_string());
384 assert_eq!(e.expected, chain_id_1.to_string());
385 },
386 v => panic!("expected ChainIdMismatch error, got: {:?}", v),
387 }
388 }
389}