diff --git a/CHANGELOG.md b/CHANGELOG.md index a03a3d12a..f3a79c62b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ - Add missing documentation to all items ([#472]) - Add major contributors as authors of the `light-client`, `light-node`, and `rpc` crate ([#472]) +- Remove and consolidate deprecated [lite] and [lite_impl] modules from the `tendermint` crate ([#500]) [#472]: https://github.com/informalsystems/tendermint-rs/pull/472 +[lite_impl]: https://github.com/informalsystems/tendermint-rs/tree/master/tendermint/src/lite_impl ## [0.15.0] (2020-07-17) diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index 17fb74c48..b1a78639e 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -28,7 +28,7 @@ pub enum AtHeight { impl From for AtHeight { fn from(height: Height) -> Self { - if height == 0 { + if height.value() == 0 { Self::Highest } else { Self::At(height) @@ -93,10 +93,10 @@ pub struct ProdIo { impl Io for ProdIo { fn fetch_light_block(&self, peer: PeerId, height: AtHeight) -> Result { let signed_header = self.fetch_signed_header(peer, height)?; - let height: Height = signed_header.header.height.into(); + let height = signed_header.header.height; let validator_set = self.fetch_validator_set(peer, height.into())?; - let next_validator_set = self.fetch_validator_set(peer, (height + 1).into())?; + let next_validator_set = self.fetch_validator_set(peer, height.increment().into())?; let light_block = LightBlock::new(signed_header, validator_set, next_validator_set, peer); diff --git a/light-client/src/components/scheduler.rs b/light-client/src/components/scheduler.rs index e92bb57c2..e8d722af1 100644 --- a/light-client/src/components/scheduler.rs +++ b/light-client/src/components/scheduler.rs @@ -122,5 +122,5 @@ pub fn valid_schedule( #[pre(low <= high)] #[post(low <= ret && ret <= high)] fn midpoint(low: Height, high: Height) -> Height { - low + (high + 1 - low) / 2 + (low.value() + (high.value() + 1 - low.value()) / 2).into() } diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index a063ae5be..25c61058a 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -91,10 +91,10 @@ impl ProdVerifier { impl Default for ProdVerifier { fn default() -> Self { Self::new( - ProdPredicates, - ProdVotingPowerCalculator, - ProdCommitValidator, - ProdHasher, + ProdPredicates::default(), + ProdVotingPowerCalculator::default(), + ProdCommitValidator::default(), + ProdHasher::default(), ) } } diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index 169468478..9d1dab2c3 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -2,12 +2,12 @@ use crate::{ bail, + operations::{Hasher, ProdHasher}, predicates::errors::VerificationError, types::{SignedHeader, ValidatorSet}, }; use tendermint::block::CommitSig; -use tendermint::lite::types::ValidatorSet as _; /// Validates the commit associated with a header against a validator set pub trait CommitValidator: Send { @@ -27,8 +27,25 @@ pub trait CommitValidator: Send { } /// Production-ready implementation of a commit validator -#[derive(Copy, Clone)] -pub struct ProdCommitValidator; +pub struct ProdCommitValidator { + hasher: Box, +} + +impl ProdCommitValidator { + /// Create a new commit validator using the given [`Hasher`] + /// to compute the hash of headers and validator sets. + pub fn new(hasher: impl Hasher + 'static) -> Self { + Self { + hasher: Box::new(hasher), + } + } +} + +impl Default for ProdCommitValidator { + fn default() -> Self { + Self::new(ProdHasher::default()) + } +} impl CommitValidator for ProdCommitValidator { fn validate( @@ -81,7 +98,7 @@ impl CommitValidator for ProdCommitValidator { bail!(VerificationError::ImplementationSpecific(format!( "Found a faulty signer ({}) not present in the validator set ({})", validator_address, - validator_set.hash() + self.hasher.hash_validator_set(validator_set) ))); } } diff --git a/light-client/src/operations/hasher.rs b/light-client/src/operations/hasher.rs index 32ef67bdb..c57643b02 100644 --- a/light-client/src/operations/hasher.rs +++ b/light-client/src/operations/hasher.rs @@ -2,9 +2,7 @@ use crate::types::{Header, ValidatorSet}; -use tendermint::lite::types::Header as _; -use tendermint::merkle; -use tendermint::Hash; +use tendermint::{merkle, Hash}; /// Hashing for headers and validator sets pub trait Hasher: Send { @@ -16,7 +14,7 @@ pub trait Hasher: Send { } /// Default implementation of a hasher -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct ProdHasher; impl Hasher for ProdHasher { diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index 10fab7c01..ecf28d349 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -11,7 +11,7 @@ use std::collections::HashSet; use std::fmt; use tendermint::block::CommitSig; -use tendermint::lite::types::TrustThreshold as _; +use tendermint::trust_threshold::TrustThreshold as _; use tendermint::vote::{SignedVote, Vote}; /// Tally for the voting power computed by the `VotingPowerCalculator` diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index efb2f460d..e0de59d21 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -4,7 +4,7 @@ use crate::{ ensure, light_client::Options, operations::{CommitValidator, Hasher, VotingPowerCalculator}, - types::{Header, Height, LightBlock, SignedHeader, Time, TrustThreshold, ValidatorSet}, + types::{Header, LightBlock, SignedHeader, Time, TrustThreshold, ValidatorSet}, }; use errors::VerificationError; @@ -14,7 +14,7 @@ pub mod errors; /// Production predicates, using the default implementation /// of the `VerificationPredicates` trait. -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct ProdPredicates; impl VerificationPredicates for ProdPredicates {} @@ -152,13 +152,13 @@ pub trait VerificationPredicates: Send { untrusted_header: &Header, trusted_header: &Header, ) -> Result<(), VerificationError> { - let trusted_height: Height = trusted_header.height.into(); + let trusted_height = trusted_header.height; ensure!( untrusted_header.height > trusted_header.height, VerificationError::NonIncreasingHeight { - got: untrusted_header.height.into(), - expected: trusted_height + 1, + got: untrusted_header.height, + expected: trusted_height.increment(), } ); @@ -260,7 +260,7 @@ pub fn verify( &trusted.signed_header.header, )?; - let trusted_next_height = trusted.height().checked_add(1).expect("height overflow"); + 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, diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 30994f5c1..635cfe1a1 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -12,7 +12,6 @@ use crate::light_client::LightClient; use crate::peer_list::PeerList; use crate::state::State; use crate::types::{Height, LatestStatus, LightBlock, PeerId, Status}; -use tendermint::lite::{Header, ValidatorSet}; /// Provides an interface to the supervisor for use in downstream code. pub trait Handle { @@ -189,7 +188,7 @@ impl Supervisor { match latest_trusted { Some(trusted) => LatestStatus::new( - Some(trusted.signed_header.header.height()), + Some(trusted.signed_header.header.height.value()), Some(trusted.signed_header.header.hash()), Some(trusted.next_validators.hash()), connected_nodes, diff --git a/light-client/src/types.rs b/light-client/src/types.rs index c74828e22..14dd3c6a6 100644 --- a/light-client/src/types.rs +++ b/light-client/src/types.rs @@ -9,12 +9,12 @@ use tendermint::{ header::Header as TMHeader, signed_header::SignedHeader as TMSignedHeader, Commit as TMCommit, }, - lite::TrustThresholdFraction, + trust_threshold::TrustThresholdFraction, validator::Info as TMValidatorInfo, validator::Set as TMValidatorSet, }; -pub use tendermint::{hash::Hash, lite::Height, time::Time}; +pub use tendermint::{block::Height, hash::Hash, time::Time}; /// Peer ID (public key) of a full node pub type PeerId = tendermint::node::Id; @@ -113,9 +113,9 @@ impl LightBlock { /// Returns the height of this block. /// /// ## Note - /// This is a shorthand for `block.signed_header.header.height.into()`. + /// This is a shorthand for `block.signed_header.header.height`. pub fn height(&self) -> Height { - self.signed_header.header.height.into() + self.signed_header.header.height } } @@ -125,7 +125,7 @@ impl LightBlock { #[display(fmt = "{:?}", self)] pub struct LatestStatus { /// The latest height we are trusting. - pub height: Option, + pub height: Option, /// The latest block hash we are trusting. pub block_hash: Option, /// The latest validator set we are trusting. @@ -143,7 +143,7 @@ impl LatestStatus { valset_hash: Option, connected_nodes: Vec, ) -> Self { - LatestStatus { + Self { height, block_hash, valset_hash, diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index c19c0ddd1..f0039e800 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -247,11 +247,11 @@ fn run_bisection_tests(dir: &str) { /// the bisection test as normal. We then assert that we get the expected error. fn run_bisection_lower_tests(dir: &str) { foreach_bisection_test(dir, |file, mut tc| { - let mut trusted_height: Height = tc.trust_options.height.into(); + let mut trusted_height = tc.trust_options.height; - if trusted_height <= 1 { - tc.trust_options.height = (trusted_height + 1).into(); - trusted_height += 1; + if trusted_height.value() <= 1 { + tc.trust_options.height = trusted_height.increment(); + trusted_height = trusted_height.increment(); } println!( @@ -259,7 +259,7 @@ fn run_bisection_lower_tests(dir: &str) { file ); - tc.height_to_verify = (trusted_height - 1).into(); + tc.height_to_verify = (trusted_height.value() - 1).into(); let test_result = run_bisection_test(tc); match test_result.new_states { diff --git a/light-client/tests/supervisor.rs b/light-client/tests/supervisor.rs index 0e9395d86..0a0e07b39 100644 --- a/light-client/tests/supervisor.rs +++ b/light-client/tests/supervisor.rs @@ -44,7 +44,7 @@ fn load_multi_peer_testcases(dir: &str) -> Vec> { } fn make_instance(peer_id: PeerId, trust_options: TrustOptions, io: MockIo, now: Time) -> Instance { - let trusted_height = trust_options.height.value(); + let trusted_height = trust_options.height; let trusted_state = io .fetch_light_block(peer_id, AtHeight::At(trusted_height)) .expect("could not 'request' light block"); diff --git a/light-client/tests/support b/light-client/tests/support deleted file mode 120000 index 580b13e7b..000000000 --- a/light-client/tests/support +++ /dev/null @@ -1 +0,0 @@ -../../tendermint/tests/support/lite \ No newline at end of file diff --git a/tendermint/tests/support/lite/bisection/multi_peer/conflicting_headers.json b/light-client/tests/support/bisection/multi_peer/conflicting_headers.json similarity index 100% rename from tendermint/tests/support/lite/bisection/multi_peer/conflicting_headers.json rename to light-client/tests/support/bisection/multi_peer/conflicting_headers.json diff --git a/tendermint/tests/support/lite/bisection/multi_peer/conflicting_valid_commits_from_one_of_the_witnesses.json b/light-client/tests/support/bisection/multi_peer/conflicting_valid_commits_from_one_of_the_witnesses.json similarity index 100% rename from tendermint/tests/support/lite/bisection/multi_peer/conflicting_valid_commits_from_one_of_the_witnesses.json rename to light-client/tests/support/bisection/multi_peer/conflicting_valid_commits_from_one_of_the_witnesses.json diff --git a/tendermint/tests/support/lite/bisection/multi_peer/conflicting_valid_commits_from_the_only_witness.json b/light-client/tests/support/bisection/multi_peer/conflicting_valid_commits_from_the_only_witness.json similarity index 100% rename from tendermint/tests/support/lite/bisection/multi_peer/conflicting_valid_commits_from_the_only_witness.json rename to light-client/tests/support/bisection/multi_peer/conflicting_valid_commits_from_the_only_witness.json diff --git a/tendermint/tests/support/lite/bisection/multi_peer/malicious_validator_set.json b/light-client/tests/support/bisection/multi_peer/malicious_validator_set.json similarity index 100% rename from tendermint/tests/support/lite/bisection/multi_peer/malicious_validator_set.json rename to light-client/tests/support/bisection/multi_peer/malicious_validator_set.json diff --git a/tendermint/tests/support/lite/bisection/single_peer/happy_path.json b/light-client/tests/support/bisection/single_peer/happy_path.json similarity index 100% rename from tendermint/tests/support/lite/bisection/single_peer/happy_path.json rename to light-client/tests/support/bisection/single_peer/happy_path.json diff --git a/tendermint/tests/support/lite/bisection/single_peer/header_out_of_trusting_period.json b/light-client/tests/support/bisection/single_peer/header_out_of_trusting_period.json similarity index 100% rename from tendermint/tests/support/lite/bisection/single_peer/header_out_of_trusting_period.json rename to light-client/tests/support/bisection/single_peer/header_out_of_trusting_period.json diff --git a/tendermint/tests/support/lite/bisection/single_peer/not_enough_commits.json b/light-client/tests/support/bisection/single_peer/not_enough_commits.json similarity index 100% rename from tendermint/tests/support/lite/bisection/single_peer/not_enough_commits.json rename to light-client/tests/support/bisection/single_peer/not_enough_commits.json diff --git a/tendermint/tests/support/lite/bisection/single_peer/worst_case.json b/light-client/tests/support/bisection/single_peer/worst_case.json similarity index 100% rename from tendermint/tests/support/lite/bisection/single_peer/worst_case.json rename to light-client/tests/support/bisection/single_peer/worst_case.json diff --git a/tendermint/tests/support/lite/single_step/sequential/commit/less_than_one_third_nil_votes.json b/light-client/tests/support/single_step/sequential/commit/less_than_one_third_nil_votes.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/commit/less_than_one_third_nil_votes.json rename to light-client/tests/support/single_step/sequential/commit/less_than_one_third_nil_votes.json diff --git a/tendermint/tests/support/lite/single_step/sequential/commit/more_than_two_third_vals_sign.json b/light-client/tests/support/single_step/sequential/commit/more_than_two_third_vals_sign.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/commit/more_than_two_third_vals_sign.json rename to light-client/tests/support/single_step/sequential/commit/more_than_two_third_vals_sign.json diff --git a/tendermint/tests/support/lite/single_step/sequential/commit/one_third_vals_dont_sign.json b/light-client/tests/support/single_step/sequential/commit/one_third_vals_dont_sign.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/commit/one_third_vals_dont_sign.json rename to light-client/tests/support/single_step/sequential/commit/one_third_vals_dont_sign.json diff --git a/tendermint/tests/support/lite/single_step/sequential/commit/wrong_commit_height.json b/light-client/tests/support/single_step/sequential/commit/wrong_commit_height.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/commit/wrong_commit_height.json rename to light-client/tests/support/single_step/sequential/commit/wrong_commit_height.json diff --git a/tendermint/tests/support/lite/single_step/sequential/commit/wrong_header_hash.json b/light-client/tests/support/single_step/sequential/commit/wrong_header_hash.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/commit/wrong_header_hash.json rename to light-client/tests/support/single_step/sequential/commit/wrong_header_hash.json diff --git a/tendermint/tests/support/lite/single_step/sequential/commit/wrong_vote_signature.json b/light-client/tests/support/single_step/sequential/commit/wrong_vote_signature.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/commit/wrong_vote_signature.json rename to light-client/tests/support/single_step/sequential/commit/wrong_vote_signature.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/non_monotonic_bft_time.json b/light-client/tests/support/single_step/sequential/header/non_monotonic_bft_time.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/non_monotonic_bft_time.json rename to light-client/tests/support/single_step/sequential/header/non_monotonic_bft_time.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/non_monotonic_header_height.json b/light-client/tests/support/single_step/sequential/header/non_monotonic_header_height.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/non_monotonic_header_height.json rename to light-client/tests/support/single_step/sequential/header/non_monotonic_header_height.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/wrong_chain_id.json b/light-client/tests/support/single_step/sequential/header/wrong_chain_id.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/wrong_chain_id.json rename to light-client/tests/support/single_step/sequential/header/wrong_chain_id.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/wrong_header_timestamp.json b/light-client/tests/support/single_step/sequential/header/wrong_header_timestamp.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/wrong_header_timestamp.json rename to light-client/tests/support/single_step/sequential/header/wrong_header_timestamp.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/wrong_last_block_id.json b/light-client/tests/support/single_step/sequential/header/wrong_last_block_id.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/wrong_last_block_id.json rename to light-client/tests/support/single_step/sequential/header/wrong_last_block_id.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/wrong_last_commit_hash.json b/light-client/tests/support/single_step/sequential/header/wrong_last_commit_hash.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/wrong_last_commit_hash.json rename to light-client/tests/support/single_step/sequential/header/wrong_last_commit_hash.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/wrong_next_valset_hash.json b/light-client/tests/support/single_step/sequential/header/wrong_next_valset_hash.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/wrong_next_valset_hash.json rename to light-client/tests/support/single_step/sequential/header/wrong_next_valset_hash.json diff --git a/tendermint/tests/support/lite/single_step/sequential/header/wrong_valset_hash.json b/light-client/tests/support/single_step/sequential/header/wrong_valset_hash.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/header/wrong_valset_hash.json rename to light-client/tests/support/single_step/sequential/header/wrong_valset_hash.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/128_validators.json b/light-client/tests/support/single_step/sequential/validator_set/128_validators.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/128_validators.json rename to light-client/tests/support/single_step/sequential/validator_set/128_validators.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/1_validator.json b/light-client/tests/support/single_step/sequential/validator_set/1_validator.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/1_validator.json rename to light-client/tests/support/single_step/sequential/validator_set/1_validator.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/8_validators.json b/light-client/tests/support/single_step/sequential/validator_set/8_validators.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/8_validators.json rename to light-client/tests/support/single_step/sequential/validator_set/8_validators.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/faulty_signer.json b/light-client/tests/support/single_step/sequential/validator_set/faulty_signer.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/faulty_signer.json rename to light-client/tests/support/single_step/sequential/validator_set/faulty_signer.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/half_valset_changes.json b/light-client/tests/support/single_step/sequential/validator_set/half_valset_changes.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/half_valset_changes.json rename to light-client/tests/support/single_step/sequential/validator_set/half_valset_changes.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/less_than_one_third_valset_changes.json b/light-client/tests/support/single_step/sequential/validator_set/less_than_one_third_valset_changes.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/less_than_one_third_valset_changes.json rename to light-client/tests/support/single_step/sequential/validator_set/less_than_one_third_valset_changes.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/more_than_two_thirds_valset_changes.json b/light-client/tests/support/single_step/sequential/validator_set/more_than_two_thirds_valset_changes.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/more_than_two_thirds_valset_changes.json rename to light-client/tests/support/single_step/sequential/validator_set/more_than_two_thirds_valset_changes.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/one_third_valset_changes.json b/light-client/tests/support/single_step/sequential/validator_set/one_third_valset_changes.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/one_third_valset_changes.json rename to light-client/tests/support/single_step/sequential/validator_set/one_third_valset_changes.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/two_thirds_valset_changes.json b/light-client/tests/support/single_step/sequential/validator_set/two_thirds_valset_changes.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/two_thirds_valset_changes.json rename to light-client/tests/support/single_step/sequential/validator_set/two_thirds_valset_changes.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/valset_changes_fully.json b/light-client/tests/support/single_step/sequential/validator_set/valset_changes_fully.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/valset_changes_fully.json rename to light-client/tests/support/single_step/sequential/validator_set/valset_changes_fully.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/valset_size_doubles.json b/light-client/tests/support/single_step/sequential/validator_set/valset_size_doubles.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/valset_size_doubles.json rename to light-client/tests/support/single_step/sequential/validator_set/valset_size_doubles.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/valset_size_halves.json b/light-client/tests/support/single_step/sequential/validator_set/valset_size_halves.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/valset_size_halves.json rename to light-client/tests/support/single_step/sequential/validator_set/valset_size_halves.json diff --git a/tendermint/tests/support/lite/single_step/sequential/validator_set/wrong_valset.json b/light-client/tests/support/single_step/sequential/validator_set/wrong_valset.json similarity index 100% rename from tendermint/tests/support/lite/single_step/sequential/validator_set/wrong_valset.json rename to light-client/tests/support/single_step/sequential/validator_set/wrong_valset.json diff --git a/tendermint/tests/support/lite/single_step/skipping/commit/more_signatures_than_validators.json b/light-client/tests/support/single_step/skipping/commit/more_signatures_than_validators.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/commit/more_signatures_than_validators.json rename to light-client/tests/support/single_step/skipping/commit/more_signatures_than_validators.json diff --git a/tendermint/tests/support/lite/single_step/skipping/commit/more_than_two_third_vals_sign.json b/light-client/tests/support/single_step/skipping/commit/more_than_two_third_vals_sign.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/commit/more_than_two_third_vals_sign.json rename to light-client/tests/support/single_step/skipping/commit/more_than_two_third_vals_sign.json diff --git a/tendermint/tests/support/lite/single_step/skipping/commit/no_signatures.json b/light-client/tests/support/single_step/skipping/commit/no_signatures.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/commit/no_signatures.json rename to light-client/tests/support/single_step/skipping/commit/no_signatures.json diff --git a/tendermint/tests/support/lite/single_step/skipping/commit/one_third_vals_dont_sign.json b/light-client/tests/support/single_step/skipping/commit/one_third_vals_dont_sign.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/commit/one_third_vals_dont_sign.json rename to light-client/tests/support/single_step/skipping/commit/one_third_vals_dont_sign.json diff --git a/tendermint/tests/support/lite/single_step/skipping/header/header_from_future.json b/light-client/tests/support/single_step/skipping/header/header_from_future.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/header/header_from_future.json rename to light-client/tests/support/single_step/skipping/header/header_from_future.json diff --git a/tendermint/tests/support/lite/single_step/skipping/header/out_of_trusting_period.json b/light-client/tests/support/single_step/skipping/header/out_of_trusting_period.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/header/out_of_trusting_period.json rename to light-client/tests/support/single_step/skipping/header/out_of_trusting_period.json diff --git a/tendermint/tests/support/lite/single_step/skipping/validator_set/skip_five_blocks.json b/light-client/tests/support/single_step/skipping/validator_set/skip_five_blocks.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/validator_set/skip_five_blocks.json rename to light-client/tests/support/single_step/skipping/validator_set/skip_five_blocks.json diff --git a/tendermint/tests/support/lite/single_step/skipping/validator_set/skip_one_block.json b/light-client/tests/support/single_step/skipping/validator_set/skip_one_block.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/validator_set/skip_one_block.json rename to light-client/tests/support/single_step/skipping/validator_set/skip_one_block.json diff --git a/tendermint/tests/support/lite/single_step/skipping/validator_set/valset_changes_less_than_trust_level.json b/light-client/tests/support/single_step/skipping/validator_set/valset_changes_less_than_trust_level.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/validator_set/valset_changes_less_than_trust_level.json rename to light-client/tests/support/single_step/skipping/validator_set/valset_changes_less_than_trust_level.json diff --git a/tendermint/tests/support/lite/single_step/skipping/validator_set/valset_changes_more_than_trust_level.json b/light-client/tests/support/single_step/skipping/validator_set/valset_changes_more_than_trust_level.json similarity index 100% rename from tendermint/tests/support/lite/single_step/skipping/validator_set/valset_changes_more_than_trust_level.json rename to light-client/tests/support/single_step/skipping/validator_set/valset_changes_more_than_trust_level.json diff --git a/tendermint/tests/support/lite/voting_power/1_empty_signatures.json b/light-client/tests/support/voting_power/1_empty_signatures.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/1_empty_signatures.json rename to light-client/tests/support/voting_power/1_empty_signatures.json diff --git a/tendermint/tests/support/lite/voting_power/2_1_all_signatures_absent.json b/light-client/tests/support/voting_power/2_1_all_signatures_absent.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/2_1_all_signatures_absent.json rename to light-client/tests/support/voting_power/2_1_all_signatures_absent.json diff --git a/tendermint/tests/support/lite/voting_power/2_2_all_signatures_nil.json b/light-client/tests/support/voting_power/2_2_all_signatures_nil.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/2_2_all_signatures_nil.json rename to light-client/tests/support/voting_power/2_2_all_signatures_nil.json diff --git a/tendermint/tests/support/lite/voting_power/3_1_one_invalid_signature.json b/light-client/tests/support/voting_power/3_1_one_invalid_signature.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/3_1_one_invalid_signature.json rename to light-client/tests/support/voting_power/3_1_one_invalid_signature.json diff --git a/tendermint/tests/support/lite/voting_power/3_2_all_signatures_invalid.json b/light-client/tests/support/voting_power/3_2_all_signatures_invalid.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/3_2_all_signatures_invalid.json rename to light-client/tests/support/voting_power/3_2_all_signatures_invalid.json diff --git a/tendermint/tests/support/lite/voting_power/4_1_missing_a_signature_in_commit.json b/light-client/tests/support/voting_power/4_1_missing_a_signature_in_commit.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/4_1_missing_a_signature_in_commit.json rename to light-client/tests/support/voting_power/4_1_missing_a_signature_in_commit.json diff --git a/tendermint/tests/support/lite/voting_power/4_2_extra_signature_in_commit.json b/light-client/tests/support/voting_power/4_2_extra_signature_in_commit.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/4_2_extra_signature_in_commit.json rename to light-client/tests/support/voting_power/4_2_extra_signature_in_commit.json diff --git a/tendermint/tests/support/lite/voting_power/5_1_all_signatures_from_same_validator.json b/light-client/tests/support/voting_power/5_1_all_signatures_from_same_validator.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/5_1_all_signatures_from_same_validator.json rename to light-client/tests/support/voting_power/5_1_all_signatures_from_same_validator.json diff --git a/tendermint/tests/support/lite/voting_power/5_1_validator_voted_twice.json b/light-client/tests/support/voting_power/5_1_validator_voted_twice.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/5_1_validator_voted_twice.json rename to light-client/tests/support/voting_power/5_1_validator_voted_twice.json diff --git a/tendermint/tests/support/lite/voting_power/7_signatures_from_diff_valset.json b/light-client/tests/support/voting_power/7_signatures_from_diff_valset.json similarity index 100% rename from tendermint/tests/support/lite/voting_power/7_signatures_from_diff_valset.json rename to light-client/tests/support/voting_power/7_signatures_from_diff_valset.json diff --git a/light-node/src/commands/initialize.rs b/light-node/src/commands/initialize.rs index 23f57d8ef..2989bb85f 100644 --- a/light-node/src/commands/initialize.rs +++ b/light-node/src/commands/initialize.rs @@ -11,16 +11,14 @@ use abscissa_core::Command; use abscissa_core::Options; use abscissa_core::Runnable; -use tendermint::hash; -use tendermint::lite::Header; -use tendermint::Hash; +use tendermint::{hash, Hash}; use tendermint_light_client::components::io::{AtHeight, Io, ProdIo}; use tendermint_light_client::operations::ProdHasher; use tendermint_light_client::predicates::{ProdPredicates, VerificationPredicates}; use tendermint_light_client::store::sled::SledStore; use tendermint_light_client::store::LightStore; -use tendermint_light_client::types::Status; +use tendermint_light_client::types::{Height, Status}; /// `initialize` subcommand #[derive(Command, Debug, Default, Options)] @@ -51,7 +49,7 @@ impl Runnable for InitCmd { let io = ProdIo::new(peer_map, Some(app_cfg.rpc_config.request_timeout)); - initialize_subjectively(self.height, subjective_header_hash, &lc, &io); + initialize_subjectively(self.height.into(), subjective_header_hash, &lc, &io); } } @@ -60,7 +58,7 @@ impl Runnable for InitCmd { // TODO(ismail): additionally here and everywhere else, we should return errors // instead of std::process::exit because no destructors will be run. fn initialize_subjectively( - height: u64, + height: Height, subjective_header_hash: Hash, l_conf: &LightClientConfig, io: &ProdIo, diff --git a/rpc/tests/integration.rs b/rpc/tests/integration.rs index de2771d85..30c02e618 100644 --- a/rpc/tests/integration.rs +++ b/rpc/tests/integration.rs @@ -3,7 +3,6 @@ mod endpoints { use std::{fs, path::PathBuf}; use tendermint::abci::Code; - use tendermint::lite::Header; use tendermint_rpc::{self as rpc, endpoint, Response}; diff --git a/tendermint/src/block/header.rs b/tendermint/src/block/header.rs index ab6f3ed6c..59678f7b0 100644 --- a/tendermint/src/block/header.rs +++ b/tendermint/src/block/header.rs @@ -1,5 +1,7 @@ //! Block headers +use crate::amino_types::{message::AminoMessage, BlockId, ConsensusVersion, TimeMsg}; +use crate::merkle::simple_hash_from_byte_vectors; use crate::serializers; use crate::{account, block, chain, Hash, Time}; use serde::{Deserialize, Serialize}; @@ -60,6 +62,62 @@ pub struct Header { pub proposer_address: account::Id, } +impl Header { + /// Hash this header + pub fn hash(&self) -> Hash { + // Note that if there is an encoding problem this will + // panic (as the golang code would): + // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/block.go#L393 + // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/encoding_helper.go#L9:6 + + let mut fields_bytes: Vec> = Vec::with_capacity(16); + fields_bytes.push(AminoMessage::bytes_vec(&ConsensusVersion::from( + &self.version, + ))); + fields_bytes.push(encode_bytes(self.chain_id.as_bytes())); + fields_bytes.push(encode_varint(self.height.value())); + fields_bytes.push(AminoMessage::bytes_vec(&TimeMsg::from(self.time))); + fields_bytes.push( + self.last_block_id + .as_ref() + .map_or(vec![], |id| AminoMessage::bytes_vec(&BlockId::from(id))), + ); + fields_bytes.push(self.last_commit_hash.as_ref().map_or(vec![], encode_hash)); + fields_bytes.push(self.data_hash.as_ref().map_or(vec![], encode_hash)); + fields_bytes.push(encode_hash(&self.validators_hash)); + fields_bytes.push(encode_hash(&self.next_validators_hash)); + fields_bytes.push(encode_hash(&self.consensus_hash)); + fields_bytes.push(encode_bytes(&self.app_hash)); + fields_bytes.push(self.last_results_hash.as_ref().map_or(vec![], encode_hash)); + fields_bytes.push(self.evidence_hash.as_ref().map_or(vec![], encode_hash)); + fields_bytes.push(encode_bytes(self.proposer_address.as_bytes())); + + Hash::Sha256(simple_hash_from_byte_vectors(fields_bytes)) + } +} + +fn encode_bytes(bytes: &[u8]) -> Vec { + let bytes_len = bytes.len(); + if bytes_len > 0 { + let mut encoded = vec![]; + prost_amino::encode_length_delimiter(bytes_len, &mut encoded).unwrap(); + encoded.append(&mut bytes.to_vec()); + encoded + } else { + vec![] + } +} + +fn encode_hash(hash: &Hash) -> Vec { + encode_bytes(hash.as_bytes()) +} + +fn encode_varint(val: u64) -> Vec { + let mut val_enc = vec![]; + prost_amino::encoding::encode_varint(val, &mut val_enc); + val_enc +} + /// `Version` contains the protocol version for the blockchain and the /// application. /// diff --git a/tendermint/src/block/height.rs b/tendermint/src/block/height.rs index 6bf98e839..82eb94459 100644 --- a/tendermint/src/block/height.rs +++ b/tendermint/src/block/height.rs @@ -21,7 +21,7 @@ impl Height { /// Increment the block height by 1 pub fn increment(self) -> Self { - Height(self.0.checked_add(1).unwrap()) + Height(self.0.checked_add(1).expect("height overflow")) } } diff --git a/tendermint/src/lib.rs b/tendermint/src/lib.rs index 8ad78b574..567acf0c6 100644 --- a/tendermint/src/lib.rs +++ b/tendermint/src/lib.rs @@ -32,9 +32,6 @@ pub mod consensus; pub mod evidence; pub mod genesis; pub mod hash; -#[allow(dead_code, missing_docs)] -pub mod lite; -pub mod lite_impl; pub mod merkle; mod moniker; pub mod net; @@ -45,6 +42,7 @@ pub mod serializers; pub mod signature; pub mod time; mod timeout; +pub mod trust_threshold; pub mod validator; mod version; pub mod vote; diff --git a/tendermint/src/lite.rs b/tendermint/src/lite.rs index ad1e8f3e4..d1fb6c52e 100644 --- a/tendermint/src/lite.rs +++ b/tendermint/src/lite.rs @@ -1,9 +1,6 @@ //! Core logic and traits of a light client. -pub mod error; pub mod types; -pub mod verifier; pub use self::types::*; -pub use self::verifier::{verify_bisection, verify_single}; diff --git a/tendermint/src/lite/error.rs b/tendermint/src/lite/error.rs deleted file mode 100644 index 969705a8f..000000000 --- a/tendermint/src/lite/error.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! All error types tied to the light client. - -use crate::Hash; -use anomaly::{BoxError, Context}; -use std::time::SystemTime; -use thiserror::Error; - -/// The main error type verification methods will return. -/// See [`Kind`] for the different kind of errors. -pub type Error = anomaly::Error; - -/// All error kinds related to the light client. -#[derive(Clone, Debug, Error)] -pub enum Kind { - /// The provided header expired. - #[error("old header has expired at {at:?} (now: {now:?})")] - Expired { at: SystemTime, now: SystemTime }, - - /// Trusted header is from the future. - #[error("trusted header time is too far in the future")] - DurationOutOfRange, - - /// Header height smaller than expected. - #[error("expected height >= {expected} (got: {got})")] - NonIncreasingHeight { got: u64, expected: u64 }, - - /// Header time is in the past compared to already trusted header. - #[error("untrusted header time <= trusted header time")] - NonIncreasingTime, - - /// Invalid validator hash. - #[error("header's validator hash does not match actual validator hash ({header_val_hash:?}!={val_hash:?})")] - InvalidValidatorSet { - header_val_hash: Hash, - val_hash: Hash, - }, - - /// Invalid next validator hash. - #[error("header's next validator hash does not match next_val_hash ({header_next_val_hash:?}!={next_val_hash:?})")] - InvalidNextValidatorSet { - header_next_val_hash: Hash, - next_val_hash: Hash, - }, - - /// Commit is not for the header we expected. - #[error( - "header hash does not match the hash in the commit ({header_hash:?}!={commit_hash:?})" - )] - InvalidCommitValue { - header_hash: Hash, - commit_hash: Hash, - }, - - /// Signed power does not account for +2/3 of total voting power. - #[error("signed voting power ({signed}) do not account for +2/3 of the total voting power: ({total})")] - InvalidCommit { total: u64, signed: u64 }, - - /// This means the trust threshold (default +1/3) is not met. - #[error("signed voting power ({}) is too small fraction of total voting power: ({}), threshold: {}", - .signed, .total, .trust_treshold - )] - InsufficientVotingPower { - total: u64, - signed: u64, - trust_treshold: String, - }, - - /// This is returned if an invalid TrustThreshold is created. - #[error("A valid threshold is `1/3 <= threshold <= 1`, got: {got}")] - InvalidTrustThreshold { got: String }, - - /// Use the [`Kind::context`] method to wrap the underlying error of - /// the implementation, if any. - #[error("Request failed")] - RequestFailed, - - /// Use the [`Kind::context`] method to wrap the underlying error of - /// the implementation, if any. - #[error("Implementation specific error")] - ImplementationSpecific, - - /// This is returned when a faulty i.e misbehaving full node is found - #[error("Found faulty full node")] - FaultyFullNode, -} - -impl Kind { - /// Add additional context. - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) - } -} - -#[cfg(test)] -mod tests { - use crate::lite::error::{Error as LiteErr, Kind}; - use std::error::Error; - - #[test] - fn test_implementation_specific() { - let e: Result<(), LiteErr> = Err(Kind::ImplementationSpecific - .context("Some implementation specific error which doesn't need to be a string") - .into()); - let err_kind = e.unwrap_err(); - // could we match against the Kind? - assert_eq!( - format!("{:?}", &Kind::ImplementationSpecific), - format!("{:?}", err_kind.kind()) - ); - // do we still have that context error? - assert_eq!( - format!( - "{:?}", - Some("Some implementation specific error which doesn't need to be a string") - ), - format!("{:?}", err_kind.source()) - ); - - // Can we do the same with some actual implementation of std::error::Error? - // produce sth we know yields an Error (we don't care what it is): - let res = "xc".parse::(); - let source_err = res.unwrap_err(); - let e: Result<(), LiteErr> = Err(Kind::ImplementationSpecific - .context(source_err.clone()) - .into()); - let err_kind = e.unwrap_err(); - assert_eq!( - format!("{:?}", &Kind::ImplementationSpecific), - format!("{:?}", err_kind.kind()) - ); - assert_eq!( - format!("{}", source_err), - format!("{}", err_kind.source().unwrap()) - ); - } -} diff --git a/tendermint/src/lite/types.rs b/tendermint/src/lite/types.rs deleted file mode 100644 index 911eead5b..000000000 --- a/tendermint/src/lite/types.rs +++ /dev/null @@ -1,487 +0,0 @@ -//! All traits that are necessary and need to be implemented to use the main -//! verification logic in [`super::verifier`] for a light client. - -use std::fmt::{self, Debug, Display}; -use std::time::SystemTime; - -use crate::serializers; -use async_trait::async_trait; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -use crate::lite::error::{Error, Kind}; -use crate::Hash; - -pub type Height = u64; - -/// Header contains meta data about the block - -/// the height, the time, the hash of the validator set -/// that should sign this header, and the hash of the validator -/// set that should sign the next header. -pub trait Header: Clone + Debug + Serialize + DeserializeOwned { - /// The header's notion of (bft-)time. - /// We assume it can be converted to SystemTime. - type Time: Into; - - fn height(&self) -> Height; - fn bft_time(&self) -> Self::Time; - fn validators_hash(&self) -> Hash; - fn next_validators_hash(&self) -> Hash; - - /// Hash of the header (ie. the hash of the block). - fn hash(&self) -> Hash; -} - -/// ValidatorSet is the full validator set. -/// It exposes its hash and its total power. -pub trait ValidatorSet: Clone + Debug + Serialize + DeserializeOwned { - /// Hash of the validator set. - fn hash(&self) -> Hash; - - /// Total voting power of the set - fn total_power(&self) -> u64; -} - -/// Commit is used to prove a Header can be trusted. -/// Verifying the Commit requires access to an associated ValidatorSet -/// to determine what voting power signed the commit. -pub trait Commit: Clone + Debug + Serialize + DeserializeOwned { - type ValidatorSet: ValidatorSet; - - /// Hash of the header this commit is for. - fn header_hash(&self) -> Hash; - - /// Compute the voting power of the validators that correctly signed the commit, - /// according to their voting power in the passed in validator set. - /// Will return an error in case an invalid signature was included. - /// TODO/XXX: This cannot detect if a signature from an incorrect validator - /// is included. That's fine when we're just trying to see if we can skip, - /// but when actually verifying it means we might accept commits that have sigs from - /// outside the correct validator set, which is something we expect to be able to detect - /// (it's not a real issue, but it would indicate a faulty full node). - /// - /// - /// This method corresponds to the (pure) auxiliary function in the spec: - /// `votingpower_in(signers(h.Commit),h.Header.V)`. - /// Note this expects the Commit to be able to compute `signers(h.Commit)`, - /// ie. the identity of the validators that signed it, so they - /// can be cross-referenced with the given `vals`. - fn voting_power_in(&self, vals: &Self::ValidatorSet) -> Result; - - /// Implementers should add addition validation against the given validator set - /// or other implementation specific validation here. - /// E.g. validate that the length of the included signatures in the commit match - /// with the number of validators. - fn validate(&self, vals: &Self::ValidatorSet) -> Result<(), Error>; -} - -/// TrustThreshold defines how much of the total voting power of a known -/// and trusted validator set is sufficient for a commit to be -/// accepted going forward. -pub trait TrustThreshold: Copy + Clone + Debug + Serialize + DeserializeOwned { - fn is_enough_power(&self, signed_voting_power: u64, total_voting_power: u64) -> bool; -} - -/// TrustThresholdFraction defines what fraction of the total voting power of a known -/// and trusted validator set is sufficient for a commit to be -/// accepted going forward. -/// The [`Default::default()`] returns true, iff at least a third of the trusted -/// voting power signed (in other words at least one honest validator signed). -/// Some clients might require more than +1/3 and can implement their own -/// [`TrustThreshold`] which can be passed into all relevant methods. -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct TrustThresholdFraction { - #[serde(with = "serializers::from_str")] - pub numerator: u64, - #[serde(with = "serializers::from_str")] - pub denominator: u64, -} - -impl TrustThresholdFraction { - /// Constant for a trust threshold of 2/3. - pub const TWO_THIRDS: Self = Self { - numerator: 2, - denominator: 3, - }; - - /// Instantiate a TrustThresholdFraction if the given denominator and - /// numerator are valid. - /// - /// The parameters are valid iff `1/3 <= numerator/denominator <= 1`. - /// In any other case we return [`Kind::InvalidTrustThreshold`]. - pub fn new(numerator: u64, denominator: u64) -> Result { - if numerator <= denominator && denominator > 0 && 3 * numerator >= denominator { - return Ok(Self { - numerator, - denominator, - }); - } - Err(Kind::InvalidTrustThreshold { - got: format!("{}/{}", numerator, denominator), - } - .into()) - } -} - -// TODO: should this go in the central place all impls live instead? (currently lite_impl) -impl TrustThreshold for TrustThresholdFraction { - fn is_enough_power(&self, signed_voting_power: u64, total_voting_power: u64) -> bool { - signed_voting_power * self.denominator > total_voting_power * self.numerator - } -} - -impl Default for TrustThresholdFraction { - fn default() -> Self { - Self::new(1, 3) - .expect("initializing TrustThresholdFraction with valid fraction mustn't panic") - } -} - -impl Display for TrustThresholdFraction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}/{}", self.numerator, self.denominator) - } -} - -/// Requester can be used to request [`SignedHeader`]s and [`ValidatorSet`]s for a -/// given height, e.g., by talking to a tendermint fullnode through RPC. -#[async_trait] -pub trait Requester -where - C: Commit, - H: Header, -{ - /// Request the [`SignedHeader`] at height h. - async fn signed_header(&self, h: Height) -> Result, Error>; - - /// Request the validator set at height h. - async fn validator_set(&self, h: Height) -> Result; -} - -/// TrustedState contains a state trusted by a lite client, -/// including the last header (at height h-1) and the validator set -/// (at height h) to use to verify the next header. -/// -/// **Note:** The `#[serde(bound = ...)]` attribute is required to -/// derive `Deserialize` for this struct as Serde is not able to infer -/// the proper bound when associated types are involved. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(bound(deserialize = "C::ValidatorSet: Deserialize<'de>"))] -pub struct TrustedState -where - H: Header, - C: Commit, -{ - last_header: SignedHeader, // height H-1 - validators: C::ValidatorSet, // height H -} - -impl TrustedState -where - H: Header, - C: Commit, -{ - /// Initialize the TrustedState with the given signed header and validator set. - /// Note that if the height of the passed in header is h-1, the passed in validator set - /// must have been requested for height h. - pub fn new(last_header: SignedHeader, validators: C::ValidatorSet) -> Self { - Self { - last_header, - validators, - } - } - - pub fn last_header(&self) -> &SignedHeader { - &self.last_header - } - - pub fn validators(&self) -> &C::ValidatorSet { - &self.validators - } -} - -/// SignedHeader bundles a [`Header`] and a [`Commit`] for convenience. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct SignedHeader { - commit: C, - header: H, -} - -impl SignedHeader -where - C: Commit, - H: Header, -{ - pub fn new(commit: C, header: H) -> Self { - Self { commit, header } - } - - pub fn commit(&self) -> &C { - &self.commit - } - - pub fn header(&self) -> &H { - &self.header - } -} - -#[cfg(test)] -pub(super) mod mocks { - use anomaly::fail; - use serde::Serialize; - use sha2::{Digest, Sha256}; - - use super::*; - use crate::{hash::Algorithm, Hash}; - - use std::collections::HashMap; - - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] - pub struct MockHeader { - height: u64, - time: SystemTime, - vals: Hash, - next_vals: Hash, - } - - impl MockHeader { - pub fn new(height: u64, time: SystemTime, vals: Hash, next_vals: Hash) -> MockHeader { - MockHeader { - height, - time, - vals, - next_vals, - } - } - - pub fn set_time(&mut self, new_time: SystemTime) { - self.time = new_time - } - } - - impl Header for MockHeader { - type Time = SystemTime; - - fn height(&self) -> Height { - self.height - } - fn bft_time(&self) -> Self::Time { - self.time - } - fn validators_hash(&self) -> Hash { - self.vals - } - fn next_validators_hash(&self) -> Hash { - self.next_vals - } - fn hash(&self) -> Hash { - json_hash(self) - } - } - - pub fn json_hash(value: &T) -> Hash { - let encoded = serde_json::to_vec(value).unwrap(); - let hashed = Sha256::digest(&encoded); - Hash::new(Algorithm::Sha256, &hashed).unwrap() - } - - // vals are just ints, each has power 1 - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] - pub struct MockValSet { - // NOTE: use HashSet instead? - vals: Vec, - } - - impl MockValSet { - pub fn new(vals: Vec) -> MockValSet { - MockValSet { vals } - } - } - - impl ValidatorSet for MockValSet { - fn hash(&self) -> Hash { - json_hash(&self) - } - fn total_power(&self) -> u64 { - self.vals.len() as u64 - } - } - - // commit is a list of vals that signed. - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] - pub struct MockCommit { - hash: Hash, - vals: Vec, - } - - impl MockCommit { - pub fn new(hash: Hash, vals: Vec) -> MockCommit { - MockCommit { hash, vals } - } - } - impl Commit for MockCommit { - type ValidatorSet = MockValSet; - - fn header_hash(&self) -> Hash { - self.hash - } - - // just the intersection - fn voting_power_in(&self, vals: &Self::ValidatorSet) -> Result { - let mut power = 0; - // if there's a signer thats not in the val set, - // we can't detect it... - for signer in self.vals.iter() { - for val in vals.vals.iter() { - if signer == val { - power += 1 - } - } - } - Ok(power) - } - - fn validate(&self, _vals: &Self::ValidatorSet) -> Result<(), Error> { - // some implementation specific checks: - if self.vals.is_empty() || self.hash.algorithm() != Algorithm::Sha256 { - fail!( - Kind::ImplementationSpecific, - "validator set is empty, or, invalid hash algo" - ); - } - Ok(()) - } - } - - pub type MockSignedHeader = SignedHeader; - pub type MockTrustedState = TrustedState; - - // Mock requester holds a map from height to - // Headers and commits. - #[derive(Clone, Debug)] - pub struct MockRequester { - pub signed_headers: HashMap, - pub validators: HashMap, - } - - impl MockRequester { - pub fn new() -> Self { - Self { - signed_headers: HashMap::new(), - validators: HashMap::new(), - } - } - } - - #[async_trait] - impl Requester for MockRequester { - async fn signed_header( - &self, - h: u64, - ) -> Result, Error> { - println!("requested signed header for height:{:?}", h); - if let Some(sh) = self.signed_headers.get(&h) { - return Ok(sh.to_owned()); - } - println!("couldn't get sh for: {}", &h); - fail!(Kind::RequestFailed, "couldn't get sh for: {}", &h); - } - - async fn validator_set(&self, h: u64) -> Result { - println!("requested validators for height:{:?}", h); - if let Some(vs) = self.validators.get(&h) { - return Ok(vs.to_owned()); - } - println!("couldn't get vals for: {}", &h); - fail!(Kind::RequestFailed, "couldn't get vals for: {}", &h); - } - } - - pub fn fixed_hash() -> Hash { - Hash::new(Algorithm::Sha256, &Sha256::digest(&[5])).unwrap() - } -} - -#[cfg(test)] -mod tests { - use crate::lite::types::mocks::*; - use crate::lite::{Commit, Header, SignedHeader, TrustedState, ValidatorSet}; - use crate::lite::{TrustThreshold, TrustThresholdFraction}; - use crate::test::test_serialization_roundtrip; - use std::time::SystemTime; - - #[test] - fn default_is_enough_power() { - let threshold = TrustThresholdFraction::default(); - - // 100% > 33% - assert!(threshold.is_enough_power(3, 3)); - - // 66% > 33% - assert!(threshold.is_enough_power(2, 3)); - - // 33% <= 33% - assert!(!threshold.is_enough_power(1, 3)); - - // 1% < 33% - assert!(!threshold.is_enough_power(1, 100)); - } - - #[test] - fn signed_header() { - let vs = &MockValSet::new(vec![1, 2]); - let h = MockHeader::new(0, SystemTime::UNIX_EPOCH, vs.hash(), vs.hash()); - let c_header_hash = h.hash(); - let c_vals: Vec = vec![1]; - let c = MockCommit::new(c_header_hash, c_vals); - assert_eq!(c.header_hash(), c_header_hash); - let sh = SignedHeader::new(c.clone(), h.clone()); - assert_eq!(sh.header(), &h); - assert_eq!(sh.commit(), &c); - - assert_eq!(sh.commit().header_hash(), h.hash()); - assert_eq!( - sh.commit() - .voting_power_in(vs) - .expect("mock shouldn't fail"), - 1 - ); - assert_eq!(sh.header().height(), h.height()); - assert_eq!(sh.header().bft_time(), h.bft_time()); - assert!(sh.commit().validate(vs).is_ok()); - } - - #[test] - fn trusted_state() { - let vs = MockValSet::new(vec![1]); - let h = MockHeader::new(0, SystemTime::UNIX_EPOCH, vs.hash(), vs.hash()); - let c = MockCommit::new(h.hash(), vec![]); - let sh = SignedHeader::new(c, h); - let ts = TrustedState::new(sh.clone(), vs.clone()); - assert_eq!(ts.last_header(), &sh); - assert_eq!(ts.validators(), &vs); - } - - #[test] - fn trust_threshold_fraction() { - assert_eq!( - TrustThresholdFraction::default(), - TrustThresholdFraction::new(1, 3).expect("mustn't panic") - ); - assert!(TrustThresholdFraction::new(2, 3).is_ok()); - assert!(TrustThresholdFraction::new(1, 1).is_ok()); - - assert!(TrustThresholdFraction::new(3, 1).is_err()); - assert!(TrustThresholdFraction::new(1, 4).is_err()); - assert!(TrustThresholdFraction::new(1, 5).is_err()); - assert!(TrustThresholdFraction::new(2, 7).is_err()); - assert!(TrustThresholdFraction::new(0, 1).is_err()); - assert!(TrustThresholdFraction::new(1, 0).is_err()); - } - - #[test] - fn trust_threshold_fraction_serialization_roundtrip() { - let json_data = - include_str!("../../tests/support/serialization/trust_threshold/fraction.json"); - test_serialization_roundtrip::(json_data); - } -} diff --git a/tendermint/src/lite/verifier.rs b/tendermint/src/lite/verifier.rs deleted file mode 100644 index d1f38b133..000000000 --- a/tendermint/src/lite/verifier.rs +++ /dev/null @@ -1,937 +0,0 @@ -//! Main verification functions that can be used to implement a light client. -//! -//! -//! # Examples -//! -//! ``` -//! // TODO: add a proper example maybe showing how a `can_trust_bisection` -//! // looks using the types and methods in this crate/module. -//! ``` - -use std::cmp::Ordering; -use std::time::{Duration, SystemTime}; - -use crate::lite::error::{Error, Kind}; -use crate::lite::{ - Commit, Header, Height, Requester, SignedHeader, TrustThreshold, TrustedState, ValidatorSet, -}; -use anomaly::ensure; -use futures::future::{FutureExt, LocalBoxFuture}; -use std::ops::Add; - -/// Returns an error if the header has expired according to the given -/// trusting_period and current time. If so, the verifier must be reset subjectively. -pub fn is_within_trust_period( - last_header: &H, - trusting_period: Duration, - now: SystemTime, -) -> Result<(), Error> -where - H: Header, -{ - let header_time: SystemTime = last_header.bft_time().into(); - let expires_at = header_time.add(trusting_period); - // Ensure now > expires_at. - if expires_at <= now { - return Err(Kind::Expired { - at: expires_at, - now, - } - .into()); - } - // Also make sure the header is not after now. - ensure!( - header_time <= now, - Kind::DurationOutOfRange, - "header time: ({:?}) > now: ({:?})", - header_time, - now - ); - Ok(()) -} - -/// Validate the validators, next validators, against the signed header. -/// This is equivalent to validateSignedHeaderAndVals in the spec. -pub fn validate( - signed_header: &SignedHeader, - vals: &C::ValidatorSet, - next_vals: &C::ValidatorSet, -) -> Result<(), Error> -where - C: Commit, - H: Header, -{ - let header = signed_header.header(); - let commit = signed_header.commit(); - - // ensure the header validator hashes match the given validators - if header.validators_hash() != vals.hash() { - return Err(Kind::InvalidValidatorSet { - header_val_hash: header.validators_hash(), - val_hash: vals.hash(), - } - .into()); - } - if header.next_validators_hash() != next_vals.hash() { - return Err(Kind::InvalidNextValidatorSet { - header_next_val_hash: header.next_validators_hash(), - next_val_hash: next_vals.hash(), - } - .into()); - } - - // ensure the header matches the commit - if header.hash() != commit.header_hash() { - return Err(Kind::InvalidCommitValue { - header_hash: header.hash(), - commit_hash: commit.header_hash(), - } - .into()); - } - - // additional implementation specific validation: - commit.validate(vals)?; - - Ok(()) -} - -/// Verify that +2/3 of the correct validator set signed this commit. -/// NOTE: These validators are expected to be the correct validators for the commit, -/// but since we're using voting_power_in, we can't actually detect if there's -/// votes from validators not in the set. -pub fn verify_commit_full(vals: &C::ValidatorSet, commit: &C) -> Result<(), Error> -where - C: Commit, -{ - let total_power = vals.total_power(); - let signed_power = commit.voting_power_in(vals)?; - - // check the signers account for +2/3 of the voting power - if signed_power * 3 <= total_power * 2 { - return Err(Kind::InvalidCommit { - total: total_power, - signed: signed_power, - } - .into()); - } - - Ok(()) -} - -/// Verify that +1/3 of the given validator set signed this commit. -/// NOTE the given validators do not necessarily correspond to the validator set for this commit, -/// but there may be some intersection. The trust_level parameter allows clients to require more -/// than +1/3 by implementing the TrustLevel trait accordingly. -pub fn verify_commit_trusting( - validators: &C::ValidatorSet, - commit: &C, - trust_level: L, -) -> Result<(), Error> -where - C: Commit, - L: TrustThreshold, -{ - let total_power = validators.total_power(); - let signed_power = commit.voting_power_in(validators)?; - - // check the signers account for +1/3 of the voting power (or more if the - // trust_level requires so) - if !trust_level.is_enough_power(signed_power, total_power) { - return Err(Kind::InsufficientVotingPower { - total: total_power, - signed: signed_power, - trust_treshold: format!("{:?}", trust_level), - } - .into()); - } - - Ok(()) -} - -// Verify a single untrusted header against a trusted state. -// Includes all validation and signature verification. -// Not publicly exposed since it does not check for expiry -// and hence it's possible to use it incorrectly. -// If trusted_state is not expired and this returns Ok, the -// untrusted_sh and untrusted_next_vals can be considered trusted. -fn verify_single_inner( - trusted_state: &TrustedState, - untrusted_sh: &SignedHeader, - untrusted_vals: &C::ValidatorSet, - untrusted_next_vals: &C::ValidatorSet, - trust_threshold: L, -) -> Result<(), Error> -where - H: Header, - C: Commit, - L: TrustThreshold, -{ - // validate the untrusted header against its commit, vals, and next_vals - let untrusted_header = untrusted_sh.header(); - let untrusted_commit = untrusted_sh.commit(); - - validate(untrusted_sh, untrusted_vals, untrusted_next_vals)?; - - // ensure the new height is higher. - // if its +1, ensure the vals are correct. - // if its >+1, ensure we can skip to it - let trusted_header = trusted_state.last_header().header(); - let trusted_height = trusted_header.height(); - let untrusted_height = untrusted_sh.header().height(); - - // ensure the untrusted_header.bft_time() > trusted_header.bft_time() - if untrusted_header.bft_time().into() <= trusted_header.bft_time().into() { - return Err(Kind::NonIncreasingTime.into()); - } - - match untrusted_height.cmp(&trusted_height.checked_add(1).expect("height overflow")) { - Ordering::Less => { - return Err(Kind::NonIncreasingHeight { - got: untrusted_height, - expected: trusted_height + 1, - } - .into()) - } - Ordering::Equal => { - let trusted_vals_hash = trusted_header.next_validators_hash(); - let untrusted_vals_hash = untrusted_header.validators_hash(); - if trusted_vals_hash != untrusted_vals_hash { - // TODO: more specific error - // ie. differentiate from when next_vals.hash() doesnt - // match the header hash ... - return Err(Kind::InvalidNextValidatorSet { - header_next_val_hash: trusted_vals_hash, - next_val_hash: untrusted_vals_hash, - } - .into()); - } - } - Ordering::Greater => { - let trusted_vals = trusted_state.validators(); - verify_commit_trusting(trusted_vals, untrusted_commit, trust_threshold)?; - } - } - - // All validation passed successfully. Verify the validators correctly committed the block. - verify_commit_full(untrusted_vals, untrusted_sh.commit()) -} - -/// Verify a single untrusted header against a trusted state. -/// Ensures our last trusted header hasn't expired yet, and that -/// the untrusted header can be verified using only our latest trusted -/// state from the store. -/// -/// On success, the caller is responsible for updating the store with the returned -/// header to be trusted. -/// -/// This function is primarily for use by IBC handlers. -pub fn verify_single( - trusted_state: TrustedState, - untrusted_sh: &SignedHeader, - untrusted_vals: &C::ValidatorSet, - untrusted_next_vals: &C::ValidatorSet, - trust_threshold: L, - trusting_period: Duration, - now: SystemTime, -) -> Result, Error> -where - H: Header, - C: Commit, - L: TrustThreshold, -{ - // Fetch the latest state and ensure it hasn't expired. - let trusted_sh = trusted_state.last_header(); - is_within_trust_period(trusted_sh.header(), trusting_period, now)?; - - verify_single_inner( - &trusted_state, - untrusted_sh, - untrusted_vals, - untrusted_next_vals, - trust_threshold, - )?; - - // The untrusted header is now trusted; - // return to the caller so they can update the store: - Ok(TrustedState::new( - untrusted_sh.clone(), - untrusted_next_vals.clone(), - )) -} - -/// Attempt to "bisect" from the passed-in trusted state (with header of height h) -/// to the given untrusted height (h+n) by requesting the necessary -/// data (signed headers and validators from height (h, h+n]). -/// -/// On success, callers are responsible for storing the returned states -/// which can now be trusted. -/// -/// Returns an error if: -/// - we're already at or past that height -/// - our latest state expired -/// - any requests fail -/// - requested data is inconsistent (eg. vals don't match hashes in header) -/// - validators did not correctly commit their blocks -/// -/// This function is recursive: it uses a bisection algorithm -/// to request data for intermediate heights as necessary. -/// Ensures our last trusted header hasn't expired yet, and that -/// data from the untrusted height can be verified, possibly using -/// data from intermediate heights. -/// -/// This function is primarily for use by a light node. -pub async fn verify_bisection( - trusted_state: TrustedState, - untrusted_height: Height, - trust_threshold: L, - trusting_period: Duration, - now: SystemTime, - req: &R, -) -> Result>, Error> -where - H: Header, - C: Commit, - L: TrustThreshold, - R: Requester, -{ - // Ensure the latest state hasn't expired. - // Note we only check for expiry once in this - // verify_and_update_bisection function since we assume the - // time is passed in and we don't access a clock internally. - // Thus the trust_period must be long enough to incorporate the - // expected time to complete this function. - let trusted_sh = trusted_state.last_header(); - is_within_trust_period(trusted_sh.header(), trusting_period, now)?; - - // TODO: consider fetching the header we're trying to sync to and - // checking that it's time is less then `now + X` for some small X. - // If not, it means that either our local clock is really slow - // or the blockchains BFT time is really wrong. - // In either case, we should probably raise an error. - // Note this would be stronger than checking that the untrusted - // header is within the trusting period, as it could still diverge - // significantly from `now`. - // NOTE: we actually have to do this for every header we fetch, - // We do check bft_time is monotonic, but that check might happen too late. - // So every header we fetch must be checked to be less than now+X - - // this is only used to memoize intermediate trusted states: - let mut cache: Vec> = Vec::new(); - - // inner recursive function which assumes - // trusting_period check is already done. - verify_bisection_inner( - &trusted_state, - untrusted_height, - trust_threshold, - req, - &mut cache, - ) - .await?; - - // return all intermediate trusted states up to untrusted_height - Ok(cache) -} - -// inner recursive function for verify_and_update_bisection. -// see that function's docs. -// A cache is passed in to memoize all new states to be trusted. -// Note: we only write to the cache and it guarantees that we do -// not store states twice. -// Additionally, a new state is returned for convenience s.t. it can -// be used for the other half of the recursion. -fn verify_bisection_inner<'a, H, C, L, R>( - trusted_state: &'a TrustedState, - untrusted_height: Height, - trust_threshold: L, - req: &'a R, - mut cache: &'a mut Vec>, -) -> LocalBoxFuture<'a, Result, Error>> -where - H: Header, - C: Commit, - L: TrustThreshold + 'a, - R: Requester, -{ - async move { - // fetch the header and vals for the new height - let untrusted_sh = req.signed_header(untrusted_height).await?; - let untrusted_vals = req.validator_set(untrusted_height).await?; - let untrusted_next_vals = req - .validator_set(untrusted_height.checked_add(1).expect("height overflow")) - .await?; - - // check if we can skip to this height and if it verifies. - match verify_single_inner( - trusted_state, - &untrusted_sh, - &untrusted_vals, - &untrusted_next_vals, - trust_threshold, - ) { - Ok(_) => { - // Successfully verified! - // memoize the new to be trusted state and return. - let ts = TrustedState::new(untrusted_sh, untrusted_next_vals); - cache.push(ts.clone()); - return Ok(ts); - } - Err(e) => { - match e.kind() { - // Insufficient voting power to update. - // Engage bisection, below. - &Kind::InsufficientVotingPower { .. } => (), - // If something went wrong, return the error. - real_err => return Err(real_err.to_owned().into()), - } - } - } - - // Get the pivot height for bisection. - let trusted_h = trusted_state.last_header().header().height(); - let untrusted_h = untrusted_height; - let pivot_height = trusted_h.checked_add(untrusted_h).expect("height overflow") / 2; - - // Recursive call to bisect to the pivot height. - // When this completes, we will either return an error or - // have updated the cache to the pivot height. - let trusted_left = verify_bisection_inner( - trusted_state, - pivot_height, - trust_threshold, - req, - &mut cache, - ) - .await?; - - // Recursive call to update to the original untrusted_height. - verify_bisection_inner( - &trusted_left, - untrusted_height, - trust_threshold, - req, - &mut cache, - ) - .await - } - .boxed_local() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::lite::mocks::*; - use crate::lite::TrustThresholdFraction; - - type MockState = TrustedState; - - // start all blockchains from here ... - fn init_time() -> SystemTime { - SystemTime::UNIX_EPOCH - } - - // create an initial trusted state from the given vals - fn init_trusted_state( - vals_and_commit_vec: ValsAndCommit, - next_vals_vec: Vec, - height: u64, - ) -> MockState { - // time has to be increasing: - let time = init_time() + Duration::new(height * 2, 0); - let vals = MockValSet::new(vals_and_commit_vec.vals_vec); - let next_vals = MockValSet::new(next_vals_vec); - let header = MockHeader::new(height, time, vals.hash(), next_vals.hash()); - let commit = MockCommit::new(header.hash(), vals_and_commit_vec.commit_vec); - let sh = MockSignedHeader::new(commit, header); - MockState::new(sh, vals) - } - - // create the next state with the given vals and commit. - fn next_state(vals_and_commit: ValsAndCommit) -> (MockSignedHeader, MockValSet, MockValSet) { - let time = init_time() + Duration::new(10, 0); - let height = 10; - let vals = MockValSet::new(vals_and_commit.vals_vec); - let next_vals = vals.clone(); - let header = MockHeader::new(height, time, vals.hash(), next_vals.hash()); - let commit = MockCommit::new(header.hash(), vals_and_commit.commit_vec); - (MockSignedHeader::new(commit, header), vals, next_vals) - } - - #[derive(Clone)] - struct ValsAndCommit { - vals_vec: Vec, - commit_vec: Vec, - } - - impl ValsAndCommit { - pub fn new(vals_vec: Vec, commit_vec: Vec) -> ValsAndCommit { - ValsAndCommit { - vals_vec, - commit_vec, - } - } - } - // Init a mock Requester. - // For each pair of lists of validators (cur and next vals) we - // init a trusted state (signed header and vals); - // Note: for bisection up-to height n, provide n+2 validators as validators for n+1 - // will be requested to verify height n. - fn init_requester(vals_and_commit_for_height: Vec) -> MockRequester { - let mut req = MockRequester::new(); - let max_height = vals_and_commit_for_height.len() as u64; - for (h, vac) in vals_and_commit_for_height.iter().enumerate() { - let height = (h + 1) as u64; // height starts with 1 ... - if height < max_height { - let next_vals = vals_and_commit_for_height - .get(h + 1) - .expect("next_vals missing") - .vals_vec - .clone(); - let ts = &init_trusted_state(vac.to_owned(), next_vals.to_owned(), height); - req.signed_headers.insert(height, ts.last_header().clone()); - req.validators.insert(height, ts.validators().to_owned()); - } - } - req - } - - // make a state with the given vals and commit and ensure we get the expected error kind. - fn assert_single_err( - ts: &TrustedState, - vals_and_commit: ValsAndCommit, - err: Error, - ) { - let (un_sh, un_vals, un_next_vals) = next_state(vals_and_commit); - let result = verify_single_inner( - ts, - &un_sh, - &un_vals, - &un_next_vals, - TrustThresholdFraction::default(), - ); - assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), err.to_string()); - } - - // make a state with the given vals and commit and ensure we get no error. - fn assert_single_ok(ts: &MockState, vals_and_commit: ValsAndCommit) { - let (un_sh, un_vals, un_next_vals) = next_state(vals_and_commit); - assert!(verify_single_inner( - ts, - &un_sh, - &un_vals, - &un_next_vals, - TrustThresholdFraction::default() - ) - .is_ok()); - } - - // use the sequence of states with the given vals for the requester - // and ensure bisection yields no error. - async fn assert_bisection_ok( - req: &MockRequester, - ts: &TrustedState, - untrusted_height: u64, - expected_num_of_requests: usize, - expected_final_state: &MockState, - ) { - let mut cache: Vec = Vec::new(); - let ts_new = verify_bisection_inner( - &ts, - untrusted_height, - TrustThresholdFraction::default(), - req, - cache.as_mut(), - ) - .await - .expect("should have passed"); - - assert_eq!(ts_new, expected_final_state.to_owned()); - assert_eq!(cache.len(), expected_num_of_requests); - assert_uniqueness(cache); - } - - fn assert_uniqueness(cache: Vec>) { - let mut uniq = cache.clone(); - uniq.dedup(); - assert_eq!(cache, uniq); - } - - // use the sequence of states with the given vals for the requester - // and ensure we get the expected error. - async fn assert_bisection_err( - req: &MockRequester, - ts: &TrustedState, - untrusted_height: u64, - err: Error, - ) { - let mut cache: Vec = Vec::new(); - let result = verify_bisection_inner( - &ts, - untrusted_height, - TrustThresholdFraction::default(), - req, - cache.as_mut(), - ) - .await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().kind().to_string(), - err.kind().to_string() - ); - } - - // valid to skip, but invalid commit. 1 validator. - #[test] - fn test_verify_single_skip_1_val_verify() { - let vac = ValsAndCommit::new(vec![0], vec![0]); - let ts = &init_trusted_state(vac, vec![0], 1); - - // 100% overlap, but wrong commit. - // NOTE: This should be an invalid commit error since there's - // a vote from a validator not in the set! - // but voting_power_in isn't smart enough to see this ... - // TODO(ismail): https://github.com/interchainio/tendermint-rs/issues/140 - let invalid_vac = ValsAndCommit::new(vec![1], vec![0]); - assert_single_err( - ts, - invalid_vac, - Kind::InvalidCommit { - total: 1, - signed: 0, - } - .into(), - ); - } - - // valid commit and data, starting with 1 validator. - // test if we can skip to it. - #[test] - fn test_verify_single_skip_1_val_skip() { - let mut vac = ValsAndCommit::new(vec![0], vec![0]); - let ts = &init_trusted_state(vac.clone(), vec![0], 1); - //***** - // Ok - - // 100% overlap (original signer is present in commit) - assert_single_ok(ts, vac); - - vac = ValsAndCommit::new(vec![0, 1], vec![0, 1]); - assert_single_ok(ts, vac); - - vac = ValsAndCommit::new(vec![0, 1, 2], vec![0, 1, 2]); - assert_single_ok(ts, vac); - - vac = ValsAndCommit::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]); - assert_single_ok(ts, vac); - - //***** - // Err - let err = Kind::InsufficientVotingPower { - total: 1, - signed: 0, - trust_treshold: "TrustThresholdFraction { numerator: 1, denominator: 3 }".to_string(), - }; - - // 0% overlap - new val set without the original signer - vac = ValsAndCommit::new(vec![1], vec![1]); - assert_single_err(ts, vac, err.clone().into()); - - // 0% overlap - val set contains original signer, but they didn't sign - vac = ValsAndCommit::new(vec![0, 1, 2, 3], vec![1, 2, 3]); - assert_single_err(ts, vac, err.into()); - } - - // valid commit and data, starting with 2 validators. - // test if we can skip to it. - #[test] - fn test_verify_single_skip_2_val_skip() { - let mut vac = ValsAndCommit::new(vec![0, 1], vec![0, 1]); - let ts = &init_trusted_state(vac.clone(), vec![0, 1], 1); - - //************* - // OK - - // 100% overlap (both original signers still present) - assert_single_ok(ts, vac); - - vac = ValsAndCommit::new(vec![0, 1, 2], vec![0, 1, 2]); - assert_single_ok(ts, vac); - - // 50% overlap (one original signer still present) - vac = ValsAndCommit::new(vec![0], vec![0]); - assert_single_ok(ts, vac); - - vac = ValsAndCommit::new(vec![0, 1, 2, 3], vec![1, 2, 3]); - assert_single_ok(ts, vac); - - //************* - // Err - let err = Kind::InsufficientVotingPower { - total: 2, - signed: 0, - trust_treshold: "TrustThresholdFraction { numerator: 1, denominator: 3 }".to_string(), - }; - - // 0% overlap (neither original signer still present) - vac = ValsAndCommit::new(vec![2], vec![2]); - assert_single_err(ts, vac, err.clone().into()); - - // 0% overlap (original signer is still in val set but not in commit) - vac = ValsAndCommit::new(vec![0, 2, 3, 4], vec![2, 3, 4]); - assert_single_err(ts, vac, err.into()); - } - - // valid commit and data, starting with 3 validators. - // test if we can skip to it. - #[test] - fn test_verify_single_skip_3_val_skip() { - let mut vac = ValsAndCommit::new(vec![0, 1, 2], vec![0, 1, 2]); - let ts = &init_trusted_state(vac.clone(), vec![0, 1, 2], 1); - - //************* - // OK - - // 100% overlap (both original signers still present) - assert_single_ok(ts, vac); - - vac = ValsAndCommit::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]); - assert_single_ok(ts, vac); - - // 66% overlap (two original signers still present) - vac = ValsAndCommit::new(vec![0, 1], vec![0, 1]); - assert_single_ok(ts, vac); - - vac = ValsAndCommit::new(vec![0, 1, 2, 3], vec![1, 2, 3]); - assert_single_ok(ts, vac); - - //************* - // Err - let trust_thres_str = "TrustThresholdFraction { numerator: 1, denominator: 3 }"; - let err = Kind::InsufficientVotingPower { - total: 3, - signed: 1, - trust_treshold: trust_thres_str.to_string(), - }; - - // 33% overlap (one original signer still present) - vac = ValsAndCommit::new(vec![0], vec![0]); - assert_single_err(ts, vac, err.clone().into()); - - vac = ValsAndCommit::new(vec![0, 3], vec![0, 3]); - assert_single_err(ts, vac, err.clone().into()); - - // 0% overlap (neither original signer still present) - vac = ValsAndCommit::new(vec![3], vec![2]); - assert_single_err(ts, vac, err.into()); - - let err = Kind::InsufficientVotingPower { - total: 3, - signed: 0, - trust_treshold: trust_thres_str.to_string(), - }; - - // 0% overlap (original signer is still in val set but not in commit) - vac = ValsAndCommit::new(vec![0, 3, 4, 5], vec![3, 4, 5]); - assert_single_err(ts, vac, err.into()); - } - - #[test] - fn test_verify_single_skip_4_val_skip() { - let vac = ValsAndCommit::new(vec![0, 1, 2, 3], vec![0, 1, 2, 3]); - let ts = &init_trusted_state(vac.clone(), vec![0, 1, 2, 3], 1); - - // 100% overlap (all signers present) - assert_single_ok(ts, vac); - - // 75% overlap (three signers present) - let vac = ValsAndCommit::new(vec![0, 1, 2], vec![0, 1, 2]); - assert_single_ok(ts, vac); - - let vac = ValsAndCommit::new(vec![0, 1, 2, 4], vec![0, 1, 2, 4]); - assert_single_ok(ts, vac); - - // 50% overlap (two signers still present) - let vac = ValsAndCommit::new(vec![0, 1], vec![0, 1]); - assert_single_ok(ts, vac); - - let vac = ValsAndCommit::new(vec![0, 1, 4, 5], vec![0, 1, 4, 5]); - assert_single_ok(ts, vac); - - let trust_thres_str = "TrustThresholdFraction { numerator: 1, denominator: 3 }"; - let err = Kind::InsufficientVotingPower { - total: 4, - signed: 1, - trust_treshold: trust_thres_str.to_string(), - }; - - // 25% overlap (one signer still present) - let vac = ValsAndCommit::new(vec![0, 4, 5, 6], vec![0, 4, 5, 6]); - assert_single_err(ts, vac, err.into()); - - let err = Kind::InsufficientVotingPower { - total: 4, - signed: 0, - trust_treshold: trust_thres_str.to_string(), - }; - - // 0% overlap (none of the signers present) - let vac = ValsAndCommit::new(vec![4, 5, 6], vec![4, 5, 6]); - assert_single_err(ts, vac, err.clone().into()); - - // 0% overlap (one signer present in val set but does not commit) - let vac = ValsAndCommit::new(vec![3, 4, 5, 6], vec![4, 5, 6]); - assert_single_err(ts, vac, err.into()); - } - - #[tokio::test] - async fn test_verify_bisection_1_val() { - let vac = ValsAndCommit::new(vec![0], vec![0]); - let final_state = init_trusted_state(vac.clone(), vec![0], 2); - let req = init_requester(vec![vac.clone(), vac.clone(), vac.clone(), vac.clone()]); - let sh = req.signed_header(1).await.expect("first sh not present"); - let vals = req - .validator_set(1) - .await - .expect("init. valset not present"); - - let ts = MockState::new(sh, vals); - - assert_bisection_ok(&req, &ts, 2, 1, &final_state).await; - - let final_state = init_trusted_state(vac.clone(), vec![0], 3); - // let vac = ValsAndCommit::new(vec![0], vec![0]); - let req = init_requester(vec![ - vac.clone(), - vac.clone(), - vac.clone(), - vac.clone(), - vac, - ]); - - assert_bisection_ok(&req, &ts, 3, 1, &final_state).await; - } - - #[tokio::test] - async fn test_verify_bisection() { - //************* - // OK - - let mut vals_and_commit_for_height: Vec = Vec::new(); - vals_and_commit_for_height.push(ValsAndCommit::new( - vec![0, 1, 2, 3, 4, 5], - vec![0, 1, 2, 3, 4, 5], - )); // 1 - vals_and_commit_for_height.push(ValsAndCommit::new(vec![0, 1, 2], vec![0, 1, 2])); // 2 -> 50% val change - vals_and_commit_for_height.push(ValsAndCommit::new(vec![0, 1], vec![0, 1])); // 3 -> 33% change - vals_and_commit_for_height.push(ValsAndCommit::new(vec![1, 2], vec![1, 2])); // 4 -> 50% change - vals_and_commit_for_height.push(ValsAndCommit::new(vec![0, 2], vec![0, 2])); // 5 -> 50% <- too much change (from 1), need to bisect... - vals_and_commit_for_height.push(ValsAndCommit::new(vec![0, 2], vec![0, 2])); // 6 -> (only needed to validate 5) - vals_and_commit_for_height.push(ValsAndCommit::new(vec![0, 2], vec![0, 2])); // 7 -> (only used to construct state 6) - let vac = ValsAndCommit::new(vec![0, 2], vec![0, 2]); - let final_ts = init_trusted_state(vac, vec![0, 2], 5); - let req = init_requester(vals_and_commit_for_height); - let sh = req.signed_header(1).await.expect("first sh not present"); - let vals = req - .validator_set(1) - .await - .expect("init. valset not present"); - - let ts = &MockState::new(sh, vals); - - assert_bisection_ok(&req, &ts, 5, 3, &final_ts).await; - - //************* - // Err - - // fails due to missing vals for height 6: - let mut faulty_req = req; - faulty_req.validators.remove(&6_u64); - assert_bisection_err(&faulty_req, &ts, 5, Kind::RequestFailed.into()).await; - - // Error: can't bisect from trusted height 1 to height 1 - // (here because non-increasing time is caught first) - let vac = ValsAndCommit::new(vec![0, 1, 2], vec![0, 1, 2]); - let req = init_requester(vec![vac.clone(), vac.clone(), vac]); - assert_bisection_err(&req, &ts, 1, Kind::NonIncreasingTime.into()).await; - - // can't bisect from trusted height 1 to height 1 (here we tamper with time but - // expect to fail on NonIncreasingHeight): - let vac = ValsAndCommit::new(vec![0, 1, 2], vec![0, 1, 2]); - let mut req = init_requester(vec![vac.clone(), vac.clone(), vac]); - let sh = req.signed_headers.get(&1_u64).unwrap(); - let mut time_tampered_header = sh.header().clone(); - time_tampered_header.set_time(init_time() + Duration::new(5, 0)); - let tampered_commit = MockCommit::new(time_tampered_header.hash(), vec![0, 1, 2]); - let new_sh = MockSignedHeader::new(tampered_commit, time_tampered_header); - // replace the signed header: - req.signed_headers.insert(1, new_sh); - assert_bisection_err( - &req, - &ts, - 1, - Kind::NonIncreasingHeight { - got: 1, - expected: 2, - } - .into(), - ) - .await; - } - - // can't bisect from height 1 to height 3 - // because there isn't enough signatures/commits for header at height 3 - // errors with an 'InvalidCommit' - #[tokio::test] - async fn test_bisection_not_enough_commits() { - let vals_vec = vec![0, 1, 2, 4]; - let commit_vec = vec![0, 1, 2, 4]; - let vac1 = ValsAndCommit::new(vals_vec.clone(), commit_vec); - let vac2 = ValsAndCommit::new(vals_vec.clone(), vec![0]); - let req = &init_requester(vec![ - vac1.clone(), - vac2.clone(), - vac2.clone(), - vac2.clone(), - vac2, - ]); - let ts = &init_trusted_state(vac1, vals_vec, 1); - - assert_bisection_err( - req, - ts, - 3, - Kind::InvalidCommit { - total: 4, - signed: 1, - } - .into(), - ) - .await; - } - - #[test] - fn test_is_within_trust_period() { - let header_time = SystemTime::UNIX_EPOCH; - let period = Duration::new(100, 0); - let now = header_time + Duration::new(10, 0); - - // less than the period, OK - let header = MockHeader::new(4, header_time, fixed_hash(), fixed_hash()); - assert!(is_within_trust_period(&header, period, now).is_ok()); - - // equal to the period, not OK - let now = header_time + period; - assert!(is_within_trust_period(&header, period, now).is_err()); - - // greater than the period, not OK - let now = header_time + period + Duration::new(1, 0); - assert!(is_within_trust_period(&header, period, now).is_err()); - - // bft time in header is later than now, not OK: - let now = SystemTime::UNIX_EPOCH; - let later_than_now = now + Duration::new(60, 0); - let future_header = MockHeader::new(4, later_than_now, fixed_hash(), fixed_hash()); - assert!(is_within_trust_period(&future_header, period, now).is_err()); - } -} diff --git a/tendermint/src/lite_impl.rs b/tendermint/src/lite_impl.rs deleted file mode 100644 index 850600aba..000000000 --- a/tendermint/src/lite_impl.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Implementation of all light client traits. - -pub mod header; -pub mod signed_header; -pub mod validator_set; diff --git a/tendermint/src/lite_impl/header.rs b/tendermint/src/lite_impl/header.rs deleted file mode 100644 index fc385b756..000000000 --- a/tendermint/src/lite_impl/header.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! [`lite::Header`] implementation for [`block::Header`]. - -use crate::amino_types::{message::AminoMessage, BlockId, ConsensusVersion, TimeMsg}; -use crate::lite::Height; -use crate::merkle::simple_hash_from_byte_vectors; -use crate::Hash; -use crate::{block, lite, Time}; - -impl lite::Header for block::Header { - type Time = Time; - - fn height(&self) -> Height { - self.height.value() - } - - fn bft_time(&self) -> Time { - self.time - } - - fn validators_hash(&self) -> Hash { - self.validators_hash - } - - fn next_validators_hash(&self) -> Hash { - self.next_validators_hash - } - - fn hash(&self) -> Hash { - // Note that if there is an encoding problem this will - // panic (as the golang code would): - // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/block.go#L393 - // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/encoding_helper.go#L9:6 - - let mut fields_bytes: Vec> = Vec::with_capacity(16); - fields_bytes.push(AminoMessage::bytes_vec(&ConsensusVersion::from( - &self.version, - ))); - fields_bytes.push(encode_bytes(self.chain_id.as_bytes())); - fields_bytes.push(encode_varint(self.height.value())); - fields_bytes.push(AminoMessage::bytes_vec(&TimeMsg::from(self.time))); - fields_bytes.push( - self.last_block_id - .as_ref() - .map_or(vec![], |id| AminoMessage::bytes_vec(&BlockId::from(id))), - ); - fields_bytes.push(self.last_commit_hash.as_ref().map_or(vec![], encode_hash)); - fields_bytes.push(self.data_hash.as_ref().map_or(vec![], encode_hash)); - fields_bytes.push(encode_hash(&self.validators_hash)); - fields_bytes.push(encode_hash(&self.next_validators_hash)); - fields_bytes.push(encode_hash(&self.consensus_hash)); - fields_bytes.push(encode_bytes(&self.app_hash)); - fields_bytes.push(self.last_results_hash.as_ref().map_or(vec![], encode_hash)); - fields_bytes.push(self.evidence_hash.as_ref().map_or(vec![], encode_hash)); - fields_bytes.push(encode_bytes(self.proposer_address.as_bytes())); - - Hash::Sha256(simple_hash_from_byte_vectors(fields_bytes)) - } -} - -fn encode_bytes(bytes: &[u8]) -> Vec { - let bytes_len = bytes.len(); - if bytes_len > 0 { - let mut encoded = vec![]; - prost_amino::encode_length_delimiter(bytes_len, &mut encoded).unwrap(); - encoded.append(&mut bytes.to_vec()); - encoded - } else { - vec![] - } -} - -fn encode_hash(hash: &Hash) -> Vec { - encode_bytes(hash.as_bytes()) -} - -fn encode_varint(val: u64) -> Vec { - let mut val_enc = vec![]; - prost_amino::encoding::encode_varint(val, &mut val_enc); - val_enc -} - -#[cfg(test)] -mod test { - use crate::block::Header; - use crate::lite::Header as _; - use crate::Hash; - use std::str::FromStr; - - #[test] - fn test_hash_height_1() { - // JSON extracted from https://github.com/tendermint/tendermint/tree/v0.33 - // more precisely `curl`ed from locally build docker image of: - // git log --pretty=format:"%H" -1 - // 606d0a89ccabbd3e59cff521f9f4d875cc366ac9 - // via - // curl -X GET "http://localhost:26657/commit?height=1" -H "accept: application/json" | jq .result.signed_header.header - let json_data = r#" -{ - "version": { - "block": "10", - "app": "1" - }, - "chain_id": "dockerchain", - "height": "1", - "time": "2020-07-09T14:24:44.7157258Z", - "last_block_id": { - "hash": "", - "parts": { - "total": "0", - "hash": "" - } - }, - "last_commit_hash": "", - "data_hash": "", - "validators_hash": "74F2AC2B6622504D08DD2509E28CE731985CFE4D133C9DB0CB85763EDCA95AA3", - "next_validators_hash": "74F2AC2B6622504D08DD2509E28CE731985CFE4D133C9DB0CB85763EDCA95AA3", - "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F", - "app_hash": "", - "last_results_hash": "", - "evidence_hash": "", - "proposer_address": "AD358F20C8CE80889E0F0248FDDC454595D632AE" -}"#; - // extracted expected hash from a commit via - // curl -X GET "http://localhost:26657/commit?height=1" -H "accept: application/json" | jq .result.signed_header.commit.block_id.hash - let header: Header = serde_json::from_str(json_data).unwrap(); - let got_hash = header.hash(); - let want_hash = - Hash::from_str("F008EACA817CF6A3918CF7A6FD44F1F2464BB24D25A7EDB45A03E8783E9AB438") - .unwrap(); - - assert_eq!(got_hash, want_hash); - } - - #[test] - fn test_hash_height_2() { - // JSON test-vector extracted from https://github.com/tendermint/tendermint/tree/v0.33 - // more precisely `curl`ed from locally build docker image of: - // git log --pretty=format:"%H" -1 - // 606d0a89ccabbd3e59cff521f9f4d875cc366ac9 - // via - // curl -X GET "http://localhost:26657/commit?height=2" -H "accept: application/json" | jq .result.signed_header.header - let json_data = r#" -{ - "version": { - "block": "10", - "app": "1" - }, - "chain_id": "dockerchain", - "height": "2", - "time": "2020-07-10T23:47:42.8655562Z", - "last_block_id": { - "hash": "F008EACA817CF6A3918CF7A6FD44F1F2464BB24D25A7EDB45A03E8783E9AB438", - "parts": { - "total": "1", - "hash": "BF5130E879A02AC4BB83E392732ED4A37BE2F01304A615467EE7960858774E57" - } - }, - "last_commit_hash": "1527AF33311EB16EF9BB15B5570DE9664D6F3598BEE08231588CED89B2D8F8EA", - "data_hash": "", - "validators_hash": "74F2AC2B6622504D08DD2509E28CE731985CFE4D133C9DB0CB85763EDCA95AA3", - "next_validators_hash": "74F2AC2B6622504D08DD2509E28CE731985CFE4D133C9DB0CB85763EDCA95AA3", - "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F", - "app_hash": "0000000000000000", - "last_results_hash": "", - "evidence_hash": "", - "proposer_address": "AD358F20C8CE80889E0F0248FDDC454595D632AE" -}"#; - // extracted expected hash from a commit via - // curl -X GET "http://localhost:26657/commit?height=2" -H "accept: application/json" | jq .result.signed_header.commit.block_id.hash - let header: Header = serde_json::from_str(json_data).unwrap(); - let got_hash = header.hash(); - let want_hash = - Hash::from_str("5CFF26AAECBEEA7CBD2181D5547BB087E4DC8004960D611ECA59CFEA724AD538") - .unwrap(); - - assert_eq!(got_hash, want_hash); - } -} diff --git a/tendermint/src/lite_impl/signed_header.rs b/tendermint/src/lite_impl/signed_header.rs deleted file mode 100644 index beb82aa68..000000000 --- a/tendermint/src/lite_impl/signed_header.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! [`lite::SignedHeader`] implementation for [`block::signed_header::SignedHeader`]. - -use crate::block::CommitSig; -use crate::lite::error::{Error, Kind}; -use crate::lite::types::ValidatorSet as _; -use crate::validator::Set; -use crate::{block, hash, lite, vote}; -use anomaly::fail; -use std::convert::TryFrom; - -impl lite::Commit for block::signed_header::SignedHeader { - type ValidatorSet = Set; - - fn header_hash(&self) -> hash::Hash { - self.commit.block_id.hash - } - fn voting_power_in(&self, validators: &Set) -> Result { - // NOTE we don't know the validators that committed this block, - // so we have to check for each vote if its validator is already known. - let mut signed_power = 0u64; - for vote in &self.signed_votes() { - // Only count if this vote is from a known validator. - // TODO: we still need to check that we didn't see a vote from this validator twice ... - let val_id = vote.validator_id(); - let val = match validators.validator(val_id) { - Some(v) => v, - None => continue, - }; - - // check vote is valid from validator - let sign_bytes = vote.sign_bytes(); - - if !val.verify_signature(&sign_bytes, vote.signature()) { - fail!( - Kind::ImplementationSpecific, - "Couldn't verify signature {:?} with validator {:?} on sign_bytes {:?}", - vote.signature(), - val, - sign_bytes, - ); - } - signed_power += val.power(); - } - - Ok(signed_power) - } - - fn validate(&self, vals: &Self::ValidatorSet) -> Result<(), Error> { - // TODO: self.commit.block_id cannot be zero in the same way as in go - // clarify if this another encoding related issue - if self.commit.signatures.len() == 0 { - fail!(Kind::ImplementationSpecific, "no signatures for commit"); - } - if self.commit.signatures.len() != vals.validators().len() { - fail!( - Kind::ImplementationSpecific, - "commit signatures count: {} doesn't match validators count: {}", - self.commit.signatures.len(), - vals.validators().len() - ); - } - - // TODO: this last check is only necessary if we do full verification (2/3) - // https://github.com/informalsystems/tendermint-rs/issues/281 - // returns ImplementationSpecific error if it detects a signer - // that is not present in the validator set: - for commit_sig in self.commit.signatures.iter() { - let extracted_validator_address; - match commit_sig { - // Todo: https://github.com/informalsystems/tendermint-rs/issues/260 - CommitSig validator address missing in Absent vote - CommitSig::BlockIDFlagAbsent => continue, - CommitSig::BlockIDFlagCommit { - validator_address, .. - } => extracted_validator_address = validator_address, - CommitSig::BlockIDFlagNil { - validator_address, .. - } => extracted_validator_address = validator_address, - } - if vals.validator(*extracted_validator_address) == None { - fail!( - Kind::ImplementationSpecific, - "Found a faulty signer ({}) not present in the validator set ({})", - extracted_validator_address, - vals.hash() - ); - } - } - - Ok(()) - } -} - -// this private helper function does *not* do any validation but extracts -// all non-BlockIDFlagAbsent votes from the commit: -fn non_absent_votes(commit: &block::Commit) -> Vec { - let mut votes: Vec = Default::default(); - for (i, commit_sig) in commit.signatures.iter().enumerate() { - let extracted_validator_address; - let extracted_timestamp; - let extracted_signature; - let block_id; - match commit_sig { - CommitSig::BlockIDFlagAbsent { .. } => continue, - CommitSig::BlockIDFlagCommit { - validator_address, - timestamp, - signature, - } => { - extracted_validator_address = validator_address; - extracted_timestamp = timestamp; - extracted_signature = signature; - block_id = Some(commit.block_id.clone()); - } - CommitSig::BlockIDFlagNil { - validator_address, - timestamp, - signature, - } => { - extracted_validator_address = validator_address; - extracted_timestamp = timestamp; - extracted_signature = signature; - block_id = None; - } - } - votes.push(vote::Vote { - vote_type: vote::Type::Precommit, - height: commit.height, - round: commit.round, - block_id, - timestamp: *extracted_timestamp, - validator_address: *extracted_validator_address, - validator_index: u64::try_from(i) - .expect("usize to u64 conversion failed for validator index"), - signature: extracted_signature.clone(), - }) - } - votes -} - -impl block::signed_header::SignedHeader { - /// This is a private helper method to iterate over the underlying - /// votes to compute the voting power (see `voting_power_in` below). - pub fn signed_votes(&self) -> Vec { - let chain_id = self.header.chain_id.to_string(); - let mut votes = non_absent_votes(&self.commit); - votes - .drain(..) - .map(|vote| { - vote::SignedVote::new( - (&vote).into(), - &chain_id, - vote.validator_address, - vote.signature, - ) - }) - .collect() - } -} - -// type alias the concrete types to make the From impls more readable -type TMSignedHeader = block::signed_header::SignedHeader; -type TMBlockHeader = block::header::Header; - -impl From - for lite::types::SignedHeader -{ - fn from(sh: block::signed_header::SignedHeader) -> Self { - Self::new(sh.clone(), sh.header) - } -} - -impl From<&block::signed_header::SignedHeader> - for lite::types::SignedHeader -{ - fn from(sh: &block::signed_header::SignedHeader) -> Self { - Self::new(sh.clone(), sh.clone().header) - } -} diff --git a/tendermint/src/lite_impl/validator_set.rs b/tendermint/src/lite_impl/validator_set.rs deleted file mode 100644 index f5f06c2fc..000000000 --- a/tendermint/src/lite_impl/validator_set.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! [`lite::ValidatorSet`] implementation for [`validator::Set`]. - -use crate::validator; -use crate::{lite, merkle, Hash}; - -impl lite::ValidatorSet for validator::Set { - /// Compute the Merkle root of the validator set - fn hash(&self) -> Hash { - let validator_bytes: Vec> = self - .validators() - .iter() - .map(|validator| validator.hash_bytes()) - .collect(); - Hash::Sha256(merkle::simple_hash_from_byte_vectors(validator_bytes)) - } - - fn total_power(&self) -> u64 { - self.validators().iter().fold(0u64, |total, val_info| { - total + val_info.voting_power.value() - }) - } -} diff --git a/tendermint/src/trust_threshold.rs b/tendermint/src/trust_threshold.rs new file mode 100644 index 000000000..5f52375db --- /dev/null +++ b/tendermint/src/trust_threshold.rs @@ -0,0 +1,75 @@ +//! Define traits and instances for dealing with trust thresholds. + +use std::fmt::{self, Debug, Display}; + +use crate::serializers; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +/// TrustThreshold defines how much of the total voting power of a known +/// and trusted validator set is sufficient for a commit to be +/// accepted going forward. +pub trait TrustThreshold: Copy + Clone + Debug + Serialize + DeserializeOwned { + /// Check whether the given signed voting power is sufficient according to + /// this trust threshold against the given total voting power. + fn is_enough_power(&self, signed_voting_power: u64, total_voting_power: u64) -> bool; +} + +/// TrustThresholdFraction defines what fraction of the total voting power of a known +/// and trusted validator set is sufficient for a commit to be +/// accepted going forward. +/// The [`Default::default()`] returns true, iff at least a third of the trusted +/// voting power signed (in other words at least one honest validator signed). +/// Some clients might require more than +1/3 and can implement their own +/// [`TrustThreshold`] which can be passed into all relevant methods. +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TrustThresholdFraction { + /// Numerator of the trust threshold fraction + #[serde(with = "serializers::from_str")] + pub numerator: u64, + /// Numerator of the trust threshold fraction + #[serde(with = "serializers::from_str")] + pub denominator: u64, +} + +impl TrustThresholdFraction { + /// Constant for a trust threshold of 2/3. + pub const TWO_THIRDS: Self = Self { + numerator: 2, + denominator: 3, + }; + + /// Instantiate a TrustThresholdFraction if the given denominator and + /// numerator are valid. + /// + /// The parameters are valid iff `1/3 <= numerator/denominator <= 1`. + /// In any other case we return `None`. + pub fn new(numerator: u64, denominator: u64) -> Option { + if numerator <= denominator && denominator > 0 && 3 * numerator >= denominator { + Some(Self { + numerator, + denominator, + }) + } else { + None + } + } +} + +impl TrustThreshold for TrustThresholdFraction { + fn is_enough_power(&self, signed_voting_power: u64, total_voting_power: u64) -> bool { + signed_voting_power * self.denominator > total_voting_power * self.numerator + } +} + +impl Default for TrustThresholdFraction { + fn default() -> Self { + Self::new(1, 3) + .expect("initializing TrustThresholdFraction with valid fraction mustn't panic") + } +} + +impl Display for TrustThresholdFraction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.numerator, self.denominator) + } +} diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index 1d6d77830..30aec3bd0 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -10,7 +10,8 @@ use signatory_dalek::Ed25519Verifier; use subtle_encoding::base64; use crate::amino_types::message::AminoMessage; -use crate::{account, vote, PublicKey}; +use crate::hash::Hash; +use crate::{account, merkle, vote, PublicKey}; /// Validator set contains a vector of validators #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -37,9 +38,7 @@ impl Set { fn sort_validators(vals: &mut Vec) { vals.sort_by_key(|v| v.address); } -} -impl Set { /// Returns the validator with the given Id if its in the Set. pub fn validator(&self, val_id: account::Id) -> Option { self.validators @@ -47,6 +46,24 @@ impl Set { .find(|val| val.address == val_id) .cloned() } + + /// Compute the hash of this validator set + pub fn hash(&self) -> Hash { + let validator_bytes: Vec> = self + .validators() + .iter() + .map(|validator| validator.hash_bytes()) + .collect(); + + Hash::Sha256(merkle::simple_hash_from_byte_vectors(validator_bytes)) + } + + /// Compute the total voting power within this validator set + pub fn total_power(&self) -> u64 { + self.validators().iter().fold(0u64, |total, val_info| { + total + val_info.voting_power.value() + }) + } } // TODO: maybe add a type (with an Option> field) instead @@ -233,8 +250,6 @@ where mod tests { use subtle_encoding::hex; - use crate::lite::ValidatorSet; - use super::*; // make a validator from a hex ed25519 pubkey and a voting power diff --git a/tendermint/tests/lite.rs b/tendermint/tests/lite.rs deleted file mode 100644 index 5b808377d..000000000 --- a/tendermint/tests/lite.rs +++ /dev/null @@ -1,332 +0,0 @@ -use anomaly::fail; -use async_trait::async_trait; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::collections::HashMap; -use std::convert::TryInto; -use std::fmt::Debug; -use std::{fs, path::PathBuf}; -use tendermint::block::{Header, Height}; -use tendermint::lite::error::{Error, Kind}; -use tendermint::lite::{Requester, TrustThresholdFraction, TrustedState}; -use tendermint::{ - block::signed_header::SignedHeader, evidence::Duration, lite, validator::Set, Hash, Time, -}; - -/// Test that a struct `T` can be: -/// -/// - serialized to JSON -/// - parsed back from the serialized JSON of the previous step -/// - that the two parsed structs are equal according to their `PartialEq` impl -pub fn test_serialization_roundtrip(obj: &T) -where - T: Debug + PartialEq + Serialize + DeserializeOwned, -{ - let serialized = serde_json::to_string(obj).unwrap(); - let parsed = serde_json::from_str(&serialized).unwrap(); - assert_eq!(obj, &parsed); -} - -#[derive(Deserialize, Clone, Debug)] -struct TestCases { - batch_name: String, - test_cases: Vec, -} - -#[derive(Deserialize, Clone, Debug)] -struct TestCase { - description: String, - initial: Initial, - input: Vec, - expected_output: Option, -} - -#[derive(Deserialize, Clone, Debug)] -struct Initial { - signed_header: SignedHeader, - next_validator_set: Set, - trusting_period: Duration, - now: Time, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -struct LiteBlock { - signed_header: SignedHeader, - validator_set: Set, - next_validator_set: Set, -} - -#[derive(Deserialize, Clone, Debug)] -struct TestBisection { - description: String, - trust_options: TrustOptions, - primary: Provider, - height_to_verify: Height, - now: Time, - expected_output: Option, -} - -#[derive(Deserialize, Clone, Debug)] -struct Provider { - chain_id: String, - lite_blocks: Vec, -} - -#[derive(Deserialize, Clone, Debug)] -struct TrustOptions { - period: Duration, - height: Height, - hash: Hash, - trust_level: TrustThresholdFraction, -} - -#[derive(Deserialize, Clone, Debug)] -struct MockRequester { - chain_id: String, - signed_headers: HashMap, - validators: HashMap, -} - -type LightSignedHeader = lite::types::SignedHeader; - -#[async_trait] -impl Requester for MockRequester { - async fn signed_header(&self, h: u64) -> Result { - println!("requested signed header for height:{:?}", h); - if let Some(sh) = self.signed_headers.get(&h) { - return Ok(sh.into()); - } - println!("couldn't get sh for: {}", &h); - fail!(Kind::RequestFailed, "couldn't get sh for: {}", &h); - } - - async fn validator_set(&self, h: u64) -> Result { - println!("requested validators for height:{:?}", h); - if let Some(vs) = self.validators.get(&h) { - return Ok(vs.to_owned()); - } - println!("couldn't get vals for: {}", &h); - fail!(Kind::RequestFailed, "couldn't get vals for: {}", &h); - } -} - -impl MockRequester { - fn new(chain_id: String, lite_blocks: Vec) -> Self { - let mut sh_map: HashMap = HashMap::new(); - let mut val_map: HashMap = HashMap::new(); - let last_block = lite_blocks.last().expect("last entry not found"); - val_map.insert( - last_block.signed_header.header.height.increment().value(), - last_block.to_owned().next_validator_set, - ); - for lite_block in lite_blocks { - let height = lite_block.signed_header.header.height; - sh_map.insert(height.into(), lite_block.signed_header); - val_map.insert(height.into(), lite_block.validator_set); - } - Self { - chain_id, - signed_headers: sh_map, - validators: val_map, - } - } -} - -// Link to the commit that generated below JSON test files: -// https://github.com/Shivani912/tendermint/commit/e02f8fd54a278f0192353e54b84a027c8fe31c1e -const TEST_FILES_PATH: &str = "./tests/support/lite/"; -fn read_json_fixture(file_path: &str) -> String { - fs::read_to_string(PathBuf::from(file_path)).unwrap() -} - -type Trusted = lite::TrustedState; - -fn run_test_case(tc: &TestCase) { - let trusted_next_vals = tc.initial.clone().next_validator_set; - let mut latest_trusted = - Trusted::new(tc.initial.signed_header.clone().into(), trusted_next_vals); - test_serialization_roundtrip(&latest_trusted); - - let expects_err = match &tc.expected_output { - Some(eo) => eo.eq("error"), - None => false, - }; - - let trusting_period: std::time::Duration = tc.initial.clone().trusting_period.into(); - let tm_now = tc.initial.now; - let now = tm_now.to_system_time().unwrap(); - - for (i, input) in tc.input.iter().enumerate() { - println!("i: {}, {}", i, tc.description); - - let untrusted_signed_header = &input.signed_header; - let untrusted_vals = &input.validator_set; - let untrusted_next_vals = &input.next_validator_set; - - match lite::verify_single( - latest_trusted.clone(), - &untrusted_signed_header.into(), - untrusted_vals, - untrusted_next_vals, - TrustThresholdFraction::default(), - trusting_period, - now, - ) { - Ok(new_state) => { - let expected_state = TrustedState::new( - untrusted_signed_header.clone().into(), - untrusted_next_vals.clone(), - ); - - assert_eq!(new_state, expected_state); - assert!(!expects_err); - - latest_trusted = new_state.clone(); - test_serialization_roundtrip(&latest_trusted); - } - Err(_) => { - assert!(expects_err); - } - } - } -} -#[tokio::test] -async fn bisection() { - // TODO: re-enable multi-peer tests as soon as the light client can handle these: - // let dir = "bisection/multi_peer"; - // run_bisection_tests(dir).await; - - let dir = "bisection/single_peer"; - run_bisection_tests(dir).await; -} - -#[test] -fn single_step_sequential() { - let dirs = [ - "single_step/sequential/commit", - "single_step/sequential/header", - "single_step/sequential/validator_set", - ]; - for dir in &dirs { - run_single_step_tests(dir); - } -} - -// #[test] -// fn single_step_skipping() { -// let dirs = [ -// "single_step/skipping/commit", -// "single_step/skipping/header", -// "single_step/skipping/validator_set", -// ]; -// for dir in &dirs { -// run_single_step_tests(dir); -// } -// } - -async fn run_bisection_tests(dir: &str) { - let paths = fs::read_dir(PathBuf::from(TEST_FILES_PATH).join(dir)).unwrap(); - - for file_path in paths { - let dir_entry = file_path.unwrap(); - let fp_str = format!("{}", dir_entry.path().display()); - println!( - "Running light client against bisection test-file:\n {:?}", - fp_str - ); - let case: TestBisection = read_bisection_test_case(&fp_str); - run_bisection_test(case).await; - } -} - -fn run_single_step_tests(dir: &str) { - // TODO: this test need further investigation: - let skipped = ["commit/one_third_vals_don't_sign.json"]; - - let paths = fs::read_dir(PathBuf::from(TEST_FILES_PATH).join(dir)).unwrap(); - - for file_path in paths { - let dir_entry = file_path.unwrap(); - let fp_str = format!("{}", dir_entry.path().display()); - - if skipped - .iter() - .any(|failing_case| fp_str.ends_with(failing_case)) - { - println!("Skipping JSON test: {}", fp_str); - continue; - } - println!( - "Running light client against 'single-step' test-file:\n {}", - fp_str - ); - let case: TestCase = read_test_case(&fp_str); - run_test_case(&case); - } -} - -fn read_bisection_test_case(file_path: &str) -> TestBisection { - serde_json::from_str(read_json_fixture(file_path).as_str()).unwrap() -} - -fn read_test_case(file_path: &str) -> TestCase { - serde_json::from_str(read_json_fixture(file_path).as_str()).unwrap() -} - -async fn run_bisection_test(case: TestBisection) { - println!("{}", case.description); - - let untrusted_height = case.height_to_verify.try_into().unwrap(); - let trust_threshold = case.trust_options.trust_level; - let trusting_period = case.trust_options.period; - let now = case.now; - - let provider = case.primary; - let req = MockRequester::new(provider.chain_id, provider.lite_blocks); - - let expects_err = match &case.expected_output { - Some(eo) => eo.eq("error"), - None => false, - }; - - let trusted_height = case.trust_options.height.try_into().unwrap(); - let trusted_header = req - .signed_header(trusted_height) - .await - .expect("could not 'request' signed header"); - let trusted_vals = req - .validator_set(trusted_height + 1) - .await - .expect("could not 'request' validator set"); - - let trusted_state = TrustedState::new(trusted_header, trusted_vals); - - match lite::verify_bisection( - trusted_state, - untrusted_height, - trust_threshold, - trusting_period.into(), - now.into(), - &req, - ) - .await - { - Ok(new_states) => { - let untrusted_signed_header = req - .signed_header(untrusted_height) - .await - .expect("header at untrusted height not found"); - - let untrusted_next_vals = req - .validator_set(untrusted_height + 1) - .await - .expect("val set at untrusted height not found"); - - let expected_state = TrustedState::new(untrusted_signed_header, untrusted_next_vals); - assert_eq!(new_states[new_states.len() - 1], expected_state); - assert!(!expects_err); - } - Err(_) => { - assert!(expects_err); - } - } -} diff --git a/testgen/src/commit.rs b/testgen/src/commit.rs index 162aa0d6a..808588892 100644 --- a/testgen/src/commit.rs +++ b/testgen/src/commit.rs @@ -1,7 +1,7 @@ use gumdrop::Options; use serde::Deserialize; use simple_error::*; -use tendermint::{block, lite}; +use tendermint::block; use crate::{helpers::*, Generator, Header, Validator, Vote}; @@ -109,7 +109,7 @@ impl Generator for Commit { Some(vs) => vs.to_vec(), }; let block_header = header.generate()?; - let block_id = block::Id::new(lite::Header::hash(&block_header), None); + let block_id = block::Id::new(block_header.hash(), None); let vote_to_sig = |v: &Vote| -> Result { let vote = v.generate()?; @@ -192,7 +192,7 @@ mod tests { signature )); } - _ => assert!(false), + _ => panic!("signature was not a commit"), }; } } diff --git a/testgen/src/header.rs b/testgen/src/header.rs index 64199efb1..b7d40ffb8 100644 --- a/testgen/src/header.rs +++ b/testgen/src/header.rs @@ -3,7 +3,7 @@ use gumdrop::Options; use serde::Deserialize; use simple_error::*; use std::str::FromStr; -use tendermint::{block, chain, lite::ValidatorSet, validator, Time}; +use tendermint::{block, chain, validator, Time}; #[derive(Debug, Options, Deserialize, Clone)] pub struct Header { @@ -159,13 +159,13 @@ mod tests { let mut block_header = header2.generate().unwrap(); block_header.chain_id = chain::Id::from_str("chain1").unwrap(); - let header = header2.clone().chain_id("chain1"); + let header = header2.chain_id("chain1"); assert_eq!(header.generate().unwrap(), block_header); block_header.proposer_address = Validator::new("b").generate().unwrap().address; assert_ne!(header.generate().unwrap(), block_header); - let header = header.clone().proposer(1); + let header = header.proposer(1); assert_eq!(header.generate().unwrap(), block_header); } } diff --git a/testgen/src/vote.rs b/testgen/src/vote.rs index de7b55825..96546b8b5 100644 --- a/testgen/src/vote.rs +++ b/testgen/src/vote.rs @@ -5,7 +5,7 @@ use signatory::{ signature::{Signature as _, Signer}, }; use simple_error::*; -use tendermint::{block, lite, signature::Signature, vote, Time}; +use tendermint::{block, signature::Signature, vote, Time}; use crate::{helpers::*, Generator, Header, Validator}; @@ -82,7 +82,7 @@ impl Generator for Vote { let signer = validator.get_signer()?; let block_validator = validator.generate()?; let block_header = header.generate()?; - let block_id = block::Id::new(lite::Header::hash(&block_header), None); + let block_id = block::Id::new(block_header.hash(), None); let validator_index = match self.index { Some(i) => i, None => match header.validators.as_ref().unwrap().iter().position(|v| *v == *validator) {