1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
//! Provides an interface and default implementation of the `Verifier` component
use serde::{Deserialize, Serialize};
use crate::{
errors::{ErrorExt, VerificationError, VerificationErrorDetail},
operations::{voting_power::VotingPowerTally, CommitValidator, VotingPowerCalculator},
options::Options,
predicates::VerificationPredicates,
types::{Time, TrustedBlockState, UntrustedBlockState},
};
#[cfg(feature = "rust-crypto")]
use crate::{
operations::{ProdCommitValidator, ProdVotingPowerCalculator},
predicates::ProdPredicates,
};
/// Represents the result of the verification performed by the
/// verifier component.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum Verdict {
/// Verification succeeded, the block is valid.
Success,
/// The minimum voting power threshold is not reached,
/// the block cannot be trusted yet.
NotEnoughTrust(VotingPowerTally),
/// Verification failed, the block is invalid.
Invalid(VerificationErrorDetail),
}
impl From<Result<(), VerificationError>> for Verdict {
fn from(result: Result<(), VerificationError>) -> Self {
match result {
Ok(()) => Self::Success,
Err(VerificationError(e, _)) => match e.not_enough_trust() {
Some(tally) => Self::NotEnoughTrust(tally),
_ => Self::Invalid(e),
},
}
}
}
/// The verifier checks:
///
/// a) whether a given untrusted light block is valid, and
/// b) whether a given untrusted light block should be trusted
/// based on a previously verified block.
///
/// ## Implements
/// - [TMBC-VAL-CONTAINS-CORR.1]
/// - [TMBC-VAL-COMMIT.1]
pub trait Verifier: Send + Sync {
/// Verify a header received in a `MsgUpdateClient`.
fn verify_update_header(
&self,
untrusted: UntrustedBlockState<'_>,
trusted: TrustedBlockState<'_>,
options: &Options,
now: Time,
) -> Verdict;
/// Verify a header received in `MsgSubmitMisbehaviour`.
/// The verification for these headers is a bit more relaxed in order to catch FLA attacks.
/// In particular the "header in the future" check for the header should be skipped
/// from `validate_against_trusted`.
fn verify_misbehaviour_header(
&self,
untrusted: UntrustedBlockState<'_>,
trusted: TrustedBlockState<'_>,
options: &Options,
now: Time,
) -> Verdict;
}
macro_rules! verdict {
($e:expr) => {{
let result = $e;
if result.is_err() {
return result.into();
}
}};
}
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, Default, PartialEq, Eq)]
pub struct PredicateVerifier<P, C, V> {
predicates: P,
voting_power_calculator: C,
commit_validator: V,
}
impl<P, C, V> PredicateVerifier<P, C, V>
where
P: VerificationPredicates,
C: VotingPowerCalculator,
V: CommitValidator,
{
/// Constructor.
pub fn new(predicates: P, voting_power_calculator: C, commit_validator: V) -> Self {
Self {
predicates,
voting_power_calculator,
commit_validator,
}
}
/// 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,
untrusted.signed_header.header.validators_hash,
));
// Ensure the header next validator hashes match the given next validators
if let Some(untrusted_next_validators) = untrusted.next_validators {
verdict!(self.predicates.next_validators_match(
untrusted_next_validators,
untrusted.signed_header.header.next_validators_hash,
));
}
// Ensure the header matches the commit
verdict!(self.predicates.header_matches_commit(
&untrusted.signed_header.header,
untrusted.signed_header.commit.block_id.hash,
));
// Additional implementation specific validation
verdict!(self.predicates.valid_commit(
untrusted.signed_header,
untrusted.validators,
&self.commit_validator,
));
Verdict::Success
}
/// Validate an `UntrustedBlockState` coming from a client update,
/// 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,
));
// 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));
// Check that the chain-id of the untrusted block matches that of the trusted state
verdict!(self
.predicates
.is_matching_chain_id(&untrusted.signed_header.header.chain_id, trusted.chain_id));
let trusted_next_height = trusted.height.increment();
if untrusted.height() == trusted_next_height {
// If the untrusted block is the very next block after the trusted block,
// check that their (next) validator sets hashes match.
verdict!(self.predicates.valid_next_validator_set(
untrusted.signed_header.header.validators_hash,
trusted.next_validators_hash,
));
} else {
// Otherwise, ensure that the untrusted block has a greater height than
// the trusted block.
verdict!(self
.predicates
.is_monotonic_height(untrusted.signed_header.header.height, trusted.height));
}
Verdict::Success
}
/// Ensure the header isn't from a future time
pub fn check_header_is_from_past(
&self,
untrusted: &UntrustedBlockState<'_>,
options: &Options,
now: Time,
) -> Verdict {
verdict!(self.predicates.is_header_from_past(
untrusted.signed_header.header.time,
options.clock_drift,
now,
));
Verdict::Success
}
/// Verify that a) there is enough overlap between the validator sets of the
/// trusted and untrusted blocks and b) more than 2/3 of the validators
/// correctly committed the block.
pub fn verify_commit(
&self,
untrusted: &UntrustedBlockState<'_>,
trusted: &TrustedBlockState<'_>,
options: &Options,
) -> Verdict {
// If the trusted validator set has changed we need to check if there’s
// overlap between the old trusted set and the new untrested header in
// addition to checking if the new set correctly signed the header.
let trusted_next_height = trusted.height.increment();
let need_both = untrusted.height() != trusted_next_height;
let result = if need_both {
self.predicates
.has_sufficient_validators_and_signers_overlap(
untrusted.signed_header,
trusted.next_validators,
&options.trust_threshold,
untrusted.validators,
&self.voting_power_calculator,
)
} else {
self.predicates.has_sufficient_signers_overlap(
untrusted.signed_header,
untrusted.validators,
&self.voting_power_calculator,
)
};
verdict!(result);
Verdict::Success
}
}
impl<P, C, V> Verifier for PredicateVerifier<P, C, V>
where
P: VerificationPredicates,
C: VotingPowerCalculator,
V: CommitValidator,
{
/// 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.
///
/// **NOTE**: It is the caller's responsibility to ensure that
/// `trusted.next_validators.hash() == trusted.next_validators_hash`,
/// as typically the `trusted.next_validators` validator set comes from the relayer,
/// and `trusted.next_validators_hash` is the hash stored on chain.
fn verify_update_header(
&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.check_header_is_from_past(&untrusted, options, now));
ensure_verdict_success!(self.verify_commit(&untrusted, &trusted, options));
Verdict::Success
}
/// Verify a header received in `MsgSubmitMisbehaviour`.
/// The verification for these headers is a bit more relaxed in order to catch FLA attacks.
/// In particular the "header in the future" check for the header should be skipped.
fn verify_misbehaviour_header(
&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(&untrusted, &trusted, options));
Verdict::Success
}
}
#[cfg(feature = "rust-crypto")]
/// The default production implementation of the [`PredicateVerifier`].
pub type ProdVerifier =
PredicateVerifier<ProdPredicates, ProdVotingPowerCalculator, ProdCommitValidator>;
#[cfg(test)]
mod tests {
use alloc::{borrow::ToOwned, string::ToString};
use core::{ops::Sub, time::Duration};
use tendermint::Time;
use tendermint_testgen::{light_block::LightBlock as TestgenLightBlock, Generator};
use crate::{
errors::VerificationErrorDetail, options::Options, types::LightBlock, ProdVerifier,
Verdict, Verifier,
};
#[allow(dead_code)]
#[cfg(feature = "rust-crypto")]
#[derive(Clone, Debug, PartialEq, Eq)]
struct ProdVerifierSupportsCommonDerivedTraits {
verifier: ProdVerifier,
}
#[test]
fn test_verification_failure_on_chain_id_mismatch() {
let now = Time::now();
// Create a default light block with a valid chain-id for height `1` with a timestamp 20
// secs before now (to be treated as trusted state)
let light_block_1: LightBlock = TestgenLightBlock::new_default_with_time_and_chain_id(
"chain-1".to_owned(),
now.sub(Duration::from_secs(20)).unwrap(),
1u64,
)
.generate()
.unwrap()
.into();
// Create another default block with a different chain-id for height `2` with a timestamp 10
// secs before now (to be treated as untrusted state)
let light_block_2: LightBlock = TestgenLightBlock::new_default_with_time_and_chain_id(
"forged-chain".to_owned(),
now.sub(Duration::from_secs(10)).unwrap(),
2u64,
)
.generate()
.unwrap()
.into();
let vp = ProdVerifier::default();
let opt = Options {
trust_threshold: Default::default(),
trusting_period: Duration::from_secs(60),
clock_drift: Default::default(),
};
let verdict = vp.verify_update_header(
light_block_2.as_untrusted_state(),
light_block_1.as_trusted_state(),
&opt,
Time::now(),
);
match verdict {
Verdict::Invalid(VerificationErrorDetail::ChainIdMismatch(e)) => {
let chain_id_1 = light_block_1.signed_header.header.chain_id;
let chain_id_2 = light_block_2.signed_header.header.chain_id;
assert_eq!(e.got, chain_id_2.to_string());
assert_eq!(e.expected, chain_id_1.to_string());
},
v => panic!("expected ChainIdMismatch error, got: {:?}", v),
}
}
}