Skip to content

Commit

Permalink
Add a verify method which is closer to the spec / the current golang …
Browse files Browse the repository at this point in the history
…code (lite2) and

tests:

 - rename former verify method to verify_header_and_commit (might be obsolete)
 - make the expired method public to be able to use and test it from the outside
 - add some helpers to be able to deal with deserialization hickups
 - add a bunch of tests using @Shivani912's generator code (with minor modifications)
  • Loading branch information
liamsi committed Dec 9, 2019
1 parent 93089b3 commit 017c1ba
Show file tree
Hide file tree
Showing 8 changed files with 15,345 additions and 14 deletions.
16 changes: 14 additions & 2 deletions tendermint/src/lite/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::block::Height;
use crate::Hash;
#[allow(clippy::all)]
use crate::Time;
use failure::_core::fmt::Debug;

/// TrustedState stores the latest state trusted by a lite client,
/// including the last header and the validator set to use to verify
Expand All @@ -19,11 +20,22 @@ where
pub validators: V, // height H
}

/// SignedHeader bundles a Header and a Commit for convenience.
pub trait SignedHeader<H, C>
where
H: Header,
C: Commit,
{
type Vote: Vote;
fn header(&self) -> H;
fn commit(&self) -> C;
}

/// 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 {
pub trait Header: Debug {
fn height(&self) -> Height;
fn bft_time(&self) -> Time;
fn validators_hash(&self) -> Hash;
Expand Down Expand Up @@ -70,7 +82,7 @@ pub trait Validator {
/// Commit is proof a Header is valid.
/// It has an underlying Vote type with the relevant vote data
/// for verification.
pub trait Commit {
pub trait Commit: Debug {
type Vote: Vote;

/// Hash of the header this commit is for.
Expand Down
49 changes: 42 additions & 7 deletions tendermint/src/lite/verifier.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[allow(clippy::all)]
use crate::lite::{
Commit, Error, Header, TrustLevel, Validator, ValidatorSet, ValidatorSetLookup, Vote,
Commit, Error, Header, SignedHeader, TrustLevel, Validator, ValidatorSet, ValidatorSetLookup,
Vote,
};
use crate::Time;
use std::time::Duration;
Expand All @@ -10,7 +11,7 @@ use std::time::Duration;
/// NOTE: this doesn't belong here. It should be called by something that handles whether to trust
/// a verified commit. Verified here is really just about the header/commit/validators. Time is an
/// external concern :)
fn expired<H>(last_header: &H, trusting_period: Duration, now: Time) -> Result<(), Error>
pub fn expired<H>(last_header: &H, trusting_period: Duration, now: Time) -> Result<(), Error>
where
H: Header,
{
Expand All @@ -36,6 +37,36 @@ where
Ok(())
}

pub fn verify<S, H, C, V, L>(
sh1: S,
h1_next_vals: V,
sh2: S,
h2_vals: V,
trust_level: L,
) -> Result<(), Error>
where
S: SignedHeader<H, C>,
H: Header,
V: ValidatorSetLookup,
C: Commit,
L: TrustLevel,
{
let h1 = sh1.header();
let h2 = sh2.header();
let commit = &sh2.commit();
if h2.height() == h1.height().increment() {
if h2.validators_hash() != h1_next_vals.hash() {
return Err(Error::InvalidNextValidatorSet);
}
} else {
// ensure that +1/3 of last trusted validators signed correctly
if let Err(e) = verify_commit_trusting(&h1_next_vals, commit, trust_level) {
return Err(e);
}
}
verify_commit_full(&h2_vals, commit)
}

// Validate the validators and commit against the header.
fn validate_vals_and_commit<H, V, C>(header: H, commit: &C, vals: &V) -> Result<(), Error>
where
Expand All @@ -57,7 +88,7 @@ where
}

/// Verify the commit is valid from the given validators for the header.
pub fn verify<H, V, C>(header: H, commit: C, validators: V) -> Result<(), Error>
fn verify_header_and_commit<H, V, C>(header: H, commit: C, validators: V) -> Result<(), Error>
where
H: Header,
V: ValidatorSet,
Expand All @@ -68,7 +99,7 @@ where
}

// ensure that +2/3 validators signed correctly
verify_commit_full(&validators, commit)
verify_commit_full(&validators, &commit)
}

/// Verify the commit is trusted according to the last validators and is valid
Expand Down Expand Up @@ -96,12 +127,12 @@ where
}

// perform same verification as in sequential case
verify(header, commit, validators)
verify_header_and_commit(header, commit, validators)
}

/// 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.
fn verify_commit_full<V, C>(vals: &V, commit: C) -> Result<(), Error>
fn verify_commit_full<V, C>(vals: &V, commit: &C) -> Result<(), Error>
where
V: ValidatorSet,
C: Commit,
Expand Down Expand Up @@ -150,7 +181,11 @@ where
/// 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.
fn verify_commit_trusting<V, C, L>(validators: &V, commit: &C, trust_level: L) -> Result<(), Error>
pub fn verify_commit_trusting<V, C, L>(
validators: &V,
commit: &C,
trust_level: L,
) -> Result<(), Error>
where
V: ValidatorSetLookup,
C: Commit,
Expand Down
12 changes: 12 additions & 0 deletions tendermint/src/rpc/endpoint/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ pub struct SignedHeader {
pub commit: block::Commit,
}

