use crate::{
ensure,
light_client::Options,
operations::{CommitValidator, Hasher, VotingPowerCalculator},
types::{Header, LightBlock, SignedHeader, Time, TrustThreshold, ValidatorSet},
};
use errors::VerificationError;
use std::time::Duration;
pub mod errors;
#[derive(Clone, Copy, Debug, Default)]
pub struct ProdPredicates;
impl VerificationPredicates for ProdPredicates {}
pub trait VerificationPredicates: Send + Sync {
fn validator_sets_match(
&self,
light_block: &LightBlock,
hasher: &dyn Hasher,
) -> Result<(), VerificationError> {
let validators_hash = hasher.hash_validator_set(&light_block.validators);
ensure!(
light_block.signed_header.header.validators_hash == validators_hash,
VerificationError::InvalidValidatorSet {
header_validators_hash: light_block.signed_header.header.validators_hash,
validators_hash,
}
);
Ok(())
}
fn next_validators_match(
&self,
light_block: &LightBlock,
hasher: &dyn Hasher,
) -> Result<(), VerificationError> {
let next_validators_hash = hasher.hash_validator_set(&light_block.next_validators);
ensure!(
light_block.signed_header.header.next_validators_hash == next_validators_hash,
VerificationError::InvalidNextValidatorSet {
header_next_validators_hash: light_block.signed_header.header.next_validators_hash,
next_validators_hash,
}
);
Ok(())
}
fn header_matches_commit(
&self,
signed_header: &SignedHeader,
hasher: &dyn Hasher,
) -> Result<(), VerificationError> {
let header_hash = hasher.hash_header(&signed_header.header);
ensure!(
header_hash == signed_header.commit.block_id.hash,
VerificationError::InvalidCommitValue {
header_hash,
commit_hash: signed_header.commit.block_id.hash,
}
);
Ok(())
}
fn valid_commit(
&self,
signed_header: &SignedHeader,
validators: &ValidatorSet,
commit_validator: &dyn CommitValidator,
) -> Result<(), VerificationError> {
commit_validator.validate(signed_header, validators)?;
commit_validator.validate_full(signed_header, validators)?;
Ok(())
}
fn is_within_trust_period(
&self,
trusted_header: &Header,
trusting_period: Duration,
now: Time,
) -> Result<(), VerificationError> {
let expires_at = trusted_header.time + trusting_period;
ensure!(
expires_at > now,
VerificationError::NotWithinTrustPeriod { expires_at, now }
);
Ok(())
}
fn is_header_from_past(
&self,
untrusted_header: &Header,
clock_drift: Duration,
now: Time,
) -> Result<(), VerificationError> {
ensure!(
untrusted_header.time < now + clock_drift,
VerificationError::HeaderFromTheFuture {
header_time: untrusted_header.time,
now
}
);
Ok(())
}
fn is_monotonic_bft_time(
&self,
untrusted_header: &Header,
trusted_header: &Header,
) -> Result<(), VerificationError> {
ensure!(
untrusted_header.time > trusted_header.time,
VerificationError::NonMonotonicBftTime {
header_bft_time: untrusted_header.time,
trusted_header_bft_time: trusted_header.time,
}
);
Ok(())
}
fn is_monotonic_height(
&self,
untrusted_header: &Header,
trusted_header: &Header,
) -> Result<(), VerificationError> {
let trusted_height = trusted_header.height;
ensure!(
untrusted_header.height > trusted_header.height,
VerificationError::NonIncreasingHeight {
got: untrusted_header.height,
expected: trusted_height.increment(),
}
);
Ok(())
}
fn has_sufficient_validators_overlap(
&self,
untrusted_sh: &SignedHeader,
trusted_validators: &ValidatorSet,
trust_threshold: &TrustThreshold,
calculator: &dyn VotingPowerCalculator,
) -> Result<(), VerificationError> {
calculator.check_enough_trust(untrusted_sh, trusted_validators, *trust_threshold)?;
Ok(())
}
fn has_sufficient_signers_overlap(
&self,
untrusted_sh: &SignedHeader,
untrusted_validators: &ValidatorSet,
calculator: &dyn VotingPowerCalculator,
) -> Result<(), VerificationError> {
calculator.check_signers_overlap(untrusted_sh, untrusted_validators)?;
Ok(())
}
fn valid_next_validator_set(
&self,
light_block: &LightBlock,
trusted_state: &LightBlock,
) -> Result<(), VerificationError> {
ensure!(
light_block.signed_header.header.validators_hash
== trusted_state.signed_header.header.next_validators_hash,
VerificationError::InvalidNextValidatorSet {
header_next_validators_hash: light_block.signed_header.header.validators_hash,
next_validators_hash: trusted_state.signed_header.header.next_validators_hash,
}
);
Ok(())
}
}
#[allow(clippy::too_many_arguments)]
pub fn verify(
vp: &dyn VerificationPredicates,
voting_power_calculator: &dyn VotingPowerCalculator,
commit_validator: &dyn CommitValidator,
hasher: &dyn Hasher,
trusted: &LightBlock,
untrusted: &LightBlock,
options: &Options,
now: Time,
) -> Result<(), VerificationError> {
vp.is_within_trust_period(&trusted.signed_header.header, options.trusting_period, now)?;
vp.is_header_from_past(&untrusted.signed_header.header, options.clock_drift, now)?;
vp.validator_sets_match(&untrusted, &*hasher)?;
vp.next_validators_match(&untrusted, &*hasher)?;
vp.header_matches_commit(&untrusted.signed_header, hasher)?;
vp.valid_commit(
&untrusted.signed_header,
&untrusted.validators,
commit_validator,
)?;
vp.is_monotonic_bft_time(
&untrusted.signed_header.header,
&trusted.signed_header.header,
)?;
let trusted_next_height = trusted.height().increment();
if untrusted.height() == trusted_next_height {
vp.valid_next_validator_set(&untrusted, trusted)?;
} else {
vp.is_monotonic_height(
&untrusted.signed_header.header,
&trusted.signed_header.header,
)?;
vp.has_sufficient_validators_overlap(
&untrusted.signed_header,
&trusted.next_validators,
&options.trust_threshold,
voting_power_calculator,
)?;
}
vp.has_sufficient_signers_overlap(
&untrusted.signed_header,
&untrusted.validators,
voting_power_calculator,
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::ops::Sub;
use std::time::Duration;
use tendermint::Time;
use tendermint_testgen::{
light_block::{LightBlock as TestgenLightBlock, TMLightBlock},
Commit, Generator, Header, Validator, ValidatorSet,
};
use crate::predicates::{errors::VerificationError, ProdPredicates, VerificationPredicates};
use crate::operations::{
Hasher, ProdCommitValidator, ProdHasher, ProdVotingPowerCalculator, VotingPowerTally,
};
use crate::types::{LightBlock, TrustThreshold};
use tendermint::block::CommitSig;
use tendermint::validator::Set;
impl From<TMLightBlock> for LightBlock {
fn from(lb: TMLightBlock) -> Self {
LightBlock {
signed_header: lb.signed_header,
validators: lb.validators,
next_validators: lb.next_validators,
provider: lb.provider,
}
}
}
#[test]
fn test_is_monotonic_bft_time() {
let val = vec![Validator::new("val-1")];
let header_one = Header::new(&val).generate().unwrap();
let header_two = Header::new(&val).generate().unwrap();
let vp = ProdPredicates::default();
let result_ok = vp.is_monotonic_bft_time(&header_two, &header_one);
assert!(result_ok.is_ok());
let result_err = vp.is_monotonic_bft_time(&header_one, &header_two);
assert!(result_err.is_err());
let error = VerificationError::NonMonotonicBftTime {
header_bft_time: header_one.time,
trusted_header_bft_time: header_two.time,
};
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_is_monotonic_height() {
let val = vec![Validator::new("val-1")];
let header_one = Header::new(&val).generate().unwrap();
let header_two = Header::new(&val).height(2).generate().unwrap();
let vp = ProdPredicates::default();
let result_ok = vp.is_monotonic_height(&header_two, &header_one);
assert!(result_ok.is_ok());
let result_err = vp.is_monotonic_height(&header_one, &header_two);
assert!(result_err.is_err());
let error = VerificationError::NonIncreasingHeight {
got: header_one.height,
expected: header_two.height.increment(),
};
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_is_within_trust_period() {
let val = Validator::new("val-1");
let header = Header::new(&[val]).generate().unwrap();
let vp = ProdPredicates::default();
let mut trusting_period = Duration::new(1000, 0);
let now = Time::now();
let result_ok = vp.is_within_trust_period(&header, trusting_period, now);
assert!(result_ok.is_ok());
trusting_period = Duration::new(0, 1);
let result_err = vp.is_within_trust_period(&header, trusting_period, now);
assert!(result_err.is_err());
let expires_at = header.time + trusting_period;
let error = VerificationError::NotWithinTrustPeriod { expires_at, now };
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_is_header_from_past() {
let val = Validator::new("val-1");
let header = Header::new(&[val]).generate().unwrap();
let vp = ProdPredicates::default();
let one_second = Duration::new(1, 0);
let result_ok = vp.is_header_from_past(&header, one_second, Time::now());
assert!(result_ok.is_ok());
let now = Time::now().sub(one_second * 15);
let result_err = vp.is_header_from_past(&header, one_second, now);
assert!(result_err.is_err());
let error = VerificationError::HeaderFromTheFuture {
header_time: header.time,
now,
};
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_validator_sets_match() {
let mut light_block: LightBlock =
TestgenLightBlock::new_default(1).generate().unwrap().into();
let bad_validator_set = ValidatorSet::new(vec!["bad-val"]).generate().unwrap();
let vp = ProdPredicates::default();
let hasher = ProdHasher::default();
let val_sets_match_ok = vp.validator_sets_match(&light_block, &hasher);
assert!(val_sets_match_ok.is_ok());
let next_val_sets_match_ok = vp.next_validators_match(&light_block, &hasher);
assert!(next_val_sets_match_ok.is_ok());
light_block.validators = bad_validator_set.clone();
let val_sets_match_err = vp.validator_sets_match(&light_block, &hasher);
assert!(val_sets_match_err.is_err());
let val_set_error = VerificationError::InvalidValidatorSet {
header_validators_hash: light_block.signed_header.header.validators_hash,
validators_hash: hasher.hash_validator_set(&light_block.validators),
};
assert_eq!(val_sets_match_err.err().unwrap(), val_set_error);
light_block.next_validators = bad_validator_set;
let next_val_sets_match_err = vp.next_validators_match(&light_block, &hasher);
assert!(next_val_sets_match_err.is_err());
let next_val_set_error = VerificationError::InvalidNextValidatorSet {
header_next_validators_hash: light_block.signed_header.header.next_validators_hash,
next_validators_hash: hasher.hash_validator_set(&light_block.next_validators),
};
assert_eq!(next_val_sets_match_err.err().unwrap(), next_val_set_error);
}
#[test]
fn test_header_matches_commit() {
let mut signed_header = TestgenLightBlock::new_default(1)
.generate()
.unwrap()
.signed_header;
let vp = ProdPredicates::default();
let hasher = ProdHasher::default();
let result_ok = vp.header_matches_commit(&signed_header, &hasher);
assert!(result_ok.is_ok());
signed_header.commit.block_id.hash =
"15F15EF50BDE2018F4B129A827F90C18222C757770C8295EB8EE7BF50E761BC0"
.parse()
.unwrap();
let result_err = vp.header_matches_commit(&signed_header, &hasher);
assert!(result_err.is_err());
let header_hash = hasher.hash_header(&signed_header.header);
let error = VerificationError::InvalidCommitValue {
header_hash,
commit_hash: signed_header.commit.block_id.hash,
};
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_valid_commit() {
let light_block: LightBlock = TestgenLightBlock::new_default(1).generate().unwrap().into();
let mut signed_header = light_block.signed_header;
let val_set = light_block.validators;
let vp = ProdPredicates::default();
let hasher = ProdHasher::default();
let commit_validator = ProdCommitValidator::new(hasher);
let mut result_ok = vp.valid_commit(&signed_header, &val_set, &commit_validator);
assert!(result_ok.is_ok());
let signatures = signed_header.commit.signatures.clone();
signed_header.commit.signatures = vec![];
let mut result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator);
assert!(result_err.is_err());
let mut error =
VerificationError::ImplementationSpecific("no signatures for commit".to_string());
assert_eq!(result_err.err().unwrap(), error);
let mut bad_sigs = vec![signatures.clone().swap_remove(1)];
signed_header.commit.signatures = bad_sigs.clone();
result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator);
assert!(result_err.is_err());
error = VerificationError::ImplementationSpecific(format!(
"pre-commit length: {} doesn't match validator length: {}",
signed_header.commit.signatures.len(),
val_set.validators().len()
));
assert_eq!(result_err.err().unwrap(), error);
bad_sigs.push(CommitSig::BlockIDFlagAbsent);
signed_header.commit.signatures = bad_sigs;
result_ok = vp.valid_commit(&signed_header, &val_set, &commit_validator);
assert!(result_ok.is_ok());
let mut bad_vals = val_set.validators().clone();
bad_vals.pop();
bad_vals.push(
Validator::new("bad-val")
.generate()
.expect("Failed to generate validator"),
);
let val_set_with_faulty_signer = Set::without_proposer(bad_vals);
signed_header.commit.signatures = signatures;
result_err = vp.valid_commit(
&signed_header,
&val_set_with_faulty_signer,
&commit_validator,
);
assert!(result_err.is_err());
error = VerificationError::ImplementationSpecific(format!(
"Found a faulty signer ({}) not present in the validator set ({})",
signed_header
.commit
.signatures
.iter()
.last()
.unwrap()
.validator_address()
.unwrap(),
hasher.hash_validator_set(&val_set_with_faulty_signer)
));
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_valid_next_validator_set() {
let test_lb1 = TestgenLightBlock::new_default(1);
let light_block1: LightBlock = test_lb1.generate().unwrap().into();
let light_block2: LightBlock = test_lb1.next().generate().unwrap().into();
let vp = ProdPredicates::default();
let result_ok = vp.valid_next_validator_set(&light_block1, &light_block2);
assert!(result_ok.is_ok());
let vals = &[Validator::new("new-1"), Validator::new("new-2")];
let header = Header::new(vals);
let commit = Commit::new(header.clone(), 1);
let light_block3: LightBlock = TestgenLightBlock::new(header, commit)
.generate()
.unwrap()
.into();
let result_err = vp.valid_next_validator_set(&light_block3, &light_block2);
assert!(result_err.is_err());
let error = VerificationError::InvalidNextValidatorSet {
header_next_validators_hash: light_block3.signed_header.header.validators_hash,
next_validators_hash: light_block2.signed_header.header.next_validators_hash,
};
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_has_sufficient_validators_overlap() {
let light_block: LightBlock = TestgenLightBlock::new_default(1).generate().unwrap().into();
let val_set = light_block.validators;
let signed_header = light_block.signed_header;
let vp = ProdPredicates::default();
let mut trust_threshold = TrustThreshold::new(1, 3).expect("Cannot make trust threshold");
let voting_power_calculator = ProdVotingPowerCalculator::default();
let result_ok = vp.has_sufficient_validators_overlap(
&signed_header,
&val_set,
&trust_threshold,
&voting_power_calculator,
);
assert!(result_ok.is_ok());
let mut vals = val_set.validators().clone();
vals.push(
Validator::new("extra-val")
.voting_power(100)
.generate()
.unwrap(),
);
let bad_valset = Set::without_proposer(vals);
trust_threshold = TrustThreshold::new(2, 3).expect("Cannot make trust threshold");
let result_err = vp.has_sufficient_validators_overlap(
&signed_header,
&bad_valset,
&trust_threshold,
&voting_power_calculator,
);
assert!(result_err.is_err());
let error = VerificationError::NotEnoughTrust(VotingPowerTally {
total: 200,
tallied: 100,
trust_threshold,
});
assert_eq!(result_err.err().unwrap(), error);
}
#[test]
fn test_has_sufficient_signers_overlap() {
let mut light_block: LightBlock =
TestgenLightBlock::new_default(2).generate().unwrap().into();
let vp = ProdPredicates::default();
let voting_power_calculator = ProdVotingPowerCalculator::default();
let result_ok = vp.has_sufficient_signers_overlap(
&light_block.signed_header,
&light_block.validators,
&voting_power_calculator,
);
assert!(result_ok.is_ok());
light_block.signed_header.commit.signatures.pop();
let result_err = vp.has_sufficient_signers_overlap(
&light_block.signed_header,
&light_block.validators,
&voting_power_calculator,
);
assert!(result_err.is_err());
let trust_threshold = TrustThreshold::TWO_THIRDS;
let error = VerificationError::InsufficientSignersOverlap(VotingPowerTally {
total: 100,
tallied: 50,
trust_threshold,
});
assert_eq!(result_err.err().unwrap(), error);
}
}