From cceaa70ffd1773d99a3880d585a977ac9d0804cf Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 23 Apr 2020 17:06:54 +0200 Subject: [PATCH 001/100] Rework predicates --- Cargo.toml | 1 + light-spike/Cargo.toml | 11 ++ light-spike/src/components/verifier.rs | 0 light-spike/src/lib.rs | 12 +++ light-spike/src/operations.rs | 61 +++++++++++ light-spike/src/predicates.rs | 120 ++++++++++++++++++++++ light-spike/src/predicates/production.rs | 123 +++++++++++++++++++++++ light-spike/src/prelude.rs | 27 +++++ light-spike/src/types.rs | 76 ++++++++++++++ 9 files changed, 431 insertions(+) create mode 100644 light-spike/Cargo.toml create mode 100644 light-spike/src/components/verifier.rs create mode 100644 light-spike/src/lib.rs create mode 100644 light-spike/src/operations.rs create mode 100644 light-spike/src/predicates.rs create mode 100644 light-spike/src/predicates/production.rs create mode 100644 light-spike/src/prelude.rs create mode 100644 light-spike/src/types.rs diff --git a/Cargo.toml b/Cargo.toml index 95befcacc..811d3db98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ members = [ "tendermint", "light-node", + "light-spike", ] diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml new file mode 100644 index 000000000..1418706ec --- /dev/null +++ b/light-spike/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "light-spike" +version = "0.1.0" +authors = ["Romain Ruetschi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tendermint = { path = "../tendermint" } +derive_more = "0.99.5" diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs new file mode 100644 index 000000000..e69de29bb diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs new file mode 100644 index 000000000..399aa13d6 --- /dev/null +++ b/light-spike/src/lib.rs @@ -0,0 +1,12 @@ +pub mod operations; +pub mod predicates; +pub mod prelude; +pub mod types; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/light-spike/src/operations.rs b/light-spike/src/operations.rs new file mode 100644 index 000000000..fd76ca6ab --- /dev/null +++ b/light-spike/src/operations.rs @@ -0,0 +1,61 @@ +//! Crypto function traits allowing mocking out during testing + +use crate::prelude::*; + +pub trait VotingPowerCalculator { + // TODO: What kind of errors should we be reporting here? + fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> Result; + fn total_power_of(&self, validators: &ValidatorSet) -> Result; +} + +impl VotingPowerCalculator for &T { + fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> Result { + (*self).voting_power_in(commit, validators) + } + + fn total_power_of(&self, validators: &ValidatorSet) -> Result { + (*self).total_power_of(validators) + } +} + +impl VotingPowerCalculator for Box { + fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> Result { + self.as_ref().voting_power_in(commit, validators) + } + + fn total_power_of(&self, validators: &ValidatorSet) -> Result { + self.as_ref().total_power_of(validators) + } +} + +pub trait CommitValidator { + fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), Error>; +} + +impl CommitValidator for &T { + fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), Error> { + (*self).validate(commit, validators) + } +} + +impl CommitValidator for Box { + fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), Error> { + self.as_ref().validate(commit, validators) + } +} + +pub trait HeaderHasher { + fn hash(&self, header: &Header) -> Hash; // Or Error? +} + +impl HeaderHasher for &T { + fn hash(&self, header: &Header) -> Hash { + (*self).hash(header) + } +} + +impl HeaderHasher for Box { + fn hash(&self, header: &Header) -> Hash { + self.as_ref().hash(header) + } +} diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs new file mode 100644 index 000000000..da5052cb3 --- /dev/null +++ b/light-spike/src/predicates.rs @@ -0,0 +1,120 @@ +use std::time::{Duration, SystemTime}; + +use crate::prelude::*; + +pub mod production; + +pub trait VerificationPredicates { + fn validator_sets_match( + &self, + signed_header: &SignedHeader, + validators: &ValidatorSet, + ) -> Result<(), Error>; + + fn next_validators_match( + &self, + signed_header: &SignedHeader, + validators: &ValidatorSet, + ) -> Result<(), Error>; + + fn header_matches_commit( + &self, + header: &Header, + commit: &Commit, + header_hasher: impl HeaderHasher, + ) -> Result<(), Error>; + + fn valid_commit( + &self, + commit: &Commit, + validators: &ValidatorSet, + validator: impl CommitValidator, + ) -> Result<(), Error>; + + fn is_within_trust_period( + &self, + header: &Header, + trusting_period: Duration, + now: SystemTime, + ) -> Result<(), Error>; + + fn is_monotonic_bft_time(&self, header_a: &Header, header_b: &Header) -> Result<(), Error>; + + fn is_monotonic_height(&self, header_a: &Header, header_b: &Header) -> Result<(), Error>; + + fn has_sufficient_voting_power( + &self, + commit: &Commit, + validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &impl VotingPowerCalculator, + ) -> Result<(), Error>; + + fn has_sufficient_validators_overlap( + &self, + untrusted_commit: &Commit, + trusted_validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &impl VotingPowerCalculator, + ) -> Result<(), Error>; + + fn has_sufficient_signers_overlap( + &self, + untrusted_commit: &Commit, + untrusted_validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &impl VotingPowerCalculator, + ) -> Result<(), Error>; + + fn valid_next_validator_set( + &self, + trusted_state: &TrustedState, + untrusted_sh: &SignedHeader, + untrusted_next_vals: &ValidatorSet, + ) -> Result<(), Error>; +} + +pub fn verify_untrusted_light_block( + pred: impl VerificationPredicates, + voting_power_calculator: impl VotingPowerCalculator, + commit_validator: impl CommitValidator, + header_hasher: impl HeaderHasher, + trusted_state: &TrustedState, + untrusted_sh: &SignedHeader, + untrusted_vals: &ValidatorSet, + untrusted_next_vals: &ValidatorSet, + trust_threshold: &TrustThreshold, +) -> Result<(), Error> { + pred.validator_sets_match(&untrusted_sh, &untrusted_vals)?; + pred.next_validators_match(&untrusted_sh, &untrusted_next_vals)?; + + pred.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, &header_hasher)?; + + pred.valid_commit( + &untrusted_sh.commit, + &untrusted_sh.validators, + &commit_validator, + )?; + + pred.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; + + pred.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; + + pred.valid_next_validator_set(&trusted_state, &untrusted_sh, &untrusted_next_vals)?; + + pred.has_sufficient_validators_overlap( + &untrusted_sh.commit, + &trusted_state.validators, + &trust_threshold, + &voting_power_calculator, + )?; + + pred.has_sufficient_signers_overlap( + &untrusted_sh.commit, + &untrusted_vals, + &trust_threshold, + &voting_power_calculator, + )?; + + Ok(()) +} diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs new file mode 100644 index 000000000..548aa5b89 --- /dev/null +++ b/light-spike/src/predicates/production.rs @@ -0,0 +1,123 @@ +use super::VerificationPredicates; +use crate::prelude::*; + +pub struct ProductionPredicates; + +impl VerificationPredicates for ProductionPredicates { + fn validator_sets_match( + &self, + signed_header: &SignedHeader, + validators: &ValidatorSet, + ) -> Result<(), Error> { + (signed_header.validators_hash == validators.hash).true_or(Error::InvalidValidatorSet) + } + + fn next_validators_match( + &self, + signed_header: &SignedHeader, + validators: &ValidatorSet, + ) -> Result<(), Error> { + (signed_header.validators_hash == validators.hash).true_or(Error::InvalidNextValidatorSet) + } + + fn header_matches_commit( + &self, + header: &Header, + commit: &Commit, + header_hasher: impl HeaderHasher, + ) -> Result<(), Error> { + (header_hasher.hash(header) == commit.header_hash).true_or(Error::InvalidCommitValue) + } + + fn valid_commit( + &self, + commit: &Commit, + validators: &ValidatorSet, + validator: impl CommitValidator, + ) -> Result<(), Error> { + validator.validate(commit, validators) + } + + fn is_within_trust_period( + &self, + header: &Header, + trusting_period: Duration, + now: SystemTime, + ) -> Result<(), Error> { + let header_time: SystemTime = header.bft_time.into(); + let expires_at = header_time + trusting_period; + + (header_time < now && expires_at > now).true_or(Error::NotWithinTrustPeriod) + } + + fn is_monotonic_bft_time(&self, header_a: &Header, header_b: &Header) -> Result<(), Error> { + (header_b.bft_time >= header_a.bft_time).true_or(Error::NonMonotonicBftTime) + } + + fn is_monotonic_height(&self, header_a: &Header, header_b: &Header) -> Result<(), Error> { + (header_a.height > header_b.height).true_or(Error::NonIncreasingHeight) + } + + fn has_sufficient_voting_power( + &self, + commit: &Commit, + validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &impl VotingPowerCalculator, + ) -> Result<(), Error> { + let total_power = calculator.total_power_of(validators); + let voting_power = calculator.voting_power_in(commit, validators); + + let result = if let (Ok(total_power), Ok(voting_power)) = (total_power, voting_power) { + voting_power * trust_threshold.denominator > total_power * trust_threshold.numerator + } else { + false + }; + + result.true_or(Error::InsufficientVotingPower) + } + + fn has_sufficient_validators_overlap( + &self, + untrusted_commit: &Commit, + trusted_validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &impl VotingPowerCalculator, + ) -> Result<(), Error> { + self.has_sufficient_voting_power( + untrusted_commit, + trusted_validators, + trust_threshold, + calculator, + ) + .map_err(|_| Error::InsufficientValidatorsOverlap) + } + + fn has_sufficient_signers_overlap( + &self, + untrusted_commit: &Commit, + untrusted_validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &impl VotingPowerCalculator, + ) -> Result<(), Error> { + self.has_sufficient_voting_power( + untrusted_commit, + untrusted_validators, + trust_threshold, + calculator, + ) + .map_err(|_| Error::InvalidCommit) + } + + fn valid_next_validator_set( + &self, + trusted_state: &TrustedState, + untrusted_sh: &SignedHeader, + untrusted_next_vals: &ValidatorSet, + ) -> Result<(), Error> { + let result = untrusted_sh.header.height == trusted_state.header.height + && trusted_state.validators.hash != untrusted_next_vals.hash; + + result.false_or(Error::InvalidNextValidatorSet) + } +} diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs new file mode 100644 index 000000000..6c1d81928 --- /dev/null +++ b/light-spike/src/prelude.rs @@ -0,0 +1,27 @@ +pub use crate::operations::*; +pub use crate::types::*; + +pub use std::time::{Duration, SystemTime}; + +pub(crate) trait BoolExt { + fn true_or(self, e: E) -> Result<(), E>; + fn false_or(self, e: E) -> Result<(), E>; +} + +impl BoolExt for bool { + fn true_or(self, e: E) -> Result<(), E> { + if self { + Ok(()) + } else { + Err(e) + } + } + + fn false_or(self, e: E) -> Result<(), E> { + if !self { + Ok(()) + } else { + Err(e) + } + } +} diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs new file mode 100644 index 000000000..96fcbf145 --- /dev/null +++ b/light-spike/src/types.rs @@ -0,0 +1,76 @@ +use derive_more::Display; +use std::time::SystemTime; + +pub use tendermint::hash::Hash; +pub use tendermint::lite::types::Height; + +#[derive(Debug, Copy, Clone)] +pub enum Error { + ImplementationSpecific, + InsufficientValidatorsOverlap, + InsufficientVotingPower, + InvalidCommit, + InvalidCommitValue, + InvalidNextValidatorSet, + InvalidValidatorSet, + NonIncreasingHeight, + NonMonotonicBftTime, + NotWithinTrustPeriod, +} + +#[derive(Clone, Debug, Display)] +#[display(fmt = "{:?}", self)] +pub struct Header { + pub height: Height, + pub bft_time: SystemTime, + pub validator_set_hash: Hash, + pub next_validator_set_hash: Hash, + pub hash: Hash, // TODO: What if we don't have this +} + +#[derive(Clone, Debug, Display)] +#[display(fmt = "{:?}", self)] +pub struct ValidatorSet { + pub hash: Hash, +} + +impl From> for ValidatorSet { + fn from(_vis: std::vec::Vec) -> Self { + todo!() + } +} + +#[derive(Clone, Debug, Display)] +#[display(fmt = "{:?}", self)] +pub struct Commit { + pub header_hash: Hash, +} + +#[derive(Copy, Clone, Debug, Display)] +#[display(fmt = "{:?}", self)] +pub struct TrustThreshold { + pub numerator: u64, + pub denominator: u64, +} + +#[derive(Clone, Debug, Display)] +#[display(fmt = "{:?}", self)] +pub struct SignedHeader { + pub header: Header, + pub commit: Commit, + pub validators: ValidatorSet, + pub validators_hash: Hash, +} + +impl From for SignedHeader { + fn from(_sh: tendermint::block::signed_header::SignedHeader) -> Self { + todo!() + } +} + +#[derive(Clone, Debug, Display)] +#[display(fmt = "{:?}", self)] +pub struct TrustedState { + pub header: Header, + pub validators: ValidatorSet, +} From a9828212cfb7c6752c55bb3a71feca617cfb76d6 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 23 Apr 2020 18:20:03 +0200 Subject: [PATCH 002/100] WIP: Add tracing --- light-spike/Cargo.toml | 6 ++ light-spike/src/components.rs | 1 + light-spike/src/components/rpc.rs | 94 +++++++++++++++++++++++++++++++ light-spike/src/lib.rs | 2 + light-spike/src/prelude.rs | 1 + light-spike/src/trace.rs | 92 ++++++++++++++++++++++++++++++ light-spike/src/types.rs | 23 +++++--- 7 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 light-spike/src/components.rs create mode 100644 light-spike/src/components/rpc.rs create mode 100644 light-spike/src/trace.rs diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index 1418706ec..7a2b0579d 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -9,3 +9,9 @@ edition = "2018" [dependencies] tendermint = { path = "../tendermint" } derive_more = "0.99.5" +serde_derive = "1.0.106" +serde = "1.0.106" +typetag = "0.1.4" + +[dev-dependencies] +serde_json = "1.0.51" diff --git a/light-spike/src/components.rs b/light-spike/src/components.rs new file mode 100644 index 000000000..06a3fd023 --- /dev/null +++ b/light-spike/src/components.rs @@ -0,0 +1 @@ +pub mod rpc; diff --git a/light-spike/src/components/rpc.rs b/light-spike/src/components/rpc.rs new file mode 100644 index 000000000..557fb3290 --- /dev/null +++ b/light-spike/src/components/rpc.rs @@ -0,0 +1,94 @@ +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::future::Future; +use std::sync::mpsc::Sender; +use tendermint::{block, rpc}; + +use crate::prelude::*; + +#[derive(Debug, Serialize, Deserialize)] +pub enum RequesterError { + RpcError(rpc::Error), +} + +#[typetag::serde] +impl Event for RequesterError {} + +#[derive(Debug, Serialize, Deserialize)] +pub enum RequesterInput { + FetchState(Height), +} + +#[typetag::serde] +impl Event for RequesterInput {} + +#[derive(Debug, Serialize, Deserialize)] +pub enum RequesterOutput { + FetchedLightBlock(LightBlock), +} + +#[typetag::serde] +impl Event for RequesterOutput {} + +pub struct Requester { + rpc_client: rpc::Client, + trace: Sender, +} + +impl Requester { + pub fn new(rpc_client: rpc::Client, trace: Sender) -> Self { + Self { rpc_client, trace } + } + + fn trace(&self, e: impl Event + 'static) { + self.trace.send(Box::new(e)).expect("could not trace event"); + } + + pub fn fetch_light_block(&self, height: Height) -> Result { + self.trace(RequesterInput::FetchState(height)); + + let signed_header = self.fetch_signed_header(height)?; + let validator_set = self.fetch_validator_set(height)?; + let next_validator_set = self.fetch_validator_set(height + 1)?; + + let light_block = LightBlock { + height, + signed_header, + validator_set, + next_validator_set, + }; + + self.trace(RequesterOutput::FetchedLightBlock(light_block.clone())); + + Ok(light_block) + } + + fn fetch_signed_header(&self, h: Height) -> Result { + let height: block::Height = h.into(); + + let res = block_on(async { + match height.value() { + 0 => self.rpc_client.latest_commit().await, + _ => self.rpc_client.commit(height).await, + } + }); + + match res { + Ok(response) => Ok(response.signed_header.into()), + Err(err) => Err(RequesterError::RpcError(err)), + } + } + + fn fetch_validator_set(&self, height: Height) -> Result { + let res = block_on(self.rpc_client.validators(height)); + + match res { + Ok(response) => Ok(response.validators.into()), + Err(err) => Err(RequesterError::RpcError(err)), + } + } +} + +fn block_on(_future: F) -> F::Output { + todo!() +} diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 399aa13d6..22d42528b 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -1,6 +1,8 @@ +pub mod components; pub mod operations; pub mod predicates; pub mod prelude; +pub mod trace; pub mod types; #[cfg(test)] diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 6c1d81928..a4c6cad33 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,4 +1,5 @@ pub use crate::operations::*; +pub use crate::trace::*; pub use crate::types::*; pub use std::time::{Duration, SystemTime}; diff --git a/light-spike/src/trace.rs b/light-spike/src/trace.rs new file mode 100644 index 000000000..167561c1c --- /dev/null +++ b/light-spike/src/trace.rs @@ -0,0 +1,92 @@ +use std::fmt::Debug; +use std::sync::mpsc::{channel, Receiver, Sender}; + +use serde::{Serialize, Serializer}; + +#[typetag::serde(tag = "type")] +pub trait Event: Debug {} + +pub type BoxedEvent = Box; + +pub struct Trace { + events: Vec, + recv: Receiver, +} + +impl Trace { + pub fn new() -> (Sender, Self) { + let (send, recv) = channel(); + + ( + send, + Self { + events: vec![], + recv, + }, + ) + } + + pub fn run(&mut self) { + loop { + if let Ok(event) = self.recv.recv() { + self.events.push(event); + } else { + break; + } + } + } + + pub fn serialize(self, ser: S) -> Result + where + S: Serializer, + { + self.events.serialize(ser) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + struct Foo { + foo: u32, + } + #[derive(Debug, Serialize, Deserialize)] + struct Bar { + bar: Option, + } + + #[typetag::serde] + impl Event for Foo {} + #[typetag::serde] + impl Event for Bar {} + + #[test] + fn test_serialize() { + let (send, mut trace) = Trace::new(); + + send.send(Box::new(Foo { foo: 42 })).unwrap(); + send.send(Box::new(Bar { + bar: Some("test".to_string()), + })) + .unwrap(); + + std::mem::drop(send); + + trace.run(); + + let mut output = Vec::new(); + let mut ser = serde_json::Serializer::pretty(&mut output); + trace.serialize(&mut ser).unwrap(); + + let as_string = String::from_utf8(output).unwrap(); + println!("{}", as_string); + + let events: Vec> = serde_json::from_str(&as_string).unwrap(); + for event in events { + dbg!(event); + } + } +} diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index 96fcbf145..25632baa0 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -1,10 +1,11 @@ use derive_more::Display; +use serde::{Deserialize, Serialize}; use std::time::SystemTime; pub use tendermint::hash::Hash; pub use tendermint::lite::types::Height; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub enum Error { ImplementationSpecific, InsufficientValidatorsOverlap, @@ -18,7 +19,7 @@ pub enum Error { NotWithinTrustPeriod, } -#[derive(Clone, Debug, Display)] +#[derive(Clone, Debug, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Header { pub height: Height, @@ -28,7 +29,7 @@ pub struct Header { pub hash: Hash, // TODO: What if we don't have this } -#[derive(Clone, Debug, Display)] +#[derive(Clone, Debug, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct ValidatorSet { pub hash: Hash, @@ -40,20 +41,20 @@ impl From> for ValidatorSet { } } -#[derive(Clone, Debug, Display)] +#[derive(Clone, Debug, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Commit { pub header_hash: Hash, } -#[derive(Copy, Clone, Debug, Display)] +#[derive(Copy, Clone, Debug, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct TrustThreshold { pub numerator: u64, pub denominator: u64, } -#[derive(Clone, Debug, Display)] +#[derive(Clone, Debug, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct SignedHeader { pub header: Header, @@ -68,9 +69,17 @@ impl From for SignedHeader { } } -#[derive(Clone, Debug, Display)] +#[derive(Clone, Debug, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct TrustedState { pub header: Header, pub validators: ValidatorSet, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LightBlock { + pub height: Height, + pub signed_header: SignedHeader, + pub validator_set: ValidatorSet, + pub next_validator_set: ValidatorSet, +} From 7e2a9d564712bbaa95767d94095777493c7f8c95 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 11:43:39 +0200 Subject: [PATCH 003/100] Fix verification procedure --- light-spike/src/predicates.rs | 43 +++++++++++++++++++++--- light-spike/src/predicates/production.rs | 19 ++++++++--- light-spike/src/types.rs | 2 +- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index da5052cb3..ddb5d2f49 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -38,9 +38,17 @@ pub trait VerificationPredicates { now: SystemTime, ) -> Result<(), Error>; - fn is_monotonic_bft_time(&self, header_a: &Header, header_b: &Header) -> Result<(), Error>; + fn is_monotonic_bft_time( + &self, + untrusted_header: &Header, + trusted_header: &Header, + ) -> Result<(), Error>; - fn is_monotonic_height(&self, header_a: &Header, header_b: &Header) -> Result<(), Error>; + fn is_monotonic_height( + &self, + untrusted_header: &Header, + trusted_header: &Header, + ) -> Result<(), Error>; fn has_sufficient_voting_power( &self, @@ -84,12 +92,22 @@ pub fn verify_untrusted_light_block( untrusted_vals: &ValidatorSet, untrusted_next_vals: &ValidatorSet, trust_threshold: &TrustThreshold, + trusting_period: Duration, + now: SystemTime, ) -> Result<(), Error> { + // Ensure the latest trusted header hasn't expired + pred.is_within_trust_period(&trusted_state.header, trusting_period, now)?; + + // Ensure the header validator hashes match the given validators pred.validator_sets_match(&untrusted_sh, &untrusted_vals)?; + + // Ensure the header next validator hashes match the given next validators pred.next_validators_match(&untrusted_sh, &untrusted_next_vals)?; + // Ensure the header matches the commit pred.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, &header_hasher)?; + // Additional implementation specific validation pred.valid_commit( &untrusted_sh.commit, &untrusted_sh.validators, @@ -98,9 +116,24 @@ pub fn verify_untrusted_light_block( pred.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; - pred.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; - - pred.valid_next_validator_set(&trusted_state, &untrusted_sh, &untrusted_next_vals)?; + if untrusted_sh.header.height == trusted_state.header.height { + pred.valid_next_validator_set(&trusted_state, &untrusted_sh, &untrusted_next_vals)?; + } else if untrusted_sh.header.height > trusted_state.header.height { + pred.has_sufficient_voting_power( + &untrusted_sh.commit, + &untrusted_sh.validators, + &trust_threshold, + &voting_power_calculator, + )?; + } else { + pred.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; + + // Ensure that the check above will always fail. + unreachable!(); + } + + // All validation passed successfully. + // Verify the validators correctly committed the block. pred.has_sufficient_validators_overlap( &untrusted_sh.commit, diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index 548aa5b89..5fa47ba8c 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -50,12 +50,23 @@ impl VerificationPredicates for ProductionPredicates { (header_time < now && expires_at > now).true_or(Error::NotWithinTrustPeriod) } - fn is_monotonic_bft_time(&self, header_a: &Header, header_b: &Header) -> Result<(), Error> { - (header_b.bft_time >= header_a.bft_time).true_or(Error::NonMonotonicBftTime) + fn is_monotonic_bft_time( + &self, + untrusted_header: &Header, + trusted_header: &Header, + ) -> Result<(), Error> { + (untrusted_header.bft_time > trusted_header.bft_time).true_or(Error::NonMonotonicBftTime) } - fn is_monotonic_height(&self, header_a: &Header, header_b: &Header) -> Result<(), Error> { - (header_a.height > header_b.height).true_or(Error::NonIncreasingHeight) + fn is_monotonic_height( + &self, + untrusted_header: &Header, + trusted_header: &Header, + ) -> Result<(), Error> { + (untrusted_header.height > trusted_header.height).true_or(Error::NonIncreasingHeight { + got: untrusted_header.height, + expected: trusted_header.height + 1, + }) } fn has_sufficient_voting_power( diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index 25632baa0..94bb2f23d 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -14,7 +14,7 @@ pub enum Error { InvalidCommitValue, InvalidNextValidatorSet, InvalidValidatorSet, - NonIncreasingHeight, + NonIncreasingHeight { got: Height, expected: Height }, NonMonotonicBftTime, NotWithinTrustPeriod, } From 8509a13daefa139a556e21341103c979b8a98f96 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 11:43:59 +0200 Subject: [PATCH 004/100] Rename requester component to rpc --- light-spike/src/components.rs | 1 + light-spike/src/components/rpc.rs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/light-spike/src/components.rs b/light-spike/src/components.rs index 06a3fd023..fe482e3eb 100644 --- a/light-spike/src/components.rs +++ b/light-spike/src/components.rs @@ -1 +1,2 @@ pub mod rpc; +pub mod verifier; diff --git a/light-spike/src/components/rpc.rs b/light-spike/src/components/rpc.rs index 557fb3290..d4b84b36e 100644 --- a/light-spike/src/components/rpc.rs +++ b/light-spike/src/components/rpc.rs @@ -7,35 +7,35 @@ use tendermint::{block, rpc}; use crate::prelude::*; #[derive(Debug, Serialize, Deserialize)] -pub enum RequesterError { +pub enum RpcError { RpcError(rpc::Error), } #[typetag::serde] -impl Event for RequesterError {} +impl Event for RpcError {} #[derive(Debug, Serialize, Deserialize)] -pub enum RequesterInput { +pub enum RpcInput { FetchState(Height), } #[typetag::serde] -impl Event for RequesterInput {} +impl Event for RpcInput {} #[derive(Debug, Serialize, Deserialize)] -pub enum RequesterOutput { +pub enum RpcOutput { FetchedLightBlock(LightBlock), } #[typetag::serde] -impl Event for RequesterOutput {} +impl Event for RpcOutput {} -pub struct Requester { +pub struct Rpc { rpc_client: rpc::Client, trace: Sender, } -impl Requester { +impl Rpc { pub fn new(rpc_client: rpc::Client, trace: Sender) -> Self { Self { rpc_client, trace } } @@ -44,8 +44,8 @@ impl Requester { self.trace.send(Box::new(e)).expect("could not trace event"); } - pub fn fetch_light_block(&self, height: Height) -> Result { - self.trace(RequesterInput::FetchState(height)); + pub fn fetch_light_block(&self, height: Height) -> Result { + self.trace(RpcInput::FetchState(height)); let signed_header = self.fetch_signed_header(height)?; let validator_set = self.fetch_validator_set(height)?; @@ -58,12 +58,12 @@ impl Requester { next_validator_set, }; - self.trace(RequesterOutput::FetchedLightBlock(light_block.clone())); + self.trace(RpcOutput::FetchedLightBlock(light_block.clone())); Ok(light_block) } - fn fetch_signed_header(&self, h: Height) -> Result { + fn fetch_signed_header(&self, h: Height) -> Result { let height: block::Height = h.into(); let res = block_on(async { @@ -75,16 +75,16 @@ impl Requester { match res { Ok(response) => Ok(response.signed_header.into()), - Err(err) => Err(RequesterError::RpcError(err)), + Err(err) => Err(RpcError::RpcError(err)), } } - fn fetch_validator_set(&self, height: Height) -> Result { + fn fetch_validator_set(&self, height: Height) -> Result { let res = block_on(self.rpc_client.validators(height)); match res { Ok(response) => Ok(response.validators.into()), - Err(err) => Err(RequesterError::RpcError(err)), + Err(err) => Err(RpcError::RpcError(err)), } } } From cdc22949c95b893cccbdd8a275b8721fbe8ba772 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 11:44:13 +0200 Subject: [PATCH 005/100] Rename Trace::run to Trace::collect --- light-spike/src/trace.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/light-spike/src/trace.rs b/light-spike/src/trace.rs index 167561c1c..5e0e986e3 100644 --- a/light-spike/src/trace.rs +++ b/light-spike/src/trace.rs @@ -26,7 +26,7 @@ impl Trace { ) } - pub fn run(&mut self) { + pub fn collect(&mut self) { loop { if let Ok(event) = self.recv.recv() { self.events.push(event); @@ -75,7 +75,7 @@ mod tests { std::mem::drop(send); - trace.run(); + trace.collect(); let mut output = Vec::new(); let mut ser = serde_json::Serializer::pretty(&mut output); From be3012f5011224eea4d96d76b5c7296c6ca0e82a Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 12:05:52 +0200 Subject: [PATCH 006/100] Return meaningful data in errors --- light-spike/src/operations.rs | 13 ++--- light-spike/src/predicates.rs | 8 +-- light-spike/src/predicates/production.rs | 73 +++++++++++++++++------- light-spike/src/types.rs | 53 +++++++++++++---- 4 files changed, 105 insertions(+), 42 deletions(-) diff --git a/light-spike/src/operations.rs b/light-spike/src/operations.rs index fd76ca6ab..dc296b83c 100644 --- a/light-spike/src/operations.rs +++ b/light-spike/src/operations.rs @@ -3,27 +3,26 @@ use crate::prelude::*; pub trait VotingPowerCalculator { - // TODO: What kind of errors should we be reporting here? - fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> Result; - fn total_power_of(&self, validators: &ValidatorSet) -> Result; + fn total_power_of(&self, validators: &ValidatorSet) -> u64; + fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> u64; } impl VotingPowerCalculator for &T { - fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> Result { + fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> u64 { (*self).voting_power_in(commit, validators) } - fn total_power_of(&self, validators: &ValidatorSet) -> Result { + fn total_power_of(&self, validators: &ValidatorSet) -> u64 { (*self).total_power_of(validators) } } impl VotingPowerCalculator for Box { - fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> Result { + fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> u64 { self.as_ref().voting_power_in(commit, validators) } - fn total_power_of(&self, validators: &ValidatorSet) -> Result { + fn total_power_of(&self, validators: &ValidatorSet) -> u64 { self.as_ref().total_power_of(validators) } } diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index ddb5d2f49..525ee77c5 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -56,7 +56,7 @@ pub trait VerificationPredicates { validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &impl VotingPowerCalculator, - ) -> Result<(), Error>; + ) -> Result<(), (Error, u64, u64)>; fn has_sufficient_validators_overlap( &self, @@ -76,7 +76,6 @@ pub trait VerificationPredicates { fn valid_next_validator_set( &self, - trusted_state: &TrustedState, untrusted_sh: &SignedHeader, untrusted_next_vals: &ValidatorSet, ) -> Result<(), Error>; @@ -117,14 +116,15 @@ pub fn verify_untrusted_light_block( pred.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; if untrusted_sh.header.height == trusted_state.header.height { - pred.valid_next_validator_set(&trusted_state, &untrusted_sh, &untrusted_next_vals)?; + pred.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; } else if untrusted_sh.header.height > trusted_state.header.height { pred.has_sufficient_voting_power( &untrusted_sh.commit, &untrusted_sh.validators, &trust_threshold, &voting_power_calculator, - )?; + ) + .map_err(|(e, _, _)| e)?; } else { pred.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index 5fa47ba8c..5d3c399a1 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -9,7 +9,10 @@ impl VerificationPredicates for ProductionPredicates { signed_header: &SignedHeader, validators: &ValidatorSet, ) -> Result<(), Error> { - (signed_header.validators_hash == validators.hash).true_or(Error::InvalidValidatorSet) + (signed_header.validators_hash == validators.hash).true_or(Error::InvalidValidatorSet { + header_validators_hash: signed_header.validators_hash, + validators_hash: validators.hash, + }) } fn next_validators_match( @@ -17,7 +20,10 @@ impl VerificationPredicates for ProductionPredicates { signed_header: &SignedHeader, validators: &ValidatorSet, ) -> Result<(), Error> { - (signed_header.validators_hash == validators.hash).true_or(Error::InvalidNextValidatorSet) + (signed_header.validators_hash == validators.hash).true_or(Error::InvalidNextValidatorSet { + header_next_validators_hash: signed_header.validators_hash, + next_validators_hash: validators.hash, + }) } fn header_matches_commit( @@ -26,7 +32,11 @@ impl VerificationPredicates for ProductionPredicates { commit: &Commit, header_hasher: impl HeaderHasher, ) -> Result<(), Error> { - (header_hasher.hash(header) == commit.header_hash).true_or(Error::InvalidCommitValue) + let header_hash = header_hasher.hash(header); + (header_hash == commit.header_hash).true_or(Error::InvalidCommitValue { + header_hash, + commit_hash: commit.header_hash, + }) } fn valid_commit( @@ -47,7 +57,14 @@ impl VerificationPredicates for ProductionPredicates { let header_time: SystemTime = header.bft_time.into(); let expires_at = header_time + trusting_period; - (header_time < now && expires_at > now).true_or(Error::NotWithinTrustPeriod) + (header_time < now && expires_at > now).true_or(Error::NotWithinTrustPeriod { + at: expires_at, + now, + })?; + + (header_time <= now).true_or(Error::HeaderFromTheFuture { header_time, now })?; + + Ok(()) } fn is_monotonic_bft_time( @@ -55,7 +72,10 @@ impl VerificationPredicates for ProductionPredicates { untrusted_header: &Header, trusted_header: &Header, ) -> Result<(), Error> { - (untrusted_header.bft_time > trusted_header.bft_time).true_or(Error::NonMonotonicBftTime) + (untrusted_header.bft_time > trusted_header.bft_time).true_or(Error::NonMonotonicBftTime { + header_bft_time: untrusted_header.bft_time, + trusted_header_bft_time: trusted_header.bft_time, + }) } fn is_monotonic_height( @@ -75,17 +95,21 @@ impl VerificationPredicates for ProductionPredicates { validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &impl VotingPowerCalculator, - ) -> Result<(), Error> { + ) -> Result<(), (Error, u64, u64)> { let total_power = calculator.total_power_of(validators); let voting_power = calculator.voting_power_in(commit, validators); - let result = if let (Ok(total_power), Ok(voting_power)) = (total_power, voting_power) { - voting_power * trust_threshold.denominator > total_power * trust_threshold.numerator - } else { - false - }; - - result.true_or(Error::InsufficientVotingPower) + let result = + voting_power * trust_threshold.denominator > total_power * trust_threshold.numerator; + + result.true_or(( + Error::InsufficientVotingPower { + total_power, + voting_power, + }, + total_power, + voting_power, + )) } fn has_sufficient_validators_overlap( @@ -101,7 +125,12 @@ impl VerificationPredicates for ProductionPredicates { trust_threshold, calculator, ) - .map_err(|_| Error::InsufficientValidatorsOverlap) + .map_err( + |(_, total_power, signed_power)| Error::InsufficientValidatorsOverlap { + total_power, + signed_power, + }, + ) } fn has_sufficient_signers_overlap( @@ -117,18 +146,22 @@ impl VerificationPredicates for ProductionPredicates { trust_threshold, calculator, ) - .map_err(|_| Error::InvalidCommit) + .map_err(|(_, total_power, signed_power)| Error::InvalidCommit { + total_power, + signed_power, + }) } fn valid_next_validator_set( &self, - trusted_state: &TrustedState, untrusted_sh: &SignedHeader, untrusted_next_vals: &ValidatorSet, ) -> Result<(), Error> { - let result = untrusted_sh.header.height == trusted_state.header.height - && trusted_state.validators.hash != untrusted_next_vals.hash; - - result.false_or(Error::InvalidNextValidatorSet) + (untrusted_sh.header.next_validators_hash != untrusted_next_vals.hash).false_or( + Error::InvalidNextValidatorSet { + header_next_validators_hash: untrusted_next_vals.hash, + next_validators_hash: untrusted_next_vals.hash, + }, + ) } } diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index 94bb2f23d..aca2679a8 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -7,16 +7,47 @@ pub use tendermint::lite::types::Height; #[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub enum Error { + HeaderFromTheFuture { + header_time: SystemTime, + now: SystemTime, + }, ImplementationSpecific, - InsufficientValidatorsOverlap, - InsufficientVotingPower, - InvalidCommit, - InvalidCommitValue, - InvalidNextValidatorSet, - InvalidValidatorSet, - NonIncreasingHeight { got: Height, expected: Height }, - NonMonotonicBftTime, - NotWithinTrustPeriod, + InsufficientValidatorsOverlap { + total_power: u64, + signed_power: u64, + }, + InsufficientVotingPower { + total_power: u64, + voting_power: u64, + }, + InvalidCommit { + total_power: u64, + signed_power: u64, + }, + InvalidCommitValue { + header_hash: Hash, + commit_hash: Hash, + }, + InvalidNextValidatorSet { + header_next_validators_hash: Hash, + next_validators_hash: Hash, + }, + InvalidValidatorSet { + header_validators_hash: Hash, + validators_hash: Hash, + }, + NonIncreasingHeight { + got: Height, + expected: Height, + }, + NonMonotonicBftTime { + header_bft_time: SystemTime, + trusted_header_bft_time: SystemTime, + }, + NotWithinTrustPeriod { + at: SystemTime, + now: SystemTime, + }, } #[derive(Clone, Debug, Display, Serialize, Deserialize)] @@ -24,8 +55,8 @@ pub enum Error { pub struct Header { pub height: Height, pub bft_time: SystemTime, - pub validator_set_hash: Hash, - pub next_validator_set_hash: Hash, + pub validators_hash: Hash, + pub next_validators_hash: Hash, pub hash: Hash, // TODO: What if we don't have this } From 6c8a011f3871e645abe2b56bbc35e0244351f044 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 12:51:40 +0200 Subject: [PATCH 007/100] Proper error handling with thiserror+anomaly --- light-spike/Cargo.toml | 2 + light-spike/src/errors.rs | 69 ++++++++++++ light-spike/src/lib.rs | 1 + light-spike/src/predicates.rs | 5 +- light-spike/src/predicates/production.rs | 133 +++++++++++++++-------- light-spike/src/prelude.rs | 2 + 6 files changed, 163 insertions(+), 49 deletions(-) create mode 100644 light-spike/src/errors.rs diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index 7a2b0579d..655af58f6 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -12,6 +12,8 @@ derive_more = "0.99.5" serde_derive = "1.0.106" serde = "1.0.106" typetag = "0.1.4" +thiserror = "1.0.15" +anomaly = "0.2.0" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-spike/src/errors.rs b/light-spike/src/errors.rs new file mode 100644 index 000000000..8d5acde35 --- /dev/null +++ b/light-spike/src/errors.rs @@ -0,0 +1,69 @@ +use anomaly::{BoxError, Context}; +use serde::{Deserialize, Serialize}; +use std::time::SystemTime; +use thiserror::Error; + +use crate::prelude::*; + +/// Ensure a condition holds, returning an error if it doesn't (ala `assert`) +#[macro_export] +macro_rules! ensure { + ($cond:expr, $kind:expr) => { + if !($cond) { + return Err($kind.into()); + } + }; +} + +pub type Error = anomaly::Error; + +#[derive(Debug, Clone, Error, Serialize, Deserialize)] +pub enum ErrorKind { + #[error("header from the future: header_time={header_time:?} now={now:?}")] + HeaderFromTheFuture { + header_time: SystemTime, + now: SystemTime, + }, + #[error("implementation specific")] + ImplementationSpecific, + #[error( + "insufficient validators overlap: total_power={total_power} signed_power={signed_power}" + )] + InsufficientValidatorsOverlap { total_power: u64, signed_power: u64 }, + #[error("insufficient voting power: total_power={total_power} voting_power={voting_power}")] + InsufficientVotingPower { total_power: u64, voting_power: u64 }, + #[error("invalid commit: total_power={total_power} signed_power={signed_power}")] + InvalidCommit { total_power: u64, signed_power: u64 }, + #[error("invalid commit value: header_hash={header_hash} commit_hash={commit_hash}")] + InvalidCommitValue { + header_hash: Hash, + commit_hash: Hash, + }, + #[error("invalid next validator set: header_next_validators_hash={header_next_validators_hash} next_validators_hash={next_validators_hash}")] + InvalidNextValidatorSet { + header_next_validators_hash: Hash, + next_validators_hash: Hash, + }, + #[error("invalid validator set: header_validators_hash={header_validators_hash} validators_hash={validators_hash}")] + InvalidValidatorSet { + header_validators_hash: Hash, + validators_hash: Hash, + }, + #[error("non increasing height: got={got} expected={expected}")] + NonIncreasingHeight { got: Height, expected: Height }, + #[error("non monotonic BFT time: header_bft_time={header_bft_time:?} trusted_header_bft_time={trusted_header_bft_time:?}")] + NonMonotonicBftTime { + header_bft_time: SystemTime, + trusted_header_bft_time: SystemTime, + }, + #[error("not withing trust period: at={at:?} now={now:?}")] + NotWithinTrustPeriod { at: SystemTime, now: SystemTime }, +} + +impl ErrorKind { + /// Add additional context (i.e. include a source error and capture a backtrace). + /// You can convert the resulting `Context` into an `Error` by calling `.into()`. + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) + } +} diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 22d42528b..3d117d639 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -1,4 +1,5 @@ pub mod components; +pub mod errors; pub mod operations; pub mod predicates; pub mod prelude; diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 525ee77c5..c0c1e15cb 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -56,7 +56,7 @@ pub trait VerificationPredicates { validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &impl VotingPowerCalculator, - ) -> Result<(), (Error, u64, u64)>; + ) -> Result<(), Error>; fn has_sufficient_validators_overlap( &self, @@ -123,8 +123,7 @@ pub fn verify_untrusted_light_block( &untrusted_sh.validators, &trust_threshold, &voting_power_calculator, - ) - .map_err(|(e, _, _)| e)?; + )?; } else { pred.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index 5d3c399a1..88097bf89 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -9,10 +9,15 @@ impl VerificationPredicates for ProductionPredicates { signed_header: &SignedHeader, validators: &ValidatorSet, ) -> Result<(), Error> { - (signed_header.validators_hash == validators.hash).true_or(Error::InvalidValidatorSet { - header_validators_hash: signed_header.validators_hash, - validators_hash: validators.hash, - }) + ensure!( + signed_header.validators_hash == validators.hash, + ErrorKind::InvalidValidatorSet { + header_validators_hash: signed_header.validators_hash, + validators_hash: validators.hash, + } + ); + + Ok(()) } fn next_validators_match( @@ -20,10 +25,15 @@ impl VerificationPredicates for ProductionPredicates { signed_header: &SignedHeader, validators: &ValidatorSet, ) -> Result<(), Error> { - (signed_header.validators_hash == validators.hash).true_or(Error::InvalidNextValidatorSet { - header_next_validators_hash: signed_header.validators_hash, - next_validators_hash: validators.hash, - }) + ensure!( + signed_header.validators_hash == validators.hash, + ErrorKind::InvalidNextValidatorSet { + header_next_validators_hash: signed_header.validators_hash, + next_validators_hash: validators.hash, + } + ); + + Ok(()) } fn header_matches_commit( @@ -33,10 +43,16 @@ impl VerificationPredicates for ProductionPredicates { header_hasher: impl HeaderHasher, ) -> Result<(), Error> { let header_hash = header_hasher.hash(header); - (header_hash == commit.header_hash).true_or(Error::InvalidCommitValue { - header_hash, - commit_hash: commit.header_hash, - }) + + ensure!( + header_hash == commit.header_hash, + ErrorKind::InvalidCommitValue { + header_hash, + commit_hash: commit.header_hash, + } + ); + + Ok(()) } fn valid_commit( @@ -57,12 +73,18 @@ impl VerificationPredicates for ProductionPredicates { let header_time: SystemTime = header.bft_time.into(); let expires_at = header_time + trusting_period; - (header_time < now && expires_at > now).true_or(Error::NotWithinTrustPeriod { - at: expires_at, - now, - })?; + ensure!( + header_time < now && expires_at > now, + ErrorKind::NotWithinTrustPeriod { + at: expires_at, + now, + } + ); - (header_time <= now).true_or(Error::HeaderFromTheFuture { header_time, now })?; + ensure!( + header_time <= now, + ErrorKind::HeaderFromTheFuture { header_time, now } + ); Ok(()) } @@ -72,10 +94,15 @@ impl VerificationPredicates for ProductionPredicates { untrusted_header: &Header, trusted_header: &Header, ) -> Result<(), Error> { - (untrusted_header.bft_time > trusted_header.bft_time).true_or(Error::NonMonotonicBftTime { - header_bft_time: untrusted_header.bft_time, - trusted_header_bft_time: trusted_header.bft_time, - }) + ensure!( + untrusted_header.bft_time > trusted_header.bft_time, + ErrorKind::NonMonotonicBftTime { + header_bft_time: untrusted_header.bft_time, + trusted_header_bft_time: trusted_header.bft_time, + } + ); + + Ok(()) } fn is_monotonic_height( @@ -83,10 +110,15 @@ impl VerificationPredicates for ProductionPredicates { untrusted_header: &Header, trusted_header: &Header, ) -> Result<(), Error> { - (untrusted_header.height > trusted_header.height).true_or(Error::NonIncreasingHeight { - got: untrusted_header.height, - expected: trusted_header.height + 1, - }) + ensure!( + untrusted_header.height > trusted_header.height, + ErrorKind::NonIncreasingHeight { + got: untrusted_header.height, + expected: trusted_header.height + 1, + } + ); + + Ok(()) } fn has_sufficient_voting_power( @@ -95,21 +127,19 @@ impl VerificationPredicates for ProductionPredicates { validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &impl VotingPowerCalculator, - ) -> Result<(), (Error, u64, u64)> { + ) -> Result<(), Error> { let total_power = calculator.total_power_of(validators); let voting_power = calculator.voting_power_in(commit, validators); - let result = - voting_power * trust_threshold.denominator > total_power * trust_threshold.numerator; - - result.true_or(( - Error::InsufficientVotingPower { + ensure!( + voting_power * trust_threshold.denominator > total_power * trust_threshold.numerator, + ErrorKind::InsufficientVotingPower { total_power, voting_power, - }, - total_power, - voting_power, - )) + } + ); + + Ok(()) } fn has_sufficient_validators_overlap( @@ -125,12 +155,15 @@ impl VerificationPredicates for ProductionPredicates { trust_threshold, calculator, ) - .map_err( - |(_, total_power, signed_power)| Error::InsufficientValidatorsOverlap { + .map_err(|_| { + let total_power = calculator.total_power_of(trusted_validators); + let signed_power = calculator.voting_power_in(untrusted_commit, trusted_validators); + ErrorKind::InsufficientValidatorsOverlap { total_power, signed_power, - }, - ) + } + .into() + }) } fn has_sufficient_signers_overlap( @@ -146,9 +179,14 @@ impl VerificationPredicates for ProductionPredicates { trust_threshold, calculator, ) - .map_err(|(_, total_power, signed_power)| Error::InvalidCommit { - total_power, - signed_power, + .map_err(|_| { + let total_power = calculator.total_power_of(untrusted_validators); + let signed_power = calculator.voting_power_in(untrusted_commit, untrusted_validators); + ErrorKind::InvalidCommit { + total_power, + signed_power, + } + .into() }) } @@ -157,11 +195,14 @@ impl VerificationPredicates for ProductionPredicates { untrusted_sh: &SignedHeader, untrusted_next_vals: &ValidatorSet, ) -> Result<(), Error> { - (untrusted_sh.header.next_validators_hash != untrusted_next_vals.hash).false_or( - Error::InvalidNextValidatorSet { + ensure!( + untrusted_sh.header.next_validators_hash == untrusted_next_vals.hash, + ErrorKind::InvalidNextValidatorSet { header_next_validators_hash: untrusted_next_vals.hash, next_validators_hash: untrusted_next_vals.hash, - }, - ) + } + ); + + Ok(()) } } diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index a4c6cad33..9b89556e6 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,3 +1,5 @@ +pub use crate::ensure; +pub use crate::errors::*; pub use crate::operations::*; pub use crate::trace::*; pub use crate::types::*; From 333afd5ba4a8e313fab51f8921abe82dc6708835 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 14:04:23 +0200 Subject: [PATCH 008/100] Make events PartialEq+Eq --- light-spike/src/components/rpc.rs | 18 +++++----- light-spike/src/event.rs | 32 +++++++++++++++++ light-spike/src/lib.rs | 1 + light-spike/src/prelude.rs | 3 +- light-spike/src/trace.rs | 25 ++++++------- light-spike/src/types.rs | 59 ++++--------------------------- tendermint/src/rpc/error.rs | 2 +- 7 files changed, 62 insertions(+), 78 deletions(-) create mode 100644 light-spike/src/event.rs diff --git a/light-spike/src/components/rpc.rs b/light-spike/src/components/rpc.rs index d4b84b36e..8661e781b 100644 --- a/light-spike/src/components/rpc.rs +++ b/light-spike/src/components/rpc.rs @@ -1,34 +1,32 @@ -use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::future::Future; use std::sync::mpsc::Sender; + +use serde::{Deserialize, Serialize}; use tendermint::{block, rpc}; use crate::prelude::*; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum RpcError { RpcError(rpc::Error), } -#[typetag::serde] -impl Event for RpcError {} +impl_event!(RpcError); -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum RpcInput { FetchState(Height), } -#[typetag::serde] -impl Event for RpcInput {} +impl_event!(RpcInput); -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum RpcOutput { FetchedLightBlock(LightBlock), } -#[typetag::serde] -impl Event for RpcOutput {} +impl_event!(RpcOutput); pub struct Rpc { rpc_client: rpc::Client, diff --git a/light-spike/src/event.rs b/light-spike/src/event.rs new file mode 100644 index 000000000..a1bf3c605 --- /dev/null +++ b/light-spike/src/event.rs @@ -0,0 +1,32 @@ +use std::any::Any; +use std::fmt::Debug; + +#[macro_export] +macro_rules! impl_event { + ($type:ty) => { + #[::typetag::serde] + impl $crate::event::Event for $type { + fn as_any(&self) -> &dyn ::std::any::Any { + self + } + + fn box_eq(&self, other: &dyn ::std::any::Any) -> bool { + other.downcast_ref::().map_or(false, |a| self == a) + } + } + }; +} + +#[typetag::serde(tag = "type")] +pub trait Event: Any + Debug { + fn as_any(&self) -> &dyn Any; + fn box_eq(&self, other: &dyn Any) -> bool; +} + +pub type BoxedEvent = Box; + +impl PartialEq for BoxedEvent { + fn eq(&self, other: &Self) -> bool { + self.box_eq(other.as_any()) + } +} diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 3d117d639..85add234e 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -1,5 +1,6 @@ pub mod components; pub mod errors; +pub mod event; pub mod operations; pub mod predicates; pub mod prelude; diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 9b89556e6..71796eca4 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,8 +1,9 @@ -pub use crate::ensure; pub use crate::errors::*; +pub use crate::event::*; pub use crate::operations::*; pub use crate::trace::*; pub use crate::types::*; +pub use crate::{ensure, impl_event}; pub use std::time::{Duration, SystemTime}; diff --git a/light-spike/src/trace.rs b/light-spike/src/trace.rs index 5e0e986e3..229535586 100644 --- a/light-spike/src/trace.rs +++ b/light-spike/src/trace.rs @@ -1,12 +1,8 @@ -use std::fmt::Debug; use std::sync::mpsc::{channel, Receiver, Sender}; use serde::{Serialize, Serializer}; -#[typetag::serde(tag = "type")] -pub trait Event: Debug {} - -pub type BoxedEvent = Box; +use crate::event::BoxedEvent; pub struct Trace { events: Vec, @@ -46,22 +42,22 @@ impl Trace { #[cfg(test)] mod tests { - use super::*; use serde::{Deserialize, Serialize}; - #[derive(Debug, Serialize, Deserialize)] + use super::*; + use crate::event::Event; + use crate::impl_event; + + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] struct Foo { foo: u32, } - #[derive(Debug, Serialize, Deserialize)] + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] struct Bar { bar: Option, } - - #[typetag::serde] - impl Event for Foo {} - #[typetag::serde] - impl Event for Bar {} + impl_event!(Foo); + impl_event!(Bar); #[test] fn test_serialize() { @@ -86,7 +82,8 @@ mod tests { let events: Vec> = serde_json::from_str(&as_string).unwrap(); for event in events { - dbg!(event); + assert_eq!(&event, &event); + dbg!(&event); } } } diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index aca2679a8..756c127e3 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -5,52 +5,7 @@ use std::time::SystemTime; pub use tendermint::hash::Hash; pub use tendermint::lite::types::Height; -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -pub enum Error { - HeaderFromTheFuture { - header_time: SystemTime, - now: SystemTime, - }, - ImplementationSpecific, - InsufficientValidatorsOverlap { - total_power: u64, - signed_power: u64, - }, - InsufficientVotingPower { - total_power: u64, - voting_power: u64, - }, - InvalidCommit { - total_power: u64, - signed_power: u64, - }, - InvalidCommitValue { - header_hash: Hash, - commit_hash: Hash, - }, - InvalidNextValidatorSet { - header_next_validators_hash: Hash, - next_validators_hash: Hash, - }, - InvalidValidatorSet { - header_validators_hash: Hash, - validators_hash: Hash, - }, - NonIncreasingHeight { - got: Height, - expected: Height, - }, - NonMonotonicBftTime { - header_bft_time: SystemTime, - trusted_header_bft_time: SystemTime, - }, - NotWithinTrustPeriod { - at: SystemTime, - now: SystemTime, - }, -} - -#[derive(Clone, Debug, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Header { pub height: Height, @@ -60,7 +15,7 @@ pub struct Header { pub hash: Hash, // TODO: What if we don't have this } -#[derive(Clone, Debug, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct ValidatorSet { pub hash: Hash, @@ -72,20 +27,20 @@ impl From> for ValidatorSet { } } -#[derive(Clone, Debug, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Commit { pub header_hash: Hash, } -#[derive(Copy, Clone, Debug, Display, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct TrustThreshold { pub numerator: u64, pub denominator: u64, } -#[derive(Clone, Debug, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct SignedHeader { pub header: Header, @@ -100,14 +55,14 @@ impl From for SignedHeader { } } -#[derive(Clone, Debug, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct TrustedState { pub header: Header, pub validators: ValidatorSet, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct LightBlock { pub height: Height, pub signed_header: SignedHeader, diff --git a/tendermint/src/rpc/error.rs b/tendermint/src/rpc/error.rs index 78bb21c02..e87222aa6 100644 --- a/tendermint/src/rpc/error.rs +++ b/tendermint/src/rpc/error.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Display}; use thiserror::Error; /// Tendermint RPC errors -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct Error { /// Error code code: Code, From 93dbe537afb1df4b7c87c2358eb52852375ece50 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 15:42:51 +0200 Subject: [PATCH 009/100] Implement verifier --- light-spike/src/components/verifier.rs | 145 +++++++++++++++++++++++ light-spike/src/errors.rs | 2 +- light-spike/src/lib.rs | 8 ++ light-spike/src/predicates.rs | 124 +++++++++---------- light-spike/src/predicates/production.rs | 1 + light-spike/src/prelude.rs | 1 + 6 files changed, 219 insertions(+), 62 deletions(-) diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index e69de29bb..d734d71aa 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -0,0 +1,145 @@ +use std::sync::mpsc::Sender; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::prelude::*; + +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +pub enum VerifierError { + #[error("invalid light block")] + InvalidLightBlock(crate::errors::ErrorKind), +} + +impl_event!(VerifierError); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum VerifierInput { + VerifyUntrustedLightBlock(LightBlock), +} + +impl_event!(VerifierInput); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum VerifierOutput { + ValidLightBlock(TrustedState), + NeedBisectionAt { + pivot_height: Height, + trusted_state: TrustedState, + trust_threshold: TrustThreshold, + }, +} + +impl_event!(VerifierOutput); + +pub struct Verifier { + trace: Sender, + predicates: VP, + voting_power_calculator: Box, + commit_validator: Box, + header_hasher: Box, +} + +impl Verifier +where + VP: VerificationPredicates, +{ + pub fn new( + trace: Sender, + predicates: VP, + voting_power_calculator: impl VotingPowerCalculator + 'static, + commit_validator: impl CommitValidator + 'static, + header_hasher: impl HeaderHasher + 'static, + ) -> Self { + Self { + trace, + predicates, + voting_power_calculator: Box::new(voting_power_calculator), + commit_validator: Box::new(commit_validator), + header_hasher: Box::new(header_hasher), + } + } + + pub fn verify_untrusted_light_block( + &self, + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + trusting_period: Duration, + now: SystemTime, + ) -> Result { + self.trace(VerifierInput::VerifyUntrustedLightBlock( + light_block.clone(), + )); + + let verification_result = self.predicates.verify_untrusted_light_block( + &self.voting_power_calculator, + &self.commit_validator, + &self.header_hasher, + &trusted_state, + &light_block, + &trust_threshold, + trusting_period, + now, + ); + + match verification_result { + Ok(()) => self.emit_verified_light_block(light_block), + Err(e) => match e.kind() { + ErrorKind::InsufficientVotingPower { .. } => { + self.on_insufficient_voting_power(trusted_state, light_block, trust_threshold) + } + kind => { + let output = VerifierError::InvalidLightBlock(kind.to_owned()); + self.trace(output.clone()); + Err(output) + } + }, + } + } + + fn emit_verified_light_block( + &self, + light_block: LightBlock, + ) -> Result { + let new_trusted_state = TrustedState { + header: light_block.signed_header.header, + validators: light_block.validator_set, + }; + + let output = VerifierOutput::ValidLightBlock(new_trusted_state); + + self.trace(output.clone()); + + Ok(output) + } + + pub fn on_insufficient_voting_power( + &self, + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + ) -> Result { + // Get the pivot height for bisection. + let trusted_height = trusted_state.header.height; + let untrusted_height = light_block.height; + let pivot_height = trusted_height + .checked_add(untrusted_height) + .expect("height overflow") + / 2; + + let output = VerifierOutput::NeedBisectionAt { + pivot_height, + trusted_state, + trust_threshold, + }; + + self.trace(output.clone()); + + Ok(output) + } + + fn trace(&self, e: impl Event + 'static) { + self.trace.send(Box::new(e)).expect("could not trace event"); + } +} diff --git a/light-spike/src/errors.rs b/light-spike/src/errors.rs index 8d5acde35..67f6d1f4c 100644 --- a/light-spike/src/errors.rs +++ b/light-spike/src/errors.rs @@ -17,7 +17,7 @@ macro_rules! ensure { pub type Error = anomaly::Error; -#[derive(Debug, Clone, Error, Serialize, Deserialize)] +#[derive(Debug, Clone, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum ErrorKind { #[error("header from the future: header_time={header_time:?} now={now:?}")] HeaderFromTheFuture { diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 85add234e..5eba3fa03 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -1,3 +1,11 @@ +#![deny(rust_2018_idioms)] +#![deny(nonstandard_style)] +#![warn( + unreachable_pub, + // missing_docs, + // missing_doc_code_examples + )] + pub mod components; pub mod errors; pub mod event; diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index c0c1e15cb..08e6aa138 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -79,74 +79,76 @@ pub trait VerificationPredicates { untrusted_sh: &SignedHeader, untrusted_next_vals: &ValidatorSet, ) -> Result<(), Error>; -} -pub fn verify_untrusted_light_block( - pred: impl VerificationPredicates, - voting_power_calculator: impl VotingPowerCalculator, - commit_validator: impl CommitValidator, - header_hasher: impl HeaderHasher, - trusted_state: &TrustedState, - untrusted_sh: &SignedHeader, - untrusted_vals: &ValidatorSet, - untrusted_next_vals: &ValidatorSet, - trust_threshold: &TrustThreshold, - trusting_period: Duration, - now: SystemTime, -) -> Result<(), Error> { - // Ensure the latest trusted header hasn't expired - pred.is_within_trust_period(&trusted_state.header, trusting_period, now)?; - - // Ensure the header validator hashes match the given validators - pred.validator_sets_match(&untrusted_sh, &untrusted_vals)?; - - // Ensure the header next validator hashes match the given next validators - pred.next_validators_match(&untrusted_sh, &untrusted_next_vals)?; - - // Ensure the header matches the commit - pred.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, &header_hasher)?; - - // Additional implementation specific validation - pred.valid_commit( - &untrusted_sh.commit, - &untrusted_sh.validators, - &commit_validator, - )?; - - pred.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; - - if untrusted_sh.header.height == trusted_state.header.height { - pred.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; - } else if untrusted_sh.header.height > trusted_state.header.height { - pred.has_sufficient_voting_power( + fn verify_untrusted_light_block( + &self, + voting_power_calculator: impl VotingPowerCalculator, + commit_validator: impl CommitValidator, + header_hasher: impl HeaderHasher, + trusted_state: &TrustedState, + light_block: &LightBlock, + trust_threshold: &TrustThreshold, + trusting_period: Duration, + now: SystemTime, + ) -> Result<(), Error> { + let untrusted_sh = &light_block.signed_header; + let untrusted_vals = &light_block.validator_set; + let untrusted_next_vals = &light_block.next_validator_set; + + // Ensure the latest trusted header hasn't expired + self.is_within_trust_period(&trusted_state.header, trusting_period, now)?; + + // Ensure the header validator hashes match the given validators + self.validator_sets_match(&untrusted_sh, &untrusted_vals)?; + + // Ensure the header next validator hashes match the given next validators + self.next_validators_match(&untrusted_sh, &untrusted_next_vals)?; + + // Ensure the header matches the commit + self.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, &header_hasher)?; + + // Additional implementation specific validation + self.valid_commit( &untrusted_sh.commit, &untrusted_sh.validators, - &trust_threshold, - &voting_power_calculator, + &commit_validator, )?; - } else { - pred.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; - // Ensure that the check above will always fail. - unreachable!(); - } + self.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; + + if untrusted_sh.header.height == trusted_state.header.height { + self.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; + } else if untrusted_sh.header.height > trusted_state.header.height { + self.has_sufficient_voting_power( + &untrusted_sh.commit, + &untrusted_sh.validators, + &trust_threshold, + &voting_power_calculator, + )?; + } else { + self.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; + + // Ensure that the check above will always fail. + unreachable!(); + } - // All validation passed successfully. - // Verify the validators correctly committed the block. + // All validation passed successfully. + // Verify the validators correctly committed the block. - pred.has_sufficient_validators_overlap( - &untrusted_sh.commit, - &trusted_state.validators, - &trust_threshold, - &voting_power_calculator, - )?; + self.has_sufficient_validators_overlap( + &untrusted_sh.commit, + &trusted_state.validators, + &trust_threshold, + &voting_power_calculator, + )?; - pred.has_sufficient_signers_overlap( - &untrusted_sh.commit, - &untrusted_vals, - &trust_threshold, - &voting_power_calculator, - )?; + self.has_sufficient_signers_overlap( + &untrusted_sh.commit, + &untrusted_vals, + &trust_threshold, + &voting_power_calculator, + )?; - Ok(()) + Ok(()) + } } diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index 88097bf89..e7ee92cf7 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -1,6 +1,7 @@ use super::VerificationPredicates; use crate::prelude::*; +#[derive(Copy, Clone, Debug)] pub struct ProductionPredicates; impl VerificationPredicates for ProductionPredicates { diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 71796eca4..316b565f3 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,6 +1,7 @@ pub use crate::errors::*; pub use crate::event::*; pub use crate::operations::*; +pub use crate::predicates::VerificationPredicates; pub use crate::trace::*; pub use crate::types::*; pub use crate::{ensure, impl_event}; From 1f325e050c4a5cf20e556c52aa1acfb84a2e7677 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 16:23:50 +0200 Subject: [PATCH 010/100] Implement scheduler and bisection --- light-spike/src/components.rs | 1 + light-spike/src/components/rpc.rs | 2 +- light-spike/src/components/scheduler.rs | 177 ++++++++++++++++++++++++ light-spike/src/components/verifier.rs | 79 ++--------- light-spike/src/lib.rs | 1 + light-spike/src/prelude.rs | 4 + light-spike/src/trusted_store.rs | 61 ++++++++ 7 files changed, 260 insertions(+), 65 deletions(-) create mode 100644 light-spike/src/components/scheduler.rs create mode 100644 light-spike/src/trusted_store.rs diff --git a/light-spike/src/components.rs b/light-spike/src/components.rs index fe482e3eb..ff6533d2f 100644 --- a/light-spike/src/components.rs +++ b/light-spike/src/components.rs @@ -1,2 +1,3 @@ pub mod rpc; +pub mod scheduler; pub mod verifier; diff --git a/light-spike/src/components/rpc.rs b/light-spike/src/components/rpc.rs index 8661e781b..b44691748 100644 --- a/light-spike/src/components/rpc.rs +++ b/light-spike/src/components/rpc.rs @@ -7,7 +7,7 @@ use tendermint::{block, rpc}; use crate::prelude::*; -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum RpcError { RpcError(rpc::Error), } diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs new file mode 100644 index 000000000..bcc5f9ffd --- /dev/null +++ b/light-spike/src/components/scheduler.rs @@ -0,0 +1,177 @@ +use std::sync::mpsc::Sender; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use super::rpc::RpcError; +use super::verifier::VerifierError; +use crate::prelude::*; + +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +pub enum SchedulerError { + #[error("RPC error")] + RpcError(RpcError), + #[error("invalid light block: {0}")] + InvalidLightBlock(VerifierError), +} + +impl_event!(SchedulerError); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum SchedulerInput { + VerifyUntrustedLightBlock(LightBlock), +} + +impl_event!(SchedulerInput); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum SchedulerOutput { + ValidLightBlock(Vec), + PerformBisectionAt { + pivot_height: Height, + trusted_state: TrustedState, + trust_threshold: TrustThreshold, + }, +} + +impl_event!(SchedulerOutput); + +pub struct Scheduler { + trace: Sender, + rpc: Rpc, + verifier: Verifier, // FIXME: Figure another way to get at the verifier + trusted_store: TSReadWriter, +} + +impl Scheduler +where + VP: VerificationPredicates, +{ + pub fn new( + trace: Sender, + rpc: Rpc, + verifier: Verifier, + trusted_store: TSReadWriter, + ) -> Self { + Self { + trace, + rpc, + verifier, + trusted_store, + } + } + + pub fn verify_untrusted_light_block( + &mut self, + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + trusting_period: Duration, + now: SystemTime, + ) -> Result, SchedulerError> { + self.trace(SchedulerInput::VerifyUntrustedLightBlock( + light_block.clone(), + )); + + if let Some(trusted_state_in_store) = self.trusted_store.get(light_block.height) { + let output = vec![trusted_state_in_store.clone()]; + self.trace(SchedulerOutput::ValidLightBlock(output.clone())); + return Ok(output); + } + + let verifier_result = self.verifier.verify_untrusted_light_block( + trusted_state.clone(), + light_block.clone(), + trust_threshold, + trusting_period, + now, + ); + + match verifier_result { + Ok(trusted_state) => self.verification_succeded(trusted_state), + Err(VerifierError::InvalidLightBlock(ErrorKind::InsufficientVotingPower { + .. + })) => self.perform_bisection( + trusted_state, + light_block, + trust_threshold, + trusting_period, + now, + ), + Err(err) => { + let output = SchedulerError::InvalidLightBlock(err); + self.trace(output.clone()); + Err(output) + } + } + } + + fn verification_succeded( + &mut self, + new_trusted_state: TrustedState, + ) -> Result, SchedulerError> { + self.trace(SchedulerOutput::ValidLightBlock(vec![ + new_trusted_state.clone() + ])); + + self.trusted_store + .set(new_trusted_state.header.height, new_trusted_state.clone()); + + Ok(vec![new_trusted_state]) + } + + pub fn perform_bisection( + &mut self, + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + trusting_period: Duration, + now: SystemTime, + ) -> Result, SchedulerError> { + // Get the pivot height for bisection. + let trusted_height = trusted_state.header.height; + let untrusted_height = light_block.height; + let pivot_height = trusted_height + .checked_add(untrusted_height) + .expect("height overflow") + / 2; + + self.trace(SchedulerOutput::PerformBisectionAt { + pivot_height, + trusted_state: trusted_state.clone(), + trust_threshold: trust_threshold.clone(), + }); + + let pivot_light_block = self + .rpc + .fetch_light_block(pivot_height) + .map_err(SchedulerError::RpcError)?; + + let mut pivot_trusted_states = self.verify_untrusted_light_block( + trusted_state, + pivot_light_block, + trust_threshold, + trusting_period, + now, + )?; + + let trusted_state_left = pivot_trusted_states.last().cloned().unwrap(); // FIXME: Unwrap + + let mut new_trusted_states = self.verify_untrusted_light_block( + trusted_state_left, + light_block, + trust_threshold, + trusting_period, + now, + )?; + + new_trusted_states.append(&mut pivot_trusted_states); + new_trusted_states.sort_by_key(|ts| ts.header.height); + + Ok(new_trusted_states) + } + + fn trace(&self, e: impl Event + 'static) { + self.trace.send(Box::new(e)).expect("could not trace event"); + } +} diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index d734d71aa..9331d53b7 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -23,11 +23,6 @@ impl_event!(VerifierInput); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierOutput { ValidLightBlock(TrustedState), - NeedBisectionAt { - pivot_height: Height, - trusted_state: TrustedState, - trust_threshold: TrustThreshold, - }, } impl_event!(VerifierOutput); @@ -67,76 +62,32 @@ where trust_threshold: TrustThreshold, trusting_period: Duration, now: SystemTime, - ) -> Result { + ) -> Result { self.trace(VerifierInput::VerifyUntrustedLightBlock( light_block.clone(), )); - let verification_result = self.predicates.verify_untrusted_light_block( - &self.voting_power_calculator, - &self.commit_validator, - &self.header_hasher, - &trusted_state, - &light_block, - &trust_threshold, - trusting_period, - now, - ); - - match verification_result { - Ok(()) => self.emit_verified_light_block(light_block), - Err(e) => match e.kind() { - ErrorKind::InsufficientVotingPower { .. } => { - self.on_insufficient_voting_power(trusted_state, light_block, trust_threshold) - } - kind => { - let output = VerifierError::InvalidLightBlock(kind.to_owned()); - self.trace(output.clone()); - Err(output) - } - }, - } - } + self.predicates + .verify_untrusted_light_block( + &self.voting_power_calculator, + &self.commit_validator, + &self.header_hasher, + &trusted_state, + &light_block, + &trust_threshold, + trusting_period, + now, + ) + .map_err(|e| VerifierError::InvalidLightBlock(e.kind().to_owned()))?; - fn emit_verified_light_block( - &self, - light_block: LightBlock, - ) -> Result { let new_trusted_state = TrustedState { header: light_block.signed_header.header, validators: light_block.validator_set, }; - let output = VerifierOutput::ValidLightBlock(new_trusted_state); - - self.trace(output.clone()); - - Ok(output) - } - - pub fn on_insufficient_voting_power( - &self, - trusted_state: TrustedState, - light_block: LightBlock, - trust_threshold: TrustThreshold, - ) -> Result { - // Get the pivot height for bisection. - let trusted_height = trusted_state.header.height; - let untrusted_height = light_block.height; - let pivot_height = trusted_height - .checked_add(untrusted_height) - .expect("height overflow") - / 2; - - let output = VerifierOutput::NeedBisectionAt { - pivot_height, - trusted_state, - trust_threshold, - }; - - self.trace(output.clone()); + self.trace(VerifierOutput::ValidLightBlock(new_trusted_state.clone())); - Ok(output) + Ok(new_trusted_state) } fn trace(&self, e: impl Event + 'static) { diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 5eba3fa03..5c4cab29f 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -13,6 +13,7 @@ pub mod operations; pub mod predicates; pub mod prelude; pub mod trace; +pub mod trusted_store; pub mod types; #[cfg(test)] diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 316b565f3..aec431180 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,8 +1,12 @@ +pub use crate::components::rpc::Rpc; +pub use crate::components::scheduler::Scheduler; +pub use crate::components::verifier::Verifier; pub use crate::errors::*; pub use crate::event::*; pub use crate::operations::*; pub use crate::predicates::VerificationPredicates; pub use crate::trace::*; +pub use crate::trusted_store::*; pub use crate::types::*; pub use crate::{ensure, impl_event}; diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs new file mode 100644 index 000000000..21d43027c --- /dev/null +++ b/light-spike/src/trusted_store.rs @@ -0,0 +1,61 @@ +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +use crate::prelude::*; + +pub struct TrustedStore { + store: HashMap, +} + +impl TrustedStore { + pub fn new() -> Self { + Self { + store: HashMap::new(), + } + } + + pub fn split(self) -> (TSReader, TSReadWriter) { + let store = Arc::new(RwLock::new(self)); + let reader = TSReader { ts: store.clone() }; + let writer = TSReadWriter { ts: store }; + + (reader, writer) + } +} + +impl TrustedStore { + pub fn get(&self, height: Height) -> Option<&TrustedState> { + self.store.get(&height) + } + + pub fn set(&mut self, height: Height, trusted_state: TrustedState) { + self.store.insert(height, trusted_state); + } +} + +pub struct TSReader { + ts: Arc>, +} + +impl TSReader { + pub fn get(&self, height: Height) -> Option { + self.ts.read().unwrap().get(height).cloned() + } +} + +pub struct TSReadWriter { + ts: Arc>, +} + +impl TSReadWriter { + pub fn get(&self, height: Height) -> Option { + self.ts.read().unwrap().get(height).cloned() + } + + pub fn set(&mut self, height: Height, trusted_state: TrustedState) { + let mut ts = self.ts.write().unwrap(); + ts.set(height, trusted_state); + } +} From 93b148b4595d97b1e6af79f5fa9f5d85d36861fe Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 16:34:12 +0200 Subject: [PATCH 011/100] Remove write access to trusted store for scheduler --- light-spike/src/components/scheduler.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index bcc5f9ffd..eaf97fe29 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -40,7 +40,7 @@ pub struct Scheduler { trace: Sender, rpc: Rpc, verifier: Verifier, // FIXME: Figure another way to get at the verifier - trusted_store: TSReadWriter, + trusted_store: TSReader, } impl Scheduler @@ -51,7 +51,7 @@ where trace: Sender, rpc: Rpc, verifier: Verifier, - trusted_store: TSReadWriter, + trusted_store: TSReader, ) -> Self { Self { trace, @@ -114,9 +114,6 @@ where new_trusted_state.clone() ])); - self.trusted_store - .set(new_trusted_state.header.height, new_trusted_state.clone()); - Ok(vec![new_trusted_state]) } From 614c87061e729a23a55cb5b0a94da7e9a9716063 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 22:52:19 +0200 Subject: [PATCH 012/100] Add a couple of FIXMEs --- light-spike/src/components/scheduler.rs | 4 +++- light-spike/src/components/verifier.rs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index eaf97fe29..921143890 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -1,3 +1,5 @@ +// FIXME: Figure out a way to decouple components + use std::sync::mpsc::Sender; use serde::{Deserialize, Serialize}; @@ -39,7 +41,7 @@ impl_event!(SchedulerOutput); pub struct Scheduler { trace: Sender, rpc: Rpc, - verifier: Verifier, // FIXME: Figure another way to get at the verifier + verifier: Verifier, trusted_store: TSReader, } diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 9331d53b7..ee581d403 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -1,3 +1,5 @@ +// FIXME: Figure out how to get rid of type parameter + use std::sync::mpsc::Sender; use serde::{Deserialize, Serialize}; From ded21c9103872a0bbcb02ee181d8a78505afe43f Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 22:52:35 +0200 Subject: [PATCH 013/100] Formatting From 745c00197156a270edebb0d60297f8fbe2fe2f3b Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 22:56:59 +0200 Subject: [PATCH 014/100] Fix clippy warnings --- light-spike/src/components/scheduler.rs | 4 ++-- light-spike/src/predicates.rs | 2 ++ light-spike/src/predicates/production.rs | 2 +- light-spike/src/trace.rs | 8 ++------ light-spike/src/trusted_store.rs | 1 + 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 921143890..6756aad73 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -76,7 +76,7 @@ where )); if let Some(trusted_state_in_store) = self.trusted_store.get(light_block.height) { - let output = vec![trusted_state_in_store.clone()]; + let output = vec![trusted_state_in_store]; self.trace(SchedulerOutput::ValidLightBlock(output.clone())); return Ok(output); } @@ -137,8 +137,8 @@ where self.trace(SchedulerOutput::PerformBisectionAt { pivot_height, + trust_threshold, trusted_state: trusted_state.clone(), - trust_threshold: trust_threshold.clone(), }); let pivot_light_block = self diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 08e6aa138..2464b7c1c 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -80,6 +80,7 @@ pub trait VerificationPredicates { untrusted_next_vals: &ValidatorSet, ) -> Result<(), Error>; + #[allow(clippy::too_many_arguments)] fn verify_untrusted_light_block( &self, voting_power_calculator: impl VotingPowerCalculator, @@ -116,6 +117,7 @@ pub trait VerificationPredicates { self.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; + #[allow(clippy::comparison_chain)] if untrusted_sh.header.height == trusted_state.header.height { self.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; } else if untrusted_sh.header.height > trusted_state.header.height { diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index e7ee92cf7..728666954 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -71,7 +71,7 @@ impl VerificationPredicates for ProductionPredicates { trusting_period: Duration, now: SystemTime, ) -> Result<(), Error> { - let header_time: SystemTime = header.bft_time.into(); + let header_time = header.bft_time; let expires_at = header_time + trusting_period; ensure!( diff --git a/light-spike/src/trace.rs b/light-spike/src/trace.rs index 229535586..4567efceb 100644 --- a/light-spike/src/trace.rs +++ b/light-spike/src/trace.rs @@ -23,12 +23,8 @@ impl Trace { } pub fn collect(&mut self) { - loop { - if let Ok(event) = self.recv.recv() { - self.events.push(event); - } else { - break; - } + while let Ok(event) = self.recv.recv() { + self.events.push(event); } } diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs index 21d43027c..ad8e82805 100644 --- a/light-spike/src/trusted_store.rs +++ b/light-spike/src/trusted_store.rs @@ -5,6 +5,7 @@ use std::{ use crate::prelude::*; +#[derive(Default)] pub struct TrustedStore { store: HashMap, } From 0969b7bd1eb94a8c16c318a374dbd5da1a777b23 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 24 Apr 2020 22:59:09 +0200 Subject: [PATCH 015/100] Fix misplaced attribute --- light-spike/src/predicates.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 2464b7c1c..1d7fcfb63 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -80,7 +80,7 @@ pub trait VerificationPredicates { untrusted_next_vals: &ValidatorSet, ) -> Result<(), Error>; - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments, clippy::comparison_chain)] fn verify_untrusted_light_block( &self, voting_power_calculator: impl VotingPowerCalculator, @@ -117,7 +117,6 @@ pub trait VerificationPredicates { self.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; - #[allow(clippy::comparison_chain)] if untrusted_sh.header.height == trusted_state.header.height { self.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; } else if untrusted_sh.header.height > trusted_state.header.height { From 5948c7bf280fc0c780485f670c96d0a540dfad8b Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 27 Apr 2020 13:30:55 +0200 Subject: [PATCH 016/100] Enable VerificationPredicates to be made into a trait object --- light-spike/src/components/scheduler.rs | 11 ++++------ light-spike/src/components/verifier.rs | 13 +++++------- light-spike/src/predicates.rs | 26 ++++++++++++------------ light-spike/src/predicates/production.rs | 10 ++++----- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 6756aad73..00a7e6a74 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -38,21 +38,18 @@ pub enum SchedulerOutput { impl_event!(SchedulerOutput); -pub struct Scheduler { +pub struct Scheduler { trace: Sender, rpc: Rpc, - verifier: Verifier, + verifier: Verifier, trusted_store: TSReader, } -impl Scheduler -where - VP: VerificationPredicates, -{ +impl Scheduler { pub fn new( trace: Sender, rpc: Rpc, - verifier: Verifier, + verifier: Verifier, trusted_store: TSReader, ) -> Self { Self { diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index ee581d403..753c1cf4c 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -29,28 +29,25 @@ pub enum VerifierOutput { impl_event!(VerifierOutput); -pub struct Verifier { +pub struct Verifier { trace: Sender, - predicates: VP, + predicates: Box, voting_power_calculator: Box, commit_validator: Box, header_hasher: Box, } -impl Verifier -where - VP: VerificationPredicates, -{ +impl Verifier { pub fn new( trace: Sender, - predicates: VP, + predicates: impl VerificationPredicates + 'static, voting_power_calculator: impl VotingPowerCalculator + 'static, commit_validator: impl CommitValidator + 'static, header_hasher: impl HeaderHasher + 'static, ) -> Self { Self { trace, - predicates, + predicates: Box::new(predicates), voting_power_calculator: Box::new(voting_power_calculator), commit_validator: Box::new(commit_validator), header_hasher: Box::new(header_hasher), diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 1d7fcfb63..b787331a0 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -21,14 +21,14 @@ pub trait VerificationPredicates { &self, header: &Header, commit: &Commit, - header_hasher: impl HeaderHasher, + header_hasher: &dyn HeaderHasher, ) -> Result<(), Error>; fn valid_commit( &self, commit: &Commit, validators: &ValidatorSet, - validator: impl CommitValidator, + validator: &dyn CommitValidator, ) -> Result<(), Error>; fn is_within_trust_period( @@ -55,7 +55,7 @@ pub trait VerificationPredicates { commit: &Commit, validators: &ValidatorSet, trust_threshold: &TrustThreshold, - calculator: &impl VotingPowerCalculator, + calculator: &dyn VotingPowerCalculator, ) -> Result<(), Error>; fn has_sufficient_validators_overlap( @@ -63,7 +63,7 @@ pub trait VerificationPredicates { untrusted_commit: &Commit, trusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, - calculator: &impl VotingPowerCalculator, + calculator: &dyn VotingPowerCalculator, ) -> Result<(), Error>; fn has_sufficient_signers_overlap( @@ -71,7 +71,7 @@ pub trait VerificationPredicates { untrusted_commit: &Commit, untrusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, - calculator: &impl VotingPowerCalculator, + calculator: &dyn VotingPowerCalculator, ) -> Result<(), Error>; fn valid_next_validator_set( @@ -83,9 +83,9 @@ pub trait VerificationPredicates { #[allow(clippy::too_many_arguments, clippy::comparison_chain)] fn verify_untrusted_light_block( &self, - voting_power_calculator: impl VotingPowerCalculator, - commit_validator: impl CommitValidator, - header_hasher: impl HeaderHasher, + voting_power_calculator: &dyn VotingPowerCalculator, + commit_validator: &dyn CommitValidator, + header_hasher: &dyn HeaderHasher, trusted_state: &TrustedState, light_block: &LightBlock, trust_threshold: &TrustThreshold, @@ -106,13 +106,13 @@ pub trait VerificationPredicates { self.next_validators_match(&untrusted_sh, &untrusted_next_vals)?; // Ensure the header matches the commit - self.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, &header_hasher)?; + self.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, header_hasher)?; // Additional implementation specific validation self.valid_commit( &untrusted_sh.commit, &untrusted_sh.validators, - &commit_validator, + commit_validator, )?; self.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; @@ -124,7 +124,7 @@ pub trait VerificationPredicates { &untrusted_sh.commit, &untrusted_sh.validators, &trust_threshold, - &voting_power_calculator, + voting_power_calculator, )?; } else { self.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; @@ -140,14 +140,14 @@ pub trait VerificationPredicates { &untrusted_sh.commit, &trusted_state.validators, &trust_threshold, - &voting_power_calculator, + voting_power_calculator, )?; self.has_sufficient_signers_overlap( &untrusted_sh.commit, &untrusted_vals, &trust_threshold, - &voting_power_calculator, + voting_power_calculator, )?; Ok(()) diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index 728666954..c1d2124a9 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -41,7 +41,7 @@ impl VerificationPredicates for ProductionPredicates { &self, header: &Header, commit: &Commit, - header_hasher: impl HeaderHasher, + header_hasher: &dyn HeaderHasher, ) -> Result<(), Error> { let header_hash = header_hasher.hash(header); @@ -60,7 +60,7 @@ impl VerificationPredicates for ProductionPredicates { &self, commit: &Commit, validators: &ValidatorSet, - validator: impl CommitValidator, + validator: &dyn CommitValidator, ) -> Result<(), Error> { validator.validate(commit, validators) } @@ -127,7 +127,7 @@ impl VerificationPredicates for ProductionPredicates { commit: &Commit, validators: &ValidatorSet, trust_threshold: &TrustThreshold, - calculator: &impl VotingPowerCalculator, + calculator: &dyn VotingPowerCalculator, ) -> Result<(), Error> { let total_power = calculator.total_power_of(validators); let voting_power = calculator.voting_power_in(commit, validators); @@ -148,7 +148,7 @@ impl VerificationPredicates for ProductionPredicates { untrusted_commit: &Commit, trusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, - calculator: &impl VotingPowerCalculator, + calculator: &dyn VotingPowerCalculator, ) -> Result<(), Error> { self.has_sufficient_voting_power( untrusted_commit, @@ -172,7 +172,7 @@ impl VerificationPredicates for ProductionPredicates { untrusted_commit: &Commit, untrusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, - calculator: &impl VotingPowerCalculator, + calculator: &dyn VotingPowerCalculator, ) -> Result<(), Error> { self.has_sufficient_voting_power( untrusted_commit, From 0661ee9cde1892482adc5309949269fc2afb3407 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 27 Apr 2020 13:31:13 +0200 Subject: [PATCH 017/100] Allow cloning TSReader --- light-spike/src/trusted_store.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs index ad8e82805..d74a7097a 100644 --- a/light-spike/src/trusted_store.rs +++ b/light-spike/src/trusted_store.rs @@ -5,7 +5,7 @@ use std::{ use crate::prelude::*; -#[derive(Default)] +#[derive(Debug, Default)] pub struct TrustedStore { store: HashMap, } @@ -36,6 +36,7 @@ impl TrustedStore { } } +#[derive(Clone, Debug)] pub struct TSReader { ts: Arc>, } @@ -46,6 +47,7 @@ impl TSReader { } } +#[derive(Debug)] pub struct TSReadWriter { ts: Arc>, } From de049fb69728045ab54fb9e4b00d45f893bfc6c4 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 27 Apr 2020 13:48:15 +0200 Subject: [PATCH 018/100] Shorter method name --- light-spike/src/components/scheduler.rs | 8 ++++---- light-spike/src/components/verifier.rs | 10 ++++------ light-spike/src/predicates.rs | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 00a7e6a74..b8b8930a8 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -60,7 +60,7 @@ impl Scheduler { } } - pub fn verify_untrusted_light_block( + pub fn verify_light_block( &mut self, trusted_state: TrustedState, light_block: LightBlock, @@ -78,7 +78,7 @@ impl Scheduler { return Ok(output); } - let verifier_result = self.verifier.verify_untrusted_light_block( + let verifier_result = self.verifier.verify_light_block( trusted_state.clone(), light_block.clone(), trust_threshold, @@ -143,7 +143,7 @@ impl Scheduler { .fetch_light_block(pivot_height) .map_err(SchedulerError::RpcError)?; - let mut pivot_trusted_states = self.verify_untrusted_light_block( + let mut pivot_trusted_states = self.verify_light_block( trusted_state, pivot_light_block, trust_threshold, @@ -153,7 +153,7 @@ impl Scheduler { let trusted_state_left = pivot_trusted_states.last().cloned().unwrap(); // FIXME: Unwrap - let mut new_trusted_states = self.verify_untrusted_light_block( + let mut new_trusted_states = self.verify_light_block( trusted_state_left, light_block, trust_threshold, diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 753c1cf4c..42ed3c01c 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -17,7 +17,7 @@ impl_event!(VerifierError); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierInput { - VerifyUntrustedLightBlock(LightBlock), + VerifyLightBlock(LightBlock), } impl_event!(VerifierInput); @@ -54,7 +54,7 @@ impl Verifier { } } - pub fn verify_untrusted_light_block( + pub fn verify_light_block( &self, trusted_state: TrustedState, light_block: LightBlock, @@ -62,12 +62,10 @@ impl Verifier { trusting_period: Duration, now: SystemTime, ) -> Result { - self.trace(VerifierInput::VerifyUntrustedLightBlock( - light_block.clone(), - )); + self.trace(VerifierInput::VerifyLightBlock(light_block.clone())); self.predicates - .verify_untrusted_light_block( + .verify_light_block( &self.voting_power_calculator, &self.commit_validator, &self.header_hasher, diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index b787331a0..51698f64d 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -81,7 +81,7 @@ pub trait VerificationPredicates { ) -> Result<(), Error>; #[allow(clippy::too_many_arguments, clippy::comparison_chain)] - fn verify_untrusted_light_block( + fn verify_light_block( &self, voting_power_calculator: &dyn VotingPowerCalculator, commit_validator: &dyn CommitValidator, From 799caaaf0b68308b1d57eeb6496674ef89826ad4 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 12:53:19 +0200 Subject: [PATCH 019/100] Decouple components using Router trait --- light-spike/Cargo.toml | 7 +- light-spike/src/components.rs | 2 + light-spike/src/components/demuxer.rs | 99 ++++++++++++++++++ light-spike/src/components/router.rs | 33 ++++++ light-spike/src/components/scheduler.rs | 132 +++++++++++++----------- light-spike/src/components/verifier.rs | 15 --- light-spike/src/prelude.rs | 8 +- light-spike/src/trusted_store.rs | 9 +- 8 files changed, 222 insertions(+), 83 deletions(-) create mode 100644 light-spike/src/components/demuxer.rs create mode 100644 light-spike/src/components/router.rs diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index 655af58f6..948b4af2d 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -8,12 +8,13 @@ edition = "2018" [dependencies] tendermint = { path = "../tendermint" } + +anomaly = "0.2.0" derive_more = "0.99.5" -serde_derive = "1.0.106" serde = "1.0.106" -typetag = "0.1.4" +serde_derive = "1.0.106" thiserror = "1.0.15" -anomaly = "0.2.0" +typetag = "0.1.4" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-spike/src/components.rs b/light-spike/src/components.rs index ff6533d2f..54c845352 100644 --- a/light-spike/src/components.rs +++ b/light-spike/src/components.rs @@ -1,3 +1,5 @@ +pub mod demuxer; +pub mod router; pub mod rpc; pub mod scheduler; pub mod verifier; diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs new file mode 100644 index 000000000..25f4f3d6a --- /dev/null +++ b/light-spike/src/components/demuxer.rs @@ -0,0 +1,99 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::prelude::*; + +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +pub enum DemuxerError {} +impl_event!(DemuxerError); + +pub struct Demuxer { + pub(super) trusted_store: TSReadWriter, + pub(super) rpc: Rpc, + pub(super) scheduler: Scheduler, + pub(super) verifier: Verifier, +} + +impl Router for &mut Demuxer { + fn query_rpc(&self, request: RpcRequest) -> RpcResponse { + match request { + RpcRequest::FetchLightBlock(height) => { + let result = self.rpc.fetch_light_block(height); + match result { + Ok(light_block) => RpcResponse::FetchedLightBlock(light_block), + Err(_err) => todo!(), + } + } + } + } + + fn query_verifier(&self, request: VerifierRequest) -> VerifierResponse { + match request { + VerifierRequest::VerifyLightBlock { + trusted_state, + light_block, + trust_threshold, + trusting_period, + now, + } => { + let result = self.verifier.verify_light_block( + trusted_state, + light_block, + trust_threshold, + trusting_period, + now, + ); + + match result { + Ok(trusted_state) => VerifierResponse::VerificationSucceeded(trusted_state), + Err(err) => VerifierResponse::VerificationFailed(err), + } + } + } + } +} + +impl Demuxer { + pub fn new( + trusted_store: TSReadWriter, + rpc: Rpc, + scheduler: Scheduler, + verifier: Verifier, + ) -> Self { + Self { + trusted_store, + rpc, + scheduler, + verifier, + } + } + + pub fn verify_light_block( + &mut self, + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + trusting_period: Duration, + now: SystemTime, + ) -> Result, DemuxerError> { + let result = self.scheduler.verify_light_block( + &self, + trusted_state, + light_block, + trust_threshold, + trusting_period, + now, + ); + + match result { + Ok(new_trusted_states) => { + for ts in &new_trusted_states { + self.trusted_store.add(ts.clone()); + } + + Ok(new_trusted_states) + } + Err(_err) => todo!(), + } + } +} diff --git a/light-spike/src/components/router.rs b/light-spike/src/components/router.rs new file mode 100644 index 000000000..210fd2a68 --- /dev/null +++ b/light-spike/src/components/router.rs @@ -0,0 +1,33 @@ +use crate::prelude::*; + +#[derive(Clone, Debug)] +pub enum RpcRequest { + FetchLightBlock(Height), +} + +#[derive(Clone, Debug)] +pub enum RpcResponse { + FetchedLightBlock(LightBlock), +} + +#[derive(Clone, Debug)] +pub enum VerifierRequest { + VerifyLightBlock { + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + trusting_period: Duration, + now: SystemTime, + }, +} + +#[derive(Clone, Debug)] +pub enum VerifierResponse { + VerificationSucceeded(TrustedState), + VerificationFailed(VerifierError), +} + +pub trait Router { + fn query_rpc(&self, request: RpcRequest) -> RpcResponse; + fn query_verifier(&self, request: VerifierRequest) -> VerifierResponse; +} diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index b8b8930a8..6fefe6cff 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -1,18 +1,10 @@ -// FIXME: Figure out a way to decouple components - -use std::sync::mpsc::Sender; - use serde::{Deserialize, Serialize}; use thiserror::Error; -use super::rpc::RpcError; -use super::verifier::VerifierError; use crate::prelude::*; #[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerError { - #[error("RPC error")] - RpcError(RpcError), #[error("invalid light block: {0}")] InvalidLightBlock(VerifierError), } @@ -39,46 +31,30 @@ pub enum SchedulerOutput { impl_event!(SchedulerOutput); pub struct Scheduler { - trace: Sender, - rpc: Rpc, - verifier: Verifier, trusted_store: TSReader, } impl Scheduler { - pub fn new( - trace: Sender, - rpc: Rpc, - verifier: Verifier, - trusted_store: TSReader, - ) -> Self { - Self { - trace, - rpc, - verifier, - trusted_store, - } + pub fn new(trusted_store: TSReader) -> Self { + Self { trusted_store } } pub fn verify_light_block( - &mut self, + &self, + router: &impl Router, trusted_state: TrustedState, light_block: LightBlock, trust_threshold: TrustThreshold, trusting_period: Duration, now: SystemTime, ) -> Result, SchedulerError> { - self.trace(SchedulerInput::VerifyUntrustedLightBlock( - light_block.clone(), - )); - if let Some(trusted_state_in_store) = self.trusted_store.get(light_block.height) { let output = vec![trusted_state_in_store]; - self.trace(SchedulerOutput::ValidLightBlock(output.clone())); return Ok(output); } - let verifier_result = self.verifier.verify_light_block( + let verifier_result = self.perform_verify_light_block( + router, trusted_state.clone(), light_block.clone(), trust_threshold, @@ -87,37 +63,76 @@ impl Scheduler { ); match verifier_result { - Ok(trusted_state) => self.verification_succeded(trusted_state), - Err(VerifierError::InvalidLightBlock(ErrorKind::InsufficientVotingPower { - .. - })) => self.perform_bisection( + VerifierResponse::VerificationSucceeded(trusted_state) => { + self.verification_succeded(trusted_state) + } + VerifierResponse::VerificationFailed(err) => self.verification_failed( + router, + err, trusted_state, light_block, trust_threshold, trusting_period, now, ), - Err(err) => { - let output = SchedulerError::InvalidLightBlock(err); - self.trace(output.clone()); - Err(output) - } } } + fn perform_verify_light_block( + &self, + router: &impl Router, + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + trusting_period: Duration, + now: SystemTime, + ) -> VerifierResponse { + router.query_verifier(VerifierRequest::VerifyLightBlock { + trusted_state, + light_block, + trust_threshold, + trusting_period, + now, + }) + } + fn verification_succeded( - &mut self, + &self, new_trusted_state: TrustedState, ) -> Result, SchedulerError> { - self.trace(SchedulerOutput::ValidLightBlock(vec![ - new_trusted_state.clone() - ])); - Ok(vec![new_trusted_state]) } - pub fn perform_bisection( - &mut self, + fn verification_failed( + &self, + router: &impl Router, + err: VerifierError, + trusted_state: TrustedState, + light_block: LightBlock, + trust_threshold: TrustThreshold, + trusting_period: Duration, + now: SystemTime, + ) -> Result, SchedulerError> { + match err { + VerifierError::InvalidLightBlock(ErrorKind::InsufficientVotingPower { .. }) => self + .perform_bisection( + router, + trusted_state, + light_block, + trust_threshold, + trusting_period, + now, + ), + err => { + let output = SchedulerError::InvalidLightBlock(err); + Err(output) + } + } + } + + fn perform_bisection( + &self, + router: &impl Router, trusted_state: TrustedState, light_block: LightBlock, trust_threshold: TrustThreshold, @@ -132,18 +147,10 @@ impl Scheduler { .expect("height overflow") / 2; - self.trace(SchedulerOutput::PerformBisectionAt { - pivot_height, - trust_threshold, - trusted_state: trusted_state.clone(), - }); - - let pivot_light_block = self - .rpc - .fetch_light_block(pivot_height) - .map_err(SchedulerError::RpcError)?; + let pivot_light_block = self.request_fetch_light_block(router, pivot_height)?; let mut pivot_trusted_states = self.verify_light_block( + router, trusted_state, pivot_light_block, trust_threshold, @@ -154,6 +161,7 @@ impl Scheduler { let trusted_state_left = pivot_trusted_states.last().cloned().unwrap(); // FIXME: Unwrap let mut new_trusted_states = self.verify_light_block( + router, trusted_state_left, light_block, trust_threshold, @@ -167,7 +175,15 @@ impl Scheduler { Ok(new_trusted_states) } - fn trace(&self, e: impl Event + 'static) { - self.trace.send(Box::new(e)).expect("could not trace event"); + fn request_fetch_light_block( + &self, + router: &impl Router, + height: Height, + ) -> Result { + let rpc_response = router.query_rpc(RpcRequest::FetchLightBlock(height)); + + match rpc_response { + RpcResponse::FetchedLightBlock(light_block) => Ok(light_block), + } } } diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 42ed3c01c..8545c270a 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -1,7 +1,3 @@ -// FIXME: Figure out how to get rid of type parameter - -use std::sync::mpsc::Sender; - use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -30,7 +26,6 @@ pub enum VerifierOutput { impl_event!(VerifierOutput); pub struct Verifier { - trace: Sender, predicates: Box, voting_power_calculator: Box, commit_validator: Box, @@ -39,14 +34,12 @@ pub struct Verifier { impl Verifier { pub fn new( - trace: Sender, predicates: impl VerificationPredicates + 'static, voting_power_calculator: impl VotingPowerCalculator + 'static, commit_validator: impl CommitValidator + 'static, header_hasher: impl HeaderHasher + 'static, ) -> Self { Self { - trace, predicates: Box::new(predicates), voting_power_calculator: Box::new(voting_power_calculator), commit_validator: Box::new(commit_validator), @@ -62,8 +55,6 @@ impl Verifier { trusting_period: Duration, now: SystemTime, ) -> Result { - self.trace(VerifierInput::VerifyLightBlock(light_block.clone())); - self.predicates .verify_light_block( &self.voting_power_calculator, @@ -82,12 +73,6 @@ impl Verifier { validators: light_block.validator_set, }; - self.trace(VerifierOutput::ValidLightBlock(new_trusted_state.clone())); - Ok(new_trusted_state) } - - fn trace(&self, e: impl Event + 'static) { - self.trace.send(Box::new(e)).expect("could not trace event"); - } } diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index aec431180..2787a392d 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,6 +1,8 @@ -pub use crate::components::rpc::Rpc; -pub use crate::components::scheduler::Scheduler; -pub use crate::components::verifier::Verifier; +pub use crate::components::demuxer::*; +pub use crate::components::router::*; +pub use crate::components::rpc::*; +pub use crate::components::scheduler::*; +pub use crate::components::verifier::*; pub use crate::errors::*; pub use crate::event::*; pub use crate::operations::*; diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs index d74a7097a..125666123 100644 --- a/light-spike/src/trusted_store.rs +++ b/light-spike/src/trusted_store.rs @@ -31,8 +31,9 @@ impl TrustedStore { self.store.get(&height) } - pub fn set(&mut self, height: Height, trusted_state: TrustedState) { - self.store.insert(height, trusted_state); + pub fn add(&mut self, trusted_state: TrustedState) { + self.store + .insert(trusted_state.header.height, trusted_state); } } @@ -57,8 +58,8 @@ impl TSReadWriter { self.ts.read().unwrap().get(height).cloned() } - pub fn set(&mut self, height: Height, trusted_state: TrustedState) { + pub fn add(&mut self, trusted_state: TrustedState) { let mut ts = self.ts.write().unwrap(); - ts.set(height, trusted_state); + ts.add(trusted_state); } } From 79b26c67482a805f5ef05d2fd1afc036bf0f6d28 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 12:55:13 +0200 Subject: [PATCH 020/100] Silence a couple Clippy warnings --- light-spike/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 5c4cab29f..1859a0356 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -1,10 +1,10 @@ -#![deny(rust_2018_idioms)] -#![deny(nonstandard_style)] +#![deny(rust_2018_idioms, nonstandard_style)] #![warn( unreachable_pub, // missing_docs, // missing_doc_code_examples )] +#![allow(clippy::too_many_arguments, clippy::match_wild_err_arm)] pub mod components; pub mod errors; From 66ef29f6adbd989967ef1c53f04e7629ab110c8c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 13:01:39 +0200 Subject: [PATCH 021/100] Cleanup trace module --- light-spike/src/trace.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/light-spike/src/trace.rs b/light-spike/src/trace.rs index 4567efceb..a100cd84f 100644 --- a/light-spike/src/trace.rs +++ b/light-spike/src/trace.rs @@ -1,11 +1,10 @@ use std::sync::mpsc::{channel, Receiver, Sender}; -use serde::{Serialize, Serializer}; - use crate::event::BoxedEvent; +#[derive(Debug)] pub struct Trace { - events: Vec, + pub events: Vec, recv: Receiver, } @@ -27,13 +26,6 @@ impl Trace { self.events.push(event); } } - - pub fn serialize(self, ser: S) -> Result - where - S: Serializer, - { - self.events.serialize(ser) - } } #[cfg(test)] @@ -69,11 +61,7 @@ mod tests { trace.collect(); - let mut output = Vec::new(); - let mut ser = serde_json::Serializer::pretty(&mut output); - trace.serialize(&mut ser).unwrap(); - - let as_string = String::from_utf8(output).unwrap(); + let as_string = serde_json::to_string(&trace.events).unwrap(); println!("{}", as_string); let events: Vec> = serde_json::from_str(&as_string).unwrap(); From ede2b763e9568c1a97ee7b536dd6ac8e17efc152 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 13:34:47 +0200 Subject: [PATCH 022/100] Revamp errors --- light-spike/src/components/rpc.rs | 6 ++- light-spike/src/components/scheduler.rs | 23 ++++----- light-spike/src/components/verifier.rs | 4 +- light-spike/src/errors.rs | 45 ++---------------- light-spike/src/operations.rs | 7 +-- light-spike/src/predicates.rs | 25 +++++----- light-spike/src/predicates/errors.rs | 60 ++++++++++++++++++++++++ light-spike/src/predicates/production.rs | 50 +++++++++++--------- light-spike/src/prelude.rs | 1 + 9 files changed, 126 insertions(+), 95 deletions(-) create mode 100644 light-spike/src/predicates/errors.rs diff --git a/light-spike/src/components/rpc.rs b/light-spike/src/components/rpc.rs index b44691748..be333e786 100644 --- a/light-spike/src/components/rpc.rs +++ b/light-spike/src/components/rpc.rs @@ -4,12 +4,14 @@ use std::sync::mpsc::Sender; use serde::{Deserialize, Serialize}; use tendermint::{block, rpc}; +use thiserror::Error; use crate::prelude::*; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum RpcError { - RpcError(rpc::Error), + #[error(transparent)] + RpcError(#[from] rpc::Error), } impl_event!(RpcError); diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 6fefe6cff..0d3d1d867 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -5,8 +5,8 @@ use crate::prelude::*; #[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerError { - #[error("invalid light block: {0}")] - InvalidLightBlock(VerifierError), + #[error("invalid light block")] + InvalidLightBlock(#[from] VerifierError), } impl_event!(SchedulerError); @@ -114,15 +114,16 @@ impl Scheduler { now: SystemTime, ) -> Result, SchedulerError> { match err { - VerifierError::InvalidLightBlock(ErrorKind::InsufficientVotingPower { .. }) => self - .perform_bisection( - router, - trusted_state, - light_block, - trust_threshold, - trusting_period, - now, - ), + VerifierError::InvalidLightBlock(VerificationErrorKind::InsufficientVotingPower { + .. + }) => self.perform_bisection( + router, + trusted_state, + light_block, + trust_threshold, + trusting_period, + now, + ), err => { let output = SchedulerError::InvalidLightBlock(err); Err(output) diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 8545c270a..4af9da057 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -6,7 +6,7 @@ use crate::prelude::*; #[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierError { #[error("invalid light block")] - InvalidLightBlock(crate::errors::ErrorKind), + InvalidLightBlock(#[from] VerificationErrorKind), } impl_event!(VerifierError); @@ -66,7 +66,7 @@ impl Verifier { trusting_period, now, ) - .map_err(|e| VerifierError::InvalidLightBlock(e.kind().to_owned()))?; + .map_err(|e| e.kind().to_owned())?; let new_trusted_state = TrustedState { header: light_block.signed_header.header, diff --git a/light-spike/src/errors.rs b/light-spike/src/errors.rs index 67f6d1f4c..0419c5eda 100644 --- a/light-spike/src/errors.rs +++ b/light-spike/src/errors.rs @@ -1,6 +1,4 @@ use anomaly::{BoxError, Context}; -use serde::{Deserialize, Serialize}; -use std::time::SystemTime; use thiserror::Error; use crate::prelude::*; @@ -17,47 +15,10 @@ macro_rules! ensure { pub type Error = anomaly::Error; -#[derive(Debug, Clone, Error, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Error)] pub enum ErrorKind { - #[error("header from the future: header_time={header_time:?} now={now:?}")] - HeaderFromTheFuture { - header_time: SystemTime, - now: SystemTime, - }, - #[error("implementation specific")] - ImplementationSpecific, - #[error( - "insufficient validators overlap: total_power={total_power} signed_power={signed_power}" - )] - InsufficientValidatorsOverlap { total_power: u64, signed_power: u64 }, - #[error("insufficient voting power: total_power={total_power} voting_power={voting_power}")] - InsufficientVotingPower { total_power: u64, voting_power: u64 }, - #[error("invalid commit: total_power={total_power} signed_power={signed_power}")] - InvalidCommit { total_power: u64, signed_power: u64 }, - #[error("invalid commit value: header_hash={header_hash} commit_hash={commit_hash}")] - InvalidCommitValue { - header_hash: Hash, - commit_hash: Hash, - }, - #[error("invalid next validator set: header_next_validators_hash={header_next_validators_hash} next_validators_hash={next_validators_hash}")] - InvalidNextValidatorSet { - header_next_validators_hash: Hash, - next_validators_hash: Hash, - }, - #[error("invalid validator set: header_validators_hash={header_validators_hash} validators_hash={validators_hash}")] - InvalidValidatorSet { - header_validators_hash: Hash, - validators_hash: Hash, - }, - #[error("non increasing height: got={got} expected={expected}")] - NonIncreasingHeight { got: Height, expected: Height }, - #[error("non monotonic BFT time: header_bft_time={header_bft_time:?} trusted_header_bft_time={trusted_header_bft_time:?}")] - NonMonotonicBftTime { - header_bft_time: SystemTime, - trusted_header_bft_time: SystemTime, - }, - #[error("not withing trust period: at={at:?} now={now:?}")] - NotWithinTrustPeriod { at: SystemTime, now: SystemTime }, + #[error("verifier error")] + Verifier(#[source] VerifierError), } impl ErrorKind { diff --git a/light-spike/src/operations.rs b/light-spike/src/operations.rs index dc296b83c..29e5106e7 100644 --- a/light-spike/src/operations.rs +++ b/light-spike/src/operations.rs @@ -1,6 +1,7 @@ //! Crypto function traits allowing mocking out during testing use crate::prelude::*; +use anomaly::BoxError; pub trait VotingPowerCalculator { fn total_power_of(&self, validators: &ValidatorSet) -> u64; @@ -28,17 +29,17 @@ impl VotingPowerCalculator for Box { } pub trait CommitValidator { - fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), Error>; + fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), BoxError>; } impl CommitValidator for &T { - fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), Error> { + fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), BoxError> { (*self).validate(commit, validators) } } impl CommitValidator for Box { - fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), Error> { + fn validate(&self, commit: &Commit, validators: &ValidatorSet) -> Result<(), BoxError> { self.as_ref().validate(commit, validators) } } diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 51698f64d..a52aa87c8 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -2,6 +2,7 @@ use std::time::{Duration, SystemTime}; use crate::prelude::*; +pub mod errors; pub mod production; pub trait VerificationPredicates { @@ -9,46 +10,46 @@ pub trait VerificationPredicates { &self, signed_header: &SignedHeader, validators: &ValidatorSet, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn next_validators_match( &self, signed_header: &SignedHeader, validators: &ValidatorSet, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn header_matches_commit( &self, header: &Header, commit: &Commit, header_hasher: &dyn HeaderHasher, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn valid_commit( &self, commit: &Commit, validators: &ValidatorSet, validator: &dyn CommitValidator, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn is_within_trust_period( &self, header: &Header, trusting_period: Duration, now: SystemTime, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn is_monotonic_bft_time( &self, untrusted_header: &Header, trusted_header: &Header, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn is_monotonic_height( &self, untrusted_header: &Header, trusted_header: &Header, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn has_sufficient_voting_power( &self, @@ -56,7 +57,7 @@ pub trait VerificationPredicates { validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &dyn VotingPowerCalculator, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn has_sufficient_validators_overlap( &self, @@ -64,7 +65,7 @@ pub trait VerificationPredicates { trusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &dyn VotingPowerCalculator, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn has_sufficient_signers_overlap( &self, @@ -72,13 +73,13 @@ pub trait VerificationPredicates { untrusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &dyn VotingPowerCalculator, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; fn valid_next_validator_set( &self, untrusted_sh: &SignedHeader, untrusted_next_vals: &ValidatorSet, - ) -> Result<(), Error>; + ) -> Result<(), VerificationError>; #[allow(clippy::too_many_arguments, clippy::comparison_chain)] fn verify_light_block( @@ -91,7 +92,7 @@ pub trait VerificationPredicates { trust_threshold: &TrustThreshold, trusting_period: Duration, now: SystemTime, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { let untrusted_sh = &light_block.signed_header; let untrusted_vals = &light_block.validator_set; let untrusted_next_vals = &light_block.next_validator_set; diff --git a/light-spike/src/predicates/errors.rs b/light-spike/src/predicates/errors.rs new file mode 100644 index 000000000..44f10f636 --- /dev/null +++ b/light-spike/src/predicates/errors.rs @@ -0,0 +1,60 @@ +use std::time::SystemTime; + +use anomaly::{BoxError, Context}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::prelude::*; + +pub type VerificationError = anomaly::Error; + +#[derive(Debug, Clone, Error, PartialEq, Eq, Serialize, Deserialize)] +pub enum VerificationErrorKind { + #[error("header from the future: header_time={header_time:?} now={now:?}")] + HeaderFromTheFuture { + header_time: SystemTime, + now: SystemTime, + }, + #[error("implementation specific")] + ImplementationSpecific, + #[error( + "insufficient validators overlap: total_power={total_power} signed_power={signed_power}" + )] + InsufficientValidatorsOverlap { total_power: u64, signed_power: u64 }, + #[error("insufficient voting power: total_power={total_power} voting_power={voting_power}")] + InsufficientVotingPower { total_power: u64, voting_power: u64 }, + #[error("invalid commit: total_power={total_power} signed_power={signed_power}")] + InvalidCommit { total_power: u64, signed_power: u64 }, + #[error("invalid commit value: header_hash={header_hash} commit_hash={commit_hash}")] + InvalidCommitValue { + header_hash: Hash, + commit_hash: Hash, + }, + #[error("invalid next validator set: header_next_validators_hash={header_next_validators_hash} next_validators_hash={next_validators_hash}")] + InvalidNextValidatorSet { + header_next_validators_hash: Hash, + next_validators_hash: Hash, + }, + #[error("invalid validator set: header_validators_hash={header_validators_hash} validators_hash={validators_hash}")] + InvalidValidatorSet { + header_validators_hash: Hash, + validators_hash: Hash, + }, + #[error("non increasing height: got={got} expected={expected}")] + NonIncreasingHeight { got: Height, expected: Height }, + #[error("non monotonic BFT time: header_bft_time={header_bft_time:?} trusted_header_bft_time={trusted_header_bft_time:?}")] + NonMonotonicBftTime { + header_bft_time: SystemTime, + trusted_header_bft_time: SystemTime, + }, + #[error("not withing trust period: at={at:?} now={now:?}")] + NotWithinTrustPeriod { at: SystemTime, now: SystemTime }, +} + +impl VerificationErrorKind { + /// Add additional context (i.e. include a source error and capture a backtrace). + /// You can convert the resulting `Context` into an `Error` by calling `.into()`. + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) + } +} diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index c1d2124a9..bfa7e5e65 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -9,10 +9,10 @@ impl VerificationPredicates for ProductionPredicates { &self, signed_header: &SignedHeader, validators: &ValidatorSet, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { ensure!( signed_header.validators_hash == validators.hash, - ErrorKind::InvalidValidatorSet { + VerificationErrorKind::InvalidValidatorSet { header_validators_hash: signed_header.validators_hash, validators_hash: validators.hash, } @@ -25,10 +25,10 @@ impl VerificationPredicates for ProductionPredicates { &self, signed_header: &SignedHeader, validators: &ValidatorSet, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { ensure!( signed_header.validators_hash == validators.hash, - ErrorKind::InvalidNextValidatorSet { + VerificationErrorKind::InvalidNextValidatorSet { header_next_validators_hash: signed_header.validators_hash, next_validators_hash: validators.hash, } @@ -42,12 +42,12 @@ impl VerificationPredicates for ProductionPredicates { header: &Header, commit: &Commit, header_hasher: &dyn HeaderHasher, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { let header_hash = header_hasher.hash(header); ensure!( header_hash == commit.header_hash, - ErrorKind::InvalidCommitValue { + VerificationErrorKind::InvalidCommitValue { header_hash, commit_hash: commit.header_hash, } @@ -61,8 +61,12 @@ impl VerificationPredicates for ProductionPredicates { commit: &Commit, validators: &ValidatorSet, validator: &dyn CommitValidator, - ) -> Result<(), Error> { - validator.validate(commit, validators) + ) -> Result<(), VerificationError> { + validator + .validate(commit, validators) + .map_err(|e| VerificationErrorKind::ImplementationSpecific.context(e))?; + + Ok(()) } fn is_within_trust_period( @@ -70,13 +74,13 @@ impl VerificationPredicates for ProductionPredicates { header: &Header, trusting_period: Duration, now: SystemTime, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { let header_time = header.bft_time; let expires_at = header_time + trusting_period; ensure!( header_time < now && expires_at > now, - ErrorKind::NotWithinTrustPeriod { + VerificationErrorKind::NotWithinTrustPeriod { at: expires_at, now, } @@ -84,7 +88,7 @@ impl VerificationPredicates for ProductionPredicates { ensure!( header_time <= now, - ErrorKind::HeaderFromTheFuture { header_time, now } + VerificationErrorKind::HeaderFromTheFuture { header_time, now } ); Ok(()) @@ -94,10 +98,10 @@ impl VerificationPredicates for ProductionPredicates { &self, untrusted_header: &Header, trusted_header: &Header, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { ensure!( untrusted_header.bft_time > trusted_header.bft_time, - ErrorKind::NonMonotonicBftTime { + VerificationErrorKind::NonMonotonicBftTime { header_bft_time: untrusted_header.bft_time, trusted_header_bft_time: trusted_header.bft_time, } @@ -110,10 +114,10 @@ impl VerificationPredicates for ProductionPredicates { &self, untrusted_header: &Header, trusted_header: &Header, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { ensure!( untrusted_header.height > trusted_header.height, - ErrorKind::NonIncreasingHeight { + VerificationErrorKind::NonIncreasingHeight { got: untrusted_header.height, expected: trusted_header.height + 1, } @@ -128,13 +132,13 @@ impl VerificationPredicates for ProductionPredicates { validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &dyn VotingPowerCalculator, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { let total_power = calculator.total_power_of(validators); let voting_power = calculator.voting_power_in(commit, validators); ensure!( voting_power * trust_threshold.denominator > total_power * trust_threshold.numerator, - ErrorKind::InsufficientVotingPower { + VerificationErrorKind::InsufficientVotingPower { total_power, voting_power, } @@ -149,7 +153,7 @@ impl VerificationPredicates for ProductionPredicates { trusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &dyn VotingPowerCalculator, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { self.has_sufficient_voting_power( untrusted_commit, trusted_validators, @@ -159,7 +163,7 @@ impl VerificationPredicates for ProductionPredicates { .map_err(|_| { let total_power = calculator.total_power_of(trusted_validators); let signed_power = calculator.voting_power_in(untrusted_commit, trusted_validators); - ErrorKind::InsufficientValidatorsOverlap { + VerificationErrorKind::InsufficientValidatorsOverlap { total_power, signed_power, } @@ -173,7 +177,7 @@ impl VerificationPredicates for ProductionPredicates { untrusted_validators: &ValidatorSet, trust_threshold: &TrustThreshold, calculator: &dyn VotingPowerCalculator, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { self.has_sufficient_voting_power( untrusted_commit, untrusted_validators, @@ -183,7 +187,7 @@ impl VerificationPredicates for ProductionPredicates { .map_err(|_| { let total_power = calculator.total_power_of(untrusted_validators); let signed_power = calculator.voting_power_in(untrusted_commit, untrusted_validators); - ErrorKind::InvalidCommit { + VerificationErrorKind::InvalidCommit { total_power, signed_power, } @@ -195,10 +199,10 @@ impl VerificationPredicates for ProductionPredicates { &self, untrusted_sh: &SignedHeader, untrusted_next_vals: &ValidatorSet, - ) -> Result<(), Error> { + ) -> Result<(), VerificationError> { ensure!( untrusted_sh.header.next_validators_hash == untrusted_next_vals.hash, - ErrorKind::InvalidNextValidatorSet { + VerificationErrorKind::InvalidNextValidatorSet { header_next_validators_hash: untrusted_next_vals.hash, next_validators_hash: untrusted_next_vals.hash, } diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 2787a392d..71ad9d9d5 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -6,6 +6,7 @@ pub use crate::components::verifier::*; pub use crate::errors::*; pub use crate::event::*; pub use crate::operations::*; +pub use crate::predicates::errors::*; pub use crate::predicates::VerificationPredicates; pub use crate::trace::*; pub use crate::trusted_store::*; From 884eebdcd669214398ecb21ded8a58f15830544d Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 13:45:51 +0200 Subject: [PATCH 023/100] Revamp error, part 2 --- light-spike/Cargo.toml | 2 +- light-spike/src/components/scheduler.rs | 2 +- light-spike/src/components/verifier.rs | 24 +++++++++++------------ light-spike/src/predicates/errors.rs | 12 ++++++------ light-spike/src/predicates/production.rs | 25 ++++++++++++------------ 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index 948b4af2d..c1cebec70 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies] tendermint = { path = "../tendermint" } -anomaly = "0.2.0" +anomaly = { version = "0.2.0", features = ["serializer"] } derive_more = "0.99.5" serde = "1.0.106" serde_derive = "1.0.106" diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 0d3d1d867..24ab86732 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -114,7 +114,7 @@ impl Scheduler { now: SystemTime, ) -> Result, SchedulerError> { match err { - VerifierError::InvalidLightBlock(VerificationErrorKind::InsufficientVotingPower { + VerifierError::InvalidLightBlock(VerificationError::InsufficientVotingPower { .. }) => self.perform_bisection( router, diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 4af9da057..b55ffc7ae 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -6,7 +6,7 @@ use crate::prelude::*; #[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierError { #[error("invalid light block")] - InvalidLightBlock(#[from] VerificationErrorKind), + InvalidLightBlock(#[from] VerificationError), } impl_event!(VerifierError); @@ -55,18 +55,16 @@ impl Verifier { trusting_period: Duration, now: SystemTime, ) -> Result { - self.predicates - .verify_light_block( - &self.voting_power_calculator, - &self.commit_validator, - &self.header_hasher, - &trusted_state, - &light_block, - &trust_threshold, - trusting_period, - now, - ) - .map_err(|e| e.kind().to_owned())?; + self.predicates.verify_light_block( + &self.voting_power_calculator, + &self.commit_validator, + &self.header_hasher, + &trusted_state, + &light_block, + &trust_threshold, + trusting_period, + now, + )?; let new_trusted_state = TrustedState { header: light_block.signed_header.header, diff --git a/light-spike/src/predicates/errors.rs b/light-spike/src/predicates/errors.rs index 44f10f636..d17432b5e 100644 --- a/light-spike/src/predicates/errors.rs +++ b/light-spike/src/predicates/errors.rs @@ -6,10 +6,8 @@ use thiserror::Error; use crate::prelude::*; -pub type VerificationError = anomaly::Error; - #[derive(Debug, Clone, Error, PartialEq, Eq, Serialize, Deserialize)] -pub enum VerificationErrorKind { +pub enum VerificationError { #[error("header from the future: header_time={header_time:?} now={now:?}")] HeaderFromTheFuture { header_time: SystemTime, @@ -23,8 +21,10 @@ pub enum VerificationErrorKind { InsufficientValidatorsOverlap { total_power: u64, signed_power: u64 }, #[error("insufficient voting power: total_power={total_power} voting_power={voting_power}")] InsufficientVotingPower { total_power: u64, voting_power: u64 }, - #[error("invalid commit: total_power={total_power} signed_power={signed_power}")] - InvalidCommit { total_power: u64, signed_power: u64 }, + #[error("invalid commit power: total_power={total_power} signed_power={signed_power}")] + InsufficientCommitPower { total_power: u64, signed_power: u64 }, + #[error("invalid commit: {0}")] + InvalidCommit(String), #[error("invalid commit value: header_hash={header_hash} commit_hash={commit_hash}")] InvalidCommitValue { header_hash: Hash, @@ -51,7 +51,7 @@ pub enum VerificationErrorKind { NotWithinTrustPeriod { at: SystemTime, now: SystemTime }, } -impl VerificationErrorKind { +impl VerificationError { /// Add additional context (i.e. include a source error and capture a backtrace). /// You can convert the resulting `Context` into an `Error` by calling `.into()`. pub fn context(self, source: impl Into) -> Context { diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index bfa7e5e65..bf8c5174c 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -12,7 +12,7 @@ impl VerificationPredicates for ProductionPredicates { ) -> Result<(), VerificationError> { ensure!( signed_header.validators_hash == validators.hash, - VerificationErrorKind::InvalidValidatorSet { + VerificationError::InvalidValidatorSet { header_validators_hash: signed_header.validators_hash, validators_hash: validators.hash, } @@ -28,7 +28,7 @@ impl VerificationPredicates for ProductionPredicates { ) -> Result<(), VerificationError> { ensure!( signed_header.validators_hash == validators.hash, - VerificationErrorKind::InvalidNextValidatorSet { + VerificationError::InvalidNextValidatorSet { header_next_validators_hash: signed_header.validators_hash, next_validators_hash: validators.hash, } @@ -47,7 +47,7 @@ impl VerificationPredicates for ProductionPredicates { ensure!( header_hash == commit.header_hash, - VerificationErrorKind::InvalidCommitValue { + VerificationError::InvalidCommitValue { header_hash, commit_hash: commit.header_hash, } @@ -62,9 +62,10 @@ impl VerificationPredicates for ProductionPredicates { validators: &ValidatorSet, validator: &dyn CommitValidator, ) -> Result<(), VerificationError> { + // FIXME: Do not discard underlying error validator .validate(commit, validators) - .map_err(|e| VerificationErrorKind::ImplementationSpecific.context(e))?; + .map_err(|e| VerificationError::InvalidCommit(e.to_string()))?; Ok(()) } @@ -80,7 +81,7 @@ impl VerificationPredicates for ProductionPredicates { ensure!( header_time < now && expires_at > now, - VerificationErrorKind::NotWithinTrustPeriod { + VerificationError::NotWithinTrustPeriod { at: expires_at, now, } @@ -88,7 +89,7 @@ impl VerificationPredicates for ProductionPredicates { ensure!( header_time <= now, - VerificationErrorKind::HeaderFromTheFuture { header_time, now } + VerificationError::HeaderFromTheFuture { header_time, now } ); Ok(()) @@ -101,7 +102,7 @@ impl VerificationPredicates for ProductionPredicates { ) -> Result<(), VerificationError> { ensure!( untrusted_header.bft_time > trusted_header.bft_time, - VerificationErrorKind::NonMonotonicBftTime { + VerificationError::NonMonotonicBftTime { header_bft_time: untrusted_header.bft_time, trusted_header_bft_time: trusted_header.bft_time, } @@ -117,7 +118,7 @@ impl VerificationPredicates for ProductionPredicates { ) -> Result<(), VerificationError> { ensure!( untrusted_header.height > trusted_header.height, - VerificationErrorKind::NonIncreasingHeight { + VerificationError::NonIncreasingHeight { got: untrusted_header.height, expected: trusted_header.height + 1, } @@ -138,7 +139,7 @@ impl VerificationPredicates for ProductionPredicates { ensure!( voting_power * trust_threshold.denominator > total_power * trust_threshold.numerator, - VerificationErrorKind::InsufficientVotingPower { + VerificationError::InsufficientVotingPower { total_power, voting_power, } @@ -163,7 +164,7 @@ impl VerificationPredicates for ProductionPredicates { .map_err(|_| { let total_power = calculator.total_power_of(trusted_validators); let signed_power = calculator.voting_power_in(untrusted_commit, trusted_validators); - VerificationErrorKind::InsufficientValidatorsOverlap { + VerificationError::InsufficientValidatorsOverlap { total_power, signed_power, } @@ -187,7 +188,7 @@ impl VerificationPredicates for ProductionPredicates { .map_err(|_| { let total_power = calculator.total_power_of(untrusted_validators); let signed_power = calculator.voting_power_in(untrusted_commit, untrusted_validators); - VerificationErrorKind::InvalidCommit { + VerificationError::InsufficientCommitPower { total_power, signed_power, } @@ -202,7 +203,7 @@ impl VerificationPredicates for ProductionPredicates { ) -> Result<(), VerificationError> { ensure!( untrusted_sh.header.next_validators_hash == untrusted_next_vals.hash, - VerificationErrorKind::InvalidNextValidatorSet { + VerificationError::InvalidNextValidatorSet { header_next_validators_hash: untrusted_next_vals.hash, next_validators_hash: untrusted_next_vals.hash, } From af46005cd6cfc2a41918b201bb27a19300b50f68 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 16:25:05 +0200 Subject: [PATCH 024/100] Bundle verification options together --- light-spike/src/components/demuxer.rs | 29 +++------- light-spike/src/components/router.rs | 4 +- light-spike/src/components/scheduler.rs | 72 ++++++++----------------- light-spike/src/components/verifier.rs | 8 +-- light-spike/src/predicates.rs | 12 ++--- 5 files changed, 37 insertions(+), 88 deletions(-) diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 25f4f3d6a..1c9509911 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -32,17 +32,11 @@ impl Router for &mut Demuxer { VerifierRequest::VerifyLightBlock { trusted_state, light_block, - trust_threshold, - trusting_period, - now, + options, } => { - let result = self.verifier.verify_light_block( - trusted_state, - light_block, - trust_threshold, - trusting_period, - now, - ); + let result = self + .verifier + .verify_light_block(trusted_state, light_block, options); match result { Ok(trusted_state) => VerifierResponse::VerificationSucceeded(trusted_state), @@ -72,18 +66,11 @@ impl Demuxer { &mut self, trusted_state: TrustedState, light_block: LightBlock, - trust_threshold: TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, ) -> Result, DemuxerError> { - let result = self.scheduler.verify_light_block( - &self, - trusted_state, - light_block, - trust_threshold, - trusting_period, - now, - ); + let result = self + .scheduler + .verify_light_block(&self, trusted_state, light_block, options); match result { Ok(new_trusted_states) => { diff --git a/light-spike/src/components/router.rs b/light-spike/src/components/router.rs index 210fd2a68..ee0e838b6 100644 --- a/light-spike/src/components/router.rs +++ b/light-spike/src/components/router.rs @@ -15,9 +15,7 @@ pub enum VerifierRequest { VerifyLightBlock { trusted_state: TrustedState, light_block: LightBlock, - trust_threshold: TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, }, } diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 24ab86732..32beaa2e1 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -3,6 +3,13 @@ use thiserror::Error; use crate::prelude::*; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct VerificationOptions { + pub trust_threshold: TrustThreshold, + pub trusting_period: Duration, + pub now: SystemTime, +} + #[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerError { #[error("invalid light block")] @@ -44,9 +51,7 @@ impl Scheduler { router: &impl Router, trusted_state: TrustedState, light_block: LightBlock, - trust_threshold: TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, ) -> Result, SchedulerError> { if let Some(trusted_state_in_store) = self.trusted_store.get(light_block.height) { let output = vec![trusted_state_in_store]; @@ -57,24 +62,16 @@ impl Scheduler { router, trusted_state.clone(), light_block.clone(), - trust_threshold, - trusting_period, - now, + options, ); match verifier_result { VerifierResponse::VerificationSucceeded(trusted_state) => { self.verification_succeded(trusted_state) } - VerifierResponse::VerificationFailed(err) => self.verification_failed( - router, - err, - trusted_state, - light_block, - trust_threshold, - trusting_period, - now, - ), + VerifierResponse::VerificationFailed(err) => { + self.verification_failed(router, err, trusted_state, light_block, options) + } } } @@ -83,16 +80,12 @@ impl Scheduler { router: &impl Router, trusted_state: TrustedState, light_block: LightBlock, - trust_threshold: TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, ) -> VerifierResponse { router.query_verifier(VerifierRequest::VerifyLightBlock { trusted_state, light_block, - trust_threshold, - trusting_period, - now, + options, }) } @@ -109,21 +102,12 @@ impl Scheduler { err: VerifierError, trusted_state: TrustedState, light_block: LightBlock, - trust_threshold: TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, ) -> Result, SchedulerError> { match err { VerifierError::InvalidLightBlock(VerificationError::InsufficientVotingPower { .. - }) => self.perform_bisection( - router, - trusted_state, - light_block, - trust_threshold, - trusting_period, - now, - ), + }) => self.perform_bisection(router, trusted_state, light_block, options), err => { let output = SchedulerError::InvalidLightBlock(err); Err(output) @@ -136,9 +120,7 @@ impl Scheduler { router: &impl Router, trusted_state: TrustedState, light_block: LightBlock, - trust_threshold: TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, ) -> Result, SchedulerError> { // Get the pivot height for bisection. let trusted_height = trusted_state.header.height; @@ -150,25 +132,13 @@ impl Scheduler { let pivot_light_block = self.request_fetch_light_block(router, pivot_height)?; - let mut pivot_trusted_states = self.verify_light_block( - router, - trusted_state, - pivot_light_block, - trust_threshold, - trusting_period, - now, - )?; + let mut pivot_trusted_states = + self.verify_light_block(router, trusted_state, pivot_light_block, options)?; let trusted_state_left = pivot_trusted_states.last().cloned().unwrap(); // FIXME: Unwrap - let mut new_trusted_states = self.verify_light_block( - router, - trusted_state_left, - light_block, - trust_threshold, - trusting_period, - now, - )?; + let mut new_trusted_states = + self.verify_light_block(router, trusted_state_left, light_block, options)?; new_trusted_states.append(&mut pivot_trusted_states); new_trusted_states.sort_by_key(|ts| ts.header.height); diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index b55ffc7ae..790abd11a 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -51,9 +51,7 @@ impl Verifier { &self, trusted_state: TrustedState, light_block: LightBlock, - trust_threshold: TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, ) -> Result { self.predicates.verify_light_block( &self.voting_power_calculator, @@ -61,9 +59,7 @@ impl Verifier { &self.header_hasher, &trusted_state, &light_block, - &trust_threshold, - trusting_period, - now, + options, )?; let new_trusted_state = TrustedState { diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index a52aa87c8..cb61663b4 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -89,16 +89,14 @@ pub trait VerificationPredicates { header_hasher: &dyn HeaderHasher, trusted_state: &TrustedState, light_block: &LightBlock, - trust_threshold: &TrustThreshold, - trusting_period: Duration, - now: SystemTime, + options: VerificationOptions, ) -> Result<(), VerificationError> { let untrusted_sh = &light_block.signed_header; let untrusted_vals = &light_block.validator_set; let untrusted_next_vals = &light_block.next_validator_set; // Ensure the latest trusted header hasn't expired - self.is_within_trust_period(&trusted_state.header, trusting_period, now)?; + self.is_within_trust_period(&trusted_state.header, options.trusting_period, options.now)?; // Ensure the header validator hashes match the given validators self.validator_sets_match(&untrusted_sh, &untrusted_vals)?; @@ -124,7 +122,7 @@ pub trait VerificationPredicates { self.has_sufficient_voting_power( &untrusted_sh.commit, &untrusted_sh.validators, - &trust_threshold, + &options.trust_threshold, voting_power_calculator, )?; } else { @@ -140,14 +138,14 @@ pub trait VerificationPredicates { self.has_sufficient_validators_overlap( &untrusted_sh.commit, &trusted_state.validators, - &trust_threshold, + &options.trust_threshold, voting_power_calculator, )?; self.has_sufficient_signers_overlap( &untrusted_sh.commit, &untrusted_vals, - &trust_threshold, + &options.trust_threshold, voting_power_calculator, )?; From fcb8c82d3681e6df4906ae59550b9c215f0781bf Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 16:32:48 +0200 Subject: [PATCH 025/100] Cleanup --- light-spike/src/predicates.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index cb61663b4..4a7d46571 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -126,9 +126,8 @@ pub trait VerificationPredicates { voting_power_calculator, )?; } else { + // This check will always fail since trusted_state.header < untrusted_sh.header self.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; - - // Ensure that the check above will always fail. unreachable!(); } From 18f22ac9fb324654e6f593675538b1bd8a62a644 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Apr 2020 16:40:06 +0200 Subject: [PATCH 026/100] Use output enum for all components --- light-spike/src/components/demuxer.rs | 10 ++++++--- light-spike/src/components/rpc.rs | 28 +++++++++++++++---------- light-spike/src/components/scheduler.rs | 24 ++++++++------------- light-spike/src/components/verifier.rs | 10 ++++++--- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 1c9509911..b76e0bdce 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -20,7 +20,9 @@ impl Router for &mut Demuxer { RpcRequest::FetchLightBlock(height) => { let result = self.rpc.fetch_light_block(height); match result { - Ok(light_block) => RpcResponse::FetchedLightBlock(light_block), + Ok(RpcOutput::FetchedLightBlock(light_block)) => { + RpcResponse::FetchedLightBlock(light_block) + } Err(_err) => todo!(), } } @@ -39,7 +41,9 @@ impl Router for &mut Demuxer { .verify_light_block(trusted_state, light_block, options); match result { - Ok(trusted_state) => VerifierResponse::VerificationSucceeded(trusted_state), + Ok(VerifierOutput::ValidLightBlock(trusted_state)) => { + VerifierResponse::VerificationSucceeded(trusted_state) + } Err(err) => VerifierResponse::VerificationFailed(err), } } @@ -73,7 +77,7 @@ impl Demuxer { .verify_light_block(&self, trusted_state, light_block, options); match result { - Ok(new_trusted_states) => { + Ok(SchedulerOutput::ValidLightBlock(new_trusted_states)) => { for ts in &new_trusted_states { self.trusted_store.add(ts.clone()); } diff --git a/light-spike/src/components/rpc.rs b/light-spike/src/components/rpc.rs index be333e786..32462521f 100644 --- a/light-spike/src/components/rpc.rs +++ b/light-spike/src/components/rpc.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; use std::future::Future; -use std::sync::mpsc::Sender; +// use std::sync::mpsc::Sender; use serde::{Deserialize, Serialize}; use tendermint::{block, rpc}; @@ -32,20 +32,26 @@ impl_event!(RpcOutput); pub struct Rpc { rpc_client: rpc::Client, - trace: Sender, + // trace: Sender, } impl Rpc { - pub fn new(rpc_client: rpc::Client, trace: Sender) -> Self { - Self { rpc_client, trace } + pub fn new( + rpc_client: rpc::Client, + // trace: Sender + ) -> Self { + Self { + rpc_client, + // trace + } } - fn trace(&self, e: impl Event + 'static) { - self.trace.send(Box::new(e)).expect("could not trace event"); - } + // fn trace(&self, e: impl Event + 'static) { + // self.trace.send(Box::new(e)).expect("could not trace event"); + // } - pub fn fetch_light_block(&self, height: Height) -> Result { - self.trace(RpcInput::FetchState(height)); + pub fn fetch_light_block(&self, height: Height) -> Result { + // self.trace(RpcInput::FetchState(height)); let signed_header = self.fetch_signed_header(height)?; let validator_set = self.fetch_validator_set(height)?; @@ -58,9 +64,9 @@ impl Rpc { next_validator_set, }; - self.trace(RpcOutput::FetchedLightBlock(light_block.clone())); + // self.trace(RpcOutput::FetchedLightBlock(light_block.clone())); - Ok(light_block) + Ok(RpcOutput::FetchedLightBlock(light_block)) } fn fetch_signed_header(&self, h: Height) -> Result { diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 32beaa2e1..e672910ea 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -28,11 +28,6 @@ impl_event!(SchedulerInput); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerOutput { ValidLightBlock(Vec), - PerformBisectionAt { - pivot_height: Height, - trusted_state: TrustedState, - trust_threshold: TrustThreshold, - }, } impl_event!(SchedulerOutput); @@ -52,10 +47,9 @@ impl Scheduler { trusted_state: TrustedState, light_block: LightBlock, options: VerificationOptions, - ) -> Result, SchedulerError> { + ) -> Result { if let Some(trusted_state_in_store) = self.trusted_store.get(light_block.height) { - let output = vec![trusted_state_in_store]; - return Ok(output); + return self.verification_succeded(trusted_state_in_store); } let verifier_result = self.perform_verify_light_block( @@ -92,8 +86,8 @@ impl Scheduler { fn verification_succeded( &self, new_trusted_state: TrustedState, - ) -> Result, SchedulerError> { - Ok(vec![new_trusted_state]) + ) -> Result { + Ok(SchedulerOutput::ValidLightBlock(vec![new_trusted_state])) } fn verification_failed( @@ -103,7 +97,7 @@ impl Scheduler { trusted_state: TrustedState, light_block: LightBlock, options: VerificationOptions, - ) -> Result, SchedulerError> { + ) -> Result { match err { VerifierError::InvalidLightBlock(VerificationError::InsufficientVotingPower { .. @@ -121,7 +115,7 @@ impl Scheduler { trusted_state: TrustedState, light_block: LightBlock, options: VerificationOptions, - ) -> Result, SchedulerError> { + ) -> Result { // Get the pivot height for bisection. let trusted_height = trusted_state.header.height; let untrusted_height = light_block.height; @@ -132,18 +126,18 @@ impl Scheduler { let pivot_light_block = self.request_fetch_light_block(router, pivot_height)?; - let mut pivot_trusted_states = + let SchedulerOutput::ValidLightBlock(mut pivot_trusted_states) = self.verify_light_block(router, trusted_state, pivot_light_block, options)?; let trusted_state_left = pivot_trusted_states.last().cloned().unwrap(); // FIXME: Unwrap - let mut new_trusted_states = + let SchedulerOutput::ValidLightBlock(mut new_trusted_states) = self.verify_light_block(router, trusted_state_left, light_block, options)?; new_trusted_states.append(&mut pivot_trusted_states); new_trusted_states.sort_by_key(|ts| ts.header.height); - Ok(new_trusted_states) + Ok(SchedulerOutput::ValidLightBlock(new_trusted_states)) } fn request_fetch_light_block( diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 790abd11a..8a8ecd68b 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -13,7 +13,11 @@ impl_event!(VerifierError); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierInput { - VerifyLightBlock(LightBlock), + VerifyLightBlock { + trusted_state: TrustedState, + light_block: LightBlock, + options: VerificationOptions, + }, } impl_event!(VerifierInput); @@ -52,7 +56,7 @@ impl Verifier { trusted_state: TrustedState, light_block: LightBlock, options: VerificationOptions, - ) -> Result { + ) -> Result { self.predicates.verify_light_block( &self.voting_power_calculator, &self.commit_validator, @@ -67,6 +71,6 @@ impl Verifier { validators: light_block.validator_set, }; - Ok(new_trusted_state) + Ok(VerifierOutput::ValidLightBlock(new_trusted_state)) } } From e1158a810b1083834b5f91dd5978eb4de6593329 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 29 Apr 2020 15:58:34 +0200 Subject: [PATCH 027/100] Split queries out --- light-spike/src/components.rs | 1 + light-spike/src/components/queries.rs | 34 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 light-spike/src/components/queries.rs diff --git a/light-spike/src/components.rs b/light-spike/src/components.rs index 54c845352..9e785d180 100644 --- a/light-spike/src/components.rs +++ b/light-spike/src/components.rs @@ -1,4 +1,5 @@ pub mod demuxer; +pub mod queries; pub mod router; pub mod rpc; pub mod scheduler; diff --git a/light-spike/src/components/queries.rs b/light-spike/src/components/queries.rs new file mode 100644 index 000000000..2aeba58e9 --- /dev/null +++ b/light-spike/src/components/queries.rs @@ -0,0 +1,34 @@ +use crate::prelude::*; + +pub fn query_rpc(rpc: &Rpc, request: RpcRequest) -> RpcResponse { + match request { + RpcRequest::FetchLightBlock(height) => { + let result = rpc.fetch_light_block(height); + match result { + Ok(RpcOutput::FetchedLightBlock(light_block)) => { + RpcResponse::FetchedLightBlock(light_block) + } + Err(_err) => todo!(), + } + } + } +} + +pub fn query_verifier(verifier: &Verifier, request: VerifierRequest) -> VerifierResponse { + match request { + VerifierRequest::VerifyLightBlock { + trusted_state, + light_block, + options, + } => { + let result = verifier.verify_light_block(trusted_state, light_block, options); + + match result { + Ok(VerifierOutput::ValidLightBlock(trusted_state)) => { + VerifierResponse::VerificationSucceeded(trusted_state) + } + Err(err) => VerifierResponse::VerificationFailed(err), + } + } + } +} From a50286f5e12f72891a69bf494dcfc6be4d6af1de Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Apr 2020 14:40:06 +0200 Subject: [PATCH 028/100] Rewrite using coroutines --- light-spike/Cargo.toml | 2 + light-spike/src/components.rs | 5 +- light-spike/src/components/demuxer.rs | 189 ++++++++++------ light-spike/src/components/io.rs | 79 +++++++ light-spike/src/components/queries.rs | 34 --- light-spike/src/components/router.rs | 31 --- light-spike/src/components/rpc.rs | 100 --------- light-spike/src/components/scheduler.rs | 277 ++++++++++++++---------- light-spike/src/components/state.rs | 31 +++ light-spike/src/components/verifier.rs | 25 ++- light-spike/src/lib.rs | 3 +- light-spike/src/macros.rs | 10 + light-spike/src/predicates.rs | 18 +- light-spike/src/predicates/errors.rs | 8 + light-spike/src/prelude.rs | 33 +-- light-spike/src/trace.rs | 73 ------- light-spike/src/trusted_store.rs | 3 +- light-spike/src/types.rs | 44 +++- light-spike/src/utils.rs | 21 ++ 19 files changed, 510 insertions(+), 476 deletions(-) create mode 100644 light-spike/src/components/io.rs delete mode 100644 light-spike/src/components/queries.rs delete mode 100644 light-spike/src/components/router.rs delete mode 100644 light-spike/src/components/rpc.rs create mode 100644 light-spike/src/components/state.rs create mode 100644 light-spike/src/macros.rs delete mode 100644 light-spike/src/trace.rs create mode 100644 light-spike/src/utils.rs diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index c1cebec70..26455f8a8 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -15,6 +15,8 @@ serde = "1.0.106" serde_derive = "1.0.106" thiserror = "1.0.15" typetag = "0.1.4" +genawaiter = "0.99.1" +futures = "0.3.4" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-spike/src/components.rs b/light-spike/src/components.rs index 9e785d180..2f828a99f 100644 --- a/light-spike/src/components.rs +++ b/light-spike/src/components.rs @@ -1,6 +1,5 @@ pub mod demuxer; -pub mod queries; -pub mod router; -pub mod rpc; +pub mod io; pub mod scheduler; +pub mod state; pub mod verifier; diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index b76e0bdce..3aa728a54 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -1,90 +1,153 @@ -use serde::{Deserialize, Serialize}; -use thiserror::Error; - +use super::{io::*, scheduler::*, verifier::*}; use crate::prelude::*; -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] -pub enum DemuxerError {} -impl_event!(DemuxerError); +#[derive(Debug)] +pub enum DemuxerError { + Scheduler(SchedulerError), + Verifier(VerifierError), + Io(IoError), +} pub struct Demuxer { - pub(super) trusted_store: TSReadWriter, - pub(super) rpc: Rpc, - pub(super) scheduler: Scheduler, - pub(super) verifier: Verifier, + state: State, + scheduler: Scheduler, + verifier: Verifier, + io: Io, } -impl Router for &mut Demuxer { - fn query_rpc(&self, request: RpcRequest) -> RpcResponse { - match request { - RpcRequest::FetchLightBlock(height) => { - let result = self.rpc.fetch_light_block(height); - match result { - Ok(RpcOutput::FetchedLightBlock(light_block)) => { - RpcResponse::FetchedLightBlock(light_block) - } - Err(_err) => todo!(), - } - } +impl Demuxer { + pub fn new(state: State, scheduler: Scheduler, verifier: Verifier, io: Io) -> Self { + Self { + state, + scheduler, + verifier, + io, } } - fn query_verifier(&self, request: VerifierRequest) -> VerifierResponse { - match request { - VerifierRequest::VerifyLightBlock { - trusted_state, - light_block, - options, - } => { - let result = self - .verifier - .verify_light_block(trusted_state, light_block, options); - - match result { - Ok(VerifierOutput::ValidLightBlock(trusted_state)) => { - VerifierResponse::VerificationSucceeded(trusted_state) - } - Err(err) => VerifierResponse::VerificationFailed(err), - } + pub fn verify_height( + &mut self, + height: Height, + trusted_state: TrustedState, + options: VerificationOptions, + ) -> Result, DemuxerError> { + let input = SchedulerInput::VerifyHeight { + height, + trusted_state, + options, + }; + + let result = self.run_scheduler(input)?; + + match result { + SchedulerOutput::TrustedStates(trusted_states) => { + self.state.add_trusted_states(trusted_states.clone()); + Ok(trusted_states) } } } -} -impl Demuxer { - pub fn new( - trusted_store: TSReadWriter, - rpc: Rpc, - scheduler: Scheduler, - verifier: Verifier, - ) -> Self { - Self { - trusted_store, - rpc, - scheduler, - verifier, + pub fn verify_light_block( + &mut self, + light_block: LightBlock, + trusted_state: TrustedState, + options: VerificationOptions, + ) -> Result, DemuxerError> { + let input = SchedulerInput::VerifyLightBlock { + light_block, + trusted_state, + options, + }; + + let result = self.run_scheduler(input)?; + + match result { + SchedulerOutput::TrustedStates(trusted_states) => { + self.state.add_trusted_states(trusted_states.clone()); + Ok(trusted_states) + } } } - pub fn verify_light_block( + pub fn validate_light_block( &mut self, - trusted_state: TrustedState, light_block: LightBlock, + trusted_state: TrustedState, options: VerificationOptions, - ) -> Result, DemuxerError> { + ) -> Result { + let input = VerifierInput::VerifyLightBlock { + light_block, + trusted_state, + options, + }; + let result = self - .scheduler - .verify_light_block(&self, trusted_state, light_block, options); + .verifier + .process(input) + .map_err(|e| DemuxerError::Verifier(e))?; match result { - Ok(SchedulerOutput::ValidLightBlock(new_trusted_states)) => { - for ts in &new_trusted_states { - self.trusted_store.add(ts.clone()); - } + VerifierOutput::ValidLightBlock(valid_light_block) => { + self.state.add_valid_light_block(valid_light_block.clone()); + Ok(valid_light_block) + } + } + } + + pub fn fetch_light_block(&mut self, height: Height) -> Result { + let input = IoInput::FetchLightBlock(height); - Ok(new_trusted_states) + let result = self.io.process(input).map_err(|e| DemuxerError::Io(e))?; + + match result { + IoOutput::FetchedLightBlock(lb) => { + self.state.add_fetched_light_block(lb.clone()); + Ok(lb) } - Err(_err) => todo!(), } } + + fn handle_request( + &mut self, + request: SchedulerRequest, + ) -> Result { + match request { + SchedulerRequest::GetLightBlock(height) => self + .fetch_light_block(height) + .map(|lb| SchedulerResponse::LightBlock(lb)), + + SchedulerRequest::VerifyLightBlock { + light_block, + trusted_state, + options, + } => match self.verify_light_block(light_block, trusted_state, options) { + Ok(ts) => Ok(SchedulerResponse::Verified(Ok(ts))), + Err(DemuxerError::Verifier(err)) => Ok(SchedulerResponse::Verified(Err(err))), + Err(err) => Err(err), + }, + + SchedulerRequest::ValidateLightBlock { + light_block, + trusted_state, + options, + } => match self.validate_light_block(light_block, trusted_state, options) { + Ok(ts) => Ok(SchedulerResponse::Validated(Ok(ts))), + Err(DemuxerError::Verifier(err)) => Ok(SchedulerResponse::Validated(Err(err))), + Err(err) => Err(err), + }, + } + } + + fn run_scheduler(&mut self, input: SchedulerInput) -> Result { + let scheduler = Gen::new(|co| { + let handler = &self.scheduler; + handler(self.state.trusted_store_reader(), input, co) + }); + + let result = drain(scheduler, SchedulerResponse::Init, move |req| { + self.handle_request(req) + })?; + + result.map_err(|e| DemuxerError::Scheduler(e)) + } } diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs new file mode 100644 index 000000000..2169b4d35 --- /dev/null +++ b/light-spike/src/components/io.rs @@ -0,0 +1,79 @@ +use futures::executor::block_on; + +use serde::{Deserialize, Serialize}; +use tendermint::{block, rpc}; +use thiserror::Error; + +use crate::prelude::*; + +pub enum IoInput { + FetchLightBlock(Height), +} + +pub enum IoOutput { + FetchedLightBlock(LightBlock), +} + +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +pub enum IoError { + #[error(transparent)] + IoError(#[from] rpc::Error), +} + +pub type IoResult = Result; + +pub struct Io { + rpc_client: rpc::Client, +} + +impl Io { + pub fn new(rpc_client: rpc::Client) -> Self { + Self { rpc_client } + } + + pub fn process(&self, input: IoInput) -> IoResult { + match input { + IoInput::FetchLightBlock(height) => self.fetch_light_block(height), + } + } + + pub fn fetch_light_block(&self, height: Height) -> IoResult { + let signed_header = self.fetch_signed_header(height)?; + let validators = self.fetch_validator_set(height)?; + let next_validators = self.fetch_validator_set(height + 1)?; + + let light_block = LightBlock { + height, + signed_header, + validators, + next_validators, + }; + + Ok(IoOutput::FetchedLightBlock(light_block)) + } + + fn fetch_signed_header(&self, h: Height) -> Result { + let height: block::Height = h.into(); + + let res = block_on(async { + match height.value() { + 0 => self.rpc_client.latest_commit().await, + _ => self.rpc_client.commit(height).await, + } + }); + + match res { + Ok(response) => Ok(response.signed_header.into()), + Err(err) => Err(IoError::IoError(err)), + } + } + + fn fetch_validator_set(&self, height: Height) -> Result { + let res = block_on(self.rpc_client.validators(height)); + + match res { + Ok(response) => Ok(response.validators.into()), + Err(err) => Err(IoError::IoError(err)), + } + } +} diff --git a/light-spike/src/components/queries.rs b/light-spike/src/components/queries.rs deleted file mode 100644 index 2aeba58e9..000000000 --- a/light-spike/src/components/queries.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::prelude::*; - -pub fn query_rpc(rpc: &Rpc, request: RpcRequest) -> RpcResponse { - match request { - RpcRequest::FetchLightBlock(height) => { - let result = rpc.fetch_light_block(height); - match result { - Ok(RpcOutput::FetchedLightBlock(light_block)) => { - RpcResponse::FetchedLightBlock(light_block) - } - Err(_err) => todo!(), - } - } - } -} - -pub fn query_verifier(verifier: &Verifier, request: VerifierRequest) -> VerifierResponse { - match request { - VerifierRequest::VerifyLightBlock { - trusted_state, - light_block, - options, - } => { - let result = verifier.verify_light_block(trusted_state, light_block, options); - - match result { - Ok(VerifierOutput::ValidLightBlock(trusted_state)) => { - VerifierResponse::VerificationSucceeded(trusted_state) - } - Err(err) => VerifierResponse::VerificationFailed(err), - } - } - } -} diff --git a/light-spike/src/components/router.rs b/light-spike/src/components/router.rs deleted file mode 100644 index ee0e838b6..000000000 --- a/light-spike/src/components/router.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::prelude::*; - -#[derive(Clone, Debug)] -pub enum RpcRequest { - FetchLightBlock(Height), -} - -#[derive(Clone, Debug)] -pub enum RpcResponse { - FetchedLightBlock(LightBlock), -} - -#[derive(Clone, Debug)] -pub enum VerifierRequest { - VerifyLightBlock { - trusted_state: TrustedState, - light_block: LightBlock, - options: VerificationOptions, - }, -} - -#[derive(Clone, Debug)] -pub enum VerifierResponse { - VerificationSucceeded(TrustedState), - VerificationFailed(VerifierError), -} - -pub trait Router { - fn query_rpc(&self, request: RpcRequest) -> RpcResponse; - fn query_verifier(&self, request: VerifierRequest) -> VerifierResponse; -} diff --git a/light-spike/src/components/rpc.rs b/light-spike/src/components/rpc.rs deleted file mode 100644 index 32462521f..000000000 --- a/light-spike/src/components/rpc.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::fmt::Debug; -use std::future::Future; -// use std::sync::mpsc::Sender; - -use serde::{Deserialize, Serialize}; -use tendermint::{block, rpc}; -use thiserror::Error; - -use crate::prelude::*; - -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] -pub enum RpcError { - #[error(transparent)] - RpcError(#[from] rpc::Error), -} - -impl_event!(RpcError); - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum RpcInput { - FetchState(Height), -} - -impl_event!(RpcInput); - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum RpcOutput { - FetchedLightBlock(LightBlock), -} - -impl_event!(RpcOutput); - -pub struct Rpc { - rpc_client: rpc::Client, - // trace: Sender, -} - -impl Rpc { - pub fn new( - rpc_client: rpc::Client, - // trace: Sender - ) -> Self { - Self { - rpc_client, - // trace - } - } - - // fn trace(&self, e: impl Event + 'static) { - // self.trace.send(Box::new(e)).expect("could not trace event"); - // } - - pub fn fetch_light_block(&self, height: Height) -> Result { - // self.trace(RpcInput::FetchState(height)); - - let signed_header = self.fetch_signed_header(height)?; - let validator_set = self.fetch_validator_set(height)?; - let next_validator_set = self.fetch_validator_set(height + 1)?; - - let light_block = LightBlock { - height, - signed_header, - validator_set, - next_validator_set, - }; - - // self.trace(RpcOutput::FetchedLightBlock(light_block.clone())); - - Ok(RpcOutput::FetchedLightBlock(light_block)) - } - - fn fetch_signed_header(&self, h: Height) -> Result { - let height: block::Height = h.into(); - - let res = block_on(async { - match height.value() { - 0 => self.rpc_client.latest_commit().await, - _ => self.rpc_client.commit(height).await, - } - }); - - match res { - Ok(response) => Ok(response.signed_header.into()), - Err(err) => Err(RpcError::RpcError(err)), - } - } - - fn fetch_validator_set(&self, height: Height) -> Result { - let res = block_on(self.rpc_client.validators(height)); - - match res { - Ok(response) => Ok(response.validators.into()), - Err(err) => Err(RpcError::RpcError(err)), - } - } -} - -fn block_on(_future: F) -> F::Output { - todo!() -} diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index e672910ea..0b6ddf78d 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -1,154 +1,193 @@ -use serde::{Deserialize, Serialize}; -use thiserror::Error; +use std::{future::Future, pin::Pin}; +use super::verifier::VerifierError; use crate::prelude::*; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct VerificationOptions { - pub trust_threshold: TrustThreshold, - pub trusting_period: Duration, - pub now: SystemTime, -} - -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] -pub enum SchedulerError { - #[error("invalid light block")] - InvalidLightBlock(#[from] VerifierError), -} - -impl_event!(SchedulerError); - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerInput { - VerifyUntrustedLightBlock(LightBlock), + VerifyHeight { + height: Height, + trusted_state: TrustedState, + options: VerificationOptions, + }, + VerifyLightBlock { + light_block: LightBlock, + trusted_state: TrustedState, + options: VerificationOptions, + }, } -impl_event!(SchedulerInput); - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerOutput { - ValidLightBlock(Vec), + TrustedStates(Vec), } -impl_event!(SchedulerOutput); - -pub struct Scheduler { - trusted_store: TSReader, +#[derive(Debug)] +pub enum SchedulerError { + InvalidLightBlock(LightBlock, VerifierError), } -impl Scheduler { - pub fn new(trusted_store: TSReader) -> Self { - Self { trusted_store } - } - - pub fn verify_light_block( - &self, - router: &impl Router, +pub enum SchedulerRequest { + GetLightBlock(Height), + VerifyLightBlock { + light_block: LightBlock, trusted_state: TrustedState, + options: VerificationOptions, + }, + ValidateLightBlock { light_block: LightBlock, + trusted_state: TrustedState, options: VerificationOptions, - ) -> Result { - if let Some(trusted_state_in_store) = self.trusted_store.get(light_block.height) { - return self.verification_succeded(trusted_state_in_store); - } + }, +} - let verifier_result = self.perform_verify_light_block( - router, - trusted_state.clone(), - light_block.clone(), - options, - ); - - match verifier_result { - VerifierResponse::VerificationSucceeded(trusted_state) => { - self.verification_succeded(trusted_state) - } - VerifierResponse::VerificationFailed(err) => { - self.verification_failed(router, err, trusted_state, light_block, options) - } - } - } +pub enum SchedulerResponse { + Init, + LightBlock(LightBlock), + Validated(Result), + Verified(Result, VerifierError>), +} - fn perform_verify_light_block( - &self, - router: &impl Router, - trusted_state: TrustedState, - light_block: LightBlock, - options: VerificationOptions, - ) -> VerifierResponse { - router.query_verifier(VerifierRequest::VerifyLightBlock { +pub type SchedulerResult = Result; + +pub async fn process( + trusted_store: TSReader, + input: SchedulerInput, + co: Co, +) -> SchedulerResult { + match input { + SchedulerInput::VerifyHeight { + height, trusted_state, + options, + } => verify_height(height, trusted_state, options, trusted_store, co).await, + + SchedulerInput::VerifyLightBlock { light_block, + trusted_state, options, - }) + } => verify_light_block(light_block, trusted_state, options, trusted_store, co).await, } +} - fn verification_succeded( - &self, - new_trusted_state: TrustedState, - ) -> Result { - Ok(SchedulerOutput::ValidLightBlock(vec![new_trusted_state])) +pub async fn verify_height( + height: Height, + trusted_state: TrustedState, + options: VerificationOptions, + trusted_store: TSReader, + co: Co, +) -> SchedulerResult { + if let Some(trusted_state) = trusted_store.get(height) { + Ok(SchedulerOutput::TrustedStates(vec![trusted_state])) + } else { + let response = co.yield_(SchedulerRequest::GetLightBlock(height)).await; + let light_block = unwrap!(SchedulerResponse::LightBlock, response); + + verify_light_block(light_block, trusted_state, options, trusted_store, co).await } +} - fn verification_failed( - &self, - router: &impl Router, - err: VerifierError, - trusted_state: TrustedState, - light_block: LightBlock, - options: VerificationOptions, - ) -> Result { - match err { - VerifierError::InvalidLightBlock(VerificationError::InsufficientVotingPower { - .. - }) => self.perform_bisection(router, trusted_state, light_block, options), - err => { - let output = SchedulerError::InvalidLightBlock(err); - Err(output) - } +pub async fn verify_light_block( + light_block: LightBlock, + trusted_state: TrustedState, + options: VerificationOptions, + trusted_store: TSReader, + co: Co, +) -> SchedulerResult { + if let Some(in_store) = trusted_store.get(light_block.height) { + return Ok(SchedulerOutput::TrustedStates(vec![in_store])); + } + + let response = co + .yield_(SchedulerRequest::ValidateLightBlock { + light_block: light_block.clone(), + trusted_state: trusted_state.clone(), + options: options.clone(), + }) + .await; + + let result = unwrap!(SchedulerResponse::Validated, response); + match result { + Err(err) if not_enough_trust(&err) => { + do_bisection(light_block, trusted_state, options, co).await } + Err(err) => Err(SchedulerError::InvalidLightBlock(light_block, err)), + Ok(light_block) => { + let trusted_state = light_block.into(); + Ok(SchedulerOutput::TrustedStates(vec![trusted_state])) + } + } +} + +fn not_enough_trust(e: &VerifierError) -> bool { + match e { + VerifierError::InvalidLightBlock(e) => e.not_enough_trust(), } +} - fn perform_bisection( - &self, - router: &impl Router, - trusted_state: TrustedState, - light_block: LightBlock, - options: VerificationOptions, - ) -> Result { - // Get the pivot height for bisection. - let trusted_height = trusted_state.header.height; - let untrusted_height = light_block.height; - let pivot_height = trusted_height - .checked_add(untrusted_height) - .expect("height overflow") - / 2; +fn compute_pivot_height(light_block: &LightBlock, trusted_state: &TrustedState) -> Height { + let trusted_height = trusted_state.height; + let untrusted_height = light_block.height; + let pivot_height = trusted_height + .checked_add(untrusted_height) + .expect("height overflow") + / 2; - let pivot_light_block = self.request_fetch_light_block(router, pivot_height)?; + pivot_height +} - let SchedulerOutput::ValidLightBlock(mut pivot_trusted_states) = - self.verify_light_block(router, trusted_state, pivot_light_block, options)?; +pub async fn do_bisection( + light_block: LightBlock, + trusted_state: TrustedState, + options: VerificationOptions, + co: Co, +) -> SchedulerResult { + let pivot_height = compute_pivot_height(&light_block, &trusted_state); + + let pivot_lb = co + .yield_(SchedulerRequest::GetLightBlock(pivot_height)) + .await; + + let pivot_light_block = unwrap!(SchedulerResponse::LightBlock, pivot_lb); + + let pivot_response = co + .yield_(SchedulerRequest::VerifyLightBlock { + light_block: pivot_light_block.clone(), + trusted_state: trusted_state.clone(), + options: options.clone(), + }) + .await; - let trusted_state_left = pivot_trusted_states.last().cloned().unwrap(); // FIXME: Unwrap + let mut valid_light_blocks = unwrap!(SchedulerResponse::Verified, pivot_response) + .map_err(|e| SchedulerError::InvalidLightBlock(pivot_light_block, e))?; - let SchedulerOutput::ValidLightBlock(mut new_trusted_states) = - self.verify_light_block(router, trusted_state_left, light_block, options)?; + let lb_response = co + .yield_(SchedulerRequest::ValidateLightBlock { + light_block: light_block.clone(), + trusted_state, + options, + }) + .await; - new_trusted_states.append(&mut pivot_trusted_states); - new_trusted_states.sort_by_key(|ts| ts.header.height); + let valid_light_block = unwrap!(SchedulerResponse::Validated, lb_response) + .map_err(|e| SchedulerError::InvalidLightBlock(light_block, e))?; - Ok(SchedulerOutput::ValidLightBlock(new_trusted_states)) - } + valid_light_blocks.push(valid_light_block); - fn request_fetch_light_block( - &self, - router: &impl Router, - height: Height, - ) -> Result { - let rpc_response = router.query_rpc(RpcRequest::FetchLightBlock(height)); + let trusted_states = valid_light_blocks.into_iter().map(Into::into).collect(); + Ok(SchedulerOutput::TrustedStates(trusted_states)) +} - match rpc_response { - RpcResponse::FetchedLightBlock(light_block) => Ok(light_block), - } - } +pub type Scheduler = Box< + dyn Fn( + TSReader, + SchedulerInput, + Co, + ) -> Pin>>, +>; + +pub fn handler(f: F0) -> Scheduler +where + F0: Fn(TSReader, SchedulerInput, Co) -> F + 'static, + F: Future + 'static, +{ + Box::new(move |s, i, c| Box::pin(f(s, i, c))) } diff --git a/light-spike/src/components/state.rs b/light-spike/src/components/state.rs new file mode 100644 index 000000000..abbf60833 --- /dev/null +++ b/light-spike/src/components/state.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; + +#[derive(Debug)] +pub struct State { + pub trusted_store_reader: TSReader, + pub trusted_store_writer: TSReadWriter, + // valid_store_reader: TSReader, + // valid_store_writer: TSReaderWriter, + // fetched_store_reader: TSReader, + // fetched_store_writer: TSReaderWriter, +} + +impl State { + pub fn trusted_store_reader(&self) -> TSReader { + self.trusted_store_reader.clone() + } + + pub fn add_trusted_states(&mut self, trusted_states: Vec) { + for trusted_state in trusted_states { + self.trusted_store_writer.add(trusted_state); + } + } + + pub fn add_valid_light_block(&mut self, _light_block: LightBlock) { + // self.valid_store_writer.add(light_block); + } + + pub fn add_fetched_light_block(&mut self, _light_block: LightBlock) { + // self.fetched_store_writer.add(light_block); + } +} diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 8a8ecd68b..72efe0a7b 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -9,8 +9,6 @@ pub enum VerifierError { InvalidLightBlock(#[from] VerificationError), } -impl_event!(VerifierError); - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierInput { VerifyLightBlock { @@ -24,10 +22,10 @@ impl_event!(VerifierInput); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierOutput { - ValidLightBlock(TrustedState), + ValidLightBlock(LightBlock), } -impl_event!(VerifierOutput); +pub type VerifierResult = Result; pub struct Verifier { predicates: Box, @@ -51,10 +49,20 @@ impl Verifier { } } + pub fn process(&self, input: VerifierInput) -> VerifierResult { + match input { + VerifierInput::VerifyLightBlock { + trusted_state, + light_block, + options, + } => self.verify_light_block(light_block, trusted_state, options), + } + } + pub fn verify_light_block( &self, - trusted_state: TrustedState, light_block: LightBlock, + trusted_state: TrustedState, options: VerificationOptions, ) -> Result { self.predicates.verify_light_block( @@ -66,11 +74,8 @@ impl Verifier { options, )?; - let new_trusted_state = TrustedState { - header: light_block.signed_header.header, - validators: light_block.validator_set, - }; - + // FIXME: Do we actually need to distinguish between LightBlock and TrustedState? + let new_trusted_state = light_block.into(); Ok(VerifierOutput::ValidLightBlock(new_trusted_state)) } } diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 1859a0356..25adc7182 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -9,12 +9,13 @@ pub mod components; pub mod errors; pub mod event; +pub mod macros; pub mod operations; pub mod predicates; pub mod prelude; -pub mod trace; pub mod trusted_store; pub mod types; +pub mod utils; #[cfg(test)] mod tests { diff --git a/light-spike/src/macros.rs b/light-spike/src/macros.rs new file mode 100644 index 000000000..770dab162 --- /dev/null +++ b/light-spike/src/macros.rs @@ -0,0 +1,10 @@ +#[macro_export] +macro_rules! unwrap { + ($enum:path, $expr:expr) => {{ + if let $enum(item) = $expr { + item + } else { + unreachable!() + } + }}; +} diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 4a7d46571..41c0a92ce 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -92,11 +92,15 @@ pub trait VerificationPredicates { options: VerificationOptions, ) -> Result<(), VerificationError> { let untrusted_sh = &light_block.signed_header; - let untrusted_vals = &light_block.validator_set; - let untrusted_next_vals = &light_block.next_validator_set; + let untrusted_vals = &light_block.validators; + let untrusted_next_vals = &light_block.next_validators; // Ensure the latest trusted header hasn't expired - self.is_within_trust_period(&trusted_state.header, options.trusting_period, options.now)?; + self.is_within_trust_period( + &trusted_state.header(), + options.trusting_period, + options.now, + )?; // Ensure the header validator hashes match the given validators self.validator_sets_match(&untrusted_sh, &untrusted_vals)?; @@ -114,11 +118,11 @@ pub trait VerificationPredicates { commit_validator, )?; - self.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header)?; + self.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header())?; - if untrusted_sh.header.height == trusted_state.header.height { + if untrusted_sh.header.height == trusted_state.header().height { self.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; - } else if untrusted_sh.header.height > trusted_state.header.height { + } else if untrusted_sh.header.height > trusted_state.header().height { self.has_sufficient_voting_power( &untrusted_sh.commit, &untrusted_sh.validators, @@ -127,7 +131,7 @@ pub trait VerificationPredicates { )?; } else { // This check will always fail since trusted_state.header < untrusted_sh.header - self.is_monotonic_height(&trusted_state.header, &untrusted_sh.header)?; + self.is_monotonic_height(&trusted_state.header(), &untrusted_sh.header)?; unreachable!(); } diff --git a/light-spike/src/predicates/errors.rs b/light-spike/src/predicates/errors.rs index d17432b5e..fe63a9c6e 100644 --- a/light-spike/src/predicates/errors.rs +++ b/light-spike/src/predicates/errors.rs @@ -57,4 +57,12 @@ impl VerificationError { pub fn context(self, source: impl Into) -> Context { Context::new(self, Some(source.into())) } + + pub fn not_enough_trust(&self) -> bool { + if let Self::InsufficientVotingPower { .. } = self { + true + } else { + false + } + } } diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 71ad9d9d5..da11b3885 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,39 +1,22 @@ pub use crate::components::demuxer::*; -pub use crate::components::router::*; -pub use crate::components::rpc::*; +pub use crate::components::io::*; pub use crate::components::scheduler::*; +pub use crate::components::state::*; pub use crate::components::verifier::*; pub use crate::errors::*; pub use crate::event::*; pub use crate::operations::*; pub use crate::predicates::errors::*; pub use crate::predicates::VerificationPredicates; -pub use crate::trace::*; pub use crate::trusted_store::*; pub use crate::types::*; -pub use crate::{ensure, impl_event}; +pub use crate::utils::*; +pub use crate::{ensure, impl_event, unwrap}; pub use std::time::{Duration, SystemTime}; -pub(crate) trait BoolExt { - fn true_or(self, e: E) -> Result<(), E>; - fn false_or(self, e: E) -> Result<(), E>; -} +pub use genawaiter::{ + rc::{Co, Gen}, + GeneratorState, +}; -impl BoolExt for bool { - fn true_or(self, e: E) -> Result<(), E> { - if self { - Ok(()) - } else { - Err(e) - } - } - - fn false_or(self, e: E) -> Result<(), E> { - if !self { - Ok(()) - } else { - Err(e) - } - } -} diff --git a/light-spike/src/trace.rs b/light-spike/src/trace.rs deleted file mode 100644 index a100cd84f..000000000 --- a/light-spike/src/trace.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::sync::mpsc::{channel, Receiver, Sender}; - -use crate::event::BoxedEvent; - -#[derive(Debug)] -pub struct Trace { - pub events: Vec, - recv: Receiver, -} - -impl Trace { - pub fn new() -> (Sender, Self) { - let (send, recv) = channel(); - - ( - send, - Self { - events: vec![], - recv, - }, - ) - } - - pub fn collect(&mut self) { - while let Ok(event) = self.recv.recv() { - self.events.push(event); - } - } -} - -#[cfg(test)] -mod tests { - use serde::{Deserialize, Serialize}; - - use super::*; - use crate::event::Event; - use crate::impl_event; - - #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - struct Foo { - foo: u32, - } - #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - struct Bar { - bar: Option, - } - impl_event!(Foo); - impl_event!(Bar); - - #[test] - fn test_serialize() { - let (send, mut trace) = Trace::new(); - - send.send(Box::new(Foo { foo: 42 })).unwrap(); - send.send(Box::new(Bar { - bar: Some("test".to_string()), - })) - .unwrap(); - - std::mem::drop(send); - - trace.collect(); - - let as_string = serde_json::to_string(&trace.events).unwrap(); - println!("{}", as_string); - - let events: Vec> = serde_json::from_str(&as_string).unwrap(); - for event in events { - assert_eq!(&event, &event); - dbg!(&event); - } - } -} diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs index 125666123..b2a676bd7 100644 --- a/light-spike/src/trusted_store.rs +++ b/light-spike/src/trusted_store.rs @@ -32,8 +32,7 @@ impl TrustedStore { } pub fn add(&mut self, trusted_state: TrustedState) { - self.store - .insert(trusted_state.header.height, trusted_state); + self.store.insert(trusted_state.height, trusted_state); } } diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index 756c127e3..d1c473ff6 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -5,6 +5,16 @@ use std::time::SystemTime; pub use tendermint::hash::Hash; pub use tendermint::lite::types::Height; +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +#[display(fmt = "{:?}", self)] +pub struct VerificationOptions { + pub trust_threshold: TrustThreshold, + pub trusting_period: Duration, + pub now: SystemTime, +} + #[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Header { @@ -55,17 +65,35 @@ impl From for SignedHeader { } } -#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] -#[display(fmt = "{:?}", self)] -pub struct TrustedState { - pub header: Header, - pub validators: ValidatorSet, -} +// FIXME: Do we actually need to distinguish between LightBlock and TrustedState? +pub type TrustedState = LightBlock; + +// #[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +// #[display(fmt = "{:?}", self)] +// pub struct TrustedState { +// pub header: Header, +// pub validators: ValidatorSet, +// } + +// impl From for TrustedState { +// fn from(light_block: LightBlock) -> Self { +// Self { +// header: light_block.signed_header.header, +// validators: light_block.validator_set, +// } +// } +// } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct LightBlock { pub height: Height, pub signed_header: SignedHeader, - pub validator_set: ValidatorSet, - pub next_validator_set: ValidatorSet, + pub validators: ValidatorSet, + pub next_validators: ValidatorSet, +} + +impl LightBlock { + pub fn header(&self) -> &Header { + &self.signed_header.header + } } diff --git a/light-spike/src/utils.rs b/light-spike/src/utils.rs new file mode 100644 index 000000000..93a30d332 --- /dev/null +++ b/light-spike/src/utils.rs @@ -0,0 +1,21 @@ +use std::future::Future; + +use genawaiter::{rc::Gen, GeneratorState}; + +pub fn drain( + mut gen: Gen, + init: O, + mut handler: impl FnMut(I) -> Result, +) -> Result +where + F: Future, +{ + let mut response = init; + + loop { + match gen.resume_with(response) { + GeneratorState::Yielded(request) => response = handler(request)?, + GeneratorState::Complete(result) => return Ok(result), + } + } +} From 3fd1d2a7445918984b324687ff48888a2f0733fc Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Apr 2020 14:45:00 +0200 Subject: [PATCH 029/100] cleanup --- light-spike/src/components/demuxer.rs | 8 ++++++- light-spike/src/components/io.rs | 2 ++ light-spike/src/components/scheduler.rs | 8 ++++++- light-spike/src/components/verifier.rs | 2 -- light-spike/src/event.rs | 32 ------------------------- light-spike/src/lib.rs | 1 - light-spike/src/prelude.rs | 3 +-- light-spike/src/types.rs | 3 ++- 8 files changed, 19 insertions(+), 40 deletions(-) delete mode 100644 light-spike/src/event.rs diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 3aa728a54..21549ab2d 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -1,10 +1,16 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + use super::{io::*, scheduler::*, verifier::*}; use crate::prelude::*; -#[derive(Debug)] +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum DemuxerError { + #[error("scheduler error")] Scheduler(SchedulerError), + #[error("verifier error")] Verifier(VerifierError), + #[error("I/O error")] Io(IoError), } diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs index 2169b4d35..1f1b62cc8 100644 --- a/light-spike/src/components/io.rs +++ b/light-spike/src/components/io.rs @@ -6,10 +6,12 @@ use thiserror::Error; use crate::prelude::*; +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum IoInput { FetchLightBlock(Height), } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum IoOutput { FetchedLightBlock(LightBlock), } diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 0b6ddf78d..f7c1ffc62 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -1,8 +1,12 @@ use std::{future::Future, pin::Pin}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + use super::verifier::VerifierError; use crate::prelude::*; +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerInput { VerifyHeight { height: Height, @@ -16,12 +20,14 @@ pub enum SchedulerInput { }, } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerOutput { TrustedStates(Vec), } -#[derive(Debug)] +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerError { + #[error("invalid light block {0} because: {1}")] InvalidLightBlock(LightBlock, VerifierError), } diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 72efe0a7b..6079a4ec6 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -18,8 +18,6 @@ pub enum VerifierInput { }, } -impl_event!(VerifierInput); - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum VerifierOutput { ValidLightBlock(LightBlock), diff --git a/light-spike/src/event.rs b/light-spike/src/event.rs deleted file mode 100644 index a1bf3c605..000000000 --- a/light-spike/src/event.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::any::Any; -use std::fmt::Debug; - -#[macro_export] -macro_rules! impl_event { - ($type:ty) => { - #[::typetag::serde] - impl $crate::event::Event for $type { - fn as_any(&self) -> &dyn ::std::any::Any { - self - } - - fn box_eq(&self, other: &dyn ::std::any::Any) -> bool { - other.downcast_ref::().map_or(false, |a| self == a) - } - } - }; -} - -#[typetag::serde(tag = "type")] -pub trait Event: Any + Debug { - fn as_any(&self) -> &dyn Any; - fn box_eq(&self, other: &dyn Any) -> bool; -} - -pub type BoxedEvent = Box; - -impl PartialEq for BoxedEvent { - fn eq(&self, other: &Self) -> bool { - self.box_eq(other.as_any()) - } -} diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 25adc7182..938e7ea80 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -8,7 +8,6 @@ pub mod components; pub mod errors; -pub mod event; pub mod macros; pub mod operations; pub mod predicates; diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index da11b3885..66e6de8cb 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -4,14 +4,13 @@ pub use crate::components::scheduler::*; pub use crate::components::state::*; pub use crate::components::verifier::*; pub use crate::errors::*; -pub use crate::event::*; pub use crate::operations::*; pub use crate::predicates::errors::*; pub use crate::predicates::VerificationPredicates; pub use crate::trusted_store::*; pub use crate::types::*; pub use crate::utils::*; -pub use crate::{ensure, impl_event, unwrap}; +pub use crate::{ensure, unwrap}; pub use std::time::{Duration, SystemTime}; diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index d1c473ff6..3ab2f6422 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -84,7 +84,8 @@ pub type TrustedState = LightBlock; // } // } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)] +#[display(fmt = "{:?}", self)] pub struct LightBlock { pub height: Height, pub signed_header: SignedHeader, From e30986284cdf4106396f002c25ded8665686dd6d Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Apr 2020 15:10:32 +0200 Subject: [PATCH 030/100] Add fork detector prototype --- light-spike/src/components.rs | 1 + light-spike/src/components/demuxer.rs | 26 ++++++++- light-spike/src/components/fork_detector.rs | 62 +++++++++++++++++++++ light-spike/src/prelude.rs | 2 +- light-spike/src/trusted_store.rs | 14 +++++ 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 light-spike/src/components/fork_detector.rs diff --git a/light-spike/src/components.rs b/light-spike/src/components.rs index 2f828a99f..85cec0e2b 100644 --- a/light-spike/src/components.rs +++ b/light-spike/src/components.rs @@ -1,4 +1,5 @@ pub mod demuxer; +pub mod fork_detector; pub mod io; pub mod scheduler; pub mod state; diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 21549ab2d..1822686a9 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -10,6 +10,8 @@ pub enum DemuxerError { Scheduler(SchedulerError), #[error("verifier error")] Verifier(VerifierError), + #[error("fork detector")] + ForkDetector(ForkDetectorError), #[error("I/O error")] Io(IoError), } @@ -18,15 +20,23 @@ pub struct Demuxer { state: State, scheduler: Scheduler, verifier: Verifier, + fork_detector: ForkDetector, io: Io, } impl Demuxer { - pub fn new(state: State, scheduler: Scheduler, verifier: Verifier, io: Io) -> Self { + pub fn new( + state: State, + scheduler: Scheduler, + verifier: Verifier, + fork_detector: ForkDetector, + io: Io, + ) -> Self { Self { state, scheduler, verifier, + fork_detector, io, } } @@ -100,6 +110,20 @@ impl Demuxer { } } + pub fn detect_forks(&mut self) -> Result<(), DemuxerError> { + let light_blocks = self.state.trusted_store_reader.all(); + let input = ForkDetectorInput::Detect(light_blocks); + + let result = self + .fork_detector + .process(input) + .map_err(DemuxerError::ForkDetector)?; + + match result { + ForkDetectorOutput::NotDetected => Ok(()), + } + } + pub fn fetch_light_block(&mut self, height: Height) -> Result { let input = IoInput::FetchLightBlock(height); diff --git a/light-spike/src/components/fork_detector.rs b/light-spike/src/components/fork_detector.rs new file mode 100644 index 000000000..b3b9aab24 --- /dev/null +++ b/light-spike/src/components/fork_detector.rs @@ -0,0 +1,62 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::prelude::*; + +#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +pub enum ForkDetectorError { + #[error("conflicting blocks: {0} and {1}")] + ConflictingBlocks(LightBlock, LightBlock), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum ForkDetectorInput { + Detect(Vec), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum ForkDetectorOutput { + NotDetected, +} + +pub type ForkDetectorResult = Result; + +pub struct ForkDetector { + header_hasher: Box, +} + +impl ForkDetector { + pub fn new(header_hasher: impl HeaderHasher + 'static) -> Self { + Self { + header_hasher: Box::new(header_hasher), + } + } + + pub fn process(&self, input: ForkDetectorInput) -> ForkDetectorResult { + match input { + ForkDetectorInput::Detect(light_blocks) => self.detect(light_blocks), + } + } + + pub fn detect(&self, mut light_blocks: Vec) -> ForkDetectorResult { + if light_blocks.is_empty() { + return Ok(ForkDetectorOutput::NotDetected); + } + + let first_block = light_blocks.pop().unwrap(); // Safety: checked above that not empty. + let first_hash = self.header_hasher.hash(first_block.header()); + + for light_block in light_blocks { + let hash = self.header_hasher.hash(light_block.header()); + + if first_hash != hash { + return Err(ForkDetectorError::ConflictingBlocks( + first_block, + light_block, + )); + } + } + + Ok(ForkDetectorOutput::NotDetected) + } +} diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 66e6de8cb..f001612d3 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,4 +1,5 @@ pub use crate::components::demuxer::*; +pub use crate::components::fork_detector::*; pub use crate::components::io::*; pub use crate::components::scheduler::*; pub use crate::components::state::*; @@ -18,4 +19,3 @@ pub use genawaiter::{ rc::{Co, Gen}, GeneratorState, }; - diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs index b2a676bd7..bc5306c37 100644 --- a/light-spike/src/trusted_store.rs +++ b/light-spike/src/trusted_store.rs @@ -34,6 +34,10 @@ impl TrustedStore { pub fn add(&mut self, trusted_state: TrustedState) { self.store.insert(trusted_state.height, trusted_state); } + + pub fn all(&self) -> Vec<&TrustedState> { + self.store.values().collect() + } } #[derive(Clone, Debug)] @@ -45,6 +49,16 @@ impl TSReader { pub fn get(&self, height: Height) -> Option { self.ts.read().unwrap().get(height).cloned() } + + pub fn all(&self) -> Vec { + self.ts + .read() + .unwrap() + .all() + .into_iter() + .map(Clone::clone) + .collect() + } } #[derive(Debug)] From 64a322f6d5b51a265cf2d1732ddfdf31fc943af6 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Apr 2020 15:10:39 +0200 Subject: [PATCH 031/100] Add stub example --- light-spike/examples/light_client.rs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 light-spike/examples/light_client.rs diff --git a/light-spike/examples/light_client.rs b/light-spike/examples/light_client.rs new file mode 100644 index 000000000..340520132 --- /dev/null +++ b/light-spike/examples/light_client.rs @@ -0,0 +1,37 @@ +use light_spike::components::scheduler; +use light_spike::prelude::*; + +pub fn main() { + let store = TrustedStore::new(); + let (trusted_store_reader, trusted_store_writer) = store.split(); + + let state = State { + trusted_store_reader, + trusted_store_writer, + }; + + let predicates = light_spike::predicates::production::ProductionPredicates; + let voting_power_calculator: Box = todo(()); + let commit_validator: Box = todo(()); + let header_hasher: Box = todo(()); + + let scheduler = scheduler::handler(scheduler::process); + let verifier = Verifier::new( + predicates, + voting_power_calculator, + commit_validator, + header_hasher, + ); + + let header_hasher: Box = todo(()); + let fork_detector = ForkDetector::new(header_hasher); + + let io = Io::new(todo(())); + + let demuxer = Demuxer::new(state, scheduler, verifier, fork_detector, io); + todo(demuxer) +} + +fn todo(_: B) -> A { + todo!() +} From 0ce586cd7df14512d18c4010a1dee70c30a53b76 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Apr 2020 15:36:52 +0200 Subject: [PATCH 032/100] Add traits to abstract of each concrete component --- light-spike/Cargo.toml | 1 + light-spike/examples/light_client.rs | 11 ++-- light-spike/src/components/demuxer.rs | 55 ++++++++--------- light-spike/src/components/fork_detector.rs | 22 ++++--- light-spike/src/components/io.rs | 20 +++--- light-spike/src/components/scheduler.rs | 68 ++++++++++----------- light-spike/src/components/verifier.rs | 30 +++++---- light-spike/src/trusted_store.rs | 18 +++--- 8 files changed, 120 insertions(+), 105 deletions(-) diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index 26455f8a8..30da5dab9 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -17,6 +17,7 @@ thiserror = "1.0.15" typetag = "0.1.4" genawaiter = "0.99.1" futures = "0.3.4" +async-trait = "0.1.30" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-spike/examples/light_client.rs b/light-spike/examples/light_client.rs index 340520132..c709b0155 100644 --- a/light-spike/examples/light_client.rs +++ b/light-spike/examples/light_client.rs @@ -1,4 +1,3 @@ -use light_spike::components::scheduler; use light_spike::prelude::*; pub fn main() { @@ -10,13 +9,14 @@ pub fn main() { trusted_store_writer, }; + let scheduler = RealScheduler; + let predicates = light_spike::predicates::production::ProductionPredicates; let voting_power_calculator: Box = todo(()); let commit_validator: Box = todo(()); let header_hasher: Box = todo(()); - let scheduler = scheduler::handler(scheduler::process); - let verifier = Verifier::new( + let verifier = RealVerifier::new( predicates, voting_power_calculator, commit_validator, @@ -24,9 +24,10 @@ pub fn main() { ); let header_hasher: Box = todo(()); - let fork_detector = ForkDetector::new(header_hasher); + let fork_detector = RealForkDetector::new(header_hasher); - let io = Io::new(todo(())); + let rpc_client = todo(()); + let io = RealIo::new(rpc_client); let demuxer = Demuxer::new(state, scheduler, verifier, fork_detector, io); todo(demuxer) diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 1822686a9..d011e23c9 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -18,31 +18,31 @@ pub enum DemuxerError { pub struct Demuxer { state: State, - scheduler: Scheduler, - verifier: Verifier, - fork_detector: ForkDetector, - io: Io, + scheduler: Box, + verifier: Box, + fork_detector: Box, + io: Box, } impl Demuxer { pub fn new( state: State, - scheduler: Scheduler, - verifier: Verifier, - fork_detector: ForkDetector, - io: Io, + scheduler: impl Scheduler + 'static, + verifier: impl Verifier + 'static, + fork_detector: impl ForkDetector + 'static, + io: impl Io + 'static, ) -> Self { Self { state, - scheduler, - verifier, - fork_detector, - io, + scheduler: Box::new(scheduler), + verifier: Box::new(verifier), + fork_detector: Box::new(fork_detector), + io: Box::new(io), } } pub fn verify_height( - &mut self, + &self, height: Height, trusted_state: TrustedState, options: VerificationOptions, @@ -57,14 +57,14 @@ impl Demuxer { match result { SchedulerOutput::TrustedStates(trusted_states) => { - self.state.add_trusted_states(trusted_states.clone()); + // self.state.add_trusted_states(trusted_states.clone()); Ok(trusted_states) } } } pub fn verify_light_block( - &mut self, + &self, light_block: LightBlock, trusted_state: TrustedState, options: VerificationOptions, @@ -79,14 +79,14 @@ impl Demuxer { match result { SchedulerOutput::TrustedStates(trusted_states) => { - self.state.add_trusted_states(trusted_states.clone()); + // self.state.add_trusted_states(trusted_states.clone()); Ok(trusted_states) } } } pub fn validate_light_block( - &mut self, + &self, light_block: LightBlock, trusted_state: TrustedState, options: VerificationOptions, @@ -104,13 +104,13 @@ impl Demuxer { match result { VerifierOutput::ValidLightBlock(valid_light_block) => { - self.state.add_valid_light_block(valid_light_block.clone()); + // self.state.add_valid_light_block(valid_light_block.clone()); Ok(valid_light_block) } } } - pub fn detect_forks(&mut self) -> Result<(), DemuxerError> { + pub fn detect_forks(&self) -> Result<(), DemuxerError> { let light_blocks = self.state.trusted_store_reader.all(); let input = ForkDetectorInput::Detect(light_blocks); @@ -124,23 +124,20 @@ impl Demuxer { } } - pub fn fetch_light_block(&mut self, height: Height) -> Result { + pub fn fetch_light_block(&self, height: Height) -> Result { let input = IoInput::FetchLightBlock(height); let result = self.io.process(input).map_err(|e| DemuxerError::Io(e))?; match result { IoOutput::FetchedLightBlock(lb) => { - self.state.add_fetched_light_block(lb.clone()); + // self.state.add_fetched_light_block(lb.clone()); Ok(lb) } } } - fn handle_request( - &mut self, - request: SchedulerRequest, - ) -> Result { + fn handle_request(&self, request: SchedulerRequest) -> Result { match request { SchedulerRequest::GetLightBlock(height) => self .fetch_light_block(height) @@ -168,13 +165,13 @@ impl Demuxer { } } - fn run_scheduler(&mut self, input: SchedulerInput) -> Result { + fn run_scheduler(&self, input: SchedulerInput) -> Result { let scheduler = Gen::new(|co| { - let handler = &self.scheduler; - handler(self.state.trusted_store_reader(), input, co) + self.scheduler + .process(self.state.trusted_store_reader(), input, co) }); - let result = drain(scheduler, SchedulerResponse::Init, move |req| { + let result = drain(scheduler, SchedulerResponse::Init, |req| { self.handle_request(req) })?; diff --git a/light-spike/src/components/fork_detector.rs b/light-spike/src/components/fork_detector.rs index b3b9aab24..32f63111c 100644 --- a/light-spike/src/components/fork_detector.rs +++ b/light-spike/src/components/fork_detector.rs @@ -21,20 +21,26 @@ pub enum ForkDetectorOutput { pub type ForkDetectorResult = Result; -pub struct ForkDetector { +pub trait ForkDetector { + fn process(&self, input: ForkDetectorInput) -> ForkDetectorResult; +} + +pub struct RealForkDetector { header_hasher: Box, } -impl ForkDetector { - pub fn new(header_hasher: impl HeaderHasher + 'static) -> Self { - Self { - header_hasher: Box::new(header_hasher), +impl ForkDetector for RealForkDetector { + fn process(&self, input: ForkDetectorInput) -> ForkDetectorResult { + match input { + ForkDetectorInput::Detect(light_blocks) => self.detect(light_blocks), } } +} - pub fn process(&self, input: ForkDetectorInput) -> ForkDetectorResult { - match input { - ForkDetectorInput::Detect(light_blocks) => self.detect(light_blocks), +impl RealForkDetector { + pub fn new(header_hasher: impl HeaderHasher + 'static) -> Self { + Self { + header_hasher: Box::new(header_hasher), } } diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs index 1f1b62cc8..0c0d67f26 100644 --- a/light-spike/src/components/io.rs +++ b/light-spike/src/components/io.rs @@ -24,20 +24,26 @@ pub enum IoError { pub type IoResult = Result; -pub struct Io { - rpc_client: rpc::Client, +pub trait Io { + fn process(&self, input: IoInput) -> IoResult; } -impl Io { - pub fn new(rpc_client: rpc::Client) -> Self { - Self { rpc_client } - } +pub struct RealIo { + rpc_client: rpc::Client, +} - pub fn process(&self, input: IoInput) -> IoResult { +impl Io for RealIo { + fn process(&self, input: IoInput) -> IoResult { match input { IoInput::FetchLightBlock(height) => self.fetch_light_block(height), } } +} + +impl RealIo { + pub fn new(rpc_client: rpc::Client) -> Self { + Self { rpc_client } + } pub fn fetch_light_block(&self, height: Height) -> IoResult { let signed_header = self.fetch_signed_header(height)?; diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index f7c1ffc62..88827b5c1 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -1,11 +1,19 @@ -use std::{future::Future, pin::Pin}; - use serde::{Deserialize, Serialize}; use thiserror::Error; use super::verifier::VerifierError; use crate::prelude::*; +#[async_trait::async_trait(?Send)] +pub trait Scheduler { + async fn process( + &self, + trusted_store: TSReader, + input: SchedulerInput, + co: Co, + ) -> SchedulerResult; +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SchedulerInput { VerifyHeight { @@ -54,23 +62,29 @@ pub enum SchedulerResponse { pub type SchedulerResult = Result; -pub async fn process( - trusted_store: TSReader, - input: SchedulerInput, - co: Co, -) -> SchedulerResult { - match input { - SchedulerInput::VerifyHeight { - height, - trusted_state, - options, - } => verify_height(height, trusted_state, options, trusted_store, co).await, - - SchedulerInput::VerifyLightBlock { - light_block, - trusted_state, - options, - } => verify_light_block(light_block, trusted_state, options, trusted_store, co).await, +pub struct RealScheduler; + +#[async_trait::async_trait(?Send)] +impl Scheduler for RealScheduler { + async fn process( + &self, + trusted_store: TSReader, + input: SchedulerInput, + co: Co, + ) -> SchedulerResult { + match input { + SchedulerInput::VerifyHeight { + height, + trusted_state, + options, + } => verify_height(height, trusted_state, options, trusted_store, co).await, + + SchedulerInput::VerifyLightBlock { + light_block, + trusted_state, + options, + } => verify_light_block(light_block, trusted_state, options, trusted_store, co).await, + } } } @@ -181,19 +195,3 @@ pub async fn do_bisection( let trusted_states = valid_light_blocks.into_iter().map(Into::into).collect(); Ok(SchedulerOutput::TrustedStates(trusted_states)) } - -pub type Scheduler = Box< - dyn Fn( - TSReader, - SchedulerInput, - Co, - ) -> Pin>>, ->; - -pub fn handler(f: F0) -> Scheduler -where - F0: Fn(TSReader, SchedulerInput, Co) -> F + 'static, - F: Future + 'static, -{ - Box::new(move |s, i, c| Box::pin(f(s, i, c))) -} diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 6079a4ec6..192603064 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -25,14 +25,30 @@ pub enum VerifierOutput { pub type VerifierResult = Result; -pub struct Verifier { +pub trait Verifier { + fn process(&self, input: VerifierInput) -> VerifierResult; +} + +pub struct RealVerifier { predicates: Box, voting_power_calculator: Box, commit_validator: Box, header_hasher: Box, } -impl Verifier { +impl Verifier for RealVerifier { + fn process(&self, input: VerifierInput) -> VerifierResult { + match input { + VerifierInput::VerifyLightBlock { + trusted_state, + light_block, + options, + } => self.verify_light_block(light_block, trusted_state, options), + } + } +} + +impl RealVerifier { pub fn new( predicates: impl VerificationPredicates + 'static, voting_power_calculator: impl VotingPowerCalculator + 'static, @@ -47,16 +63,6 @@ impl Verifier { } } - pub fn process(&self, input: VerifierInput) -> VerifierResult { - match input { - VerifierInput::VerifyLightBlock { - trusted_state, - light_block, - options, - } => self.verify_light_block(light_block, trusted_state, options), - } - } - pub fn verify_light_block( &self, light_block: LightBlock, diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs index bc5306c37..57d7615e8 100644 --- a/light-spike/src/trusted_store.rs +++ b/light-spike/src/trusted_store.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - sync::{Arc, RwLock}, + sync::{Arc, Mutex}, }; use crate::prelude::*; @@ -18,7 +18,7 @@ impl TrustedStore { } pub fn split(self) -> (TSReader, TSReadWriter) { - let store = Arc::new(RwLock::new(self)); + let store = Arc::new(Mutex::new(self)); let reader = TSReader { ts: store.clone() }; let writer = TSReadWriter { ts: store }; @@ -42,17 +42,17 @@ impl TrustedStore { #[derive(Clone, Debug)] pub struct TSReader { - ts: Arc>, + ts: Arc>, } impl TSReader { pub fn get(&self, height: Height) -> Option { - self.ts.read().unwrap().get(height).cloned() + self.ts.lock().unwrap().get(height).cloned() } pub fn all(&self) -> Vec { self.ts - .read() + .lock() .unwrap() .all() .into_iter() @@ -63,16 +63,16 @@ impl TSReader { #[derive(Debug)] pub struct TSReadWriter { - ts: Arc>, + ts: Arc>, } impl TSReadWriter { pub fn get(&self, height: Height) -> Option { - self.ts.read().unwrap().get(height).cloned() + self.ts.lock().unwrap().get(height).cloned() } - pub fn add(&mut self, trusted_state: TrustedState) { - let mut ts = self.ts.write().unwrap(); + pub fn add(&self, trusted_state: TrustedState) { + let mut ts = self.ts.lock().unwrap(); ts.add(trusted_state); } } From eca78729cd9043b46e704aa0fa88fa50c7f84176 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Apr 2020 15:58:35 +0200 Subject: [PATCH 033/100] Add actual commit to Commit struct --- light-spike/src/components/demuxer.rs | 2 +- light-spike/src/components/fork_detector.rs | 6 +++--- light-spike/src/components/io.rs | 6 +++--- light-spike/src/components/scheduler.rs | 6 +++--- light-spike/src/components/verifier.rs | 6 +++--- light-spike/src/predicates/errors.rs | 2 +- light-spike/src/types.rs | 17 +++++++++-------- 7 files changed, 23 insertions(+), 22 deletions(-) diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index d011e23c9..c9097f1b2 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -4,7 +4,7 @@ use thiserror::Error; use super::{io::*, scheduler::*, verifier::*}; use crate::prelude::*; -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] pub enum DemuxerError { #[error("scheduler error")] Scheduler(SchedulerError), diff --git a/light-spike/src/components/fork_detector.rs b/light-spike/src/components/fork_detector.rs index 32f63111c..39e48bc27 100644 --- a/light-spike/src/components/fork_detector.rs +++ b/light-spike/src/components/fork_detector.rs @@ -3,18 +3,18 @@ use thiserror::Error; use crate::prelude::*; -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] pub enum ForkDetectorError { #[error("conflicting blocks: {0} and {1}")] ConflictingBlocks(LightBlock, LightBlock), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetectorInput { Detect(Vec), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetectorOutput { NotDetected, } diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs index 0c0d67f26..84e959f14 100644 --- a/light-spike/src/components/io.rs +++ b/light-spike/src/components/io.rs @@ -6,17 +6,17 @@ use thiserror::Error; use crate::prelude::*; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum IoInput { FetchLightBlock(Height), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum IoOutput { FetchedLightBlock(LightBlock), } -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] pub enum IoError { #[error(transparent)] IoError(#[from] rpc::Error), diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 88827b5c1..64ca0be7b 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -14,7 +14,7 @@ pub trait Scheduler { ) -> SchedulerResult; } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum SchedulerInput { VerifyHeight { height: Height, @@ -28,12 +28,12 @@ pub enum SchedulerInput { }, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum SchedulerOutput { TrustedStates(Vec), } -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] pub enum SchedulerError { #[error("invalid light block {0} because: {1}")] InvalidLightBlock(LightBlock, VerifierError), diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index 192603064..deb6259cd 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -3,13 +3,13 @@ use thiserror::Error; use crate::prelude::*; -#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] pub enum VerifierError { #[error("invalid light block")] InvalidLightBlock(#[from] VerificationError), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum VerifierInput { VerifyLightBlock { trusted_state: TrustedState, @@ -18,7 +18,7 @@ pub enum VerifierInput { }, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum VerifierOutput { ValidLightBlock(LightBlock), } diff --git a/light-spike/src/predicates/errors.rs b/light-spike/src/predicates/errors.rs index fe63a9c6e..af54304e9 100644 --- a/light-spike/src/predicates/errors.rs +++ b/light-spike/src/predicates/errors.rs @@ -6,7 +6,7 @@ use thiserror::Error; use crate::prelude::*; -#[derive(Debug, Clone, Error, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Error, PartialEq, Serialize, Deserialize)] pub enum VerificationError { #[error("header from the future: header_time={header_time:?} now={now:?}")] HeaderFromTheFuture { diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index 3ab2f6422..b6acbd748 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -7,7 +7,7 @@ pub use tendermint::lite::types::Height; use crate::prelude::*; -#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct VerificationOptions { pub trust_threshold: TrustThreshold, @@ -15,7 +15,7 @@ pub struct VerificationOptions { pub now: SystemTime, } -#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Header { pub height: Height, @@ -25,7 +25,7 @@ pub struct Header { pub hash: Hash, // TODO: What if we don't have this } -#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct ValidatorSet { pub hash: Hash, @@ -37,20 +37,21 @@ impl From> for ValidatorSet { } } -#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Commit { pub header_hash: Hash, + pub commit: tendermint::block::Commit, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct TrustThreshold { pub numerator: u64, pub denominator: u64, } -#[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct SignedHeader { pub header: Header, @@ -68,7 +69,7 @@ impl From for SignedHeader { // FIXME: Do we actually need to distinguish between LightBlock and TrustedState? pub type TrustedState = LightBlock; -// #[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] +// #[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] // #[display(fmt = "{:?}", self)] // pub struct TrustedState { // pub header: Header, @@ -84,7 +85,7 @@ pub type TrustedState = LightBlock; // } // } -#[derive(Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Display, PartialEq, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct LightBlock { pub height: Height, From 4c9ea47ca03aa9f196e584afbe2a9ab8104fe4f9 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Apr 2020 18:52:56 +0200 Subject: [PATCH 034/100] Refactor and simplify --- light-spike/Cargo.toml | 3 - light-spike/TODO.md | 1 + light-spike/examples/light_client.rs | 11 +- light-spike/src/components/demuxer.rs | 152 +++++---------- light-spike/src/components/fork_detector.rs | 25 +-- light-spike/src/components/io.rs | 9 + light-spike/src/components/scheduler.rs | 201 +++----------------- light-spike/src/components/state.rs | 26 +-- light-spike/src/components/verifier.rs | 43 +++-- light-spike/src/errors.rs | 30 --- light-spike/src/lib.rs | 6 +- light-spike/src/macros.rs | 14 +- light-spike/src/predicates.rs | 132 ++++++------- light-spike/src/prelude.rs | 15 +- light-spike/src/store.rs | 99 ++++++++++ light-spike/src/trusted_store.rs | 78 -------- light-spike/src/types.rs | 17 -- light-spike/src/utils.rs | 21 -- 18 files changed, 308 insertions(+), 575 deletions(-) create mode 100644 light-spike/TODO.md delete mode 100644 light-spike/src/errors.rs create mode 100644 light-spike/src/store.rs delete mode 100644 light-spike/src/trusted_store.rs delete mode 100644 light-spike/src/utils.rs diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index 30da5dab9..ab80d1f98 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -14,10 +14,7 @@ derive_more = "0.99.5" serde = "1.0.106" serde_derive = "1.0.106" thiserror = "1.0.15" -typetag = "0.1.4" -genawaiter = "0.99.1" futures = "0.3.4" -async-trait = "0.1.30" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-spike/TODO.md b/light-spike/TODO.md new file mode 100644 index 000000000..4cb86bd73 --- /dev/null +++ b/light-spike/TODO.md @@ -0,0 +1 @@ +- Split NotEnoughTrust into its own predicate diff --git a/light-spike/examples/light_client.rs b/light-spike/examples/light_client.rs index c709b0155..c9c377ee8 100644 --- a/light-spike/examples/light_client.rs +++ b/light-spike/examples/light_client.rs @@ -1,16 +1,17 @@ +use light_spike::components::scheduler; use light_spike::prelude::*; pub fn main() { - let store = TrustedStore::new(); - let (trusted_store_reader, trusted_store_writer) = store.split(); + let (trusted_store_reader, trusted_store_writer) = Store::new().split(); + let (untrusted_store_reader, untrusted_store_writer) = Store::new().split(); let state = State { trusted_store_reader, trusted_store_writer, + untrusted_store_reader, + untrusted_store_writer, }; - let scheduler = RealScheduler; - let predicates = light_spike::predicates::production::ProductionPredicates; let voting_power_calculator: Box = todo(()); let commit_validator: Box = todo(()); @@ -29,7 +30,7 @@ pub fn main() { let rpc_client = todo(()); let io = RealIo::new(rpc_client); - let demuxer = Demuxer::new(state, scheduler, verifier, fork_detector, io); + let demuxer = Demuxer::new(state, scheduler::schedule, verifier, fork_detector, io); todo(demuxer) } diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index c9097f1b2..33d25c3d8 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -6,12 +6,6 @@ use crate::prelude::*; #[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] pub enum DemuxerError { - #[error("scheduler error")] - Scheduler(SchedulerError), - #[error("verifier error")] - Verifier(VerifierError), - #[error("fork detector")] - ForkDetector(ForkDetectorError), #[error("I/O error")] Io(IoError), } @@ -41,140 +35,82 @@ impl Demuxer { } } - pub fn verify_height( - &self, - height: Height, - trusted_state: TrustedState, - options: VerificationOptions, - ) -> Result, DemuxerError> { - let input = SchedulerInput::VerifyHeight { - height, - trusted_state, - options, - }; + pub fn run(&mut self) { + // self.verify(); + // self.detect_forks(); + } - let result = self.run_scheduler(input)?; + pub fn verify(&mut self, mut light_block: LightBlock, options: VerificationOptions) { + loop { + let trusted_state = self.state.trusted_store_reader.latest().unwrap(); // FIXME + let verif_result = self.verify_light_block(&light_block, &trusted_state, &options); - match result { - SchedulerOutput::TrustedStates(trusted_states) => { - // self.state.add_trusted_states(trusted_states.clone()); - Ok(trusted_states) + if let VerifierOutput::Success = verif_result { + self.state.add_trusted_state(light_block.clone()); + } + + let schedule = self.schedule(&light_block, &trusted_state, verif_result); + + match schedule { + SchedulerOutput::Done => { + // Done + } + SchedulerOutput::NextHeight(next_height) => { + light_block = self.fetch_light_block(next_height).unwrap() // FIXME + } + SchedulerOutput::Abort => todo!(), } } } pub fn verify_light_block( &self, - light_block: LightBlock, - trusted_state: TrustedState, - options: VerificationOptions, - ) -> Result, DemuxerError> { - let input = SchedulerInput::VerifyLightBlock { - light_block, - trusted_state, - options, + light_block: &LightBlock, + trusted_state: &TrustedState, + options: &VerificationOptions, + ) -> VerifierOutput { + let input = VerifierInput::VerifyLightBlock { + light_block: light_block.clone(), + trusted_state: trusted_state.clone(), + options: options.clone(), }; - let result = self.run_scheduler(input)?; - - match result { - SchedulerOutput::TrustedStates(trusted_states) => { - // self.state.add_trusted_states(trusted_states.clone()); - Ok(trusted_states) - } - } + self.verifier.process(input) } - pub fn validate_light_block( + pub fn schedule( &self, - light_block: LightBlock, - trusted_state: TrustedState, - options: VerificationOptions, - ) -> Result { - let input = VerifierInput::VerifyLightBlock { - light_block, - trusted_state, - options, + light_block: &LightBlock, + trusted_state: &TrustedState, + verifier_result: VerifierOutput, + ) -> SchedulerOutput { + let input = SchedulerInput::Schedule { + light_block: light_block.clone(), + trusted_state: trusted_state.clone(), + verifier_result, }; - let result = self - .verifier - .process(input) - .map_err(|e| DemuxerError::Verifier(e))?; - - match result { - VerifierOutput::ValidLightBlock(valid_light_block) => { - // self.state.add_valid_light_block(valid_light_block.clone()); - Ok(valid_light_block) - } - } + self.scheduler.process(input) } pub fn detect_forks(&self) -> Result<(), DemuxerError> { let light_blocks = self.state.trusted_store_reader.all(); let input = ForkDetectorInput::Detect(light_blocks); - let result = self - .fork_detector - .process(input) - .map_err(DemuxerError::ForkDetector)?; + let result = self.fork_detector.process(input); match result { ForkDetectorOutput::NotDetected => Ok(()), + ForkDetectorOutput::Detected(_, _) => Ok(()), // TODO } } pub fn fetch_light_block(&self, height: Height) -> Result { let input = IoInput::FetchLightBlock(height); - let result = self.io.process(input).map_err(|e| DemuxerError::Io(e))?; match result { - IoOutput::FetchedLightBlock(lb) => { - // self.state.add_fetched_light_block(lb.clone()); - Ok(lb) - } + IoOutput::FetchedLightBlock(light_block) => Ok(light_block), } } - - fn handle_request(&self, request: SchedulerRequest) -> Result { - match request { - SchedulerRequest::GetLightBlock(height) => self - .fetch_light_block(height) - .map(|lb| SchedulerResponse::LightBlock(lb)), - - SchedulerRequest::VerifyLightBlock { - light_block, - trusted_state, - options, - } => match self.verify_light_block(light_block, trusted_state, options) { - Ok(ts) => Ok(SchedulerResponse::Verified(Ok(ts))), - Err(DemuxerError::Verifier(err)) => Ok(SchedulerResponse::Verified(Err(err))), - Err(err) => Err(err), - }, - - SchedulerRequest::ValidateLightBlock { - light_block, - trusted_state, - options, - } => match self.validate_light_block(light_block, trusted_state, options) { - Ok(ts) => Ok(SchedulerResponse::Validated(Ok(ts))), - Err(DemuxerError::Verifier(err)) => Ok(SchedulerResponse::Validated(Err(err))), - Err(err) => Err(err), - }, - } - } - - fn run_scheduler(&self, input: SchedulerInput) -> Result { - let scheduler = Gen::new(|co| { - self.scheduler - .process(self.state.trusted_store_reader(), input, co) - }); - - let result = drain(scheduler, SchedulerResponse::Init, |req| { - self.handle_request(req) - })?; - - result.map_err(|e| DemuxerError::Scheduler(e)) - } } diff --git a/light-spike/src/components/fork_detector.rs b/light-spike/src/components/fork_detector.rs index 39e48bc27..4664c5005 100644 --- a/light-spike/src/components/fork_detector.rs +++ b/light-spike/src/components/fork_detector.rs @@ -1,14 +1,7 @@ use serde::{Deserialize, Serialize}; -use thiserror::Error; use crate::prelude::*; -#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] -pub enum ForkDetectorError { - #[error("conflicting blocks: {0} and {1}")] - ConflictingBlocks(LightBlock, LightBlock), -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetectorInput { Detect(Vec), @@ -16,13 +9,12 @@ pub enum ForkDetectorInput { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetectorOutput { + Detected(LightBlock, LightBlock), NotDetected, } -pub type ForkDetectorResult = Result; - pub trait ForkDetector { - fn process(&self, input: ForkDetectorInput) -> ForkDetectorResult; + fn process(&self, input: ForkDetectorInput) -> ForkDetectorOutput; } pub struct RealForkDetector { @@ -30,7 +22,7 @@ pub struct RealForkDetector { } impl ForkDetector for RealForkDetector { - fn process(&self, input: ForkDetectorInput) -> ForkDetectorResult { + fn process(&self, input: ForkDetectorInput) -> ForkDetectorOutput { match input { ForkDetectorInput::Detect(light_blocks) => self.detect(light_blocks), } @@ -44,9 +36,9 @@ impl RealForkDetector { } } - pub fn detect(&self, mut light_blocks: Vec) -> ForkDetectorResult { + pub fn detect(&self, mut light_blocks: Vec) -> ForkDetectorOutput { if light_blocks.is_empty() { - return Ok(ForkDetectorOutput::NotDetected); + return ForkDetectorOutput::NotDetected; } let first_block = light_blocks.pop().unwrap(); // Safety: checked above that not empty. @@ -56,13 +48,10 @@ impl RealForkDetector { let hash = self.header_hasher.hash(light_block.header()); if first_hash != hash { - return Err(ForkDetectorError::ConflictingBlocks( - first_block, - light_block, - )); + return ForkDetectorOutput::Detected(first_block, light_block); } } - Ok(ForkDetectorOutput::NotDetected) + ForkDetectorOutput::NotDetected } } diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs index 84e959f14..24c15e21b 100644 --- a/light-spike/src/components/io.rs +++ b/light-spike/src/components/io.rs @@ -28,6 +28,15 @@ pub trait Io { fn process(&self, input: IoInput) -> IoResult; } +impl Io for F +where + F: Fn(IoInput) -> IoResult, +{ + fn process(&self, input: IoInput) -> IoResult { + self(input) + } +} + pub struct RealIo { rpc_client: rpc::Client, } diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 64ca0be7b..6948549fe 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -1,197 +1,58 @@ use serde::{Deserialize, Serialize}; -use thiserror::Error; -use super::verifier::VerifierError; use crate::prelude::*; -#[async_trait::async_trait(?Send)] pub trait Scheduler { - async fn process( - &self, - trusted_store: TSReader, - input: SchedulerInput, - co: Co, - ) -> SchedulerResult; + fn process(&self, input: SchedulerInput) -> SchedulerOutput; +} + +impl Scheduler for F +where + F: Fn(SchedulerInput) -> SchedulerOutput, +{ + fn process(&self, input: SchedulerInput) -> SchedulerOutput { + self(input) + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum SchedulerInput { - VerifyHeight { - height: Height, - trusted_state: TrustedState, - options: VerificationOptions, - }, - VerifyLightBlock { + Schedule { light_block: LightBlock, trusted_state: TrustedState, - options: VerificationOptions, + verifier_result: VerifierOutput, }, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum SchedulerOutput { - TrustedStates(Vec), -} - -#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] -pub enum SchedulerError { - #[error("invalid light block {0} because: {1}")] - InvalidLightBlock(LightBlock, VerifierError), -} - -pub enum SchedulerRequest { - GetLightBlock(Height), - VerifyLightBlock { - light_block: LightBlock, - trusted_state: TrustedState, - options: VerificationOptions, - }, - ValidateLightBlock { - light_block: LightBlock, - trusted_state: TrustedState, - options: VerificationOptions, - }, -} - -pub enum SchedulerResponse { - Init, - LightBlock(LightBlock), - Validated(Result), - Verified(Result, VerifierError>), -} - -pub type SchedulerResult = Result; - -pub struct RealScheduler; - -#[async_trait::async_trait(?Send)] -impl Scheduler for RealScheduler { - async fn process( - &self, - trusted_store: TSReader, - input: SchedulerInput, - co: Co, - ) -> SchedulerResult { - match input { - SchedulerInput::VerifyHeight { - height, - trusted_state, - options, - } => verify_height(height, trusted_state, options, trusted_store, co).await, - - SchedulerInput::VerifyLightBlock { - light_block, - trusted_state, - options, - } => verify_light_block(light_block, trusted_state, options, trusted_store, co).await, - } - } -} - -pub async fn verify_height( - height: Height, - trusted_state: TrustedState, - options: VerificationOptions, - trusted_store: TSReader, - co: Co, -) -> SchedulerResult { - if let Some(trusted_state) = trusted_store.get(height) { - Ok(SchedulerOutput::TrustedStates(vec![trusted_state])) - } else { - let response = co.yield_(SchedulerRequest::GetLightBlock(height)).await; - let light_block = unwrap!(SchedulerResponse::LightBlock, response); - - verify_light_block(light_block, trusted_state, options, trusted_store, co).await - } -} - -pub async fn verify_light_block( - light_block: LightBlock, - trusted_state: TrustedState, - options: VerificationOptions, - trusted_store: TSReader, - co: Co, -) -> SchedulerResult { - if let Some(in_store) = trusted_store.get(light_block.height) { - return Ok(SchedulerOutput::TrustedStates(vec![in_store])); - } - - let response = co - .yield_(SchedulerRequest::ValidateLightBlock { - light_block: light_block.clone(), - trusted_state: trusted_state.clone(), - options: options.clone(), - }) - .await; - - let result = unwrap!(SchedulerResponse::Validated, response); - match result { - Err(err) if not_enough_trust(&err) => { - do_bisection(light_block, trusted_state, options, co).await - } - Err(err) => Err(SchedulerError::InvalidLightBlock(light_block, err)), - Ok(light_block) => { - let trusted_state = light_block.into(); - Ok(SchedulerOutput::TrustedStates(vec![trusted_state])) - } - } + Abort, + Done, + NextHeight(Height), } -fn not_enough_trust(e: &VerifierError) -> bool { - match e { - VerifierError::InvalidLightBlock(e) => e.not_enough_trust(), +pub fn schedule(input: SchedulerInput) -> SchedulerOutput { + match input { + SchedulerInput::Schedule { + light_block, + trusted_state, + verifier_result, + } => match verifier_result { + VerifierOutput::Success => SchedulerOutput::Done, + VerifierOutput::NotEnoughTrust => { + SchedulerOutput::NextHeight(compute_pivot_height(&light_block, &trusted_state)) + } + VerifierOutput::Invalid(_) => SchedulerOutput::Abort, + }, } } fn compute_pivot_height(light_block: &LightBlock, trusted_state: &TrustedState) -> Height { let trusted_height = trusted_state.height; let untrusted_height = light_block.height; - let pivot_height = trusted_height - .checked_add(untrusted_height) - .expect("height overflow") - / 2; - - pivot_height -} - -pub async fn do_bisection( - light_block: LightBlock, - trusted_state: TrustedState, - options: VerificationOptions, - co: Co, -) -> SchedulerResult { - let pivot_height = compute_pivot_height(&light_block, &trusted_state); - - let pivot_lb = co - .yield_(SchedulerRequest::GetLightBlock(pivot_height)) - .await; - - let pivot_light_block = unwrap!(SchedulerResponse::LightBlock, pivot_lb); - - let pivot_response = co - .yield_(SchedulerRequest::VerifyLightBlock { - light_block: pivot_light_block.clone(), - trusted_state: trusted_state.clone(), - options: options.clone(), - }) - .await; - - let mut valid_light_blocks = unwrap!(SchedulerResponse::Verified, pivot_response) - .map_err(|e| SchedulerError::InvalidLightBlock(pivot_light_block, e))?; - - let lb_response = co - .yield_(SchedulerRequest::ValidateLightBlock { - light_block: light_block.clone(), - trusted_state, - options, - }) - .await; - - let valid_light_block = unwrap!(SchedulerResponse::Validated, lb_response) - .map_err(|e| SchedulerError::InvalidLightBlock(light_block, e))?; - valid_light_blocks.push(valid_light_block); + assert!(trusted_height < untrusted_height); - let trusted_states = valid_light_blocks.into_iter().map(Into::into).collect(); - Ok(SchedulerOutput::TrustedStates(trusted_states)) + // Equivalent to (trusted_height + untrusted_height) / 2, but avoid overflows + trusted_height + (untrusted_height - trusted_height) / 2 } diff --git a/light-spike/src/components/state.rs b/light-spike/src/components/state.rs index abbf60833..e46d95307 100644 --- a/light-spike/src/components/state.rs +++ b/light-spike/src/components/state.rs @@ -2,30 +2,20 @@ use crate::prelude::*; #[derive(Debug)] pub struct State { - pub trusted_store_reader: TSReader, - pub trusted_store_writer: TSReadWriter, - // valid_store_reader: TSReader, - // valid_store_writer: TSReaderWriter, - // fetched_store_reader: TSReader, - // fetched_store_writer: TSReaderWriter, + pub trusted_store_reader: StoreReader, + pub trusted_store_writer: StoreReadWriter, + pub untrusted_store_reader: StoreReader, + pub untrusted_store_writer: StoreReadWriter, } impl State { - pub fn trusted_store_reader(&self) -> TSReader { - self.trusted_store_reader.clone() + pub fn add_trusted_state(&mut self, trusted_state: LightBlock) { + self.trusted_store_writer.add(trusted_state); } - pub fn add_trusted_states(&mut self, trusted_states: Vec) { + pub fn add_trusted_states(&mut self, trusted_states: Vec) { for trusted_state in trusted_states { - self.trusted_store_writer.add(trusted_state); + self.add_trusted_state(trusted_state) } } - - pub fn add_valid_light_block(&mut self, _light_block: LightBlock) { - // self.valid_store_writer.add(light_block); - } - - pub fn add_fetched_light_block(&mut self, _light_block: LightBlock) { - // self.fetched_store_writer.add(light_block); - } } diff --git a/light-spike/src/components/verifier.rs b/light-spike/src/components/verifier.rs index deb6259cd..7fb8b1b2d 100644 --- a/light-spike/src/components/verifier.rs +++ b/light-spike/src/components/verifier.rs @@ -1,14 +1,7 @@ use serde::{Deserialize, Serialize}; -use thiserror::Error; use crate::prelude::*; -#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] -pub enum VerifierError { - #[error("invalid light block")] - InvalidLightBlock(#[from] VerificationError), -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum VerifierInput { VerifyLightBlock { @@ -20,13 +13,22 @@ pub enum VerifierInput { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum VerifierOutput { - ValidLightBlock(LightBlock), + Success, + NotEnoughTrust, + Invalid(VerificationError), } -pub type VerifierResult = Result; - pub trait Verifier { - fn process(&self, input: VerifierInput) -> VerifierResult; + fn process(&self, input: VerifierInput) -> VerifierOutput; +} + +impl Verifier for F +where + F: Fn(VerifierInput) -> VerifierOutput, +{ + fn process(&self, input: VerifierInput) -> VerifierOutput { + self(input) + } } pub struct RealVerifier { @@ -37,7 +39,7 @@ pub struct RealVerifier { } impl Verifier for RealVerifier { - fn process(&self, input: VerifierInput) -> VerifierResult { + fn process(&self, input: VerifierInput) -> VerifierOutput { match input { VerifierInput::VerifyLightBlock { trusted_state, @@ -68,18 +70,23 @@ impl RealVerifier { light_block: LightBlock, trusted_state: TrustedState, options: VerificationOptions, - ) -> Result { - self.predicates.verify_light_block( + ) -> VerifierOutput { + let result = crate::predicates::verify_light_block( + &*self.predicates, &self.voting_power_calculator, &self.commit_validator, &self.header_hasher, &trusted_state, &light_block, options, - )?; + ); - // FIXME: Do we actually need to distinguish between LightBlock and TrustedState? - let new_trusted_state = light_block.into(); - Ok(VerifierOutput::ValidLightBlock(new_trusted_state)) + match result { + Ok(()) => VerifierOutput::Success, + Err(VerificationError::InsufficientVotingPower { .. }) => { + VerifierOutput::NotEnoughTrust + } + Err(e) => VerifierOutput::Invalid(e), + } } } diff --git a/light-spike/src/errors.rs b/light-spike/src/errors.rs deleted file mode 100644 index 0419c5eda..000000000 --- a/light-spike/src/errors.rs +++ /dev/null @@ -1,30 +0,0 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; - -use crate::prelude::*; - -/// Ensure a condition holds, returning an error if it doesn't (ala `assert`) -#[macro_export] -macro_rules! ensure { - ($cond:expr, $kind:expr) => { - if !($cond) { - return Err($kind.into()); - } - }; -} - -pub type Error = anomaly::Error; - -#[derive(Debug, Clone, Error)] -pub enum ErrorKind { - #[error("verifier error")] - Verifier(#[source] VerifierError), -} - -impl ErrorKind { - /// Add additional context (i.e. include a source error and capture a backtrace). - /// You can convert the resulting `Context` into an `Error` by calling `.into()`. - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) - } -} diff --git a/light-spike/src/lib.rs b/light-spike/src/lib.rs index 938e7ea80..a3c51966c 100644 --- a/light-spike/src/lib.rs +++ b/light-spike/src/lib.rs @@ -2,19 +2,15 @@ #![warn( unreachable_pub, // missing_docs, - // missing_doc_code_examples )] -#![allow(clippy::too_many_arguments, clippy::match_wild_err_arm)] pub mod components; -pub mod errors; pub mod macros; pub mod operations; pub mod predicates; pub mod prelude; -pub mod trusted_store; +pub mod store; pub mod types; -pub mod utils; #[cfg(test)] mod tests { diff --git a/light-spike/src/macros.rs b/light-spike/src/macros.rs index 770dab162..50fea18b8 100644 --- a/light-spike/src/macros.rs +++ b/light-spike/src/macros.rs @@ -1,10 +1,10 @@ +/// Ensure a condition holds, returning an error if it doesn't (ala `assert`) #[macro_export] -macro_rules! unwrap { - ($enum:path, $expr:expr) => {{ - if let $enum(item) = $expr { - item - } else { - unreachable!() +macro_rules! ensure { + ($cond:expr, $kind:expr) => { + if !($cond) { + return Err($kind.into()); } - }}; + }; } + diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 41c0a92ce..85e20bfcc 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -80,78 +80,78 @@ pub trait VerificationPredicates { untrusted_sh: &SignedHeader, untrusted_next_vals: &ValidatorSet, ) -> Result<(), VerificationError>; +} - #[allow(clippy::too_many_arguments, clippy::comparison_chain)] - fn verify_light_block( - &self, - voting_power_calculator: &dyn VotingPowerCalculator, - commit_validator: &dyn CommitValidator, - header_hasher: &dyn HeaderHasher, - trusted_state: &TrustedState, - light_block: &LightBlock, - options: VerificationOptions, - ) -> Result<(), VerificationError> { - let untrusted_sh = &light_block.signed_header; - let untrusted_vals = &light_block.validators; - let untrusted_next_vals = &light_block.next_validators; - - // Ensure the latest trusted header hasn't expired - self.is_within_trust_period( - &trusted_state.header(), - options.trusting_period, - options.now, - )?; - - // Ensure the header validator hashes match the given validators - self.validator_sets_match(&untrusted_sh, &untrusted_vals)?; - - // Ensure the header next validator hashes match the given next validators - self.next_validators_match(&untrusted_sh, &untrusted_next_vals)?; - - // Ensure the header matches the commit - self.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, header_hasher)?; - - // Additional implementation specific validation - self.valid_commit( +#[allow(clippy::too_many_arguments, clippy::comparison_chain)] +pub fn verify_light_block( + vp: &dyn VerificationPredicates, + voting_power_calculator: &dyn VotingPowerCalculator, + commit_validator: &dyn CommitValidator, + header_hasher: &dyn HeaderHasher, + trusted_state: &TrustedState, + light_block: &LightBlock, + options: VerificationOptions, +) -> Result<(), VerificationError> { + let untrusted_sh = &light_block.signed_header; + let untrusted_vals = &light_block.validators; + let untrusted_next_vals = &light_block.next_validators; + + // Ensure the latest trusted header hasn't expired + vp.is_within_trust_period( + &trusted_state.header(), + options.trusting_period, + options.now, + )?; + + // Ensure the header validator hashes match the given validators + vp.validator_sets_match(&untrusted_sh, &untrusted_vals)?; + + // Ensure the header next validator hashes match the given next validators + vp.next_validators_match(&untrusted_sh, &untrusted_next_vals)?; + + // Ensure the header matches the commit + vp.header_matches_commit(&untrusted_sh.header, &untrusted_sh.commit, header_hasher)?; + + // Additional implementation specific validation + vp.valid_commit( + &untrusted_sh.commit, + &untrusted_sh.validators, + commit_validator, + )?; + + vp.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header())?; + + if untrusted_sh.header.height == trusted_state.header().height { + vp.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; + } else if untrusted_sh.header.height > trusted_state.header().height { + vp.has_sufficient_voting_power( &untrusted_sh.commit, &untrusted_sh.validators, - commit_validator, - )?; - - self.is_monotonic_bft_time(&untrusted_sh.header, &trusted_state.header())?; - - if untrusted_sh.header.height == trusted_state.header().height { - self.valid_next_validator_set(&untrusted_sh, &untrusted_next_vals)?; - } else if untrusted_sh.header.height > trusted_state.header().height { - self.has_sufficient_voting_power( - &untrusted_sh.commit, - &untrusted_sh.validators, - &options.trust_threshold, - voting_power_calculator, - )?; - } else { - // This check will always fail since trusted_state.header < untrusted_sh.header - self.is_monotonic_height(&trusted_state.header(), &untrusted_sh.header)?; - unreachable!(); - } - - // All validation passed successfully. - // Verify the validators correctly committed the block. - - self.has_sufficient_validators_overlap( - &untrusted_sh.commit, - &trusted_state.validators, &options.trust_threshold, voting_power_calculator, )?; + } else { + // This check will always fail since trusted_state.header < untrusted_sh.header + vp.is_monotonic_height(&trusted_state.header(), &untrusted_sh.header)?; + unreachable!(); + } - self.has_sufficient_signers_overlap( - &untrusted_sh.commit, - &untrusted_vals, - &options.trust_threshold, - voting_power_calculator, - )?; + // All validation passed successfully. + // Verify the validators correctly committed the block. - Ok(()) - } + vp.has_sufficient_validators_overlap( + &untrusted_sh.commit, + &trusted_state.validators, + &options.trust_threshold, + voting_power_calculator, + )?; + + vp.has_sufficient_signers_overlap( + &untrusted_sh.commit, + &untrusted_vals, + &options.trust_threshold, + voting_power_calculator, + )?; + + Ok(()) } diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index f001612d3..0674af7cd 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,21 +1,14 @@ +pub use std::time::{Duration, SystemTime}; + pub use crate::components::demuxer::*; pub use crate::components::fork_detector::*; pub use crate::components::io::*; pub use crate::components::scheduler::*; pub use crate::components::state::*; pub use crate::components::verifier::*; -pub use crate::errors::*; +pub use crate::ensure; pub use crate::operations::*; pub use crate::predicates::errors::*; pub use crate::predicates::VerificationPredicates; -pub use crate::trusted_store::*; +pub use crate::store::*; pub use crate::types::*; -pub use crate::utils::*; -pub use crate::{ensure, unwrap}; - -pub use std::time::{Duration, SystemTime}; - -pub use genawaiter::{ - rc::{Co, Gen}, - GeneratorState, -}; diff --git a/light-spike/src/store.rs b/light-spike/src/store.rs new file mode 100644 index 000000000..e77ff97da --- /dev/null +++ b/light-spike/src/store.rs @@ -0,0 +1,99 @@ +use std::{ + collections::HashMap, + marker::PhantomData, + sync::{Arc, RwLock}, +}; + +use crate::prelude::*; + +#[derive(Debug)] +pub struct Trusted; + +#[derive(Debug)] +pub struct Untrusted; + +#[derive(Debug, Default)] +pub struct Store { + store: HashMap, + marker: PhantomData, +} + +impl Store { + pub fn new() -> Self { + Self { + store: HashMap::new(), + marker: PhantomData, + } + } + + pub fn split(self) -> (StoreReader, StoreReadWriter) { + let store = Arc::new(RwLock::new(self)); + + let reader = StoreReader { + store: store.clone(), + }; + + let writer = StoreReadWriter { store }; + + (reader, writer) + } +} + +impl Store { + pub fn get(&self, height: Height) -> Option<&LightBlock> { + self.store.get(&height) + } + + pub fn add(&mut self, light_block: LightBlock) { + self.store.insert(light_block.height, light_block); + } + + pub fn all(&self) -> Vec<&LightBlock> { + self.store.values().collect() + } + + pub fn latest(&self) -> Option<&LightBlock> { + todo!() + } +} + +#[derive(Clone, Debug)] +pub struct StoreReader { + store: Arc>>, +} + +impl StoreReader { + pub fn get(&self, height: Height) -> Option { + self.store.read().unwrap().get(height).cloned() + } + + pub fn latest(&self) -> Option { + self.store.read().unwrap().latest().cloned() + } + + pub fn all(&self) -> Vec { + self.store + .read() + .unwrap() + .all() + .into_iter() + .cloned() + .collect() + } +} + +#[derive(Debug)] +pub struct StoreReadWriter { + store: Arc>>, +} + +impl StoreReadWriter { + pub fn get(&self, height: Height) -> Option { + self.store.read().unwrap().get(height).cloned() + } + + pub fn add(&mut self, light_block: LightBlock) { + let mut store = self.store.write().unwrap(); + store.add(light_block); + } +} diff --git a/light-spike/src/trusted_store.rs b/light-spike/src/trusted_store.rs deleted file mode 100644 index 57d7615e8..000000000 --- a/light-spike/src/trusted_store.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; - -use crate::prelude::*; - -#[derive(Debug, Default)] -pub struct TrustedStore { - store: HashMap, -} - -impl TrustedStore { - pub fn new() -> Self { - Self { - store: HashMap::new(), - } - } - - pub fn split(self) -> (TSReader, TSReadWriter) { - let store = Arc::new(Mutex::new(self)); - let reader = TSReader { ts: store.clone() }; - let writer = TSReadWriter { ts: store }; - - (reader, writer) - } -} - -impl TrustedStore { - pub fn get(&self, height: Height) -> Option<&TrustedState> { - self.store.get(&height) - } - - pub fn add(&mut self, trusted_state: TrustedState) { - self.store.insert(trusted_state.height, trusted_state); - } - - pub fn all(&self) -> Vec<&TrustedState> { - self.store.values().collect() - } -} - -#[derive(Clone, Debug)] -pub struct TSReader { - ts: Arc>, -} - -impl TSReader { - pub fn get(&self, height: Height) -> Option { - self.ts.lock().unwrap().get(height).cloned() - } - - pub fn all(&self) -> Vec { - self.ts - .lock() - .unwrap() - .all() - .into_iter() - .map(Clone::clone) - .collect() - } -} - -#[derive(Debug)] -pub struct TSReadWriter { - ts: Arc>, -} - -impl TSReadWriter { - pub fn get(&self, height: Height) -> Option { - self.ts.lock().unwrap().get(height).cloned() - } - - pub fn add(&self, trusted_state: TrustedState) { - let mut ts = self.ts.lock().unwrap(); - ts.add(trusted_state); - } -} diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index b6acbd748..7ddcfee31 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -66,25 +66,8 @@ impl From for SignedHeader { } } -// FIXME: Do we actually need to distinguish between LightBlock and TrustedState? pub type TrustedState = LightBlock; -// #[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] -// #[display(fmt = "{:?}", self)] -// pub struct TrustedState { -// pub header: Header, -// pub validators: ValidatorSet, -// } - -// impl From for TrustedState { -// fn from(light_block: LightBlock) -> Self { -// Self { -// header: light_block.signed_header.header, -// validators: light_block.validator_set, -// } -// } -// } - #[derive(Clone, Debug, Display, PartialEq, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct LightBlock { diff --git a/light-spike/src/utils.rs b/light-spike/src/utils.rs deleted file mode 100644 index 93a30d332..000000000 --- a/light-spike/src/utils.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::future::Future; - -use genawaiter::{rc::Gen, GeneratorState}; - -pub fn drain( - mut gen: Gen, - init: O, - mut handler: impl FnMut(I) -> Result, -) -> Result -where - F: Future, -{ - let mut response = init; - - loop { - match gen.resume_with(response) { - GeneratorState::Yielded(request) => response = handler(request)?, - GeneratorState::Complete(result) => return Ok(result), - } - } -} From 51f6939fcc533545ad64e40d0b1fef2a373d0a9c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 1 May 2020 14:02:00 +0200 Subject: [PATCH 035/100] Implement Store::latest --- light-spike/src/store.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/light-spike/src/store.rs b/light-spike/src/store.rs index e77ff97da..3257cec37 100644 --- a/light-spike/src/store.rs +++ b/light-spike/src/store.rs @@ -1,5 +1,7 @@ +// TODO: Replace this in-memory store with a proper `sled` based implementation + use std::{ - collections::HashMap, + collections::BTreeMap, marker::PhantomData, sync::{Arc, RwLock}, }; @@ -14,14 +16,14 @@ pub struct Untrusted; #[derive(Debug, Default)] pub struct Store { - store: HashMap, + store: BTreeMap, marker: PhantomData, } impl Store { pub fn new() -> Self { Self { - store: HashMap::new(), + store: BTreeMap::new(), marker: PhantomData, } } @@ -52,8 +54,12 @@ impl Store { self.store.values().collect() } + pub fn latest_height(&self) -> Option { + self.store.keys().last().copied() + } + pub fn latest(&self) -> Option<&LightBlock> { - todo!() + self.latest_height().and_then(|h| self.get(h)) } } @@ -67,6 +73,10 @@ impl StoreReader { self.store.read().unwrap().get(height).cloned() } + pub fn latest_height(&self) -> Option { + self.store.read().unwrap().latest_height() + } + pub fn latest(&self) -> Option { self.store.read().unwrap().latest().cloned() } @@ -88,10 +98,6 @@ pub struct StoreReadWriter { } impl StoreReadWriter { - pub fn get(&self, height: Height) -> Option { - self.store.read().unwrap().get(height).cloned() - } - pub fn add(&mut self, light_block: LightBlock) { let mut store = self.store.write().unwrap(); store.add(light_block); From e789f8b909119f159af33d4936de8f237b4424bc Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 1 May 2020 14:32:04 +0200 Subject: [PATCH 036/100] Better verification loop --- light-spike/src/components/demuxer.rs | 56 +++++++++++++++------------ light-spike/src/components/io.rs | 2 + 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 33d25c3d8..019ba24ba 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -1,17 +1,9 @@ -use serde::{Deserialize, Serialize}; -use thiserror::Error; - use super::{io::*, scheduler::*, verifier::*}; use crate::prelude::*; -#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] -pub enum DemuxerError { - #[error("I/O error")] - Io(IoError), -} - pub struct Demuxer { state: State, + options: VerificationOptions, scheduler: Box, verifier: Box, fork_detector: Box, @@ -21,6 +13,7 @@ pub struct Demuxer { impl Demuxer { pub fn new( state: State, + options: VerificationOptions, scheduler: impl Scheduler + 'static, verifier: impl Verifier + 'static, fork_detector: impl ForkDetector + 'static, @@ -28,6 +21,7 @@ impl Demuxer { ) -> Self { Self { state, + options, scheduler: Box::new(scheduler), verifier: Box::new(verifier), fork_detector: Box::new(fork_detector), @@ -36,14 +30,27 @@ impl Demuxer { } pub fn run(&mut self) { - // self.verify(); - // self.detect_forks(); + loop { + self.verify(); + self.detect_forks(); + } } - pub fn verify(&mut self, mut light_block: LightBlock, options: VerificationOptions) { + pub fn verify(&mut self) { + let trusted_state = match self.state.trusted_store_reader.latest() { + Some(trusted_state) => trusted_state, + None => return, // No trusted state to start from, abort. + }; + + let last_block = match self.fetch_light_block(LATEST_HEIGHT) { + Ok(last_block) => last_block, + Err(_) => return, // No block to sync up to, abort. TODO: Deal with error + }; + + let mut light_block = last_block; + loop { - let trusted_state = self.state.trusted_store_reader.latest().unwrap(); // FIXME - let verif_result = self.verify_light_block(&light_block, &trusted_state, &options); + let verif_result = self.verify_light_block(&light_block, &trusted_state, &self.options); if let VerifierOutput::Success = verif_result { self.state.add_trusted_state(light_block.clone()); @@ -52,13 +59,14 @@ impl Demuxer { let schedule = self.schedule(&light_block, &trusted_state, verif_result); match schedule { - SchedulerOutput::Done => { - // Done - } + SchedulerOutput::Done => (), + SchedulerOutput::Abort => (), SchedulerOutput::NextHeight(next_height) => { - light_block = self.fetch_light_block(next_height).unwrap() // FIXME + light_block = match self.fetch_light_block(next_height) { + Ok(light_block) => light_block, + Err(_) => return, // couldn't fetch next block, abort. + } } - SchedulerOutput::Abort => todo!(), } } } @@ -93,21 +101,21 @@ impl Demuxer { self.scheduler.process(input) } - pub fn detect_forks(&self) -> Result<(), DemuxerError> { + pub fn detect_forks(&self) { let light_blocks = self.state.trusted_store_reader.all(); let input = ForkDetectorInput::Detect(light_blocks); let result = self.fork_detector.process(input); match result { - ForkDetectorOutput::NotDetected => Ok(()), - ForkDetectorOutput::Detected(_, _) => Ok(()), // TODO + ForkDetectorOutput::NotDetected => (), // TODO + ForkDetectorOutput::Detected(_, _) => (), // TODO } } - pub fn fetch_light_block(&self, height: Height) -> Result { + pub fn fetch_light_block(&self, height: Height) -> Result { let input = IoInput::FetchLightBlock(height); - let result = self.io.process(input).map_err(|e| DemuxerError::Io(e))?; + let result = self.io.process(input)?; match result { IoOutput::FetchedLightBlock(light_block) => Ok(light_block), diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs index 24c15e21b..5fcd99758 100644 --- a/light-spike/src/components/io.rs +++ b/light-spike/src/components/io.rs @@ -6,6 +6,8 @@ use thiserror::Error; use crate::prelude::*; +pub const LATEST_HEIGHT: Height = 0; + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum IoInput { FetchLightBlock(Height), From cab2d447bf6d4d830fae58806cc9c468dc4d70fb Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 1 May 2020 14:58:42 +0200 Subject: [PATCH 037/100] Add pre/post conditions to demuxer::verify --- light-spike/src/components/demuxer.rs | 81 +++++++++++++++++++++++++-- light-spike/src/macros.rs | 15 +++++ light-spike/src/prelude.rs | 2 +- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 019ba24ba..e0a860686 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -42,12 +42,33 @@ impl Demuxer { None => return, // No trusted state to start from, abort. }; - let last_block = match self.fetch_light_block(LATEST_HEIGHT) { + let target_block = match self.fetch_light_block(LATEST_HEIGHT) { Ok(last_block) => last_block, Err(_) => return, // No block to sync up to, abort. TODO: Deal with error }; - let mut light_block = last_block; + self.verify_loop(trusted_state, target_block); + } + + fn verify_loop(&mut self, trusted_state: LightBlock, target_block: LightBlock) { + let target_height = target_block.height; + + precondition!( + contracts::verify::trusted_state_contains_block_within_trusting_period( + &self.state.trusted_store_reader, + self.options.trusting_period, + self.options.now + ) + ); + + precondition!( + contracts::verify::target_height_greater_than_all_blocks_in_trusted_store( + target_height, + &self.state.trusted_store_reader, + ) + ); + + let mut light_block = target_block; loop { let verif_result = self.verify_light_block(&light_block, &trusted_state, &self.options); @@ -59,8 +80,8 @@ impl Demuxer { let schedule = self.schedule(&light_block, &trusted_state, verif_result); match schedule { - SchedulerOutput::Done => (), - SchedulerOutput::Abort => (), + SchedulerOutput::Done => break, + SchedulerOutput::Abort => return, SchedulerOutput::NextHeight(next_height) => { light_block = match self.fetch_light_block(next_height) { Ok(light_block) => light_block, @@ -69,6 +90,13 @@ impl Demuxer { } } } + + postcondition!( + contracts::verify::trusted_store_contains_block_at_target_height( + target_height, + &self.state.trusted_store_reader, + ) + ); } pub fn verify_light_block( @@ -122,3 +150,48 @@ impl Demuxer { } } } + +pub mod contracts { + pub mod verify { + use crate::prelude::*; + + pub fn trusted_state_contains_block_within_trusting_period( + trusted_store: &StoreReader, + trusting_period: Duration, + now: SystemTime, + ) -> bool { + trusted_store + .all() + .iter() + .any(|lb| is_within_trust_period(lb, trusting_period, now)) + } + + pub fn target_height_greater_than_all_blocks_in_trusted_store( + target_height: Height, + trusted_store: &StoreReader, + ) -> bool { + trusted_store + .all() + .iter() + .all(|lb| lb.height < target_height) + } + + pub fn trusted_store_contains_block_at_target_height( + target_height: Height, + trusted_store: &StoreReader, + ) -> bool { + trusted_store.get(target_height).is_some() + } + + fn is_within_trust_period( + light_block: &LightBlock, + trusting_period: Duration, + now: SystemTime, + ) -> bool { + let header_time = light_block.header().bft_time; + let expires_at = header_time + trusting_period; + + header_time < now && expires_at > now && header_time <= now + } + } +} diff --git a/light-spike/src/macros.rs b/light-spike/src/macros.rs index 50fea18b8..844c53363 100644 --- a/light-spike/src/macros.rs +++ b/light-spike/src/macros.rs @@ -8,3 +8,18 @@ macro_rules! ensure { }; } +/// Require that a precondition holds, panics if it doesn't. +#[macro_export] +macro_rules! precondition { + ($cond:expr) => { + debug_assert!($cond, "precondition failed: {}", stringify!($cond)); + }; +} + +/// Require that a precondition holds, panics if it doesn't. +#[macro_export] +macro_rules! postcondition { + ($cond:expr) => { + debug_assert!($cond, "postcondition failed: {}", stringify!($cond)); + }; +} diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 0674af7cd..9fccb63c5 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -6,9 +6,9 @@ pub use crate::components::io::*; pub use crate::components::scheduler::*; pub use crate::components::state::*; pub use crate::components::verifier::*; -pub use crate::ensure; pub use crate::operations::*; pub use crate::predicates::errors::*; pub use crate::predicates::VerificationPredicates; pub use crate::store::*; pub use crate::types::*; +pub use crate::{ensure, postcondition, precondition}; From a77f1fcba8d2cf0fa7e4e8baef22aa069c34d652 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 1 May 2020 15:16:15 +0200 Subject: [PATCH 038/100] Add contract for schedule --- light-spike/examples/light_client.rs | 19 ++++++++++++- light-spike/src/components/demuxer.rs | 36 ++++++++++++++++++++++--- light-spike/src/components/scheduler.rs | 10 +++---- light-spike/src/components/state.rs | 12 --------- 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/light-spike/examples/light_client.rs b/light-spike/examples/light_client.rs index c9c377ee8..702471476 100644 --- a/light-spike/examples/light_client.rs +++ b/light-spike/examples/light_client.rs @@ -12,6 +12,15 @@ pub fn main() { untrusted_store_writer, }; + let options = VerificationOptions { + trust_threshold: TrustThreshold { + numerator: 1, + denominator: 3, + }, + trusting_period: Duration::from_secs(3600), + now: SystemTime::now(), + }; + let predicates = light_spike::predicates::production::ProductionPredicates; let voting_power_calculator: Box = todo(()); let commit_validator: Box = todo(()); @@ -30,7 +39,15 @@ pub fn main() { let rpc_client = todo(()); let io = RealIo::new(rpc_client); - let demuxer = Demuxer::new(state, scheduler::schedule, verifier, fork_detector, io); + let demuxer = Demuxer::new( + state, + options, + scheduler::schedule, + verifier, + fork_detector, + io, + ); + todo(demuxer) } diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index e0a860686..f4f60c752 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -74,7 +74,9 @@ impl Demuxer { let verif_result = self.verify_light_block(&light_block, &trusted_state, &self.options); if let VerifierOutput::Success = verif_result { - self.state.add_trusted_state(light_block.clone()); + self.state.trusted_store_writer.add(light_block.clone()); + } else { + self.state.untrusted_store_writer.add(light_block.clone()); } let schedule = self.schedule(&light_block, &trusted_state, verif_result); @@ -83,6 +85,14 @@ impl Demuxer { SchedulerOutput::Done => break, SchedulerOutput::Abort => return, SchedulerOutput::NextHeight(next_height) => { + postcondition!(contracts::schedule::postcondition( + &light_block, + target_height, + next_height, + &self.state.trusted_store_reader, + &self.state.untrusted_store_reader + )); + light_block = match self.fetch_light_block(next_height) { Ok(light_block) => light_block, Err(_) => return, // couldn't fetch next block, abort. @@ -116,12 +126,12 @@ impl Demuxer { pub fn schedule( &self, - light_block: &LightBlock, + checked_header: &LightBlock, trusted_state: &TrustedState, verifier_result: VerifierOutput, ) -> SchedulerOutput { let input = SchedulerInput::Schedule { - light_block: light_block.clone(), + checked_header: checked_header.clone(), trusted_state: trusted_state.clone(), verifier_result, }; @@ -194,4 +204,24 @@ pub mod contracts { header_time < now && expires_at > now && header_time <= now } } + + pub mod schedule { + use crate::prelude::*; + + pub fn postcondition( + checked_header: &LightBlock, + target_height: Height, + next_height: Height, + trusted_store: &StoreReader, + untrusted_store: &StoreReader, + ) -> bool { + let current_height = checked_header.height; + + next_height <= target_height + && (next_height > current_height + || next_height == current_height && current_height == target_height) + && (trusted_store.get(current_height).as_ref() == Some(checked_header) + || untrusted_store.get(current_height).as_ref() == Some(checked_header)) + } + } } diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 6948549fe..0b69c6fa5 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -18,7 +18,7 @@ where #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum SchedulerInput { Schedule { - light_block: LightBlock, + checked_header: LightBlock, trusted_state: TrustedState, verifier_result: VerifierOutput, }, @@ -34,22 +34,22 @@ pub enum SchedulerOutput { pub fn schedule(input: SchedulerInput) -> SchedulerOutput { match input { SchedulerInput::Schedule { - light_block, + checked_header, trusted_state, verifier_result, } => match verifier_result { VerifierOutput::Success => SchedulerOutput::Done, VerifierOutput::NotEnoughTrust => { - SchedulerOutput::NextHeight(compute_pivot_height(&light_block, &trusted_state)) + SchedulerOutput::NextHeight(compute_pivot_height(&checked_header, &trusted_state)) } VerifierOutput::Invalid(_) => SchedulerOutput::Abort, }, } } -fn compute_pivot_height(light_block: &LightBlock, trusted_state: &TrustedState) -> Height { +fn compute_pivot_height(checked_header: &LightBlock, trusted_state: &TrustedState) -> Height { let trusted_height = trusted_state.height; - let untrusted_height = light_block.height; + let untrusted_height = checked_header.height; assert!(trusted_height < untrusted_height); diff --git a/light-spike/src/components/state.rs b/light-spike/src/components/state.rs index e46d95307..829b5b652 100644 --- a/light-spike/src/components/state.rs +++ b/light-spike/src/components/state.rs @@ -7,15 +7,3 @@ pub struct State { pub untrusted_store_reader: StoreReader, pub untrusted_store_writer: StoreReadWriter, } - -impl State { - pub fn add_trusted_state(&mut self, trusted_state: LightBlock) { - self.trusted_store_writer.add(trusted_state); - } - - pub fn add_trusted_states(&mut self, trusted_states: Vec) { - for trusted_state in trusted_states { - self.add_trusted_state(trusted_state) - } - } -} From 2b80f14d9e6aa757c9182b751723201eba85c76b Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 1 May 2020 15:51:05 +0200 Subject: [PATCH 039/100] Convert between tendermint and spike types --- light-spike/src/components/io.rs | 31 ++++++++----------- light-spike/src/types.rs | 51 +++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs index 5fcd99758..6b27dbfd2 100644 --- a/light-spike/src/components/io.rs +++ b/light-spike/src/components/io.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; use tendermint::{block, rpc}; use thiserror::Error; +use tendermint::{block::signed_header::SignedHeader as TMSignedHeader, lite::types::Height}; + use crate::prelude::*; pub const LATEST_HEIGHT: Height = 0; @@ -58,20 +60,11 @@ impl RealIo { pub fn fetch_light_block(&self, height: Height) -> IoResult { let signed_header = self.fetch_signed_header(height)?; - let validators = self.fetch_validator_set(height)?; - let next_validators = self.fetch_validator_set(height + 1)?; - - let light_block = LightBlock { - height, - signed_header, - validators, - next_validators, - }; - + let light_block = signed_header.into(); Ok(IoOutput::FetchedLightBlock(light_block)) } - fn fetch_signed_header(&self, h: Height) -> Result { + fn fetch_signed_header(&self, h: Height) -> Result { let height: block::Height = h.into(); let res = block_on(async { @@ -82,17 +75,17 @@ impl RealIo { }); match res { - Ok(response) => Ok(response.signed_header.into()), + Ok(response) => Ok(response.signed_header), Err(err) => Err(IoError::IoError(err)), } } - fn fetch_validator_set(&self, height: Height) -> Result { - let res = block_on(self.rpc_client.validators(height)); + // fn fetch_validator_set(&self, height: Height) -> Result { + // let res = block_on(self.rpc_client.validators(height)); - match res { - Ok(response) => Ok(response.validators.into()), - Err(err) => Err(IoError::IoError(err)), - } - } + // match res { + // Ok(response) => Ok(TMValidatorSet::new(response.validators)), + // Err(err) => Err(IoError::IoError(err)), + // } + // } } diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index 7ddcfee31..d458c6499 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -3,7 +3,9 @@ use serde::{Deserialize, Serialize}; use std::time::SystemTime; pub use tendermint::hash::Hash; -pub use tendermint::lite::types::Height; +pub use tendermint::lite::Height; + +use tendermint::{block::signed_header::SignedHeader as TMSignedHeader, lite::Header as _}; use crate::prelude::*; @@ -60,9 +62,27 @@ pub struct SignedHeader { pub validators_hash: Hash, } -impl From for SignedHeader { - fn from(_sh: tendermint::block::signed_header::SignedHeader) -> Self { - todo!() +impl From for SignedHeader { + fn from(sh: TMSignedHeader) -> Self { + let validators = ValidatorSet { + hash: sh.header.validators_hash(), + }; + + Self { + header: Header { + height: sh.header.height().into(), + bft_time: sh.header.bft_time().to_system_time().unwrap(), + validators_hash: sh.header.validators_hash(), + next_validators_hash: sh.header.next_validators_hash(), + hash: sh.header.hash(), + }, + commit: Commit { + header_hash: sh.header.hash(), + commit: sh.commit, + }, + validators: validators.clone(), + validators_hash: validators.hash, + } } } @@ -82,3 +102,26 @@ impl LightBlock { &self.signed_header.header } } + +impl From for LightBlock { + fn from(sh: tendermint::block::signed_header::SignedHeader) -> Self { + let height = sh.header.height.into(); + + let validators = ValidatorSet { + hash: sh.header.validators_hash(), + }; + + let next_validators = ValidatorSet { + hash: sh.header.next_validators_hash(), + }; + + let signed_header = sh.into(); + + Self { + height, + signed_header, + validators, + next_validators, + } + } +} From 811500fb5e6369dc73682f641d735d23df575613 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 1 May 2020 18:55:08 +0200 Subject: [PATCH 040/100] Working example --- light-spike/Cargo.toml | 2 + light-spike/examples/light_client.rs | 63 ++++++++++++++----- light-spike/src/components/demuxer.rs | 90 +++++++++++++++++++++------ light-spike/src/components/io.rs | 11 +++- light-spike/src/components/state.rs | 13 ++++ light-spike/src/macros.rs | 6 ++ light-spike/src/prelude.rs | 2 +- light-spike/src/types.rs | 9 +++ 8 files changed, 161 insertions(+), 35 deletions(-) diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index ab80d1f98..d0e2f6e73 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -15,6 +15,8 @@ serde = "1.0.106" serde_derive = "1.0.106" thiserror = "1.0.15" futures = "0.3.4" +color-backtrace = "0.3.0" +tokio = "0.2.20" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-spike/examples/light_client.rs b/light-spike/examples/light_client.rs index 702471476..be8a89377 100644 --- a/light-spike/examples/light_client.rs +++ b/light-spike/examples/light_client.rs @@ -2,14 +2,23 @@ use light_spike::components::scheduler; use light_spike::prelude::*; pub fn main() { - let (trusted_store_reader, trusted_store_writer) = Store::new().split(); + color_backtrace::install(); + + let (trusted_store_reader, mut trusted_store_writer) = Store::new().split(); let (untrusted_store_reader, untrusted_store_writer) = Store::new().split(); + let rpc_client = tendermint::rpc::Client::new("tcp://127.0.0.1:26657".parse().unwrap()); + let io = RealIo::new(rpc_client); + + let IoOutput::FetchedLightBlock(trusted_state) = io.fetch_light_block(1520).unwrap(); + trusted_store_writer.add(trusted_state); + let state = State { trusted_store_reader, trusted_store_writer, untrusted_store_reader, untrusted_store_writer, + errors: vec![], }; let options = VerificationOptions { @@ -17,14 +26,14 @@ pub fn main() { numerator: 1, denominator: 3, }, - trusting_period: Duration::from_secs(3600), + trusting_period: Duration::from_secs(36000), now: SystemTime::now(), }; let predicates = light_spike::predicates::production::ProductionPredicates; - let voting_power_calculator: Box = todo(()); - let commit_validator: Box = todo(()); - let header_hasher: Box = todo(()); + let voting_power_calculator = MockVotingPower; + let commit_validator = MockCommitValidator; + let header_hasher = MockHeaderHasher; let verifier = RealVerifier::new( predicates, @@ -33,24 +42,50 @@ pub fn main() { header_hasher, ); - let header_hasher: Box = todo(()); + let clock = SystemClock; + let scheduler = scheduler::schedule; let fork_detector = RealForkDetector::new(header_hasher); - let rpc_client = todo(()); - let io = RealIo::new(rpc_client); - - let demuxer = Demuxer::new( + let mut demuxer = Demuxer::new( state, options, - scheduler::schedule, + clock, + scheduler, verifier, fork_detector, io, ); - todo(demuxer) + demuxer.run(); +} + +#[derive(Copy, Clone)] +struct MockHeaderHasher; +impl HeaderHasher for MockHeaderHasher { + fn hash(&self, header: &Header) -> Hash { + header.hash + } +} + +#[derive(Copy, Clone)] +struct MockCommitValidator; +impl CommitValidator for MockCommitValidator { + fn validate( + &self, + _commit: &Commit, + _validators: &ValidatorSet, + ) -> Result<(), anomaly::BoxError> { + Ok(()) + } } -fn todo(_: B) -> A { - todo!() +#[derive(Copy, Clone)] +struct MockVotingPower; +impl VotingPowerCalculator for MockVotingPower { + fn total_power_of(&self, _validators: &ValidatorSet) -> u64 { + 8 + } + fn voting_power_in(&self, _commit: &Commit, _validators: &ValidatorSet) -> u64 { + 4 + } } diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index f4f60c752..652ac105c 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -1,9 +1,21 @@ use super::{io::*, scheduler::*, verifier::*}; use crate::prelude::*; +pub trait Clock { + fn now(&self) -> SystemTime; +} + +pub struct SystemClock; +impl Clock for SystemClock { + fn now(&self) -> SystemTime { + SystemTime::now() + } +} + pub struct Demuxer { state: State, options: VerificationOptions, + clock: Box, scheduler: Box, verifier: Box, fork_detector: Box, @@ -14,6 +26,7 @@ impl Demuxer { pub fn new( state: State, options: VerificationOptions, + clock: impl Clock + 'static, scheduler: impl Scheduler + 'static, verifier: impl Verifier + 'static, fork_detector: impl ForkDetector + 'static, @@ -22,6 +35,7 @@ impl Demuxer { Self { state, options, + clock: Box::new(clock), scheduler: Box::new(scheduler), verifier: Box::new(verifier), fork_detector: Box::new(fork_detector), @@ -31,33 +45,64 @@ impl Demuxer { pub fn run(&mut self) { loop { - self.verify(); - self.detect_forks(); + if let Err(e) = self.verify() { + eprintln!("verification error: {}", e); + color_backtrace::print_backtrace( + e.backtrace().unwrap(), + &mut color_backtrace::Settings::new(), + ) + .unwrap(); + } + + dbg!(&self.state.trusted_store_reader.latest_height()); + + if let Err(e) = self.detect_forks() { + eprintln!("fork detection error: {}", e); + color_backtrace::print_backtrace( + e.backtrace().unwrap(), + &mut color_backtrace::Settings::new(), + ) + .unwrap(); + } + + std::thread::sleep(Duration::from_secs(1)); } } - pub fn verify(&mut self) { + fn is_trusted(&self, light_block: &LightBlock) -> bool { + let in_store = self.state.trusted_store_reader.get(light_block.height); + in_store.as_ref() == Some(light_block) + } + + pub fn verify(&mut self) -> Result<(), Error> { let trusted_state = match self.state.trusted_store_reader.latest() { Some(trusted_state) => trusted_state, - None => return, // No trusted state to start from, abort. + None => bail!(ErrorKind::NoInitialTrustedState), }; let target_block = match self.fetch_light_block(LATEST_HEIGHT) { Ok(last_block) => last_block, - Err(_) => return, // No block to sync up to, abort. TODO: Deal with error + Err(io_error) => bail!(ErrorKind::Io(io_error)), }; - self.verify_loop(trusted_state, target_block); + if !self.is_trusted(&target_block) { + self.verify_loop(trusted_state, target_block); + } + + Ok(()) } fn verify_loop(&mut self, trusted_state: LightBlock, target_block: LightBlock) { let target_height = target_block.height; + dbg!(target_height); + + let options = self.options.set_now(self.clock.now()); precondition!( contracts::verify::trusted_state_contains_block_within_trusting_period( &self.state.trusted_store_reader, self.options.trusting_period, - self.options.now + options.now ) ); @@ -71,11 +116,12 @@ impl Demuxer { let mut light_block = target_block; loop { - let verif_result = self.verify_light_block(&light_block, &trusted_state, &self.options); + let verif_result = self.verify_light_block(&light_block, &trusted_state, &options); - if let VerifierOutput::Success = verif_result { + if let &VerifierOutput::Success = &verif_result { self.state.trusted_store_writer.add(light_block.clone()); } else { + dbg!(&verif_result); self.state.untrusted_store_writer.add(light_block.clone()); } @@ -86,7 +132,7 @@ impl Demuxer { SchedulerOutput::Abort => return, SchedulerOutput::NextHeight(next_height) => { postcondition!(contracts::schedule::postcondition( - &light_block, + &trusted_state, target_height, next_height, &self.state.trusted_store_reader, @@ -99,6 +145,8 @@ impl Demuxer { } } } + + eprintln!(); } postcondition!( @@ -139,7 +187,7 @@ impl Demuxer { self.scheduler.process(input) } - pub fn detect_forks(&self) { + pub fn detect_forks(&self) -> Result<(), Error> { let light_blocks = self.state.trusted_store_reader.all(); let input = ForkDetectorInput::Detect(light_blocks); @@ -149,6 +197,8 @@ impl Demuxer { ForkDetectorOutput::NotDetected => (), // TODO ForkDetectorOutput::Detected(_, _) => (), // TODO } + + Ok(()) } pub fn fetch_light_block(&self, height: Height) -> Result { @@ -209,19 +259,23 @@ pub mod contracts { use crate::prelude::*; pub fn postcondition( - checked_header: &LightBlock, + trusted_state: &LightBlock, target_height: Height, next_height: Height, trusted_store: &StoreReader, untrusted_store: &StoreReader, ) -> bool { - let current_height = checked_header.height; + let current_height = trusted_state.height; + + dbg!(current_height); + dbg!(target_height); + dbg!(next_height); - next_height <= target_height - && (next_height > current_height - || next_height == current_height && current_height == target_height) - && (trusted_store.get(current_height).as_ref() == Some(checked_header) - || untrusted_store.get(current_height).as_ref() == Some(checked_header)) + (next_height <= target_height) + && ((next_height > current_height) + || (next_height == current_height && current_height == target_height)) + && ((trusted_store.get(current_height).as_ref() == Some(trusted_state)) + || (untrusted_store.get(current_height).as_ref() == Some(trusted_state))) } } } diff --git a/light-spike/src/components/io.rs b/light-spike/src/components/io.rs index 6b27dbfd2..9cc0e264d 100644 --- a/light-spike/src/components/io.rs +++ b/light-spike/src/components/io.rs @@ -1,5 +1,3 @@ -use futures::executor::block_on; - use serde::{Deserialize, Serialize}; use tendermint::{block, rpc}; use thiserror::Error; @@ -89,3 +87,12 @@ impl RealIo { // } // } } + +fn block_on(f: F) -> F::Output { + tokio::runtime::Builder::new() + .basic_scheduler() + .enable_all() + .build() + .unwrap() + .block_on(f) +} diff --git a/light-spike/src/components/state.rs b/light-spike/src/components/state.rs index 829b5b652..e0aa47b67 100644 --- a/light-spike/src/components/state.rs +++ b/light-spike/src/components/state.rs @@ -1,9 +1,22 @@ use crate::prelude::*; +use thiserror::Error; + +#[derive(Debug, Clone, Error)] +pub enum ErrorKind { + #[error("I/O error")] + Io(#[from] IoError), + #[error("no initial trusted state")] + NoInitialTrustedState, +} + +pub type Error = anomaly::Error; + #[derive(Debug)] pub struct State { pub trusted_store_reader: StoreReader, pub trusted_store_writer: StoreReadWriter, pub untrusted_store_reader: StoreReader, pub untrusted_store_writer: StoreReadWriter, + pub errors: Vec, } diff --git a/light-spike/src/macros.rs b/light-spike/src/macros.rs index 844c53363..71f7e9730 100644 --- a/light-spike/src/macros.rs +++ b/light-spike/src/macros.rs @@ -1,3 +1,9 @@ +#[macro_export] +macro_rules! bail { + ($kind:expr) => { + return Err($kind.into()); + }; +} /// Ensure a condition holds, returning an error if it doesn't (ala `assert`) #[macro_export] macro_rules! ensure { diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 9fccb63c5..669f8a7c5 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -11,4 +11,4 @@ pub use crate::predicates::errors::*; pub use crate::predicates::VerificationPredicates; pub use crate::store::*; pub use crate::types::*; -pub use crate::{ensure, postcondition, precondition}; +pub use crate::{bail, ensure, postcondition, precondition}; diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index d458c6499..ebb938248 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -17,6 +17,15 @@ pub struct VerificationOptions { pub now: SystemTime, } +impl VerificationOptions { + pub fn set_now(&self, now: SystemTime) -> Self { + Self { + now, + ..self.clone() + } + } +} + #[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Header { From 8f7cff02ae181f9f5384d200f38f4ab436d8cccd Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 1 May 2020 19:38:44 +0200 Subject: [PATCH 041/100] Better working version --- light-spike/examples/light_client.rs | 134 ++++++++++++++++++++++- light-spike/src/components/demuxer.rs | 61 ++++++----- light-spike/src/components/scheduler.rs | 5 +- light-spike/src/predicates/production.rs | 1 - 4 files changed, 168 insertions(+), 33 deletions(-) diff --git a/light-spike/examples/light_client.rs b/light-spike/examples/light_client.rs index be8a89377..ec6710472 100644 --- a/light-spike/examples/light_client.rs +++ b/light-spike/examples/light_client.rs @@ -1,4 +1,5 @@ use light_spike::components::scheduler; +use light_spike::predicates::production::ProductionPredicates; use light_spike::prelude::*; pub fn main() { @@ -30,7 +31,7 @@ pub fn main() { now: SystemTime::now(), }; - let predicates = light_spike::predicates::production::ProductionPredicates; + let predicates = MockPredicates; let voting_power_calculator = MockVotingPower; let commit_validator = MockCommitValidator; let header_hasher = MockHeaderHasher; @@ -75,6 +76,11 @@ impl CommitValidator for MockCommitValidator { _commit: &Commit, _validators: &ValidatorSet, ) -> Result<(), anomaly::BoxError> { + // let first_byte = commit.header_hash.as_bytes()[0]; + // if first_byte < 90 { + // return Err("invalid commit".into()); + // } + Ok(()) } } @@ -89,3 +95,129 @@ impl VotingPowerCalculator for MockVotingPower { 4 } } + +#[derive(Copy, Clone, Debug)] +pub struct MockPredicates; + +impl VerificationPredicates for MockPredicates { + fn validator_sets_match( + &self, + signed_header: &SignedHeader, + validators: &ValidatorSet, + ) -> Result<(), VerificationError> { + ProductionPredicates.validator_sets_match(signed_header, validators) + } + + fn next_validators_match( + &self, + signed_header: &SignedHeader, + validators: &ValidatorSet, + ) -> Result<(), VerificationError> { + ProductionPredicates.next_validators_match(signed_header, validators) + } + + fn header_matches_commit( + &self, + header: &Header, + commit: &Commit, + header_hasher: &dyn HeaderHasher, + ) -> Result<(), VerificationError> { + ProductionPredicates.header_matches_commit(header, commit, header_hasher) + } + + fn valid_commit( + &self, + commit: &Commit, + validators: &ValidatorSet, + validator: &dyn CommitValidator, + ) -> Result<(), VerificationError> { + ProductionPredicates.valid_commit(commit, validators, validator) + } + + fn is_within_trust_period( + &self, + header: &Header, + trusting_period: Duration, + now: SystemTime, + ) -> Result<(), VerificationError> { + ProductionPredicates.is_within_trust_period(header, trusting_period, now) + } + + fn is_monotonic_bft_time( + &self, + untrusted_header: &Header, + trusted_header: &Header, + ) -> Result<(), VerificationError> { + ProductionPredicates.is_monotonic_bft_time(untrusted_header, trusted_header) + } + + fn is_monotonic_height( + &self, + untrusted_header: &Header, + trusted_header: &Header, + ) -> Result<(), VerificationError> { + ProductionPredicates.is_monotonic_height(untrusted_header, trusted_header) + } + + fn has_sufficient_voting_power( + &self, + commit: &Commit, + validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &dyn VotingPowerCalculator, + ) -> Result<(), VerificationError> { + let first_byte = commit.header_hash.as_bytes()[0]; + + if first_byte > 140 { + return Err(VerificationError::InsufficientVotingPower { + total_power: 0, + voting_power: 0, + }); + } + + ProductionPredicates.has_sufficient_voting_power( + commit, + validators, + trust_threshold, + calculator, + ) + } + + fn has_sufficient_validators_overlap( + &self, + untrusted_commit: &Commit, + trusted_validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &dyn VotingPowerCalculator, + ) -> Result<(), VerificationError> { + ProductionPredicates.has_sufficient_validators_overlap( + untrusted_commit, + trusted_validators, + trust_threshold, + calculator, + ) + } + + fn has_sufficient_signers_overlap( + &self, + untrusted_commit: &Commit, + untrusted_validators: &ValidatorSet, + trust_threshold: &TrustThreshold, + calculator: &dyn VotingPowerCalculator, + ) -> Result<(), VerificationError> { + ProductionPredicates.has_sufficient_signers_overlap( + untrusted_commit, + untrusted_validators, + trust_threshold, + calculator, + ) + } + + fn valid_next_validator_set( + &self, + untrusted_sh: &SignedHeader, + untrusted_next_vals: &ValidatorSet, + ) -> Result<(), VerificationError> { + ProductionPredicates.valid_next_validator_set(untrusted_sh, untrusted_next_vals) + } +} diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 652ac105c..523fd510d 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -75,9 +75,8 @@ impl Demuxer { } pub fn verify(&mut self) -> Result<(), Error> { - let trusted_state = match self.state.trusted_store_reader.latest() { - Some(trusted_state) => trusted_state, - None => bail!(ErrorKind::NoInitialTrustedState), + if self.state.trusted_store_reader.latest().is_none() { + bail!(ErrorKind::NoInitialTrustedState) }; let target_block = match self.fetch_light_block(LATEST_HEIGHT) { @@ -86,16 +85,13 @@ impl Demuxer { }; if !self.is_trusted(&target_block) { - self.verify_loop(trusted_state, target_block); + self.verify_loop(target_block.height); } Ok(()) } - fn verify_loop(&mut self, trusted_state: LightBlock, target_block: LightBlock) { - let target_height = target_block.height; - dbg!(target_height); - + fn verify_loop(&mut self, target_height: Height) { let options = self.options.set_now(self.clock.now()); precondition!( @@ -113,40 +109,51 @@ impl Demuxer { ) ); - let mut light_block = target_block; + let mut next_height = target_height; + let mut trusted_state = self.state.trusted_store_reader.latest().unwrap(); - loop { - let verif_result = self.verify_light_block(&light_block, &trusted_state, &options); + while trusted_state.height < target_height { + trusted_state = self.state.trusted_store_reader.latest().unwrap(); + + dbg!(target_height); + dbg!(trusted_state.height); + dbg!(next_height); + + let current_block = match self.fetch_light_block(next_height) { + Ok(current_block) => current_block, + Err(_) => return, + }; - if let &VerifierOutput::Success = &verif_result { - self.state.trusted_store_writer.add(light_block.clone()); + let verif_result = self.verify_light_block(¤t_block, &trusted_state, &options); + dbg!(&verif_result); + + if let VerifierOutput::Success = verif_result { + self.state.trusted_store_writer.add(current_block.clone()); } else { - dbg!(&verif_result); - self.state.untrusted_store_writer.add(light_block.clone()); + self.state.untrusted_store_writer.add(current_block.clone()); } - let schedule = self.schedule(&light_block, &trusted_state, verif_result); + let schedule = self.schedule(¤t_block, &trusted_state, verif_result); + dbg!(&schedule); match schedule { - SchedulerOutput::Done => break, + SchedulerOutput::Done => continue, SchedulerOutput::Abort => return, - SchedulerOutput::NextHeight(next_height) => { + SchedulerOutput::NextHeight(height) if height <= trusted_state.height => { + return; + } + SchedulerOutput::NextHeight(height) => { postcondition!(contracts::schedule::postcondition( &trusted_state, target_height, - next_height, + height, &self.state.trusted_store_reader, &self.state.untrusted_store_reader )); - light_block = match self.fetch_light_block(next_height) { - Ok(light_block) => light_block, - Err(_) => return, // couldn't fetch next block, abort. - } + next_height = height; } } - - eprintln!(); } postcondition!( @@ -267,10 +274,6 @@ pub mod contracts { ) -> bool { let current_height = trusted_state.height; - dbg!(current_height); - dbg!(target_height); - dbg!(next_height); - (next_height <= target_height) && ((next_height > current_height) || (next_height == current_height && current_height == target_height)) diff --git a/light-spike/src/components/scheduler.rs b/light-spike/src/components/scheduler.rs index 0b69c6fa5..bd7201969 100644 --- a/light-spike/src/components/scheduler.rs +++ b/light-spike/src/components/scheduler.rs @@ -39,10 +39,11 @@ pub fn schedule(input: SchedulerInput) -> SchedulerOutput { verifier_result, } => match verifier_result { VerifierOutput::Success => SchedulerOutput::Done, + VerifierOutput::Invalid(_) => SchedulerOutput::Abort, VerifierOutput::NotEnoughTrust => { - SchedulerOutput::NextHeight(compute_pivot_height(&checked_header, &trusted_state)) + let pivot_height = compute_pivot_height(&checked_header, &trusted_state); + SchedulerOutput::NextHeight(pivot_height) } - VerifierOutput::Invalid(_) => SchedulerOutput::Abort, }, } } diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index bf8c5174c..a68616d65 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -1,4 +1,3 @@ -use super::VerificationPredicates; use crate::prelude::*; #[derive(Copy, Clone, Debug)] From 2173ec8155f25c1cefc4e696c0f8304aa9a15e3e Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 4 May 2020 12:32:14 +0200 Subject: [PATCH 042/100] Implement production header hasher --- light-spike/Cargo.toml | 1 + light-spike/examples/light_client.rs | 4 +- light-spike/src/components/demuxer.rs | 10 +-- light-spike/src/operations.rs | 19 +---- light-spike/src/operations/header_hasher.rs | 83 +++++++++++++++++++++ light-spike/src/predicates.rs | 4 +- light-spike/src/predicates/errors.rs | 19 ++--- light-spike/src/predicates/production.rs | 2 +- light-spike/src/prelude.rs | 4 +- light-spike/src/types.rs | 44 ++++++++--- tendermint/src/time.rs | 10 +++ 11 files changed, 148 insertions(+), 52 deletions(-) create mode 100644 light-spike/src/operations/header_hasher.rs diff --git a/light-spike/Cargo.toml b/light-spike/Cargo.toml index d0e2f6e73..c1fd348dc 100644 --- a/light-spike/Cargo.toml +++ b/light-spike/Cargo.toml @@ -17,6 +17,7 @@ thiserror = "1.0.15" futures = "0.3.4" color-backtrace = "0.3.0" tokio = "0.2.20" +prost-amino = "0.5.0" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-spike/examples/light_client.rs b/light-spike/examples/light_client.rs index ec6710472..952dfbe0c 100644 --- a/light-spike/examples/light_client.rs +++ b/light-spike/examples/light_client.rs @@ -28,7 +28,7 @@ pub fn main() { denominator: 3, }, trusting_period: Duration::from_secs(36000), - now: SystemTime::now(), + now: Time::now(), }; let predicates = MockPredicates; @@ -138,7 +138,7 @@ impl VerificationPredicates for MockPredicates { &self, header: &Header, trusting_period: Duration, - now: SystemTime, + now: Time, ) -> Result<(), VerificationError> { ProductionPredicates.is_within_trust_period(header, trusting_period, now) } diff --git a/light-spike/src/components/demuxer.rs b/light-spike/src/components/demuxer.rs index 523fd510d..8f6d99da7 100644 --- a/light-spike/src/components/demuxer.rs +++ b/light-spike/src/components/demuxer.rs @@ -2,13 +2,13 @@ use super::{io::*, scheduler::*, verifier::*}; use crate::prelude::*; pub trait Clock { - fn now(&self) -> SystemTime; + fn now(&self) -> Time; } pub struct SystemClock; impl Clock for SystemClock { - fn now(&self) -> SystemTime { - SystemTime::now() + fn now(&self) -> Time { + Time::now() } } @@ -225,7 +225,7 @@ pub mod contracts { pub fn trusted_state_contains_block_within_trusting_period( trusted_store: &StoreReader, trusting_period: Duration, - now: SystemTime, + now: Time, ) -> bool { trusted_store .all() @@ -253,7 +253,7 @@ pub mod contracts { fn is_within_trust_period( light_block: &LightBlock, trusting_period: Duration, - now: SystemTime, + now: Time, ) -> bool { let header_time = light_block.header().bft_time; let expires_at = header_time + trusting_period; diff --git a/light-spike/src/operations.rs b/light-spike/src/operations.rs index 29e5106e7..86e6726ac 100644 --- a/light-spike/src/operations.rs +++ b/light-spike/src/operations.rs @@ -3,6 +3,9 @@ use crate::prelude::*; use anomaly::BoxError; +pub mod header_hasher; +pub use self::header_hasher::*; + pub trait VotingPowerCalculator { fn total_power_of(&self, validators: &ValidatorSet) -> u64; fn voting_power_in(&self, commit: &Commit, validators: &ValidatorSet) -> u64; @@ -43,19 +46,3 @@ impl CommitValidator for Box { self.as_ref().validate(commit, validators) } } - -pub trait HeaderHasher { - fn hash(&self, header: &Header) -> Hash; // Or Error? -} - -impl HeaderHasher for &T { - fn hash(&self, header: &Header) -> Hash { - (*self).hash(header) - } -} - -impl HeaderHasher for Box { - fn hash(&self, header: &Header) -> Hash { - self.as_ref().hash(header) - } -} diff --git a/light-spike/src/operations/header_hasher.rs b/light-spike/src/operations/header_hasher.rs new file mode 100644 index 000000000..0f3befc25 --- /dev/null +++ b/light-spike/src/operations/header_hasher.rs @@ -0,0 +1,83 @@ +use crate::prelude::*; + +use tendermint::amino_types::{message::AminoMessage, BlockId, ConsensusVersion, TimeMsg}; +use tendermint::merkle::simple_hash_from_byte_vectors; +use tendermint::Hash; + +pub trait HeaderHasher { + fn hash(&self, header: &Header) -> Hash; // Or Error? +} + +impl HeaderHasher for &T { + fn hash(&self, header: &Header) -> Hash { + (*self).hash(header) + } +} + +impl HeaderHasher for Box { + fn hash(&self, header: &Header) -> Hash { + self.as_ref().hash(header) + } +} + +pub struct ProdHeaderHasher; + +impl HeaderHasher for ProdHeaderHasher { + fn hash(&self, header: &Header) -> Hash { + amino_hash(header) + } +} + +fn amino_hash(header: &Header) -> 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( + &header.version, + ))); + fields_bytes.push(bytes_enc(header.chain_id.as_bytes())); + fields_bytes.push(encode_varint(header.height)); + fields_bytes.push(AminoMessage::bytes_vec(&TimeMsg::from(header.bft_time))); + fields_bytes.push( + header + .last_block_id + .as_ref() + .map_or(vec![], |id| AminoMessage::bytes_vec(&BlockId::from(id))), + ); + fields_bytes.push(header.last_commit_hash.as_ref().map_or(vec![], encode_hash)); + fields_bytes.push(header.data_hash.as_ref().map_or(vec![], encode_hash)); + fields_bytes.push(encode_hash(&header.validators_hash)); + fields_bytes.push(encode_hash(&header.next_validators_hash)); + fields_bytes.push(encode_hash(&header.consensus_hash)); + fields_bytes.push(bytes_enc(&header.app_hash)); + fields_bytes.push( + header + .last_results_hash + .as_ref() + .map_or(vec![], encode_hash), + ); + fields_bytes.push(header.evidence_hash.as_ref().map_or(vec![], encode_hash)); + fields_bytes.push(bytes_enc(header.proposer_address.as_bytes())); + + Hash::Sha256(simple_hash_from_byte_vectors(fields_bytes)) +} + +fn bytes_enc(bytes: &[u8]) -> Vec { + let mut chain_id_enc = vec![]; + prost_amino::encode_length_delimiter(bytes.len(), &mut chain_id_enc).unwrap(); + chain_id_enc.append(&mut bytes.to_vec()); + chain_id_enc +} + +fn encode_hash(hash: &Hash) -> Vec { + bytes_enc(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 +} diff --git a/light-spike/src/predicates.rs b/light-spike/src/predicates.rs index 85e20bfcc..db7d1a5ff 100644 --- a/light-spike/src/predicates.rs +++ b/light-spike/src/predicates.rs @@ -1,5 +1,3 @@ -use std::time::{Duration, SystemTime}; - use crate::prelude::*; pub mod errors; @@ -36,7 +34,7 @@ pub trait VerificationPredicates { &self, header: &Header, trusting_period: Duration, - now: SystemTime, + now: Time, ) -> Result<(), VerificationError>; fn is_monotonic_bft_time( diff --git a/light-spike/src/predicates/errors.rs b/light-spike/src/predicates/errors.rs index af54304e9..a9dd3426a 100644 --- a/light-spike/src/predicates/errors.rs +++ b/light-spike/src/predicates/errors.rs @@ -1,5 +1,3 @@ -use std::time::SystemTime; - use anomaly::{BoxError, Context}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -8,11 +6,8 @@ use crate::prelude::*; #[derive(Debug, Clone, Error, PartialEq, Serialize, Deserialize)] pub enum VerificationError { - #[error("header from the future: header_time={header_time:?} now={now:?}")] - HeaderFromTheFuture { - header_time: SystemTime, - now: SystemTime, - }, + #[error("header from the future: header_time={header_time} now={now}")] + HeaderFromTheFuture { header_time: Time, now: Time }, #[error("implementation specific")] ImplementationSpecific, #[error( @@ -42,13 +37,13 @@ pub enum VerificationError { }, #[error("non increasing height: got={got} expected={expected}")] NonIncreasingHeight { got: Height, expected: Height }, - #[error("non monotonic BFT time: header_bft_time={header_bft_time:?} trusted_header_bft_time={trusted_header_bft_time:?}")] + #[error("non monotonic BFT time: header_bft_time={header_bft_time} trusted_header_bft_time={trusted_header_bft_time}")] NonMonotonicBftTime { - header_bft_time: SystemTime, - trusted_header_bft_time: SystemTime, + header_bft_time: Time, + trusted_header_bft_time: Time, }, - #[error("not withing trust period: at={at:?} now={now:?}")] - NotWithinTrustPeriod { at: SystemTime, now: SystemTime }, + #[error("not withing trust period: at={at} now={now}")] + NotWithinTrustPeriod { at: Time, now: Time }, } impl VerificationError { diff --git a/light-spike/src/predicates/production.rs b/light-spike/src/predicates/production.rs index a68616d65..c4c933704 100644 --- a/light-spike/src/predicates/production.rs +++ b/light-spike/src/predicates/production.rs @@ -73,7 +73,7 @@ impl VerificationPredicates for ProductionPredicates { &self, header: &Header, trusting_period: Duration, - now: SystemTime, + now: Time, ) -> Result<(), VerificationError> { let header_time = header.bft_time; let expires_at = header_time + trusting_period; diff --git a/light-spike/src/prelude.rs b/light-spike/src/prelude.rs index 669f8a7c5..513b0b0bc 100644 --- a/light-spike/src/prelude.rs +++ b/light-spike/src/prelude.rs @@ -1,4 +1,6 @@ -pub use std::time::{Duration, SystemTime}; +pub use std::time::Duration; + +pub use tendermint::time::Time; pub use crate::components::demuxer::*; pub use crate::components::fork_detector::*; diff --git a/light-spike/src/types.rs b/light-spike/src/types.rs index ebb938248..1960f0afd 100644 --- a/light-spike/src/types.rs +++ b/light-spike/src/types.rs @@ -1,24 +1,26 @@ use derive_more::Display; use serde::{Deserialize, Serialize}; -use std::time::SystemTime; -pub use tendermint::hash::Hash; -pub use tendermint::lite::Height; - -use tendermint::{block::signed_header::SignedHeader as TMSignedHeader, lite::Header as _}; +use tendermint::{ + account::Id as AccountId, block::header::Version as HeaderVersion, + block::signed_header::SignedHeader as TMSignedHeader, block::Id as BlockId, + chain::Id as ChainId, lite::Header as _, Time, +}; use crate::prelude::*; +pub use tendermint::{hash::Hash, lite::Height}; + #[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct VerificationOptions { pub trust_threshold: TrustThreshold, pub trusting_period: Duration, - pub now: SystemTime, + pub now: Time, } impl VerificationOptions { - pub fn set_now(&self, now: SystemTime) -> Self { + pub fn set_now(&self, now: Time) -> Self { Self { now, ..self.clone() @@ -29,11 +31,20 @@ impl VerificationOptions { #[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] #[display(fmt = "{:?}", self)] pub struct Header { + pub version: HeaderVersion, + pub chain_id: ChainId, pub height: Height, - pub bft_time: SystemTime, + pub bft_time: Time, pub validators_hash: Hash, pub next_validators_hash: Hash, - pub hash: Hash, // TODO: What if we don't have this + pub proposer_address: AccountId, + pub evidence_hash: Option, + pub last_results_hash: Option, + pub last_block_id: Option, + pub last_commit_hash: Option, + pub data_hash: Option, + pub consensus_hash: Hash, + pub app_hash: Vec, } #[derive(Clone, Debug, PartialEq, Display, Serialize, Deserialize)] @@ -80,13 +91,22 @@ impl From for SignedHeader { Self { header: Header { height: sh.header.height().into(), - bft_time: sh.header.bft_time().to_system_time().unwrap(), + bft_time: sh.header.bft_time(), validators_hash: sh.header.validators_hash(), next_validators_hash: sh.header.next_validators_hash(), - hash: sh.header.hash(), + version: sh.header.version, + chain_id: sh.header.chain_id, + proposer_address: sh.header.proposer_address, + evidence_hash: sh.header.evidence_hash, + last_results_hash: sh.header.last_results_hash, + last_block_id: sh.header.last_block_id, + last_commit_hash: sh.header.last_commit_hash, + data_hash: sh.header.data_hash, + consensus_hash: sh.header.consensus_hash, + app_hash: sh.header.app_hash, }, commit: Commit { - header_hash: sh.header.hash(), + header_hash: sh.commit.block_id.hash, commit: sh.commit, }, validators: validators.clone(), diff --git a/tendermint/src/time.rs b/tendermint/src/time.rs index 014a4e560..3280224f8 100644 --- a/tendermint/src/time.rs +++ b/tendermint/src/time.rs @@ -4,6 +4,7 @@ use crate::error::{Error, Kind}; use chrono::{DateTime, SecondsFormat, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; +use std::ops::Add; use std::str::FromStr; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tai64::TAI64N; @@ -100,6 +101,15 @@ impl From