impl lite::SignedHeader<block::Header, SignedHeader> for SignedHeader {
type Vote = SignedVote;

fn header(&self) -> block::Header {
self.clone().header
}

fn commit(&self) -> Self {
self.clone()
}
}

impl lite::Commit for SignedHeader {
type Vote = SignedVote;
fn header_hash(&self) -> Hash {
Expand Down
10 changes: 10 additions & 0 deletions tendermint/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use subtle_encoding::base64;
/// Validator set contains a vector of validators
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Set {
#[serde(deserialize_with = "parse_vals")]
validators: Vec<Info>,
}

Expand Down Expand Up @@ -58,6 +59,15 @@ impl lite::ValidatorSetLookup for Set {
}
}

// TODO: maybe add a type (with an Option<Vec<Info>> field) instead
// for light client integration tests only
fn parse_vals<'de, D>(d: D) -> Result<Vec<Info>, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default())
}

/// Validator information
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct Info {
Expand Down
114 changes: 109 additions & 5 deletions tendermint/tests/lite.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use serde::{Deserialize, Serialize};
use serde::{de::Error as _, Deserialize, Deserializer, Serialize};
use serde_json;
use std::{fs, path::PathBuf};
use tendermint::{lite, rpc::endpoint::commit::SignedHeader, validator};
use tendermint::validator::Set;
use tendermint::{lite, rpc::endpoint::commit::SignedHeader, validator, Time};

#[derive(Serialize, Deserialize, Clone, Debug)]
struct TestSuite {
Expand All @@ -10,16 +11,103 @@ struct TestSuite {
validators: Vec<validator::Info>,
}

#[derive(Clone, Debug)]
struct Duration(u64);

#[derive(Deserialize, Clone, Debug)]
struct TestCases {
test_cases: Vec<TestCase>,
}

#[derive(Deserialize, Clone, Debug)]
struct TestCase {
test_name: String,
description: String,
initial: Initial,
input: Vec<LiteBlock>,
expected_output: Option<String>,
}

#[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,
}

pub struct DefaultTrustLevel {}
impl lite::TrustLevel for DefaultTrustLevel {}

fn read_json_fixture(name: &str) -> String {
fs::read_to_string(PathBuf::from("./tests/support/lite/").join(name.to_owned() + ".json"))
.unwrap()
}

#[test]
fn verify_trusting_with_one_validator_no_changes() {
pub struct DefaultTrustLevel {}
impl lite::TrustLevel for DefaultTrustLevel {}
fn val_set_tests_verify() {
let cases: TestCases = serde_json::from_str(&read_json_fixture("val_set_tests")).unwrap();
run_test_cases(cases);
}

#[test]
fn commit_tests_verify() {
let cases: TestCases = serde_json::from_str(&read_json_fixture("commit_tests")).unwrap();
run_test_cases(cases);
}

#[test]
fn header_tests_verify() {
let cases: TestCases = serde_json::from_str(&read_json_fixture("header_tests")).unwrap();
run_test_cases(cases);
}

fn run_test_cases(cases: TestCases) -> () {
for (_, tc) in cases.test_cases.iter().enumerate() {
let mut trusted_signed_header = &tc.initial.signed_header;
let mut trusted_next_vals = tc.initial.clone().next_validator_set;
let trusting_period: std::time::Duration = tc.initial.clone().trusting_period.into();
let now = tc.initial.now;
let expexts_err = match &tc.expected_output {
Some(eo) => eo.eq("error"),
None => false,
};

for (_, input) in tc.input.iter().enumerate() {
println!("{}", tc.description);
if let Err(e) = lite::expired(&trusted_signed_header.header, trusting_period, now) {
println!("Expired: {:?}", e);
assert_eq!(expexts_err, true);
}
let new_signed_header = &input.signed_header;
let new_vals = input.validator_set.clone();
let res = &lite::verify(
trusted_signed_header.clone(),
trusted_next_vals.clone(),
new_signed_header.clone(),
new_vals,
DefaultTrustLevel {},
);
assert_eq!(res.is_err(), expexts_err);
if !res.is_err() {
trusted_signed_header = new_signed_header;
trusted_next_vals = input.next_validator_set.clone();
} else {
println!("Got error: {:?}", res.as_ref().err());
}
}
}
}

#[test]
fn verify_trusting_with_one_validator_no_changes() {
let suite: TestSuite = serde_json::from_str(&read_json_fixture("basic")).unwrap();
lite::verify_trusting(
suite.signed_header.header.clone(),
Expand All @@ -30,3 +118,19 @@ fn verify_trusting_with_one_validator_no_changes() {
)
.expect("verify_trusting failed");
}

impl<'de> Deserialize<'de> for Duration {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(Duration(
String::deserialize(deserializer)?
.parse()
.map_err(|e| D::Error::custom(format!("{}", e)))?,
))
}
}

impl From<Duration> for std::time::Duration {
fn from(d: Duration) -> std::time::Duration {
std::time::Duration::from_nanos(d.0)
}
}
Loading

0 comments on commit 017c1ba

Please sign in to comment.