diff --git a/.changelog/unreleased/improvements/1222-verify-component-methods.md b/.changelog/unreleased/improvements/1222-verify-component-methods.md new file mode 100644 index 000000000..f2eca7bab --- /dev/null +++ b/.changelog/unreleased/improvements/1222-verify-component-methods.md @@ -0,0 +1,2 @@ +- `[light-client]` Added `validate`, `validate_against_trusted`, `verify_commit` and `verify_commit_against_trusted` methods to `PredicateVerifier`. + ([#1222](https://github.com/informalsystems/tendermint-rs/issues/1222)) \ No newline at end of file diff --git a/light-client-verifier/src/operations/voting_power.rs b/light-client-verifier/src/operations/voting_power.rs index 58dbeec65..442db0510 100644 --- a/light-client-verifier/src/operations/voting_power.rs +++ b/light-client-verifier/src/operations/voting_power.rs @@ -67,8 +67,7 @@ pub trait VotingPowerCalculator: Send + Sync { } } - /// Check against the given threshold that there is enough signers - /// overlap between an untrusted header and untrusted validator set + /// Check if there is 2/3rd overlap between an untrusted header and untrusted validator set fn check_signers_overlap( &self, untrusted_header: &SignedHeader, diff --git a/light-client-verifier/src/verifier.rs b/light-client-verifier/src/verifier.rs index 8525cf666..677d64715 100644 --- a/light-client-verifier/src/verifier.rs +++ b/light-client-verifier/src/verifier.rs @@ -68,6 +68,15 @@ macro_rules! verdict { }; } +macro_rules! ensure_verdict_success { + ($e:expr) => { + let verdict = $e; + if !matches!(verdict, Verdict::Success) { + return verdict; + } + }; +} + /// Predicate verifier encapsulating components necessary to facilitate /// verification. #[derive(Debug, Clone, PartialEq, Eq)] @@ -111,50 +120,9 @@ where hasher, } } -} - -impl Verifier for PredicateVerifier -where - P: VerificationPredicates, - C: VotingPowerCalculator, - V: CommitValidator, - H: Hasher, -{ - /// Validate the given light block state. - /// - /// - Ensure the latest trusted header hasn't expired - /// - Ensure the header validator hashes match the given validators - /// - Ensure the header next validator hashes match the given next validators - /// - Additional implementation specific validation via `commit_validator` - /// - Check that the untrusted block is more recent than the trusted state - /// - If the untrusted block is the very next block after the trusted block, check that their - /// (next) validator sets hashes match. - /// - Otherwise, ensure that the untrusted block has a greater height than the trusted block. - /// - /// **NOTE**: If the untrusted state's `next_validators` field is `None`, - /// this will not (and will not be able to) check whether the untrusted - /// state's `next_validators_hash` field is valid. - fn verify( - &self, - untrusted: UntrustedBlockState<'_>, - trusted: TrustedBlockState<'_>, - options: &Options, - now: Time, - ) -> Verdict { - // Ensure the latest trusted header hasn't expired - verdict!(self.predicates.is_within_trust_period( - trusted.header_time, - options.trusting_period, - now, - )); - - // Ensure the header isn't from a future time - verdict!(self.predicates.is_header_from_past( - untrusted.signed_header.header.time, - options.clock_drift, - now, - )); + /// Validates an `UntrustedBlockState`. + pub fn verify_validator_sets(&self, untrusted: &UntrustedBlockState<'_>) -> Verdict { // Ensure the header validator hashes match the given validators verdict!(self.predicates.validator_sets_match( untrusted.validators, @@ -162,9 +130,8 @@ where &self.hasher, )); - // TODO(thane): Is this check necessary for IBC? + // Ensure the header next validator hashes match the given next validators if let Some(untrusted_next_validators) = untrusted.next_validators { - // Ensure the header next validator hashes match the given next validators verdict!(self.predicates.next_validators_match( untrusted_next_validators, untrusted.signed_header.header.next_validators_hash, @@ -186,10 +153,47 @@ where &self.commit_validator, )); + Verdict::Success + } + + /// Verify that more than 2/3 of the validators correctly committed the block. + pub fn verify_commit(&self, untrusted: &UntrustedBlockState<'_>) -> Verdict { + verdict!(self.predicates.has_sufficient_signers_overlap( + untrusted.signed_header, + untrusted.validators, + &self.voting_power_calculator, + )); + + Verdict::Success + } + + /// Validate an `UntrustedBlockState`, based on the given `TrustedBlockState`, `Options` and + /// current time. + pub fn validate_against_trusted( + &self, + untrusted: &UntrustedBlockState<'_>, + trusted: &TrustedBlockState<'_>, + options: &Options, + now: Time, + ) -> Verdict { + // Ensure the latest trusted header hasn't expired + verdict!(self.predicates.is_within_trust_period( + trusted.header_time, + options.trusting_period, + now, + )); + + // Ensure the header isn't from a future time + verdict!(self.predicates.is_header_from_past( + untrusted.signed_header.header.time, + options.clock_drift, + now, + )); + // Check that the untrusted block is more recent than the trusted state verdict!(self .predicates - .is_monotonic_bft_time(untrusted.signed_header.header.time, trusted.header_time,)); + .is_monotonic_bft_time(untrusted.signed_header.header.time, trusted.header_time)); let trusted_next_height = trusted.height.increment(); @@ -206,7 +210,22 @@ where verdict!(self .predicates .is_monotonic_height(untrusted.signed_header.header.height, trusted.height)); + } + + Verdict::Success + } + /// Check there is enough overlap between the validator sets of the trusted and untrusted + /// blocks. + pub fn verify_commit_against_trusted( + &self, + untrusted: &UntrustedBlockState<'_>, + trusted: &TrustedBlockState<'_>, + options: &Options, + ) -> Verdict { + let trusted_next_height = trusted.height.increment(); + + if untrusted.height() != trusted_next_height { // Check there is enough overlap between the validator sets of // the trusted and untrusted blocks. verdict!(self.predicates.has_sufficient_validators_overlap( @@ -217,13 +236,50 @@ where )); } - // Verify that more than 2/3 of the validators correctly committed the block. - verdict!(self.predicates.has_sufficient_signers_overlap( - untrusted.signed_header, - untrusted.validators, - &self.voting_power_calculator, - )); + Verdict::Success + } +} +impl Verifier for PredicateVerifier +where + P: VerificationPredicates, + C: VotingPowerCalculator, + V: CommitValidator, + H: Hasher, +{ + /// Validate the given light block state by performing the following checks -> + /// + /// - Validate the untrusted header + /// - Ensure the header validator hashes match the given validators + /// - Ensure the header next validator hashes match the given next validators + /// - Ensure the header matches the commit + /// - Ensure commit is valid + /// - Validate the untrusted header against the trusted header + /// - Ensure the latest trusted header hasn't expired + /// - Ensure the header isn't from a future time + /// - Check that the untrusted block is more recent than the trusted state + /// - If the untrusted block is the very next block after the trusted block, check that + /// their (next) validator sets hashes match. + /// - Otherwise, ensure that the untrusted block has a greater height than the trusted + /// block. + /// - Check there is enough overlap between the validator sets of the trusted and untrusted + /// blocks. + /// - Verify that more than 2/3 of the validators correctly committed the block. + /// + /// **NOTE**: If the untrusted state's `next_validators` field is `None`, + /// this will not (and will not be able to) check whether the untrusted + /// state's `next_validators_hash` field is valid. + fn verify( + &self, + untrusted: UntrustedBlockState<'_>, + trusted: TrustedBlockState<'_>, + options: &Options, + now: Time, + ) -> Verdict { + ensure_verdict_success!(self.verify_validator_sets(&untrusted)); + ensure_verdict_success!(self.validate_against_trusted(&untrusted, &trusted, options, now)); + ensure_verdict_success!(self.verify_commit_against_trusted(&untrusted, &trusted, options)); + ensure_verdict_success!(self.verify_commit(&untrusted)); Verdict::Success } } diff --git a/light-client/src/tests.rs b/light-client/src/tests.rs index eb64a13b6..1bfeede80 100644 --- a/light-client/src/tests.rs +++ b/light-client/src/tests.rs @@ -153,7 +153,6 @@ pub fn verify_single( now: Time, ) -> Result { let verifier = ProdVerifier::default(); - let options = Options { trust_threshold, trusting_period,