From 101a96f3bfb7edab7aa5ea7ce9ab1ec76d39f942 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 12:48:52 +0200 Subject: [PATCH 01/39] Remove Peers struct, only supply one peer to each light client instance --- light-client/examples/light_client.rs | 7 +------ light-client/src/light_client.rs | 11 ++++++----- light-client/src/state.rs | 11 ----------- light-client/tests/light_client.rs | 5 +---- 4 files changed, 8 insertions(+), 26 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 8a9dc4961..616f305ef 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -85,13 +85,7 @@ fn sync_cmd(opts: SyncOpts) { light_store.insert(trusted_state, VerifiedStatus::Verified); } - let peers = Peers { - primary, - witnesses: Vec::new(), - }; - let state = State { - peers, light_store: Box::new(light_store), verification_trace: HashMap::new(), }; @@ -112,6 +106,7 @@ fn sync_cmd(opts: SyncOpts) { let fork_detector = ProdForkDetector::default(); let mut light_client = LightClient::new( + primary, state, options, clock, diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index d86c225d5..73173fa49 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -46,6 +46,7 @@ impl Options { /// correct for the duration of the trusted period. The fault-tolerant read operation /// is designed for this security model. pub struct LightClient { + peer: PeerId, state: State, options: Options, clock: Box, @@ -58,6 +59,7 @@ pub struct LightClient { impl LightClient { /// Constructs a new light client pub fn new( + peer: PeerId, state: State, options: Options, clock: impl Clock + 'static, @@ -67,6 +69,7 @@ impl LightClient { io: impl Io + 'static, ) -> Self { Self { + peer, state, options, clock: Box::new(clock), @@ -81,8 +84,7 @@ impl LightClient { /// /// Note: This functin delegates the actual work to `verify_to_target`. pub fn verify_to_highest(&mut self) -> Result { - let peer = self.state.peers.primary; - let target_block = match self.io.fetch_light_block(peer, LATEST_HEIGHT) { + let target_block = match self.io.fetch_light_block(self.peer, LATEST_HEIGHT) { Ok(last_block) => last_block, Err(io_error) => bail!(ErrorKind::Io(io_error)), }; @@ -164,9 +166,8 @@ impl LightClient { return Ok(trusted_state); } - // Fetch the block at the current height from the primary node - let current_block = - self.get_or_fetch_block(self.state.peers.primary, current_height)?; + // Fetch the block at the current height from our peer + let current_block = self.get_or_fetch_block(self.peer, current_height)?; // Validate and verify the current block let verdict = self diff --git a/light-client/src/state.rs b/light-client/src/state.rs index fb1f2bac6..1e92e6ea3 100644 --- a/light-client/src/state.rs +++ b/light-client/src/state.rs @@ -8,20 +8,9 @@ use std::collections::{HashMap, HashSet}; /// Records which blocks were needed to verify a target block, eg. during bisection. pub type VerificationTrace = HashMap>; -/// The set of peers of a light client. -#[derive(Debug)] -pub struct Peers { - /// The primary peer from which the light client will fetch blocks. - pub primary: PeerId, - /// Witnesses used for fork detection. - pub witnesses: Vec, -} - /// The state managed by the light client. #[derive(Debug)] pub struct State { - /// Set of peers of the light client. - pub peers: Peers, /// Store for light blocks. pub light_store: Box, /// Records which blocks were needed to verify a target block, eg. during bisection. diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index 6eb195592..f69440f27 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -183,10 +183,6 @@ fn run_bisection_test(tc: TestBisection) { light_store.insert(trusted_state, VerifiedStatus::Verified); let state = State { - peers: Peers { - primary: primary.clone(), - witnesses: vec![], - }, light_store: Box::new(light_store), verification_trace: HashMap::new(), }; @@ -194,6 +190,7 @@ fn run_bisection_test(tc: TestBisection) { let verifier = ProdVerifier::default(); let mut light_client = LightClient::new( + primary, state, options, clock, From b60811e1fb10f269c52b17ff7dd494edd2987da3 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 12:58:36 +0200 Subject: [PATCH 02/39] Remove fork detector from light client instance --- light-client/examples/light_client.rs | 16 ++++------------ light-client/src/light_client.rs | 21 --------------------- light-client/tests/light_client.rs | 3 +-- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 616f305ef..33c3bf168 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -103,18 +103,10 @@ fn sync_cmd(opts: SyncOpts) { let verifier = ProdVerifier::default(); let clock = SystemClock; let scheduler = scheduler::basic_bisecting_schedule; - let fork_detector = ProdForkDetector::default(); - - let mut light_client = LightClient::new( - primary, - state, - options, - clock, - scheduler, - verifier, - fork_detector, - io, - ); + let _fork_detector = ProdForkDetector::default(); + + let mut light_client = + LightClient::new(primary, state, options, clock, scheduler, verifier, io); loop { match light_client.verify_to_highest() { diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 73173fa49..ab7f439dd 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -52,7 +52,6 @@ pub struct LightClient { clock: Box, scheduler: Box, verifier: Box, - fork_detector: Box, io: Box, } @@ -65,7 +64,6 @@ impl LightClient { clock: impl Clock + 'static, scheduler: impl Scheduler + 'static, verifier: impl Verifier + 'static, - fork_detector: impl ForkDetector + 'static, io: impl Io + 'static, ) -> Self { Self { @@ -75,7 +73,6 @@ impl LightClient { clock: Box::new(clock), scheduler: Box::new(scheduler), verifier: Box::new(verifier), - fork_detector: Box::new(fork_detector), io: Box::new(io), } } @@ -209,24 +206,6 @@ impl LightClient { } } - /// TODO - pub fn detect_forks(&self) -> Result<(), Error> { - let light_blocks = self - .state - .light_store - .all(VerifiedStatus::Verified) - .collect(); - - let result = self.fork_detector.detect(light_blocks); - - match result { - ForkDetection::NotDetected => (), // TODO - ForkDetection::Detected(_, _) => (), // TODO - } - - Ok(()) - } - /// Get the verification trace for the block at target_height. pub fn get_trace(&self, target_height: Height) -> Vec { self.state.get_trace(target_height) diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index f69440f27..0a7be9054 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -157,7 +157,7 @@ fn run_bisection_test(tc: TestBisection) { let clock_drift = Duration::from_secs(1); let clock = MockClock { now }; - let fork_detector = ProdForkDetector::new(ProdHeaderHasher); + let _fork_detector = ProdForkDetector::new(ProdHeaderHasher); let options = Options { trust_threshold, @@ -196,7 +196,6 @@ fn run_bisection_test(tc: TestBisection) { clock, scheduler::basic_bisecting_schedule, verifier, - fork_detector, io.clone(), ); From 4fccace03d411c064e58ea31e4933469ad25829c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 12:58:55 +0200 Subject: [PATCH 03/39] Add Debug instance to LightClient --- light-client/src/light_client.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index ab7f439dd..bf8ce0760 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -5,6 +5,7 @@ use contracts::*; use derive_more::Display; use serde::{Deserialize, Serialize}; +use std::fmt; use crate::components::{io::*, scheduler::*, verifier::*}; use crate::contracts::*; @@ -55,6 +56,16 @@ pub struct LightClient { io: Box, } +impl fmt::Debug for LightClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LightClient") + .field("peer", &self.peer) + .field("options", &self.options) + .field("state", &self.state) + .finish() + } +} + impl LightClient { /// Constructs a new light client pub fn new( From 6478b46e7181371c257fe6ba80a4f01e7f19a2be Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 19:36:46 +0200 Subject: [PATCH 04/39] Require all components and their deps to be clonable --- light-client/Cargo.toml | 1 + light-client/src/components/clock.rs | 4 ++- light-client/src/components/io.rs | 6 ++-- light-client/src/components/scheduler.rs | 6 ++-- light-client/src/components/verifier.rs | 21 ++++++++++--- light-client/src/light_client.rs | 12 ++++++++ .../src/operations/commit_validator.rs | 28 +++++++++-------- light-client/src/operations/header_hasher.rs | 15 +++++----- light-client/src/operations/voting_power.rs | 30 ++++++++++--------- light-client/src/predicates.rs | 3 +- light-client/src/state.rs | 16 ++++++++++ light-client/src/store.rs | 3 +- light-client/src/store/memory.rs | 2 +- light-client/src/store/sled.rs | 2 +- light-client/tests/light_client.rs | 1 + tendermint/src/rpc/client.rs | 1 + 16 files changed, 104 insertions(+), 47 deletions(-) diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index 08faad28f..a6dcc11ab 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -18,6 +18,7 @@ prost-amino = "0.5.0" contracts = "0.4.0" sled = "0.31.0" serde_cbor = "0.11.1" +dyn-clone = "1.0.1" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-client/src/components/clock.rs b/light-client/src/components/clock.rs index 873cf4707..666156199 100644 --- a/light-client/src/components/clock.rs +++ b/light-client/src/components/clock.rs @@ -1,12 +1,14 @@ use crate::prelude::*; +use dyn_clone::DynClone; /// Abstracts over the current time. -pub trait Clock { +pub trait Clock: Send + DynClone { /// Get the current time. fn now(&self) -> Time; } /// Provides the current wall clock time. +#[derive(Copy, Clone)] pub struct SystemClock; impl Clock for SystemClock { fn now(&self) -> Time { diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index cbfb78cff..a11a2d10a 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use contracts::{contract_trait, post, pre}; +use dyn_clone::DynClone; use serde::{Deserialize, Serialize}; use tendermint::{block, rpc}; use thiserror::Error; @@ -21,7 +22,7 @@ pub enum IoError { /// Interface for fetching light blocks from a full node, typically via the RPC client. #[contract_trait] -pub trait Io { +pub trait Io: Send + DynClone { /// Fetch a light block at the given height from the peer with the given peer ID. /// /// ## Postcondition @@ -31,7 +32,7 @@ pub trait Io { } #[contract_trait] -impl Io for F +impl Io for F where F: FnMut(PeerId, Height) -> Result, { @@ -42,6 +43,7 @@ where /// Production implementation of the Io component, which fetches /// light blocks from full nodes via RPC. +#[derive(Clone, Debug)] pub struct ProdIo { rpc_clients: HashMap, peer_map: HashMap, diff --git a/light-client/src/components/scheduler.rs b/light-client/src/components/scheduler.rs index 982d915e8..47ab2587f 100644 --- a/light-client/src/components/scheduler.rs +++ b/light-client/src/components/scheduler.rs @@ -1,12 +1,14 @@ use crate::prelude::*; + use contracts::*; +use dyn_clone::DynClone; /// The scheduler decides what block to verify next given the current and target heights. /// /// The scheduler is given access to the light store, in order to optionally /// improve performance by picking a next block that has already been fetched. #[contract_trait] -pub trait Scheduler { +pub trait Scheduler: Send + DynClone { /// Decides what block to verify next. /// /// ## Precondition @@ -25,7 +27,7 @@ pub trait Scheduler { } #[contract_trait] -impl Scheduler for F +impl Scheduler for F where F: Fn(&dyn LightStore, Height, Height) -> Height, { diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index 26ee1b659..bd1084d18 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -1,6 +1,8 @@ use crate::predicates as preds; use crate::prelude::*; +use dyn_clone::DynClone; + /// Represents the result of the verification performed by the /// verifier component. #[derive(Debug)] @@ -33,7 +35,7 @@ impl From> for Verdict { /// ## Implements /// - [TMBC-VAL-CONTAINS-CORR.1] /// - [TMBC-VAL-COMMIT.1] -pub trait Verifier { +pub trait Verifier: Send + DynClone { /// Perform the verification. fn verify(&self, untrusted: &LightBlock, trusted: &LightBlock, options: &Options) -> Verdict; } @@ -54,6 +56,17 @@ pub struct ProdVerifier { header_hasher: Box, } +impl Clone for ProdVerifier { + fn clone(&self) -> Self { + Self { + predicates: dyn_clone::clone_box(&*self.predicates), + voting_power_calculator: dyn_clone::clone_box(&*self.voting_power_calculator), + commit_validator: dyn_clone::clone_box(&*self.commit_validator), + header_hasher: dyn_clone::clone_box(&*self.header_hasher), + } + } +} + impl ProdVerifier { pub fn new( predicates: impl VerificationPredicates + 'static, @@ -85,9 +98,9 @@ impl Verifier for ProdVerifier { fn verify(&self, untrusted: &LightBlock, trusted: &TrustedState, options: &Options) -> Verdict { preds::verify( &*self.predicates, - &self.voting_power_calculator, - &self.commit_validator, - &self.header_hasher, + &*self.voting_power_calculator, + &*self.commit_validator, + &*self.header_hasher, &trusted, &untrusted, options, diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index bf8ce0760..673ce006e 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -261,4 +261,16 @@ impl LightClient { }) .map_err(|e| ErrorKind::Io(e).into()) } + + pub fn with_state(&self, state: State) -> Self { + Self { + state, + peer: self.peer, + options: self.options.clone(), + clock: dyn_clone::clone_box(&*self.clock), + scheduler: dyn_clone::clone_box(&*self.scheduler), + verifier: dyn_clone::clone_box(&*self.verifier), + io: dyn_clone::clone_box(&*self.io), + } + } } diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index cf6d7691c..2e62715bb 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -1,9 +1,10 @@ use crate::prelude::*; -use anomaly::BoxError; +use anomaly::BoxError; +use dyn_clone::DynClone; use tendermint::lite::types::Commit as _; -pub trait CommitValidator { +pub trait CommitValidator: Send + DynClone { fn validate( &self, signed_header: &SignedHeader, @@ -11,7 +12,7 @@ pub trait CommitValidator { ) -> Result<(), BoxError>; } -impl CommitValidator for &T { +impl CommitValidator for &T { fn validate( &self, signed_header: &SignedHeader, @@ -21,16 +22,17 @@ impl CommitValidator for &T { } } -impl CommitValidator for Box { - fn validate( - &self, - signed_header: &SignedHeader, - validators: &ValidatorSet, - ) -> Result<(), BoxError> { - self.as_ref().validate(signed_header, validators) - } -} - +// impl CommitValidator for Box { +// fn validate( +// &self, +// signed_header: &SignedHeader, +// validators: &ValidatorSet, +// ) -> Result<(), BoxError> { +// self.as_ref().validate(signed_header, validators) +// } +// } + +#[derive(Copy, Clone)] pub struct ProdCommitValidator; impl CommitValidator for ProdCommitValidator { diff --git a/light-client/src/operations/header_hasher.rs b/light-client/src/operations/header_hasher.rs index 3d5d39551..38d4fa7d9 100644 --- a/light-client/src/operations/header_hasher.rs +++ b/light-client/src/operations/header_hasher.rs @@ -1,24 +1,25 @@ use crate::prelude::*; +use dyn_clone::DynClone; use tendermint::amino_types::{message::AminoMessage, BlockId, ConsensusVersion, TimeMsg}; use tendermint::merkle::simple_hash_from_byte_vectors; use tendermint::Hash; -pub trait HeaderHasher { +pub trait HeaderHasher: Send + DynClone { fn hash(&self, header: &Header) -> Hash; // Or Error? } -impl HeaderHasher for &T { +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) - } -} +// impl HeaderHasher for Box { +// fn hash(&self, header: &Header) -> Hash { +// self.as_ref().hash(header) +// } +// } #[derive(Copy, Clone, Debug)] pub struct ProdHeaderHasher; diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index 954b2c54d..564ac7313 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -1,9 +1,10 @@ use crate::prelude::*; use anomaly::BoxError; +use dyn_clone::DynClone; use tendermint::lite::types::ValidatorSet as _; -pub trait VotingPowerCalculator { +pub trait VotingPowerCalculator: Send + DynClone { fn total_power_of(&self, validators: &ValidatorSet) -> u64; fn voting_power_in( &self, @@ -12,7 +13,7 @@ pub trait VotingPowerCalculator { ) -> Result; } -impl VotingPowerCalculator for &T { +impl VotingPowerCalculator for &T { fn total_power_of(&self, validators: &ValidatorSet) -> u64 { (*self).total_power_of(validators) } @@ -26,20 +27,21 @@ impl VotingPowerCalculator for &T { } } -impl VotingPowerCalculator for Box { - fn total_power_of(&self, validators: &ValidatorSet) -> u64 { - self.as_ref().total_power_of(validators) - } +// impl VotingPowerCalculator for Box { +// fn total_power_of(&self, validators: &ValidatorSet) -> u64 { +// self.as_ref().total_power_of(validators) +// } - fn voting_power_in( - &self, - signed_header: &SignedHeader, - validators: &ValidatorSet, - ) -> Result { - self.as_ref().voting_power_in(signed_header, validators) - } -} +// fn voting_power_in( +// &self, +// signed_header: &SignedHeader, +// validators: &ValidatorSet, +// ) -> Result { +// self.as_ref().voting_power_in(signed_header, validators) +// } +// } +#[derive(Copy, Clone, Debug)] pub struct ProdVotingPowerCalculator; impl VotingPowerCalculator for ProdVotingPowerCalculator { diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index 6c5660868..494ca9dba 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -2,6 +2,7 @@ use crate::prelude::*; +use dyn_clone::DynClone; use tendermint::lite::ValidatorSet as _; pub mod errors; @@ -18,7 +19,7 @@ impl VerificationPredicates for ProdPredicates {} /// /// This enables test implementations to only override a single method rather than /// have to re-define every predicate. -pub trait VerificationPredicates { +pub trait VerificationPredicates: Send + DynClone { fn validator_sets_match(&self, light_block: &LightBlock) -> Result<(), VerificationError> { ensure!( light_block.signed_header.header.validators_hash == light_block.validators.hash(), diff --git a/light-client/src/state.rs b/light-client/src/state.rs index 1e92e6ea3..f659b3e5f 100644 --- a/light-client/src/state.rs +++ b/light-client/src/state.rs @@ -17,7 +17,23 @@ pub struct State { pub verification_trace: VerificationTrace, } +impl Clone for State { + fn clone(&self) -> Self { + Self { + light_store: dyn_clone::clone_box(&*self.light_store), + verification_trace: self.verification_trace.clone(), + } + } +} + impl State { + pub fn new(light_store: impl LightStore + 'static) -> Self { + Self { + light_store: Box::new(light_store), + verification_trace: VerificationTrace::new(), + } + } + /// Record that the block at `height` was needed to verify the block at `target_height`. /// /// ## Preconditions diff --git a/light-client/src/store.rs b/light-client/src/store.rs index 97b18c227..55f764832 100644 --- a/light-client/src/store.rs +++ b/light-client/src/store.rs @@ -6,6 +6,7 @@ use crate::prelude::*; +use dyn_clone::DynClone; use serde::{Deserialize, Serialize}; pub mod memory; @@ -43,7 +44,7 @@ impl VerifiedStatus { /// /// ## Implements /// - [LCV-DIST-STORE.1] -pub trait LightStore: std::fmt::Debug { +pub trait LightStore: std::fmt::Debug + Send + DynClone { /// Get the light block at the given height with the given status, or return `None` otherwise. fn get(&self, height: Height, status: VerifiedStatus) -> Option; /// Update the `status` of the given `light_block`. diff --git a/light-client/src/store/memory.rs b/light-client/src/store/memory.rs index 4da14c14b..488ab511d 100644 --- a/light-client/src/store/memory.rs +++ b/light-client/src/store/memory.rs @@ -20,7 +20,7 @@ impl StoreEntry { } /// Transient in-memory store. -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default)] pub struct MemoryStore { store: BTreeMap, } diff --git a/light-client/src/store/sled.rs b/light-client/src/store/sled.rs index 559332597..5d5f0f0b8 100644 --- a/light-client/src/store/sled.rs +++ b/light-client/src/store/sled.rs @@ -10,7 +10,7 @@ const UNVERIFIED_PREFIX: &str = "light_store/unverified"; const FAILED_PREFIX: &str = "light_store/failed"; /// Persistent store backed by an on-disk `sled` database. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SledStore { db: SledDb, verified_db: KeyValueDb, diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index 0a7be9054..a166452ac 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -125,6 +125,7 @@ impl Io for MockIo { } } +#[derive(Clone)] struct MockClock { now: Time, } diff --git a/tendermint/src/rpc/client.rs b/tendermint/src/rpc/client.rs index e2f358ec0..e66a00040 100644 --- a/tendermint/src/rpc/client.rs +++ b/tendermint/src/rpc/client.rs @@ -13,6 +13,7 @@ use hyper::header; /// Tendermint RPC client. /// /// Presently supports JSONRPC via HTTP. +#[derive(Clone, Debug)] pub struct Client { /// Address of the RPC server address: net::Address, From d5436d32b4b628300ba278c9cf38c52f37a771e8 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 19:37:49 +0200 Subject: [PATCH 05/39] Start work on supervisor and fork detection --- light-client/Cargo.toml | 1 + light-client/examples/light_client.rs | 1 - light-client/src/callback.rs | 23 ++ light-client/src/components.rs | 1 - light-client/src/components/fork_detector.rs | 54 ---- light-client/src/errors.rs | 6 + light-client/src/fork_detector.rs | 75 +++++ light-client/src/lib.rs | 3 + light-client/src/light_client.rs | 27 +- light-client/src/prelude.rs | 2 +- light-client/src/supervisor.rs | 290 +++++++++++++++++++ light-client/tests/light_client.rs | 1 - 12 files changed, 414 insertions(+), 70 deletions(-) create mode 100644 light-client/src/callback.rs delete mode 100644 light-client/src/components/fork_detector.rs create mode 100644 light-client/src/fork_detector.rs create mode 100644 light-client/src/supervisor.rs diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index a6dcc11ab..5d60e638c 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -18,6 +18,7 @@ prost-amino = "0.5.0" contracts = "0.4.0" sled = "0.31.0" serde_cbor = "0.11.1" +crossbeam-channel = "0.4.2" dyn-clone = "1.0.1" [dev-dependencies] diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 33c3bf168..47bdb50fa 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -103,7 +103,6 @@ fn sync_cmd(opts: SyncOpts) { let verifier = ProdVerifier::default(); let clock = SystemClock; let scheduler = scheduler::basic_bisecting_schedule; - let _fork_detector = ProdForkDetector::default(); let mut light_client = LightClient::new(primary, state, options, clock, scheduler, verifier, io); diff --git a/light-client/src/callback.rs b/light-client/src/callback.rs new file mode 100644 index 000000000..ba1cdcf4f --- /dev/null +++ b/light-client/src/callback.rs @@ -0,0 +1,23 @@ +use std::fmt; + +pub struct Callback { + inner: Box () + Send>, +} + +impl fmt::Debug for Callback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Callback").finish() + } +} + +impl Callback { + pub fn new(inner: impl FnOnce(A) -> () + Send + 'static) -> Self { + Self { + inner: Box::new(inner), + } + } + + pub fn call(self, result: A) -> () { + (self.inner)(result); + } +} diff --git a/light-client/src/components.rs b/light-client/src/components.rs index a3eb766dc..50d709c64 100644 --- a/light-client/src/components.rs +++ b/light-client/src/components.rs @@ -1,7 +1,6 @@ //! Components used by the Light Client. pub mod clock; -pub mod fork_detector; pub mod io; pub mod scheduler; pub mod verifier; diff --git a/light-client/src/components/fork_detector.rs b/light-client/src/components/fork_detector.rs deleted file mode 100644 index a264cd967..000000000 --- a/light-client/src/components/fork_detector.rs +++ /dev/null @@ -1,54 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::prelude::*; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum ForkDetection { - // NOTE: We box the fields to reduce the overall size of the enum. - // See https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant - Detected(Box, Box), - NotDetected, -} - -pub trait ForkDetector { - fn detect(&self, light_blocks: Vec) -> ForkDetection; -} - -pub struct ProdForkDetector { - header_hasher: Box, -} - -impl ProdForkDetector { - pub fn new(header_hasher: impl HeaderHasher + 'static) -> Self { - Self { - header_hasher: Box::new(header_hasher), - } - } -} - -impl Default for ProdForkDetector { - fn default() -> Self { - Self::new(ProdHeaderHasher) - } -} - -impl ForkDetector for ProdForkDetector { - fn detect(&self, mut light_blocks: Vec) -> ForkDetection { - if light_blocks.is_empty() { - return ForkDetection::NotDetected; - } - - let first_block = light_blocks.pop().unwrap(); // Safety: checked above that not empty. - let first_hash = self.header_hasher.hash(&first_block.signed_header.header); - - for light_block in light_blocks { - let hash = self.header_hasher.hash(&light_block.signed_header.header); - - if first_hash != hash { - return ForkDetection::Detected(Box::new(first_block), Box::new(light_block)); - } - } - - ForkDetection::NotDetected - } -} diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 162fc274c..204584bdf 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -16,6 +16,12 @@ pub enum ErrorKind { #[error("store error")] Store, + #[error("no valid peer left")] + NoValidPeerLeft, + + #[error("fork detected peers={0:?}")] + ForkDetected(Vec), + #[error("no initial trusted state")] NoInitialTrustedState, diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs new file mode 100644 index 000000000..8665277dd --- /dev/null +++ b/light-client/src/fork_detector.rs @@ -0,0 +1,75 @@ +use serde::{Deserialize, Serialize}; + +use crate::prelude::*; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ForkDetection { + NoFork, +} + +pub trait ForkDetector { + fn detect_forks( + &self, + light_block: &LightBlock, + primary: LightClient, + secondaries: Vec, + ) -> ForkDetection; +} + +pub struct ProdForkDetector {} + +impl ProdForkDetector { + pub fn new() -> Self { + Self {} + } +} + +impl Default for ProdForkDetector { + fn default() -> Self { + Self::new() + } +} + +impl ForkDetector for ProdForkDetector { + fn detect_forks( + &self, + light_block: &LightBlock, + primary: LightClient, + secondaries: Vec, + ) -> ForkDetection { + for mut secondary in secondaries { + let secondary_block = secondary.get_or_fetch_block(light_block.height()).unwrap(); // FIXME: unwrap + + if light_block.signed_header.header == secondary_block.signed_header.header { + // Header matches, we continue. + continue; + } + + let latest_trusted = primary + .state + .light_store + .latest(VerifiedStatus::Verified) + .unwrap(); // FIXME: unwrap + + secondary + .state + .light_store + .update(latest_trusted, VerifiedStatus::Verified); + + secondary + .state + .light_store + .update(secondary_block, VerifiedStatus::Unverified); + + let result = secondary.verify_to_target(light_block.height()); + + // TODO: Handle case where block expired + match result { + Ok(_) => todo!(), // There is a fork, report `secondary_block` + Err(_) => todo!(), // `secondary` is faulty, report it + } + } + + ForkDetection::NoFork + } +} diff --git a/light-client/src/lib.rs b/light-client/src/lib.rs index 1eb3a2bf8..47796d800 100644 --- a/light-client/src/lib.rs +++ b/light-client/src/lib.rs @@ -12,15 +12,18 @@ //! See the `light_client` module for the main documentation. +pub mod callback; pub mod components; pub mod contracts; pub mod errors; +pub mod fork_detector; pub mod light_client; pub mod operations; pub mod predicates; pub mod prelude; pub mod state; pub mod store; +pub mod supervisor; pub mod types; mod macros; diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 673ce006e..3e82e0355 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -47,9 +47,9 @@ impl Options { /// correct for the duration of the trusted period. The fault-tolerant read operation /// is designed for this security model. pub struct LightClient { - peer: PeerId, - state: State, - options: Options, + pub peer: PeerId, + pub state: State, + pub options: Options, clock: Box, scheduler: Box, verifier: Box, @@ -143,6 +143,15 @@ impl LightClient { ) )] pub fn verify_to_target(&mut self, target_height: Height) -> Result { + // Let's first look in the store to see whether we have already successfully verified this block + if let Some(light_block) = self + .state + .light_store + .get(target_height, VerifiedStatus::Verified) + { + return Ok(light_block); + } + // Override the `now` fields in the given verification options with the current time, // as per the given `clock`. let options = self.options.with_now(self.clock.now()); @@ -175,7 +184,7 @@ impl LightClient { } // Fetch the block at the current height from our peer - let current_block = self.get_or_fetch_block(self.peer, current_height)?; + let current_block = self.get_or_fetch_block(current_height)?; // Validate and verify the current block let verdict = self @@ -229,21 +238,15 @@ impl LightClient { /// - The provider of block that is returned matches the given peer. // TODO: Uncomment when provider field is available // #[post(ret.map(|lb| lb.provider == peer).unwrap_or(false))] - fn get_or_fetch_block( - &mut self, - peer: PeerId, - current_height: Height, - ) -> Result { + pub fn get_or_fetch_block(&mut self, current_height: Height) -> Result { let current_block = self .state .light_store .get(current_height, VerifiedStatus::Verified) - // .filter(|lb| lb.provider == peer) .or_else(|| { self.state .light_store .get(current_height, VerifiedStatus::Unverified) - // .filter(|lb| lb.provider == peer) }); if let Some(current_block) = current_block { @@ -251,7 +254,7 @@ impl LightClient { } self.io - .fetch_light_block(peer, current_height) + .fetch_light_block(self.peer, current_height) .map(|current_block| { self.state .light_store diff --git a/light-client/src/prelude.rs b/light-client/src/prelude.rs index bb8cd1b77..7e1e5c8e5 100644 --- a/light-client/src/prelude.rs +++ b/light-client/src/prelude.rs @@ -4,7 +4,7 @@ pub use std::time::{Duration, SystemTime}; pub use crate::{bail, ensure}; pub use crate::{ - components::{clock::*, fork_detector::*, io::*, scheduler::*, verifier::*}, + components::{clock::*, io::*, scheduler::*, verifier::*}, errors::*, light_client::*, operations::*, diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs new file mode 100644 index 000000000..d32321006 --- /dev/null +++ b/light-client/src/supervisor.rs @@ -0,0 +1,290 @@ +use crate::callback::Callback; +use crate::prelude::*; + +use contracts::pre; +use crossbeam_channel as channel; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +pub type VerificationResult = Result; + +#[derive(Debug)] +pub enum Event { + Terminate(Callback<()>), + Terminated, + VerifyToTarget(Height, Callback), + VerificationSuccessed(LightBlock), + VerificationFailed(Error), +} + +#[derive(Default)] +pub struct PeerListBuilder { + primary: Option, + peers: HashMap, +} + +impl PeerListBuilder { + pub fn primary(&mut self, primary: PeerId) -> &mut Self { + self.primary = Some(primary); + self + } + + pub fn peer(&mut self, peer_id: PeerId, client: LightClient) -> &mut Self { + self.peers.insert(peer_id, client); + self + } + + #[pre( + self.primary.is_some() && self.peers.contains_key(self.primary.as_ref().unwrap()) + )] + pub fn build(self) -> PeerList { + PeerList { + primary: self.primary.unwrap(), + peers: self.peers, + } + } +} + +#[derive(Debug)] +pub struct PeerList { + peers: HashMap, + primary: PeerId, +} + +impl PeerList { + pub fn builder() -> PeerListBuilder { + PeerListBuilder::default() + } + + pub fn get(&self, peer_id: &PeerId) -> Option<&LightClient> { + self.peers.get(peer_id) + } + + pub fn get_mut(&mut self, peer_id: &PeerId) -> Option<&mut LightClient> { + self.peers.get_mut(peer_id) + } + + pub fn primary(&mut self) -> Option<&mut LightClient> { + self.peers.get_mut(&self.primary) + } + + pub fn secondary_peers(&self) -> Vec { + self.peers + .keys() + .filter(|peer_id| peer_id != &&self.primary) + .copied() + .collect() + } + + #[pre(peer_id != &self.primary)] + pub fn remove_secondary(&mut self, peer_id: &PeerId) { + self.peers.remove(peer_id); + } + + pub fn swap_primary(&mut self) -> Result<(), Error> { + if let Some(peer_id) = self.peers.keys().next() { + self.primary = *peer_id; + return Ok(()); + } + + bail!(ErrorKind::NoValidPeerLeft) + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum Fork { + PassedVerification(PeerId), + FailedVerification(PeerId), +} + +#[derive(Debug)] +pub struct Supervisor { + peers: PeerList, + sender: channel::Sender, + receiver: channel::Receiver, +} + +impl Supervisor { + pub fn new(peers: PeerList) -> Self { + let (sender, receiver) = channel::unbounded::(); + + Self { + sender, + receiver, + peers, + } + } + + #[pre(self.peers.primary().is_some())] + pub fn verify_to_target(&mut self, height: Height) -> VerificationResult { + while let Some(primary) = self.peers.primary() { + let verdict = primary.verify_to_target(height); + + match verdict { + Ok(light_block) => { + let outcome = self.detect_forks(&light_block); + match outcome { + Some(forks) => { + let mut forked = Vec::with_capacity(forks.len()); + + for fork in forks { + match fork { + Fork::PassedVerification(peer_id) => { + self.report_evidence(&light_block); + forked.push(peer_id); + } + Fork::FailedVerification(peer_id) => { + self.peers.remove_secondary(&peer_id); + } + } + } + + if !forked.is_empty() { + // Fork detected, exiting + bail!(ErrorKind::ForkDetected(forked)) + } + } + None => { + // No fork detected, exiting + // TODO: Send to relayer, maybe the run method does this? + return Ok(light_block); + } + } + } + // Verification failed + Err(_err) => { + // Swap primary, and continue with new primary, if any + self.peers.swap_primary()?; + continue; + } + } + } + + bail!(ErrorKind::NoValidPeerLeft) + } + + fn report_evidence(&mut self, _light_block: &LightBlock) { + () + } + + #[pre(self.peers.primary().is_some())] + fn detect_forks(&mut self, light_block: &LightBlock) -> Option> { + if self.peers.secondary_peers().is_empty() { + return None; + } + + let state = State::new(MemoryStore::new()); + let primary = self.peers.primary().unwrap().with_state(state.clone()); + let secondaries = self + .peers + .secondary_peers() + .iter() + .filter_map(|peer_id| self.peers.get(peer_id)) + .map(|client| client.with_state(state.clone())) + .collect::>(); + + use crate::fork_detector::{ForkDetector, ProdForkDetector}; + let fork_detector = ProdForkDetector::new(); + let result = fork_detector.detect_forks(light_block, primary, secondaries); + Some(todo!()) + } + + pub fn handler(&mut self) -> Handler { + Handler::new(self.sender.clone()) + } + + // Consume the instance here but return a runtime which will allow interaction + // Maybe return an output channnel here? + pub fn run(mut self) { + loop { + let event = self.receiver.recv().unwrap(); + match event { + Event::Terminate(callback) => { + callback.call(()); + return; + } + Event::VerifyToTarget(height, callback) => { + let outcome = self.verify_to_target(height); + callback.call(outcome); + } + _ => { + // NoOp? + } + } + } + } +} + +pub struct Handler { + sender: channel::Sender, +} + +// Assume single handler +impl Handler { + // How do we connect with the runtime? + pub fn new(sender: channel::Sender) -> Self { + Self { sender } + } + + pub fn verify_to_target(&mut self, height: Height) -> VerificationResult { + let (sender, receiver) = channel::bounded::(1); + + let callback = Callback::new(move |result| { + // We need to create an event here + let event = match result { + Ok(header) => Event::VerificationSuccessed(header), + Err(err) => Event::VerificationFailed(err), + }; + + sender.send(event).unwrap(); + }); + + self.sender + .send(Event::VerifyToTarget(height, callback)) + .unwrap(); + + match receiver.recv().unwrap() { + Event::VerificationSuccessed(header) => Ok(header), + Event::VerificationFailed(_err) => todo!(), + _ => todo!(), + } + } + + pub fn verify_to_target_async( + &mut self, + height: Height, + callback: impl FnOnce(VerificationResult) -> () + Send + 'static, + ) { + let event = Event::VerifyToTarget(height, Callback::new(callback)); + self.sender.send(event).unwrap(); + } + + pub fn terminate(&mut self) { + let (sender, receiver) = channel::bounded::(1); + + let callback = Callback::new(move |_| { + sender.send(Event::Terminated).unwrap(); + }); + + self.sender.send(Event::Terminate(callback)).unwrap(); + + while let Ok(event) = receiver.recv() { + match event { + Event::Terminated => return, + _ => continue, + } + } + } +} + +#[allow(dead_code, unused_variables)] +mod test { + use super::*; + + fn test(mut s: Supervisor) { + let h1 = s.handler(); + let h2 = s.handler(); + + std::thread::spawn(move || s.run()); + } +} diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index a166452ac..1dffc5fbe 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -158,7 +158,6 @@ fn run_bisection_test(tc: TestBisection) { let clock_drift = Duration::from_secs(1); let clock = MockClock { now }; - let _fork_detector = ProdForkDetector::new(ProdHeaderHasher); let options = Options { trust_threshold, From 4210a8c580ed81c19475b18332ec5fb7625cbf52 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 20:37:26 +0200 Subject: [PATCH 06/39] Allowing supplying the state to work over to the light client --- light-client/examples/light_client.rs | 2 +- light-client/src/components/io.rs | 32 ++++--------- light-client/src/fork_detector.rs | 23 +++++----- light-client/src/light_client.rs | 65 ++++++++++++++++----------- light-client/src/prelude.rs | 4 ++ light-client/src/supervisor.rs | 14 +++--- light-client/tests/light_client.rs | 4 +- 7 files changed, 76 insertions(+), 68 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 47bdb50fa..ff48e91e5 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -67,7 +67,7 @@ fn sync_cmd(opts: SyncOpts) { let mut peer_map = HashMap::new(); peer_map.insert(primary, primary_addr); - let mut io = ProdIo::new(peer_map); + let io = ProdIo::new(peer_map); let db = sled::open(opts.db_path).unwrap_or_else(|e| { println!("[ error ] could not open database: {}", e); diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index a11a2d10a..c8809c5eb 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -28,15 +28,15 @@ pub trait Io: Send + DynClone { /// ## Postcondition /// - The provider of the returned light block matches the given peer [LCV-IO-POST-PROVIDER] #[post(ret.as_ref().map(|lb| lb.provider == peer).unwrap_or(true))] - fn fetch_light_block(&mut self, peer: PeerId, height: Height) -> Result; + fn fetch_light_block(&self, peer: PeerId, height: Height) -> Result; } #[contract_trait] impl Io for F where - F: FnMut(PeerId, Height) -> Result, + F: Fn(PeerId, Height) -> Result, { - fn fetch_light_block(&mut self, peer: PeerId, height: Height) -> Result { + fn fetch_light_block(&self, peer: PeerId, height: Height) -> Result { self(peer, height) } } @@ -45,13 +45,12 @@ where /// light blocks from full nodes via RPC. #[derive(Clone, Debug)] pub struct ProdIo { - rpc_clients: HashMap, peer_map: HashMap, } #[contract_trait] impl Io for ProdIo { - fn fetch_light_block(&mut self, peer: PeerId, height: Height) -> Result { + fn fetch_light_block(&self, peer: PeerId, height: Height) -> Result { let signed_header = self.fetch_signed_header(peer, height)?; let height = signed_header.header.height.into(); @@ -69,18 +68,11 @@ impl ProdIo { /// /// A peer map which maps peer IDS to their network address must be supplied. pub fn new(peer_map: HashMap) -> Self { - Self { - rpc_clients: HashMap::new(), - peer_map, - } + Self { peer_map } } #[pre(self.peer_map.contains_key(&peer))] - fn fetch_signed_header( - &mut self, - peer: PeerId, - height: Height, - ) -> Result { + fn fetch_signed_header(&self, peer: PeerId, height: Height) -> Result { let height: block::Height = height.into(); let rpc_client = self.rpc_client_for(peer); @@ -98,11 +90,7 @@ impl ProdIo { } #[pre(self.peer_map.contains_key(&peer))] - fn fetch_validator_set( - &mut self, - peer: PeerId, - height: Height, - ) -> Result { + fn fetch_validator_set(&self, peer: PeerId, height: Height) -> Result { let res = block_on(self.rpc_client_for(peer).validators(height)); match res { @@ -113,11 +101,9 @@ impl ProdIo { // FIXME: Cannot enable precondition because of "autoref lifetime" issue // #[pre(self.peer_map.contains_key(&peer))] - fn rpc_client_for(&mut self, peer: PeerId) -> &mut rpc::Client { + fn rpc_client_for(&self, peer: PeerId) -> rpc::Client { let peer_addr = self.peer_map.get(&peer).unwrap().to_owned(); - self.rpc_clients - .entry(peer) - .or_insert_with(|| rpc::Client::new(peer_addr)) + rpc::Client::new(peer_addr) } } diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 8665277dd..d4efbc8d6 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -11,8 +11,8 @@ pub trait ForkDetector { fn detect_forks( &self, light_block: &LightBlock, - primary: LightClient, - secondaries: Vec, + primary: &LightClient, + secondaries: Vec<&LightClient>, ) -> ForkDetection; } @@ -34,11 +34,14 @@ impl ForkDetector for ProdForkDetector { fn detect_forks( &self, light_block: &LightBlock, - primary: LightClient, - secondaries: Vec, + primary: &LightClient, + secondaries: Vec<&LightClient>, ) -> ForkDetection { - for mut secondary in secondaries { - let secondary_block = secondary.get_or_fetch_block(light_block.height()).unwrap(); // FIXME: unwrap + for secondary in secondaries { + let mut state: State = todo(); + let secondary_block = secondary + .get_or_fetch_block(light_block.height(), &mut state) + .unwrap(); // FIXME: unwrap if light_block.signed_header.header == secondary_block.signed_header.header { // Header matches, we continue. @@ -51,17 +54,15 @@ impl ForkDetector for ProdForkDetector { .latest(VerifiedStatus::Verified) .unwrap(); // FIXME: unwrap - secondary - .state + state .light_store .update(latest_trusted, VerifiedStatus::Verified); - secondary - .state + state .light_store .update(secondary_block, VerifiedStatus::Unverified); - let result = secondary.verify_to_target(light_block.height()); + let result = secondary.verify_to_target_with_state(light_block.height(), &mut state); // TODO: Handle case where block expired match result { diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 3e82e0355..d99e99b41 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -100,6 +100,27 @@ impl LightClient { self.verify_to_target(target_block.height()) } + /// See `verify_to_target_with_state` + // #[pre( + // light_store_contains_block_within_trusting_period( + // self.state.light_store.as_ref(), + // self.options.trusting_period, + // self.clock.now(), + // ) + // )] + #[post( + ret.is_ok() ==> trusted_store_contains_block_at_target_height( + self.state.light_store.as_ref(), + target_height, + ) + )] + pub fn verify_to_target(&mut self, target_height: Height) -> Result { + let mut state = std::mem::replace(&mut self.state, State::new(MemoryStore::new())); + let result = self.verify_to_target_with_state(target_height, &mut state); + self.state = state; + result + } + /// Attemps to update the light client to a block of the primary node at the given height. /// /// This is the main function and uses the following components: @@ -142,7 +163,11 @@ impl LightClient { target_height, ) )] - pub fn verify_to_target(&mut self, target_height: Height) -> Result { + pub fn verify_to_target_with_state( + &self, + target_height: Height, + state: &mut State, + ) -> Result { // Let's first look in the store to see whether we have already successfully verified this block if let Some(light_block) = self .state @@ -175,7 +200,7 @@ impl LightClient { } // Trace the current height as a dependency of the block at the target height - self.state.trace_block(target_height, current_height); + state.trace_block(target_height, current_height); // If the trusted state is now at the height greater or equal to the target height, // we now trust this target height, and are thus done :) [LCV-DIST-LIFE.1] @@ -184,7 +209,7 @@ impl LightClient { } // Fetch the block at the current height from our peer - let current_block = self.get_or_fetch_block(current_height)?; + let current_block = self.get_or_fetch_block(current_height, state)?; // Validate and verify the current block let verdict = self @@ -194,13 +219,13 @@ impl LightClient { match verdict { Verdict::Success => { // Verification succeeded, add the block to the light store with `verified` status - self.state + state .light_store .update(current_block, VerifiedStatus::Verified); } Verdict::Invalid(e) => { // Verification failed, add the block to the light store with `failed` status, and abort. - self.state + state .light_store .update(current_block, VerifiedStatus::Failed); @@ -211,18 +236,16 @@ impl LightClient { // Add the block to the light store with `unverified` status. // This will engage bisection in an attempt to raise the height of the latest // trusted state until there is enough overlap. - self.state + state .light_store .update(current_block, VerifiedStatus::Unverified); } } // Compute the next height to fetch and verify - current_height = self.scheduler.schedule( - self.state.light_store.as_ref(), - current_height, - target_height, - ); + current_height = + self.scheduler + .schedule(state.light_store.as_ref(), current_height, target_height); } } @@ -238,7 +261,11 @@ impl LightClient { /// - The provider of block that is returned matches the given peer. // TODO: Uncomment when provider field is available // #[post(ret.map(|lb| lb.provider == peer).unwrap_or(false))] - pub fn get_or_fetch_block(&mut self, current_height: Height) -> Result { + pub fn get_or_fetch_block( + &self, + current_height: Height, + state: &mut State, + ) -> Result { let current_block = self .state .light_store @@ -256,7 +283,7 @@ impl LightClient { self.io .fetch_light_block(self.peer, current_height) .map(|current_block| { - self.state + state .light_store .insert(current_block.clone(), VerifiedStatus::Unverified); @@ -264,16 +291,4 @@ impl LightClient { }) .map_err(|e| ErrorKind::Io(e).into()) } - - pub fn with_state(&self, state: State) -> Self { - Self { - state, - peer: self.peer, - options: self.options.clone(), - clock: dyn_clone::clone_box(&*self.clock), - scheduler: dyn_clone::clone_box(&*self.scheduler), - verifier: dyn_clone::clone_box(&*self.verifier), - io: dyn_clone::clone_box(&*self.io), - } - } } diff --git a/light-client/src/prelude.rs b/light-client/src/prelude.rs index 7e1e5c8e5..a5e0bb0bf 100644 --- a/light-client/src/prelude.rs +++ b/light-client/src/prelude.rs @@ -13,3 +13,7 @@ pub use crate::{ store::{memory::*, sled::*, LightStore, VerifiedStatus}, types::*, }; + +pub fn todo() -> A { + unreachable!() +} diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index d32321006..904fa1c6e 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -64,7 +64,11 @@ impl PeerList { self.peers.get_mut(peer_id) } - pub fn primary(&mut self) -> Option<&mut LightClient> { + pub fn primary(&self) -> Option<&LightClient> { + self.peers.get(&self.primary) + } + + pub fn primary_mut(&mut self) -> Option<&mut LightClient> { self.peers.get_mut(&self.primary) } @@ -117,7 +121,7 @@ impl Supervisor { #[pre(self.peers.primary().is_some())] pub fn verify_to_target(&mut self, height: Height) -> VerificationResult { - while let Some(primary) = self.peers.primary() { + while let Some(primary) = self.peers.primary_mut() { let verdict = primary.verify_to_target(height); match verdict { @@ -173,20 +177,18 @@ impl Supervisor { return None; } - let state = State::new(MemoryStore::new()); - let primary = self.peers.primary().unwrap().with_state(state.clone()); + let primary = self.peers.primary().unwrap(); let secondaries = self .peers .secondary_peers() .iter() .filter_map(|peer_id| self.peers.get(peer_id)) - .map(|client| client.with_state(state.clone())) .collect::>(); use crate::fork_detector::{ForkDetector, ProdForkDetector}; let fork_detector = ProdForkDetector::new(); let result = fork_detector.detect_forks(light_block, primary, secondaries); - Some(todo!()) + Some(todo()) } pub fn handler(&mut self) -> Handler { diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index 1dffc5fbe..9f450e239 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -117,7 +117,7 @@ impl MockIo { #[contract_trait] impl Io for MockIo { - fn fetch_light_block(&mut self, _peer: PeerId, height: Height) -> Result { + fn fetch_light_block(&self, _peer: PeerId, height: Height) -> Result { self.light_blocks .get(&height) .cloned() @@ -172,7 +172,7 @@ fn run_bisection_test(tc: TestBisection) { }; let provider = tc.primary; - let mut io = MockIo::new(provider.chain_id, provider.lite_blocks); + let io = MockIo::new(provider.chain_id, provider.lite_blocks); let trusted_height = tc.trust_options.height.try_into().unwrap(); let trusted_state = io From 6829e5b343c23a847f5e03c5c790e679faebef30 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 20:42:23 +0200 Subject: [PATCH 07/39] Remove unnecessary instances of operations traits --- .../src/operations/commit_validator.rs | 20 ------------- light-client/src/operations/header_hasher.rs | 12 -------- light-client/src/operations/voting_power.rs | 28 ------------------- 3 files changed, 60 deletions(-) diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index 2e62715bb..2a27ff03f 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -12,26 +12,6 @@ pub trait CommitValidator: Send + DynClone { ) -> Result<(), BoxError>; } -impl CommitValidator for &T { - fn validate( - &self, - signed_header: &SignedHeader, - validators: &ValidatorSet, - ) -> Result<(), BoxError> { - (*self).validate(signed_header, validators) - } -} - -// impl CommitValidator for Box { -// fn validate( -// &self, -// signed_header: &SignedHeader, -// validators: &ValidatorSet, -// ) -> Result<(), BoxError> { -// self.as_ref().validate(signed_header, validators) -// } -// } - #[derive(Copy, Clone)] pub struct ProdCommitValidator; diff --git a/light-client/src/operations/header_hasher.rs b/light-client/src/operations/header_hasher.rs index 38d4fa7d9..924991adc 100644 --- a/light-client/src/operations/header_hasher.rs +++ b/light-client/src/operations/header_hasher.rs @@ -9,18 +9,6 @@ pub trait HeaderHasher: Send + DynClone { 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) -// } -// } - #[derive(Copy, Clone, Debug)] pub struct ProdHeaderHasher; diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index 564ac7313..21cb8b06e 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -13,34 +13,6 @@ pub trait VotingPowerCalculator: Send + DynClone { ) -> Result; } -impl VotingPowerCalculator for &T { - fn total_power_of(&self, validators: &ValidatorSet) -> u64 { - (*self).total_power_of(validators) - } - - fn voting_power_in( - &self, - signed_header: &SignedHeader, - validators: &ValidatorSet, - ) -> Result { - (*self).voting_power_in(signed_header, validators) - } -} - -// impl VotingPowerCalculator for Box { -// fn total_power_of(&self, validators: &ValidatorSet) -> u64 { -// self.as_ref().total_power_of(validators) -// } - -// fn voting_power_in( -// &self, -// signed_header: &SignedHeader, -// validators: &ValidatorSet, -// ) -> Result { -// self.as_ref().voting_power_in(signed_header, validators) -// } -// } - #[derive(Copy, Clone, Debug)] pub struct ProdVotingPowerCalculator; From 97a999fd0a3fee4f2ee67a89ebc28bb10cf6e864 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 20:43:00 +0200 Subject: [PATCH 08/39] Remove unnecessary Clone and DynClone instances --- light-client/Cargo.toml | 1 - light-client/src/components/clock.rs | 3 +-- light-client/src/components/io.rs | 5 ++--- light-client/src/components/scheduler.rs | 3 +-- light-client/src/components/verifier.rs | 15 +-------------- light-client/src/operations/commit_validator.rs | 3 +-- light-client/src/operations/header_hasher.rs | 3 +-- light-client/src/operations/voting_power.rs | 3 +-- light-client/src/predicates.rs | 3 +-- light-client/src/state.rs | 9 --------- light-client/src/store.rs | 3 +-- 11 files changed, 10 insertions(+), 41 deletions(-) diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index 5d60e638c..b1c766fc2 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -19,7 +19,6 @@ contracts = "0.4.0" sled = "0.31.0" serde_cbor = "0.11.1" crossbeam-channel = "0.4.2" -dyn-clone = "1.0.1" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-client/src/components/clock.rs b/light-client/src/components/clock.rs index 666156199..426e79cc1 100644 --- a/light-client/src/components/clock.rs +++ b/light-client/src/components/clock.rs @@ -1,8 +1,7 @@ use crate::prelude::*; -use dyn_clone::DynClone; /// Abstracts over the current time. -pub trait Clock: Send + DynClone { +pub trait Clock: Send { /// Get the current time. fn now(&self) -> Time; } diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index c8809c5eb..c5c1a24f6 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use contracts::{contract_trait, post, pre}; -use dyn_clone::DynClone; use serde::{Deserialize, Serialize}; use tendermint::{block, rpc}; use thiserror::Error; @@ -22,7 +21,7 @@ pub enum IoError { /// Interface for fetching light blocks from a full node, typically via the RPC client. #[contract_trait] -pub trait Io: Send + DynClone { +pub trait Io: Send { /// Fetch a light block at the given height from the peer with the given peer ID. /// /// ## Postcondition @@ -32,7 +31,7 @@ pub trait Io: Send + DynClone { } #[contract_trait] -impl Io for F +impl Io for F where F: Fn(PeerId, Height) -> Result, { diff --git a/light-client/src/components/scheduler.rs b/light-client/src/components/scheduler.rs index 47ab2587f..bdd306e9c 100644 --- a/light-client/src/components/scheduler.rs +++ b/light-client/src/components/scheduler.rs @@ -1,14 +1,13 @@ use crate::prelude::*; use contracts::*; -use dyn_clone::DynClone; /// The scheduler decides what block to verify next given the current and target heights. /// /// The scheduler is given access to the light store, in order to optionally /// improve performance by picking a next block that has already been fetched. #[contract_trait] -pub trait Scheduler: Send + DynClone { +pub trait Scheduler: Send { /// Decides what block to verify next. /// /// ## Precondition diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index bd1084d18..f3bc16219 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -1,8 +1,6 @@ use crate::predicates as preds; use crate::prelude::*; -use dyn_clone::DynClone; - /// Represents the result of the verification performed by the /// verifier component. #[derive(Debug)] @@ -35,7 +33,7 @@ impl From> for Verdict { /// ## Implements /// - [TMBC-VAL-CONTAINS-CORR.1] /// - [TMBC-VAL-COMMIT.1] -pub trait Verifier: Send + DynClone { +pub trait Verifier: Send { /// Perform the verification. fn verify(&self, untrusted: &LightBlock, trusted: &LightBlock, options: &Options) -> Verdict; } @@ -56,17 +54,6 @@ pub struct ProdVerifier { header_hasher: Box, } -impl Clone for ProdVerifier { - fn clone(&self) -> Self { - Self { - predicates: dyn_clone::clone_box(&*self.predicates), - voting_power_calculator: dyn_clone::clone_box(&*self.voting_power_calculator), - commit_validator: dyn_clone::clone_box(&*self.commit_validator), - header_hasher: dyn_clone::clone_box(&*self.header_hasher), - } - } -} - impl ProdVerifier { pub fn new( predicates: impl VerificationPredicates + 'static, diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index 2a27ff03f..f871170ee 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -1,10 +1,9 @@ use crate::prelude::*; use anomaly::BoxError; -use dyn_clone::DynClone; use tendermint::lite::types::Commit as _; -pub trait CommitValidator: Send + DynClone { +pub trait CommitValidator: Send { fn validate( &self, signed_header: &SignedHeader, diff --git a/light-client/src/operations/header_hasher.rs b/light-client/src/operations/header_hasher.rs index 924991adc..150237e64 100644 --- a/light-client/src/operations/header_hasher.rs +++ b/light-client/src/operations/header_hasher.rs @@ -1,11 +1,10 @@ use crate::prelude::*; -use dyn_clone::DynClone; use tendermint::amino_types::{message::AminoMessage, BlockId, ConsensusVersion, TimeMsg}; use tendermint::merkle::simple_hash_from_byte_vectors; use tendermint::Hash; -pub trait HeaderHasher: Send + DynClone { +pub trait HeaderHasher: Send { fn hash(&self, header: &Header) -> Hash; // Or Error? } diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index 21cb8b06e..6d1e3573b 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -1,10 +1,9 @@ use crate::prelude::*; use anomaly::BoxError; -use dyn_clone::DynClone; use tendermint::lite::types::ValidatorSet as _; -pub trait VotingPowerCalculator: Send + DynClone { +pub trait VotingPowerCalculator: Send { fn total_power_of(&self, validators: &ValidatorSet) -> u64; fn voting_power_in( &self, diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index 494ca9dba..deadd4839 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -2,7 +2,6 @@ use crate::prelude::*; -use dyn_clone::DynClone; use tendermint::lite::ValidatorSet as _; pub mod errors; @@ -19,7 +18,7 @@ impl VerificationPredicates for ProdPredicates {} /// /// This enables test implementations to only override a single method rather than /// have to re-define every predicate. -pub trait VerificationPredicates: Send + DynClone { +pub trait VerificationPredicates: Send { fn validator_sets_match(&self, light_block: &LightBlock) -> Result<(), VerificationError> { ensure!( light_block.signed_header.header.validators_hash == light_block.validators.hash(), diff --git a/light-client/src/state.rs b/light-client/src/state.rs index f659b3e5f..2a0c902ad 100644 --- a/light-client/src/state.rs +++ b/light-client/src/state.rs @@ -17,15 +17,6 @@ pub struct State { pub verification_trace: VerificationTrace, } -impl Clone for State { - fn clone(&self) -> Self { - Self { - light_store: dyn_clone::clone_box(&*self.light_store), - verification_trace: self.verification_trace.clone(), - } - } -} - impl State { pub fn new(light_store: impl LightStore + 'static) -> Self { Self { diff --git a/light-client/src/store.rs b/light-client/src/store.rs index 55f764832..35c30e0ad 100644 --- a/light-client/src/store.rs +++ b/light-client/src/store.rs @@ -6,7 +6,6 @@ use crate::prelude::*; -use dyn_clone::DynClone; use serde::{Deserialize, Serialize}; pub mod memory; @@ -44,7 +43,7 @@ impl VerifiedStatus { /// /// ## Implements /// - [LCV-DIST-STORE.1] -pub trait LightStore: std::fmt::Debug + Send + DynClone { +pub trait LightStore: std::fmt::Debug + Send { /// Get the light block at the given height with the given status, or return `None` otherwise. fn get(&self, height: Height, status: VerifiedStatus) -> Option; /// Update the `status` of the given `light_block`. From 667a770798c3515184cad620fa5b8931397b8c17 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 20:45:04 +0200 Subject: [PATCH 09/39] Improve docs --- light-client/src/light_client.rs | 45 +++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index d99e99b41..02a0fdb3e 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -100,27 +100,6 @@ impl LightClient { self.verify_to_target(target_block.height()) } - /// See `verify_to_target_with_state` - // #[pre( - // light_store_contains_block_within_trusting_period( - // self.state.light_store.as_ref(), - // self.options.trusting_period, - // self.clock.now(), - // ) - // )] - #[post( - ret.is_ok() ==> trusted_store_contains_block_at_target_height( - self.state.light_store.as_ref(), - target_height, - ) - )] - pub fn verify_to_target(&mut self, target_height: Height) -> Result { - let mut state = std::mem::replace(&mut self.state, State::new(MemoryStore::new())); - let result = self.verify_to_target_with_state(target_height, &mut state); - self.state = state; - result - } - /// Attemps to update the light client to a block of the primary node at the given height. /// /// This is the main function and uses the following components: @@ -150,6 +129,30 @@ impl LightClient { /// - If the core verification loop invariant is violated [LCV-INV-TP.1] /// - If verification of a light block fails /// - If it cannot fetch a block from the blockchain + /// + /// ## Note + /// - This method actually delegates the actual work to `verify_to_target_with_state` over the current state. + // #[pre( + // light_store_contains_block_within_trusting_period( + // self.state.light_store.as_ref(), + // self.options.trusting_period, + // self.clock.now(), + // ) + // )] + #[post( + ret.is_ok() ==> trusted_store_contains_block_at_target_height( + self.state.light_store.as_ref(), + target_height, + ) + )] + pub fn verify_to_target(&mut self, target_height: Height) -> Result { + let mut state = std::mem::replace(&mut self.state, State::new(MemoryStore::new())); + let result = self.verify_to_target_with_state(target_height, &mut state); + self.state = state; + result + } + + /// See `verify_to_target` // #[pre( // light_store_contains_block_within_trusting_period( // self.state.light_store.as_ref(), From f0e71d420aed6106e1c24c6bcbfec60db915780c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 4 Jun 2020 20:46:57 +0200 Subject: [PATCH 10/39] Small refactor in supervisor --- light-client/src/supervisor.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 904fa1c6e..bed4db6c4 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -72,11 +72,11 @@ impl PeerList { self.peers.get_mut(&self.primary) } - pub fn secondary_peers(&self) -> Vec { + pub fn secondaries(&self) -> Vec<&LightClient> { self.peers .keys() .filter(|peer_id| peer_id != &&self.primary) - .copied() + .filter_map(|peer_id| self.get(peer_id)) .collect() } @@ -173,21 +173,17 @@ impl Supervisor { #[pre(self.peers.primary().is_some())] fn detect_forks(&mut self, light_block: &LightBlock) -> Option> { - if self.peers.secondary_peers().is_empty() { + use crate::fork_detector::{ForkDetector, ProdForkDetector}; + + if self.peers.secondaries().is_empty() { return None; } let primary = self.peers.primary().unwrap(); - let secondaries = self - .peers - .secondary_peers() - .iter() - .filter_map(|peer_id| self.peers.get(peer_id)) - .collect::>(); + let secondaries = self.peers.secondaries(); - use crate::fork_detector::{ForkDetector, ProdForkDetector}; let fork_detector = ProdForkDetector::new(); - let result = fork_detector.detect_forks(light_block, primary, secondaries); + let _result = fork_detector.detect_forks(light_block, primary, secondaries); Some(todo()) } From 41fe43eaf75dbe0e6f0e7751dacc091e088c9ab3 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 5 Jun 2020 15:04:03 +0200 Subject: [PATCH 11/39] Finalize fork detection --- light-client/src/errors.rs | 32 +++++++++++++++++++++++++++ light-client/src/fork_detector.rs | 27 ++++++++++++++++------ light-client/src/predicates/errors.rs | 30 ++++++++++++++++++++++--- light-client/src/supervisor.rs | 28 ++++++++++------------- 4 files changed, 91 insertions(+), 26 deletions(-) diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 204584bdf..50b6d51b6 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -45,3 +45,35 @@ impl ErrorKind { Context::new(self, Some(source.into())) } } + +pub trait ErrorExt { + /// Whether this error means that the light block + /// cannot be trusted w.r.t. the latest trusted state. + fn not_enough_trust(&self) -> bool; + + /// Whether this error means that the light block has expired, + /// ie. it's outside of the trusting period. + fn has_expired(&self) -> bool; +} + +impl ErrorExt for ErrorKind { + /// Whether this error means that the light block + /// cannot be trusted w.r.t. the latest trusted state. + fn not_enough_trust(&self) -> bool { + if let Self::InvalidLightBlock(e) = self { + e.not_enough_trust() + } else { + false + } + } + + /// Whether this error means that the light block has expired, + /// ie. it's outside of the trusting period. + fn has_expired(&self) -> bool { + if let Self::InvalidLightBlock(e) = self { + e.has_expired() + } else { + false + } + } +} diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index d4efbc8d6..752a556ae 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -4,7 +4,14 @@ use crate::prelude::*; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetection { - NoFork, + Detected(Vec), + NotDetected, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Fork { + Forked(LightBlock), + Faulty(LightBlock), } pub trait ForkDetector { @@ -37,14 +44,16 @@ impl ForkDetector for ProdForkDetector { primary: &LightClient, secondaries: Vec<&LightClient>, ) -> ForkDetection { + let mut forks = Vec::with_capacity(secondaries.len()); + for secondary in secondaries { let mut state: State = todo(); let secondary_block = secondary .get_or_fetch_block(light_block.height(), &mut state) .unwrap(); // FIXME: unwrap + // TODO: Should hash the headers here instead of comparing them for equality? if light_block.signed_header.header == secondary_block.signed_header.header { - // Header matches, we continue. continue; } @@ -60,17 +69,21 @@ impl ForkDetector for ProdForkDetector { state .light_store - .update(secondary_block, VerifiedStatus::Unverified); + .update(secondary_block.clone(), VerifiedStatus::Unverified); let result = secondary.verify_to_target_with_state(light_block.height(), &mut state); - // TODO: Handle case where block expired match result { - Ok(_) => todo!(), // There is a fork, report `secondary_block` - Err(_) => todo!(), // `secondary` is faulty, report it + Ok(_) => forks.push(Fork::Forked(secondary_block)), + Err(e) if e.kind().has_expired() => forks.push(Fork::Forked(secondary_block)), + Err(_) => forks.push(Fork::Faulty(secondary_block)), } } - ForkDetection::NoFork + if forks.is_empty() { + ForkDetection::NotDetected + } else { + ForkDetection::Detected(forks) + } } } diff --git a/light-client/src/predicates/errors.rs b/light-client/src/predicates/errors.rs index 52231979a..e3446e4b3 100644 --- a/light-client/src/predicates/errors.rs +++ b/light-client/src/predicates/errors.rs @@ -2,6 +2,7 @@ use anomaly::{BoxError, Context}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::errors::ErrorExt; use crate::prelude::*; /// The various errors which can be raised by the verifier component, @@ -10,8 +11,10 @@ use crate::prelude::*; pub enum VerificationError { #[error("header from the future: header_time={header_time} now={now}")] HeaderFromTheFuture { header_time: Time, now: Time }, + #[error("implementation specific: {0}")] ImplementationSpecific(String), + #[error( "insufficient validators overlap: total_power={total_power} signed_power={signed_power} trust_threshold={trust_threshold}" )] @@ -20,34 +23,43 @@ pub enum VerificationError { signed_power: u64, trust_threshold: TrustThreshold, }, + #[error("insufficient voting power: total_power={total_power} voting_power={voting_power}")] InsufficientVotingPower { total_power: u64, voting_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, 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: Time, trusted_header_bft_time: Time, }, + #[error("not withing trust period: at={at} now={now}")] NotWithinTrustPeriod { at: Time, now: Time }, } @@ -58,14 +70,26 @@ impl VerificationError { pub fn context(self, source: impl Into) -> Context { Context::new(self, Some(source.into())) } +} - /// Determines whether this error means that the light block is outright invalid, - /// or just cannot be trusted w.r.t. the latest trusted state. - pub fn not_enough_trust(&self) -> bool { +impl ErrorExt for VerificationError { + /// Whether this error means that the light block + /// cannot be trusted w.r.t. the latest trusted state. + fn not_enough_trust(&self) -> bool { if let Self::InsufficientValidatorsOverlap { .. } = self { true } else { false } } + + /// Whether this error means that the light block has expired, + /// ie. it's outside of the trusting period. + fn has_expired(&self) -> bool { + if let Self::NotWithinTrustPeriod { .. } = self { + true + } else { + false + } + } } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index bed4db6c4..ff6e573ae 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -1,9 +1,9 @@ use crate::callback::Callback; +use crate::fork_detector::{Fork, ForkDetection, ForkDetector, ProdForkDetector}; use crate::prelude::*; use contracts::pre; use crossbeam_channel as channel; -use serde::{Deserialize, Serialize}; use std::collections::HashMap; pub type VerificationResult = Result; @@ -95,12 +95,6 @@ impl PeerList { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub enum Fork { - PassedVerification(PeerId), - FailedVerification(PeerId), -} - #[derive(Debug)] pub struct Supervisor { peers: PeerList, @@ -133,12 +127,12 @@ impl Supervisor { for fork in forks { match fork { - Fork::PassedVerification(peer_id) => { - self.report_evidence(&light_block); - forked.push(peer_id); + Fork::Forked(block) => { + self.report_evidence(&block); + forked.push(block.provider); } - Fork::FailedVerification(peer_id) => { - self.peers.remove_secondary(&peer_id); + Fork::Faulty(block) => { + self.peers.remove_secondary(&block.provider); } } } @@ -173,8 +167,6 @@ impl Supervisor { #[pre(self.peers.primary().is_some())] fn detect_forks(&mut self, light_block: &LightBlock) -> Option> { - use crate::fork_detector::{ForkDetector, ProdForkDetector}; - if self.peers.secondaries().is_empty() { return None; } @@ -183,8 +175,12 @@ impl Supervisor { let secondaries = self.peers.secondaries(); let fork_detector = ProdForkDetector::new(); - let _result = fork_detector.detect_forks(light_block, primary, secondaries); - Some(todo()) + let result = fork_detector.detect_forks(light_block, primary, secondaries); + + match result { + ForkDetection::Detected(forks) => Some(forks), + ForkDetection::NotDetected => None, + } } pub fn handler(&mut self) -> Handler { From 215d24fd1ee3085f8f9081d14ec87fd8246ac68b Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 5 Jun 2020 15:27:21 +0200 Subject: [PATCH 12/39] Fix wrong uses of internal light client state instead of the supplied one --- light-client/src/light_client.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 02a0fdb3e..256bb2825 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -155,14 +155,14 @@ impl LightClient { /// See `verify_to_target` // #[pre( // light_store_contains_block_within_trusting_period( - // self.state.light_store.as_ref(), + // state.light_store.as_ref(), // self.options.trusting_period, // self.clock.now(), // ) // )] #[post( ret.is_ok() ==> trusted_store_contains_block_at_target_height( - self.state.light_store.as_ref(), + state.light_store.as_ref(), target_height, ) )] @@ -188,8 +188,7 @@ impl LightClient { loop { // Get the latest trusted state - let trusted_state = self - .state + let trusted_state = state .light_store .latest(VerifiedStatus::Verified) .ok_or_else(|| ErrorKind::NoInitialTrustedState)?; @@ -262,19 +261,17 @@ impl LightClient { /// /// ## Postcondition /// - The provider of block that is returned matches the given peer. - // TODO: Uncomment when provider field is available - // #[post(ret.map(|lb| lb.provider == peer).unwrap_or(false))] + #[post(ret.as_ref().map(|lb| lb.provider == self.peer).unwrap_or(true))] pub fn get_or_fetch_block( &self, current_height: Height, state: &mut State, ) -> Result { - let current_block = self - .state + let current_block = state .light_store .get(current_height, VerifiedStatus::Verified) .or_else(|| { - self.state + state .light_store .get(current_height, VerifiedStatus::Unverified) }); From 2f0c90d09ec6975b93e548a4264db66a3d4e024f Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 5 Jun 2020 15:50:55 +0200 Subject: [PATCH 13/39] Remove light client's internal state --- light-client/examples/light_client.rs | 7 ++-- light-client/src/fork_detector.rs | 25 ++++++------- light-client/src/light_client.rs | 42 ++------------------- light-client/src/supervisor.rs | 54 ++++++++++++++++++--------- light-client/tests/light_client.rs | 10 ++--- 5 files changed, 60 insertions(+), 78 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index ff48e91e5..8f5e3df7e 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -85,7 +85,7 @@ fn sync_cmd(opts: SyncOpts) { light_store.insert(trusted_state, VerifiedStatus::Verified); } - let state = State { + let mut state = State { light_store: Box::new(light_store), verification_trace: HashMap::new(), }; @@ -104,11 +104,10 @@ fn sync_cmd(opts: SyncOpts) { let clock = SystemClock; let scheduler = scheduler::basic_bisecting_schedule; - let mut light_client = - LightClient::new(primary, state, options, clock, scheduler, verifier, io); + let mut light_client = LightClient::new(primary, options, clock, scheduler, verifier, io); loop { - match light_client.verify_to_highest() { + match light_client.verify_to_highest(&mut state) { Ok(light_block) => { println!("[ info ] synced to block {}", light_block.height()); } diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 752a556ae..a370a18f3 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; +use crate::supervisor::Instance; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetection { @@ -18,8 +19,8 @@ pub trait ForkDetector { fn detect_forks( &self, light_block: &LightBlock, - primary: &LightClient, - secondaries: Vec<&LightClient>, + trusted_state: &LightBlock, + secondaries: Vec<&Instance>, ) -> ForkDetection; } @@ -41,14 +42,16 @@ impl ForkDetector for ProdForkDetector { fn detect_forks( &self, light_block: &LightBlock, - primary: &LightClient, - secondaries: Vec<&LightClient>, + trusted_state: &LightBlock, + secondaries: Vec<&Instance>, ) -> ForkDetection { let mut forks = Vec::with_capacity(secondaries.len()); for secondary in secondaries { - let mut state: State = todo(); + let mut state = State::new(MemoryStore::new()); + let secondary_block = secondary + .light_client .get_or_fetch_block(light_block.height(), &mut state) .unwrap(); // FIXME: unwrap @@ -57,21 +60,17 @@ impl ForkDetector for ProdForkDetector { continue; } - let latest_trusted = primary - .state - .light_store - .latest(VerifiedStatus::Verified) - .unwrap(); // FIXME: unwrap - state .light_store - .update(latest_trusted, VerifiedStatus::Verified); + .update(trusted_state.clone(), VerifiedStatus::Verified); state .light_store .update(secondary_block.clone(), VerifiedStatus::Unverified); - let result = secondary.verify_to_target_with_state(light_block.height(), &mut state); + let result = secondary + .light_client + .verify_to_target(light_block.height(), &mut state); match result { Ok(_) => forks.push(Fork::Forked(secondary_block)), diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 256bb2825..cebda5c34 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -48,7 +48,6 @@ impl Options { /// is designed for this security model. pub struct LightClient { pub peer: PeerId, - pub state: State, pub options: Options, clock: Box, scheduler: Box, @@ -61,7 +60,6 @@ impl fmt::Debug for LightClient { f.debug_struct("LightClient") .field("peer", &self.peer) .field("options", &self.options) - .field("state", &self.state) .finish() } } @@ -70,7 +68,6 @@ impl LightClient { /// Constructs a new light client pub fn new( peer: PeerId, - state: State, options: Options, clock: impl Clock + 'static, scheduler: impl Scheduler + 'static, @@ -79,7 +76,6 @@ impl LightClient { ) -> Self { Self { peer, - state, options, clock: Box::new(clock), scheduler: Box::new(scheduler), @@ -91,13 +87,13 @@ impl LightClient { /// Attempt to update the light client to the latest block of the primary node. /// /// Note: This functin delegates the actual work to `verify_to_target`. - pub fn verify_to_highest(&mut self) -> Result { + pub fn verify_to_highest(&mut self, state: &mut State) -> Result { let target_block = match self.io.fetch_light_block(self.peer, LATEST_HEIGHT) { Ok(last_block) => last_block, Err(io_error) => bail!(ErrorKind::Io(io_error)), }; - self.verify_to_target(target_block.height()) + self.verify_to_target(target_block.height(), state) } /// Attemps to update the light client to a block of the primary node at the given height. @@ -129,30 +125,6 @@ impl LightClient { /// - If the core verification loop invariant is violated [LCV-INV-TP.1] /// - If verification of a light block fails /// - If it cannot fetch a block from the blockchain - /// - /// ## Note - /// - This method actually delegates the actual work to `verify_to_target_with_state` over the current state. - // #[pre( - // light_store_contains_block_within_trusting_period( - // self.state.light_store.as_ref(), - // self.options.trusting_period, - // self.clock.now(), - // ) - // )] - #[post( - ret.is_ok() ==> trusted_store_contains_block_at_target_height( - self.state.light_store.as_ref(), - target_height, - ) - )] - pub fn verify_to_target(&mut self, target_height: Height) -> Result { - let mut state = std::mem::replace(&mut self.state, State::new(MemoryStore::new())); - let result = self.verify_to_target_with_state(target_height, &mut state); - self.state = state; - result - } - - /// See `verify_to_target` // #[pre( // light_store_contains_block_within_trusting_period( // state.light_store.as_ref(), @@ -166,14 +138,13 @@ impl LightClient { target_height, ) )] - pub fn verify_to_target_with_state( + pub fn verify_to_target( &self, target_height: Height, state: &mut State, ) -> Result { // Let's first look in the store to see whether we have already successfully verified this block - if let Some(light_block) = self - .state + if let Some(light_block) = state .light_store .get(target_height, VerifiedStatus::Verified) { @@ -251,11 +222,6 @@ impl LightClient { } } - /// Get the verification trace for the block at target_height. - pub fn get_trace(&self, target_height: Height) -> Vec { - self.state.get_trace(target_height) - } - /// Look in the light store for a block from the given peer at the given height. /// If one cannot be found, fetch the block from the given peer. /// diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index ff6e573ae..4b5e36c10 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -20,7 +20,7 @@ pub enum Event { #[derive(Default)] pub struct PeerListBuilder { primary: Option, - peers: HashMap, + peers: HashMap, } impl PeerListBuilder { @@ -29,8 +29,8 @@ impl PeerListBuilder { self } - pub fn peer(&mut self, peer_id: PeerId, client: LightClient) -> &mut Self { - self.peers.insert(peer_id, client); + pub fn peer(&mut self, peer_id: PeerId, instance: Instance) -> &mut Self { + self.peers.insert(peer_id, instance); self } @@ -45,9 +45,15 @@ impl PeerListBuilder { } } +#[derive(Debug)] +pub struct Instance { + pub light_client: LightClient, + pub state: State, +} + #[derive(Debug)] pub struct PeerList { - peers: HashMap, + peers: HashMap, primary: PeerId, } @@ -56,23 +62,23 @@ impl PeerList { PeerListBuilder::default() } - pub fn get(&self, peer_id: &PeerId) -> Option<&LightClient> { + pub fn get(&self, peer_id: &PeerId) -> Option<&Instance> { self.peers.get(peer_id) } - pub fn get_mut(&mut self, peer_id: &PeerId) -> Option<&mut LightClient> { + pub fn get_mut(&mut self, peer_id: &PeerId) -> Option<&mut Instance> { self.peers.get_mut(peer_id) } - pub fn primary(&self) -> Option<&LightClient> { + pub fn primary(&self) -> Option<&Instance> { self.peers.get(&self.primary) } - pub fn primary_mut(&mut self) -> Option<&mut LightClient> { + pub fn primary_mut(&mut self) -> Option<&mut Instance> { self.peers.get_mut(&self.primary) } - pub fn secondaries(&self) -> Vec<&LightClient> { + pub fn secondaries(&self) -> Vec<&Instance> { self.peers .keys() .filter(|peer_id| peer_id != &&self.primary) @@ -116,11 +122,21 @@ impl Supervisor { #[pre(self.peers.primary().is_some())] pub fn verify_to_target(&mut self, height: Height) -> VerificationResult { while let Some(primary) = self.peers.primary_mut() { - let verdict = primary.verify_to_target(height); + let verdict = primary + .light_client + .verify_to_target(height, &mut primary.state); match verdict { Ok(light_block) => { - let outcome = self.detect_forks(&light_block); + // SAFETY: There must be a latest trusted state otherwise verification would have failed. + let trusted_state = primary + .state + .light_store + .latest(VerifiedStatus::Verified) + .unwrap(); + + let outcome = self.detect_forks(&light_block, &trusted_state); + match outcome { Some(forks) => { let mut forked = Vec::with_capacity(forks.len()); @@ -166,16 +182,18 @@ impl Supervisor { } #[pre(self.peers.primary().is_some())] - fn detect_forks(&mut self, light_block: &LightBlock) -> Option> { + fn detect_forks( + &mut self, + light_block: &LightBlock, + trusted_state: &LightBlock, + ) -> Option> { if self.peers.secondaries().is_empty() { return None; } - let primary = self.peers.primary().unwrap(); - let secondaries = self.peers.secondaries(); - - let fork_detector = ProdForkDetector::new(); - let result = fork_detector.detect_forks(light_block, primary, secondaries); + let fork_detector = ProdForkDetector::new(); // TODO: Should be injectable + let result = + fork_detector.detect_forks(light_block, &trusted_state, self.peers.secondaries()); match result { ForkDetection::Detected(forks) => Some(forks), @@ -239,7 +257,7 @@ impl Handler { match receiver.recv().unwrap() { Event::VerificationSuccessed(header) => Ok(header), - Event::VerificationFailed(_err) => todo!(), + Event::VerificationFailed(err) => Err(err), _ => todo!(), } } diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index 9f450e239..df1dbc067 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -139,10 +139,11 @@ impl Clock for MockClock { fn verify_bisection( untrusted_height: Height, light_client: &mut LightClient, + state: &mut State, ) -> Result, Error> { light_client - .verify_to_target(untrusted_height) - .map(|_| light_client.get_trace(untrusted_height)) + .verify_to_target(untrusted_height, state) + .map(|_| state.get_trace(untrusted_height)) } fn run_bisection_test(tc: TestBisection) { @@ -182,7 +183,7 @@ fn run_bisection_test(tc: TestBisection) { let mut light_store = MemoryStore::new(); light_store.insert(trusted_state, VerifiedStatus::Verified); - let state = State { + let mut state = State { light_store: Box::new(light_store), verification_trace: HashMap::new(), }; @@ -191,7 +192,6 @@ fn run_bisection_test(tc: TestBisection) { let mut light_client = LightClient::new( primary, - state, options, clock, scheduler::basic_bisecting_schedule, @@ -199,7 +199,7 @@ fn run_bisection_test(tc: TestBisection) { io.clone(), ); - match verify_bisection(untrusted_height, &mut light_client) { + match verify_bisection(untrusted_height, &mut light_client, &mut state) { Ok(new_states) => { let untrusted_light_block = io .fetch_light_block(primary.clone(), untrusted_height) From 0926735eac3b4cb68236f1e16166fadcd935b18f Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 12:14:20 +0200 Subject: [PATCH 14/39] Remove outdated comments --- light-client/src/supervisor.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 4b5e36c10..0586a067d 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -205,8 +205,6 @@ impl Supervisor { Handler::new(self.sender.clone()) } - // Consume the instance here but return a runtime which will allow interaction - // Maybe return an output channnel here? pub fn run(mut self) { loop { let event = self.receiver.recv().unwrap(); @@ -231,9 +229,7 @@ pub struct Handler { sender: channel::Sender, } -// Assume single handler impl Handler { - // How do we connect with the runtime? pub fn new(sender: channel::Sender) -> Self { Self { sender } } From f96f4d597fb13ffc9b5d6a6c9332c8108a01e949 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 12:18:54 +0200 Subject: [PATCH 15/39] Ensure the supervisor can be sent across thread boundaries --- light-client/Cargo.toml | 13 +++++++------ light-client/src/supervisor.rs | 14 +++----------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index b1c766fc2..64ffe434c 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -8,17 +8,18 @@ edition = "2018" tendermint = { path = "../tendermint" } anomaly = { version = "0.2.0", features = ["serializer"] } +contracts = "0.4.0" +crossbeam-channel = "0.4.2" derive_more = "0.99.5" +futures = "0.3.4" +prost-amino = "0.5.0" serde = "1.0.106" +serde_cbor = "0.11.1" serde_derive = "1.0.106" +sled = "0.31.0" +static_assertions = "1.1.0" thiserror = "1.0.15" -futures = "0.3.4" tokio = "0.2.20" -prost-amino = "0.5.0" -contracts = "0.4.0" -sled = "0.31.0" -serde_cbor = "0.11.1" -crossbeam-channel = "0.4.2" [dev-dependencies] serde_json = "1.0.51" diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 0586a067d..747e203d7 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -108,6 +108,9 @@ pub struct Supervisor { receiver: channel::Receiver, } +// Ensure the `Supervisor` can be sent across thread boundaries. +static_assertions::assert_impl_all!(Supervisor: Send); + impl Supervisor { pub fn new(peers: PeerList) -> Self { let (sender, receiver) = channel::unbounded::(); @@ -285,14 +288,3 @@ impl Handler { } } -#[allow(dead_code, unused_variables)] -mod test { - use super::*; - - fn test(mut s: Supervisor) { - let h1 = s.handler(); - let h2 = s.handler(); - - std::thread::spawn(move || s.run()); - } -} From 14c595992c80517586f37e38a846f85ae83b8374 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 12:19:02 +0200 Subject: [PATCH 16/39] Rename Handler to Handle --- light-client/src/supervisor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 747e203d7..7576414e2 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -204,8 +204,8 @@ impl Supervisor { } } - pub fn handler(&mut self) -> Handler { - Handler::new(self.sender.clone()) + pub fn handle(&mut self) -> Handle { + Handle::new(self.sender.clone()) } pub fn run(mut self) { @@ -228,11 +228,11 @@ impl Supervisor { } } -pub struct Handler { +pub struct Handle { sender: channel::Sender, } -impl Handler { +impl Handle { pub fn new(sender: channel::Sender) -> Self { Self { sender } } From f6c1fae39e5ce2b91229be128e58a52bbf1c09b9 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 12:23:58 +0200 Subject: [PATCH 17/39] Update comment --- light-client/src/light_client.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index cebda5c34..4ce04aed9 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -96,15 +96,16 @@ impl LightClient { self.verify_to_target(target_block.height(), state) } - /// Attemps to update the light client to a block of the primary node at the given height. + /// Update the light client to a block of the primary node at the given height. /// /// This is the main function and uses the following components: /// - /// - The I/O component is called to download the next light block. + /// - The I/O component is called to fetch the next light block. /// It is the only component that communicates with other nodes. /// - The Verifier component checks whether a header is valid and checks if a new /// light block should be trusted based on a previously verified light block. - /// - The Scheduler component decides which height to try to verify next. + /// - The Scheduler component decides which height to try to verify next, in case + /// the current block pass verification but cannot be trusted yet. /// /// ## Implements /// - [LCV-DIST-SAFE.1] From 76857496d8be96344b1348d25b177d545715b4c7 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 12:40:57 +0200 Subject: [PATCH 18/39] Update example to invoke supervisor --- light-client/examples/light_client.rs | 31 +++++++++++++------ light-client/src/supervisor.rs | 44 +++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 8f5e3df7e..23826b37c 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -1,5 +1,9 @@ +use tendermint_light_client::components::scheduler; +use tendermint_light_client::light_client; +use tendermint_light_client::prelude::*; +use tendermint_light_client::supervisor::*; + use gumdrop::Options; -use tendermint_light_client::prelude::Height; use std::collections::HashMap; use std::path::PathBuf; @@ -58,15 +62,11 @@ fn main() { } fn sync_cmd(opts: SyncOpts) { - use tendermint_light_client::components::scheduler; - use tendermint_light_client::prelude::*; - let primary_addr = opts.address; let primary: PeerId = "BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap(); let mut peer_map = HashMap::new(); peer_map.insert(primary, primary_addr); - let io = ProdIo::new(peer_map); let db = sled::open(opts.db_path).unwrap_or_else(|e| { @@ -85,12 +85,12 @@ fn sync_cmd(opts: SyncOpts) { light_store.insert(trusted_state, VerifiedStatus::Verified); } - let mut state = State { + let state = State { light_store: Box::new(light_store), verification_trace: HashMap::new(), }; - let options = Options { + let options = light_client::Options { trust_threshold: TrustThreshold { numerator: 1, denominator: 3, @@ -104,17 +104,28 @@ fn sync_cmd(opts: SyncOpts) { let clock = SystemClock; let scheduler = scheduler::basic_bisecting_schedule; - let mut light_client = LightClient::new(primary, options, clock, scheduler, verifier, io); + let light_client = LightClient::new(primary, options, clock, scheduler, verifier, io); + + let instance = Instance::new(light_client, state); + + let peer_list = PeerList::builder() + .primary(primary) + .peer(primary, instance) + .build(); + + let mut supervisor = Supervisor::new(peer_list); + let mut handle = supervisor.handle(); loop { - match light_client.verify_to_highest(&mut state) { + handle.verify_to_highest_async(|result| match result { Ok(light_block) => { println!("[ info ] synced to block {}", light_block.height()); } Err(e) => { println!("[ error ] sync failed: {}", e); } - } + }); + std::thread::sleep(Duration::from_millis(800)); } } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 7576414e2..1154caa0e 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -12,6 +12,7 @@ pub type VerificationResult = Result; pub enum Event { Terminate(Callback<()>), Terminated, + VerifyToHighest(Callback), VerifyToTarget(Height, Callback), VerificationSuccessed(LightBlock), VerificationFailed(Error), @@ -24,12 +25,12 @@ pub struct PeerListBuilder { } impl PeerListBuilder { - pub fn primary(&mut self, primary: PeerId) -> &mut Self { + pub fn primary(mut self, primary: PeerId) -> Self { self.primary = Some(primary); self } - pub fn peer(&mut self, peer_id: PeerId, instance: Instance) -> &mut Self { + pub fn peer(mut self, peer_id: PeerId, instance: Instance) -> Self { self.peers.insert(peer_id, instance); self } @@ -51,6 +52,15 @@ pub struct Instance { pub state: State, } +impl Instance { + pub fn new(light_client: LightClient, state: State) -> Self { + Self { + light_client, + state, + } + } +} + #[derive(Debug)] pub struct PeerList { peers: HashMap, @@ -237,6 +247,28 @@ impl Handle { Self { sender } } + pub fn verify_to_highest(&mut self) -> VerificationResult { + let (sender, receiver) = channel::bounded::(1); + + let callback = Callback::new(move |result| { + // We need to create an event here + let event = match result { + Ok(header) => Event::VerificationSuccessed(header), + Err(err) => Event::VerificationFailed(err), + }; + + sender.send(event).unwrap(); + }); + + self.sender.send(Event::VerifyToHighest(callback)).unwrap(); + + match receiver.recv().unwrap() { + Event::VerificationSuccessed(header) => Ok(header), + Event::VerificationFailed(err) => Err(err), + _ => todo!(), + } + } + pub fn verify_to_target(&mut self, height: Height) -> VerificationResult { let (sender, receiver) = channel::bounded::(1); @@ -261,6 +293,14 @@ impl Handle { } } + pub fn verify_to_highest_async( + &mut self, + callback: impl FnOnce(VerificationResult) -> () + Send + 'static, + ) { + let event = Event::VerifyToHighest(Callback::new(callback)); + self.sender.send(event).unwrap(); + } + pub fn verify_to_target_async( &mut self, height: Height, From b86006067cc06d02fc114879aea310ffc04c6e7e Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 12:41:48 +0200 Subject: [PATCH 19/39] Formatting --- light-client/src/supervisor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 1154caa0e..ec4968564 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -327,4 +327,3 @@ impl Handle { } } } - From 8b77006c78e6f9652aa27f4bd895c42307d680a7 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 13:09:49 +0200 Subject: [PATCH 20/39] Working supervisor example --- light-client/examples/light_client.rs | 7 ++++ light-client/lightstore/conf | 4 +++ light-client/lightstore/db | Bin 0 -> 208142 bytes light-client/lightstore/snap.000000000001DC32 | Bin 0 -> 176 bytes light-client/src/supervisor.rs | 30 +++++++++++++++--- 5 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 light-client/lightstore/conf create mode 100644 light-client/lightstore/db create mode 100644 light-client/lightstore/snap.000000000001DC32 diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 23826b37c..6b15bb7a6 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -83,6 +83,11 @@ fn sync_cmd(opts: SyncOpts) { }); light_store.insert(trusted_state, VerifiedStatus::Verified); + } else { + if light_store.latest(VerifiedStatus::Verified).is_none() { + println!("[ error ] no trusted state in database, please specify a trusted header"); + std::process::exit(1); + } } let state = State { @@ -116,6 +121,8 @@ fn sync_cmd(opts: SyncOpts) { let mut supervisor = Supervisor::new(peer_list); let mut handle = supervisor.handle(); + std::thread::spawn(|| supervisor.run()); + loop { handle.verify_to_highest_async(|result| match result { Ok(light_block) => { diff --git a/light-client/lightstore/conf b/light-client/lightstore/conf new file mode 100644 index 000000000..0482a9188 --- /dev/null +++ b/light-client/lightstore/conf @@ -0,0 +1,4 @@ +segment_size: 8388608 +use_compression: false +version: 0.31 +¶ª \ No newline at end of file diff --git a/light-client/lightstore/db b/light-client/lightstore/db new file mode 100644 index 0000000000000000000000000000000000000000..c554a6b94a9be40a72251b565d232c905ec9e75e GIT binary patch literal 208142 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 literal 0 HcmV?d00001 diff --git a/light-client/lightstore/snap.000000000001DC32 b/light-client/lightstore/snap.000000000001DC32 new file mode 100644 index 0000000000000000000000000000000000000000..46257ba1c3c2b290ff8dab8cd816bca7a7dd7373 GIT binary patch literal 176 zcmXrI!^i*vzl@*^5QotfM1p{;BojMVF+>T&FCgn@(AwqaV0<7;{urDGWPMu_brmKF zWPMxq;Wms5WGM^7?Dz#_$-`{~+5K$^Bclw&P6h@UPG&|E7{^3#36%MTFLxdQN-ZWn literal 0 HcmV?d00001 diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index ec4968564..b67bd9b05 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -103,8 +103,10 @@ impl PeerList { pub fn swap_primary(&mut self) -> Result<(), Error> { if let Some(peer_id) = self.peers.keys().next() { - self.primary = *peer_id; - return Ok(()); + if peer_id != &self.primary { + self.primary = *peer_id; + return Ok(()); + } } bail!(ErrorKind::NoValidPeerLeft) @@ -132,12 +134,25 @@ impl Supervisor { } } + #[pre(self.peers.primary().is_some())] + pub fn verify_to_highest(&mut self) -> VerificationResult { + self.verify(None) + } + #[pre(self.peers.primary().is_some())] pub fn verify_to_target(&mut self, height: Height) -> VerificationResult { + self.verify(Some(height)) + } + + #[pre(self.peers.primary().is_some())] + fn verify(&mut self, height: Option) -> VerificationResult { while let Some(primary) = self.peers.primary_mut() { - let verdict = primary - .light_client - .verify_to_target(height, &mut primary.state); + let verdict = match height { + None => primary.light_client.verify_to_highest(&mut primary.state), + Some(height) => primary + .light_client + .verify_to_target(height, &mut primary.state), + }; match verdict { Ok(light_block) => { @@ -221,6 +236,7 @@ impl Supervisor { pub fn run(mut self) { loop { let event = self.receiver.recv().unwrap(); + match event { Event::Terminate(callback) => { callback.call(()); @@ -230,6 +246,10 @@ impl Supervisor { let outcome = self.verify_to_target(height); callback.call(outcome); } + Event::VerifyToHighest(callback) => { + let outcome = self.verify_to_highest(); + callback.call(outcome); + } _ => { // NoOp? } From e8b0b66d4ac1388507cce9881ad715db25fbc8fc Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 13:12:23 +0200 Subject: [PATCH 21/39] Remove light store committed by error --- light-client/lightstore/conf | 4 ---- light-client/lightstore/db | Bin 208142 -> 0 bytes light-client/lightstore/snap.000000000001DC32 | Bin 176 -> 0 bytes 3 files changed, 4 deletions(-) delete mode 100644 light-client/lightstore/conf delete mode 100644 light-client/lightstore/db delete mode 100644 light-client/lightstore/snap.000000000001DC32 diff --git a/light-client/lightstore/conf b/light-client/lightstore/conf deleted file mode 100644 index 0482a9188..000000000 --- a/light-client/lightstore/conf +++ /dev/null @@ -1,4 +0,0 @@ -segment_size: 8388608 -use_compression: false -version: 0.31 -¶ª \ No newline at end of file diff --git a/light-client/lightstore/db b/light-client/lightstore/db deleted file mode 100644 index c554a6b94a9be40a72251b565d232c905ec9e75e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208142 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 diff --git a/light-client/lightstore/snap.000000000001DC32 b/light-client/lightstore/snap.000000000001DC32 deleted file mode 100644 index 46257ba1c3c2b290ff8dab8cd816bca7a7dd7373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmXrI!^i*vzl@*^5QotfM1p{;BojMVF+>T&FCgn@(AwqaV0<7;{urDGWPMu_brmKF zWPMxq;Wms5WGM^7?Dz#_$-`{~+5K$^Bclw&P6h@UPG&|E7{^3#36%MTFLxdQN-ZWn From ae571d7c37279a11f0ab142bc30cd318b0e1b719 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 16:51:28 +0200 Subject: [PATCH 22/39] Compare headers hashes instead of for equality in fork detector --- light-client/src/fork_detector.rs | 21 +++++++++++++++------ light-client/src/supervisor.rs | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index a370a18f3..f344cf3f0 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -24,17 +24,21 @@ pub trait ForkDetector { ) -> ForkDetection; } -pub struct ProdForkDetector {} +pub struct ProdForkDetector { + header_hasher: Box, +} impl ProdForkDetector { - pub fn new() -> Self { - Self {} + pub fn new(header_hasher: impl HeaderHasher + 'static) -> Self { + Self { + header_hasher: Box::new(header_hasher), + } } } impl Default for ProdForkDetector { fn default() -> Self { - Self::new() + Self::new(ProdHeaderHasher) } } @@ -55,8 +59,13 @@ impl ForkDetector for ProdForkDetector { .get_or_fetch_block(light_block.height(), &mut state) .unwrap(); // FIXME: unwrap - // TODO: Should hash the headers here instead of comparing them for equality? - if light_block.signed_header.header == secondary_block.signed_header.header { + let primary_hash = self.header_hasher.hash(&light_block.signed_header.header); + let secondary_hash = self + .header_hasher + .hash(&secondary_block.signed_header.header); + + if primary_hash == secondary_hash { + // Hashes match, continue with next secondary, if any. continue; } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index b67bd9b05..e2fa40083 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -219,7 +219,7 @@ impl Supervisor { return None; } - let fork_detector = ProdForkDetector::new(); // TODO: Should be injectable + let fork_detector = ProdForkDetector::default(); // TODO: Should be injectable let result = fork_detector.detect_forks(light_block, &trusted_state, self.peers.secondaries()); From 431b950954b5c963ee9c3b5297298c7905379b39 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 16:53:53 +0200 Subject: [PATCH 23/39] Bubble up I/O errors that may occur during fork detection --- light-client/src/fork_detector.rs | 8 ++++---- light-client/src/supervisor.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index f344cf3f0..863143cc1 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -21,7 +21,7 @@ pub trait ForkDetector { light_block: &LightBlock, trusted_state: &LightBlock, secondaries: Vec<&Instance>, - ) -> ForkDetection; + ) -> Result; } pub struct ProdForkDetector { @@ -48,7 +48,7 @@ impl ForkDetector for ProdForkDetector { light_block: &LightBlock, trusted_state: &LightBlock, secondaries: Vec<&Instance>, - ) -> ForkDetection { + ) -> Result { let mut forks = Vec::with_capacity(secondaries.len()); for secondary in secondaries { @@ -89,9 +89,9 @@ impl ForkDetector for ProdForkDetector { } if forks.is_empty() { - ForkDetection::NotDetected + Ok(ForkDetection::NotDetected) } else { - ForkDetection::Detected(forks) + Ok(ForkDetection::Detected(forks)) } } } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index e2fa40083..de6798bc0 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -163,7 +163,7 @@ impl Supervisor { .latest(VerifiedStatus::Verified) .unwrap(); - let outcome = self.detect_forks(&light_block, &trusted_state); + let outcome = self.detect_forks(&light_block, &trusted_state)?; match outcome { Some(forks) => { @@ -214,18 +214,18 @@ impl Supervisor { &mut self, light_block: &LightBlock, trusted_state: &LightBlock, - ) -> Option> { + ) -> Result>, Error> { if self.peers.secondaries().is_empty() { - return None; + return Ok(None); } let fork_detector = ProdForkDetector::default(); // TODO: Should be injectable let result = - fork_detector.detect_forks(light_block, &trusted_state, self.peers.secondaries()); + fork_detector.detect_forks(light_block, &trusted_state, self.peers.secondaries())?; match result { - ForkDetection::Detected(forks) => Some(forks), - ForkDetection::NotDetected => None, + ForkDetection::Detected(forks) => Ok(Some(forks)), + ForkDetection::NotDetected => Ok(None), } } From 81a61d4af92fe65316e3cb277f0b507ca06db229 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 16:59:12 +0200 Subject: [PATCH 24/39] Inject fork detector into supervisor --- light-client/examples/light_client.rs | 3 ++- light-client/src/fork_detector.rs | 2 +- light-client/src/supervisor.rs | 25 ++++++++++++++++++------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 6b15bb7a6..aa7660a2c 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -1,4 +1,5 @@ use tendermint_light_client::components::scheduler; +use tendermint_light_client::fork_detector::ProdForkDetector; use tendermint_light_client::light_client; use tendermint_light_client::prelude::*; use tendermint_light_client::supervisor::*; @@ -118,7 +119,7 @@ fn sync_cmd(opts: SyncOpts) { .peer(primary, instance) .build(); - let mut supervisor = Supervisor::new(peer_list); + let mut supervisor = Supervisor::new(peer_list, ProdForkDetector::default()); let mut handle = supervisor.handle(); std::thread::spawn(|| supervisor.run()); diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 863143cc1..10ce22841 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -15,7 +15,7 @@ pub enum Fork { Faulty(LightBlock), } -pub trait ForkDetector { +pub trait ForkDetector: Send { fn detect_forks( &self, light_block: &LightBlock, diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index de6798bc0..784abbe91 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -1,5 +1,5 @@ use crate::callback::Callback; -use crate::fork_detector::{Fork, ForkDetection, ForkDetector, ProdForkDetector}; +use crate::fork_detector::{Fork, ForkDetection, ForkDetector}; use crate::prelude::*; use contracts::pre; @@ -113,24 +113,33 @@ impl PeerList { } } -#[derive(Debug)] pub struct Supervisor { peers: PeerList, + fork_detector: Box, sender: channel::Sender, receiver: channel::Receiver, } +impl std::fmt::Debug for Supervisor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Supervisor") + .field("peers", &self.peers) + .finish() + } +} + // Ensure the `Supervisor` can be sent across thread boundaries. static_assertions::assert_impl_all!(Supervisor: Send); impl Supervisor { - pub fn new(peers: PeerList) -> Self { + pub fn new(peers: PeerList, fork_detector: impl ForkDetector + 'static) -> Self { let (sender, receiver) = channel::unbounded::(); Self { + peers, sender, receiver, - peers, + fork_detector: Box::new(fork_detector), } } @@ -219,9 +228,11 @@ impl Supervisor { return Ok(None); } - let fork_detector = ProdForkDetector::default(); // TODO: Should be injectable - let result = - fork_detector.detect_forks(light_block, &trusted_state, self.peers.secondaries())?; + let result = self.fork_detector.detect_forks( + light_block, + &trusted_state, + self.peers.secondaries(), + )?; match result { ForkDetection::Detected(forks) => Ok(Some(forks)), From 4dbcde0085025cb21d5aa44be093ed3b4a16a838 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 17:06:41 +0200 Subject: [PATCH 25/39] Deduplicate Handle::verify_to_highest/verify_to_target --- light-client/src/supervisor.rs | 37 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 784abbe91..1bda8338d 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -204,8 +204,9 @@ impl Supervisor { } // Verification failed Err(_err) => { - // Swap primary, and continue with new primary, if any + // Swap primary, and continue with new primary, if any. self.peers.swap_primary()?; + // TODO: Log/record error continue; } } @@ -262,7 +263,7 @@ impl Supervisor { callback.call(outcome); } _ => { - // NoOp? + // TODO: Log/record unexpected event } } } @@ -279,28 +280,17 @@ impl Handle { } pub fn verify_to_highest(&mut self) -> VerificationResult { - let (sender, receiver) = channel::bounded::(1); - - let callback = Callback::new(move |result| { - // We need to create an event here - let event = match result { - Ok(header) => Event::VerificationSuccessed(header), - Err(err) => Event::VerificationFailed(err), - }; - - sender.send(event).unwrap(); - }); - - self.sender.send(Event::VerifyToHighest(callback)).unwrap(); - - match receiver.recv().unwrap() { - Event::VerificationSuccessed(header) => Ok(header), - Event::VerificationFailed(err) => Err(err), - _ => todo!(), - } + self.verify(Event::VerifyToHighest) } pub fn verify_to_target(&mut self, height: Height) -> VerificationResult { + self.verify(|callback| Event::VerifyToTarget(height, callback)) + } + + fn verify( + &mut self, + make_event: impl FnOnce(Callback) -> Event, + ) -> VerificationResult { let (sender, receiver) = channel::bounded::(1); let callback = Callback::new(move |result| { @@ -313,9 +303,8 @@ impl Handle { sender.send(event).unwrap(); }); - self.sender - .send(Event::VerifyToTarget(height, callback)) - .unwrap(); + let event = make_event(callback); + self.sender.send(event).unwrap(); match receiver.recv().unwrap() { Event::VerificationSuccessed(header) => Ok(header), From b2a37565cae3362d5b9a19c52675196a10a7d9f3 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 17:15:08 +0200 Subject: [PATCH 26/39] Move PeerList and its builder into their own module --- light-client/examples/light_client.rs | 1 + light-client/src/lib.rs | 1 + light-client/src/peer_list.rs | 85 ++++++++++++++++++++++++++ light-client/src/supervisor.rs | 87 ++------------------------- 4 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 light-client/src/peer_list.rs diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index aa7660a2c..65a2f5c0a 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -1,6 +1,7 @@ use tendermint_light_client::components::scheduler; use tendermint_light_client::fork_detector::ProdForkDetector; use tendermint_light_client::light_client; +use tendermint_light_client::peer_list::PeerList; use tendermint_light_client::prelude::*; use tendermint_light_client::supervisor::*; diff --git a/light-client/src/lib.rs b/light-client/src/lib.rs index 47796d800..90b7863cc 100644 --- a/light-client/src/lib.rs +++ b/light-client/src/lib.rs @@ -19,6 +19,7 @@ pub mod errors; pub mod fork_detector; pub mod light_client; pub mod operations; +pub mod peer_list; pub mod predicates; pub mod prelude; pub mod state; diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs new file mode 100644 index 000000000..49f26db23 --- /dev/null +++ b/light-client/src/peer_list.rs @@ -0,0 +1,85 @@ +use crate::prelude::*; +use crate::supervisor::Instance; + +use contracts::pre; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct PeerList { + peers: HashMap, + primary: PeerId, +} + +impl PeerList { + pub fn builder() -> PeerListBuilder { + PeerListBuilder::default() + } + + pub fn get(&self, peer_id: &PeerId) -> Option<&Instance> { + self.peers.get(peer_id) + } + + pub fn get_mut(&mut self, peer_id: &PeerId) -> Option<&mut Instance> { + self.peers.get_mut(peer_id) + } + + pub fn primary(&self) -> Option<&Instance> { + self.peers.get(&self.primary) + } + + pub fn primary_mut(&mut self) -> Option<&mut Instance> { + self.peers.get_mut(&self.primary) + } + + pub fn secondaries(&self) -> Vec<&Instance> { + self.peers + .keys() + .filter(|peer_id| peer_id != &&self.primary) + .filter_map(|peer_id| self.get(peer_id)) + .collect() + } + + #[pre(peer_id != &self.primary)] + pub fn remove_secondary(&mut self, peer_id: &PeerId) { + self.peers.remove(peer_id); + } + + pub fn swap_primary(&mut self) -> Result<(), Error> { + if let Some(peer_id) = self.peers.keys().next() { + if peer_id != &self.primary { + self.primary = *peer_id; + return Ok(()); + } + } + + bail!(ErrorKind::NoValidPeerLeft) + } +} + +#[derive(Default)] +pub struct PeerListBuilder { + primary: Option, + peers: HashMap, +} + +impl PeerListBuilder { + pub fn primary(mut self, primary: PeerId) -> Self { + self.primary = Some(primary); + self + } + + pub fn peer(mut self, peer_id: PeerId, instance: Instance) -> Self { + self.peers.insert(peer_id, instance); + self + } + + #[pre( + self.primary.is_some() && self.peers.contains_key(self.primary.as_ref().unwrap()) + )] + pub fn build(self) -> PeerList { + PeerList { + primary: self.primary.unwrap(), + peers: self.peers, + } + } +} diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 1bda8338d..aebd79e93 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -1,51 +1,26 @@ use crate::callback::Callback; use crate::fork_detector::{Fork, ForkDetection, ForkDetector}; +use crate::peer_list::PeerList; use crate::prelude::*; use contracts::pre; use crossbeam_channel as channel; -use std::collections::HashMap; pub type VerificationResult = Result; #[derive(Debug)] pub enum Event { + // Inputs Terminate(Callback<()>), - Terminated, VerifyToHighest(Callback), VerifyToTarget(Height, Callback), + + // Outputs + Terminated, VerificationSuccessed(LightBlock), VerificationFailed(Error), } -#[derive(Default)] -pub struct PeerListBuilder { - primary: Option, - peers: HashMap, -} - -impl PeerListBuilder { - pub fn primary(mut self, primary: PeerId) -> Self { - self.primary = Some(primary); - self - } - - pub fn peer(mut self, peer_id: PeerId, instance: Instance) -> Self { - self.peers.insert(peer_id, instance); - self - } - - #[pre( - self.primary.is_some() && self.peers.contains_key(self.primary.as_ref().unwrap()) - )] - pub fn build(self) -> PeerList { - PeerList { - primary: self.primary.unwrap(), - peers: self.peers, - } - } -} - #[derive(Debug)] pub struct Instance { pub light_client: LightClient, @@ -61,58 +36,6 @@ impl Instance { } } -#[derive(Debug)] -pub struct PeerList { - peers: HashMap, - primary: PeerId, -} - -impl PeerList { - pub fn builder() -> PeerListBuilder { - PeerListBuilder::default() - } - - pub fn get(&self, peer_id: &PeerId) -> Option<&Instance> { - self.peers.get(peer_id) - } - - pub fn get_mut(&mut self, peer_id: &PeerId) -> Option<&mut Instance> { - self.peers.get_mut(peer_id) - } - - pub fn primary(&self) -> Option<&Instance> { - self.peers.get(&self.primary) - } - - pub fn primary_mut(&mut self) -> Option<&mut Instance> { - self.peers.get_mut(&self.primary) - } - - pub fn secondaries(&self) -> Vec<&Instance> { - self.peers - .keys() - .filter(|peer_id| peer_id != &&self.primary) - .filter_map(|peer_id| self.get(peer_id)) - .collect() - } - - #[pre(peer_id != &self.primary)] - pub fn remove_secondary(&mut self, peer_id: &PeerId) { - self.peers.remove(peer_id); - } - - pub fn swap_primary(&mut self) -> Result<(), Error> { - if let Some(peer_id) = self.peers.keys().next() { - if peer_id != &self.primary { - self.primary = *peer_id; - return Ok(()); - } - } - - bail!(ErrorKind::NoValidPeerLeft) - } -} - pub struct Supervisor { peers: PeerList, fork_detector: Box, From 2df87dc7853a661e73d49e3f72c8b6b692d0b760 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 10 Jun 2020 17:34:24 +0200 Subject: [PATCH 27/39] Record error which caused peer to be deemed faulty --- light-client/src/fork_detector.rs | 4 ++-- light-client/src/supervisor.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 10ce22841..de9fa5848 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -12,7 +12,7 @@ pub enum ForkDetection { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Fork { Forked(LightBlock), - Faulty(LightBlock), + Faulty(LightBlock, ErrorKind), } pub trait ForkDetector: Send { @@ -84,7 +84,7 @@ impl ForkDetector for ProdForkDetector { match result { Ok(_) => forks.push(Fork::Forked(secondary_block)), Err(e) if e.kind().has_expired() => forks.push(Fork::Forked(secondary_block)), - Err(_) => forks.push(Fork::Faulty(secondary_block)), + Err(e) => forks.push(Fork::Faulty(secondary_block, e.kind().clone())), } } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index aebd79e93..1fda88de2 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -107,8 +107,9 @@ impl Supervisor { self.report_evidence(&block); forked.push(block.provider); } - Fork::Faulty(block) => { + Fork::Faulty(block, _error) => { self.peers.remove_secondary(&block.provider); + // TODO: Log/record the error } } } From 0b7b0661c64914e57e5ebc8519aa35903e8e5c86 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 11 Jun 2020 14:27:08 +0200 Subject: [PATCH 28/39] Remove prelude module in favor of explicit imports --- light-client/examples/light_client.rs | 23 +++++++++++++------ light-client/src/components/clock.rs | 2 +- light-client/src/components/io.rs | 2 +- light-client/src/components/scheduler.rs | 5 +++- light-client/src/components/verifier.rs | 13 +++++++++-- light-client/src/contracts.rs | 7 +++++- light-client/src/errors.rs | 9 ++++++-- light-client/src/fork_detector.rs | 10 ++++++-- light-client/src/lib.rs | 1 - light-client/src/light_client.rs | 12 +++++++--- .../src/operations/commit_validator.rs | 6 ++++- light-client/src/operations/header_hasher.rs | 2 +- light-client/src/operations/voting_power.rs | 6 ++++- light-client/src/peer_list.rs | 8 +++++-- light-client/src/predicates.rs | 13 ++++++++--- light-client/src/predicates/errors.rs | 2 +- light-client/src/prelude.rs | 19 --------------- light-client/src/state.rs | 5 +++- light-client/src/store.rs | 2 +- light-client/src/store/memory.rs | 5 +++- light-client/src/store/sled.rs | 7 ++++-- light-client/src/supervisor.rs | 9 +++++++- light-client/src/tests.rs | 2 +- light-client/tests/light_client.rs | 22 ++++++++++++++---- 24 files changed, 132 insertions(+), 60 deletions(-) delete mode 100644 light-client/src/prelude.rs diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 65a2f5c0a..09d99564e 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -1,14 +1,23 @@ -use tendermint_light_client::components::scheduler; -use tendermint_light_client::fork_detector::ProdForkDetector; -use tendermint_light_client::light_client; -use tendermint_light_client::peer_list::PeerList; -use tendermint_light_client::prelude::*; -use tendermint_light_client::supervisor::*; +use tendermint_light_client::{ + components::{ + clock::SystemClock, + io::{Io, ProdIo}, + scheduler, + verifier::ProdVerifier, + }, + fork_detector::ProdForkDetector, + light_client::{self, LightClient}, + peer_list::PeerList, + state::State, + store::{sled::SledStore, LightStore, VerifiedStatus}, + supervisor::{Instance, Supervisor}, + types::{Height, PeerId, Time, TrustThreshold}, +}; use gumdrop::Options; use std::collections::HashMap; -use std::path::PathBuf; +use std::{path::PathBuf, time::Duration}; #[derive(Debug, Options)] struct CliOptions { diff --git a/light-client/src/components/clock.rs b/light-client/src/components/clock.rs index 426e79cc1..4edc99da3 100644 --- a/light-client/src/components/clock.rs +++ b/light-client/src/components/clock.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::types::Time; /// Abstracts over the current time. pub trait Clock: Send { diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index c5c1a24f6..1a2c9f4ff 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -8,7 +8,7 @@ use thiserror::Error; use tendermint::block::signed_header::SignedHeader as TMSignedHeader; use tendermint::validator::Set as TMValidatorSet; -use crate::prelude::*; +use crate::types::{Height, LightBlock, PeerId}; pub const LATEST_HEIGHT: Height = 0; diff --git a/light-client/src/components/scheduler.rs b/light-client/src/components/scheduler.rs index bdd306e9c..abb928079 100644 --- a/light-client/src/components/scheduler.rs +++ b/light-client/src/components/scheduler.rs @@ -1,4 +1,7 @@ -use crate::prelude::*; +use crate::{ + store::{LightStore, VerifiedStatus}, + types::Height, +}; use contracts::*; diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index f3bc16219..07bf2f316 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -1,5 +1,14 @@ use crate::predicates as preds; -use crate::prelude::*; +use crate::{ + errors::ErrorExt, + light_client::Options, + operations::{ + CommitValidator, HeaderHasher, ProdCommitValidator, ProdHeaderHasher, + ProdVotingPowerCalculator, VotingPowerCalculator, + }, + types::LightBlock, +}; +use preds::{errors::VerificationError, ProdPredicates, VerificationPredicates}; /// Represents the result of the verification performed by the /// verifier component. @@ -82,7 +91,7 @@ impl Default for ProdVerifier { } impl Verifier for ProdVerifier { - fn verify(&self, untrusted: &LightBlock, trusted: &TrustedState, options: &Options) -> Verdict { + fn verify(&self, untrusted: &LightBlock, trusted: &LightBlock, options: &Options) -> Verdict { preds::verify( &*self.predicates, &*self.voting_power_calculator, diff --git a/light-client/src/contracts.rs b/light-client/src/contracts.rs index eb9a9f476..8af703976 100644 --- a/light-client/src/contracts.rs +++ b/light-client/src/contracts.rs @@ -1,6 +1,11 @@ //! Predicates used in components contracts. -use crate::prelude::*; +use crate::{ + store::{LightStore, VerifiedStatus}, + types::{Height, LightBlock, Time}, +}; + +use std::time::Duration; pub fn trusted_store_contains_block_at_target_height( light_store: &dyn LightStore, diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 50b6d51b6..8f58cb45a 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -4,7 +4,12 @@ use anomaly::{BoxError, Context}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::prelude::*; +use crate::{ + components::io::IoError, + light_client::Options, + predicates::errors::VerificationError, + types::{Height, LightBlock, PeerId}, +}; pub type Error = anomaly::Error; @@ -27,7 +32,7 @@ pub enum ErrorKind { #[error("latest trusted state outside of trusting period")] TrustedStateOutsideTrustingPeriod { - trusted_state: Box, + trusted_state: Box, options: Options, }, diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index de9fa5848..1bfe95fa7 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -1,7 +1,13 @@ use serde::{Deserialize, Serialize}; -use crate::prelude::*; -use crate::supervisor::Instance; +use crate::{ + errors::{Error, ErrorExt, ErrorKind}, + operations::{HeaderHasher, ProdHeaderHasher}, + state::State, + store::{memory::MemoryStore, VerifiedStatus}, + supervisor::Instance, + types::LightBlock, +}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetection { diff --git a/light-client/src/lib.rs b/light-client/src/lib.rs index 90b7863cc..be1fa46dc 100644 --- a/light-client/src/lib.rs +++ b/light-client/src/lib.rs @@ -21,7 +21,6 @@ pub mod light_client; pub mod operations; pub mod peer_list; pub mod predicates; -pub mod prelude; pub mod state; pub mod store; pub mod supervisor; diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 4ce04aed9..c010bf52a 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -5,11 +5,17 @@ use contracts::*; use derive_more::Display; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, time::Duration}; -use crate::components::{io::*, scheduler::*, verifier::*}; +use crate::components::{clock::Clock, io::*, scheduler::*, verifier::*}; use crate::contracts::*; -use crate::prelude::*; +use crate::{ + bail, + errors::{Error, ErrorKind}, + state::State, + store::VerifiedStatus, + types::{Height, LightBlock, PeerId, Time, TrustThreshold}, +}; /// Verification parameters /// diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index f871170ee..653f0d495 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -1,4 +1,8 @@ -use crate::prelude::*; +use crate::{ + bail, + predicates::errors::VerificationError, + types::{SignedHeader, ValidatorSet}, +}; use anomaly::BoxError; use tendermint::lite::types::Commit as _; diff --git a/light-client/src/operations/header_hasher.rs b/light-client/src/operations/header_hasher.rs index 150237e64..8060d0788 100644 --- a/light-client/src/operations/header_hasher.rs +++ b/light-client/src/operations/header_hasher.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::types::Header; use tendermint::amino_types::{message::AminoMessage, BlockId, ConsensusVersion, TimeMsg}; use tendermint::merkle::simple_hash_from_byte_vectors; diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index 6d1e3573b..e291cb1c6 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -1,4 +1,8 @@ -use crate::prelude::*; +use crate::{ + bail, + predicates::errors::VerificationError, + types::{SignedHeader, ValidatorSet}, +}; use anomaly::BoxError; use tendermint::lite::types::ValidatorSet as _; diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index 49f26db23..ce9081ec7 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -1,5 +1,9 @@ -use crate::prelude::*; -use crate::supervisor::Instance; +use crate::{ + bail, + errors::{Error, ErrorKind}, + supervisor::Instance, + types::PeerId, +}; use contracts::pre; use std::collections::HashMap; diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index deadd4839..949769c0c 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -1,7 +1,14 @@ //! Predicates for light block validation and verification. -use crate::prelude::*; - +use crate::{ + ensure, + light_client::Options, + operations::{CommitValidator, HeaderHasher, VotingPowerCalculator}, + types::{Header, Height, LightBlock, SignedHeader, Time, TrustThreshold, ValidatorSet}, +}; + +use errors::VerificationError; +use std::time::Duration; use tendermint::lite::ValidatorSet as _; pub mod errors; @@ -212,7 +219,7 @@ pub trait VerificationPredicates: Send { fn valid_next_validator_set( &self, light_block: &LightBlock, - trusted_state: &TrustedState, + trusted_state: &LightBlock, ) -> Result<(), VerificationError> { ensure!( light_block.signed_header.header.validators_hash diff --git a/light-client/src/predicates/errors.rs b/light-client/src/predicates/errors.rs index e3446e4b3..64272d531 100644 --- a/light-client/src/predicates/errors.rs +++ b/light-client/src/predicates/errors.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::errors::ErrorExt; -use crate::prelude::*; +use crate::types::{Hash, Height, Time, TrustThreshold}; /// The various errors which can be raised by the verifier component, /// when validating or verifying a light block. diff --git a/light-client/src/prelude.rs b/light-client/src/prelude.rs deleted file mode 100644 index a5e0bb0bf..000000000 --- a/light-client/src/prelude.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Re-exports all the types which are commonly used within the light client codebase. - -pub use std::time::{Duration, SystemTime}; - -pub use crate::{bail, ensure}; -pub use crate::{ - components::{clock::*, io::*, scheduler::*, verifier::*}, - errors::*, - light_client::*, - operations::*, - predicates::{errors::*, ProdPredicates, VerificationPredicates}, - state::*, - store::{memory::*, sled::*, LightStore, VerifiedStatus}, - types::*, -}; - -pub fn todo() -> A { - unreachable!() -} diff --git a/light-client/src/state.rs b/light-client/src/state.rs index 2a0c902ad..f890b984a 100644 --- a/light-client/src/state.rs +++ b/light-client/src/state.rs @@ -1,6 +1,9 @@ //! State maintained by the light client. -use crate::prelude::*; +use crate::{ + store::{LightStore, VerifiedStatus}, + types::{Height, LightBlock}, +}; use contracts::*; use std::collections::{HashMap, HashSet}; diff --git a/light-client/src/store.rs b/light-client/src/store.rs index 35c30e0ad..8e9a23cb4 100644 --- a/light-client/src/store.rs +++ b/light-client/src/store.rs @@ -4,7 +4,7 @@ //! - a transient, in-memory implementation for testing purposes //! - a persistent, on-disk, sled-backed implementation for production -use crate::prelude::*; +use crate::types::{Height, LightBlock}; use serde::{Deserialize, Serialize}; diff --git a/light-client/src/store/memory.rs b/light-client/src/store/memory.rs index 488ab511d..cac874c9e 100644 --- a/light-client/src/store/memory.rs +++ b/light-client/src/store/memory.rs @@ -1,4 +1,7 @@ -use crate::prelude::*; +use crate::{ + store::{LightStore, VerifiedStatus}, + types::{Height, LightBlock}, +}; use std::collections::btree_map::Entry::*; use std::collections::BTreeMap; diff --git a/light-client/src/store/sled.rs b/light-client/src/store/sled.rs index 5d5f0f0b8..479ccdfd9 100644 --- a/light-client/src/store/sled.rs +++ b/light-client/src/store/sled.rs @@ -1,8 +1,11 @@ pub mod utils; -use self::utils::*; -use crate::prelude::*; +use crate::{ + store::sled::utils::*, + types::{Height, LightBlock}, +}; +use super::{LightStore, VerifiedStatus}; use ::sled::Db as SledDb; const VERIFIED_PREFIX: &str = "light_store/verified"; diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 1fda88de2..dde0fffe9 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -1,7 +1,14 @@ use crate::callback::Callback; use crate::fork_detector::{Fork, ForkDetection, ForkDetector}; use crate::peer_list::PeerList; -use crate::prelude::*; +use crate::{ + bail, + errors::{Error, ErrorKind}, + light_client::LightClient, + state::State, + store::VerifiedStatus, + types::{Height, LightBlock}, +}; use contracts::pre; use crossbeam_channel as channel; diff --git a/light-client/src/tests.rs b/light-client/src/tests.rs index 7dd335c23..68ceb0dd5 100644 --- a/light-client/src/tests.rs +++ b/light-client/src/tests.rs @@ -1,6 +1,6 @@ //! Utilities and datatypes for use in tests. -use crate::prelude::*; +use crate::types::{Hash, LightBlock, PeerId, SignedHeader, Time, TrustThreshold, ValidatorSet}; use serde::Deserialize; diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index df1dbc067..bddf178c0 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -1,11 +1,25 @@ -use tendermint_light_client::components::scheduler; -use tendermint_light_client::prelude::*; -use tendermint_light_client::tests::{Trusted, *}; +use tendermint_light_client::{ + components::{ + clock::Clock, + io::{Io, IoError}, + scheduler, + verifier::{ProdVerifier, Verdict, Verifier}, + }, + errors::Error, + light_client::{LightClient, Options}, + state::State, + store::{memory::MemoryStore, LightStore, VerifiedStatus}, + tests::{Trusted, *}, + types::{Height, LightBlock, PeerId, Time, TrustThreshold}, +}; use std::collections::HashMap; use std::convert::TryInto; use std::fs; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + time::{Duration, SystemTime}, +}; use contracts::contract_trait; use tendermint::rpc; From e044ab58b2836e11845a79395678d7ed1dec7a26 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 11 Jun 2020 14:34:47 +0200 Subject: [PATCH 29/39] Hoist primary hash computation outside of for loop --- light-client/src/fork_detector.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 1bfe95fa7..3b79829a8 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -55,6 +55,8 @@ impl ForkDetector for ProdForkDetector { trusted_state: &LightBlock, secondaries: Vec<&Instance>, ) -> Result { + let primary_hash = self.header_hasher.hash(&light_block.signed_header.header); + let mut forks = Vec::with_capacity(secondaries.len()); for secondary in secondaries { @@ -65,7 +67,6 @@ impl ForkDetector for ProdForkDetector { .get_or_fetch_block(light_block.height(), &mut state) .unwrap(); // FIXME: unwrap - let primary_hash = self.header_hasher.hash(&light_block.signed_header.header); let secondary_hash = self .header_hasher .hash(&secondary_block.signed_header.header); From 7e9279c44a3cc1a2af8c332a8b7766c2b2cfbb63 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 11 Jun 2020 14:39:36 +0200 Subject: [PATCH 30/39] Throw an error if there are no witnesses when doing fork detection --- light-client/src/errors.rs | 3 +++ light-client/src/peer_list.rs | 4 ++-- light-client/src/supervisor.rs | 14 ++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 8f58cb45a..afb2de148 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -21,6 +21,9 @@ pub enum ErrorKind { #[error("store error")] Store, + #[error("no witnesses")] + NoWitnesses, + #[error("no valid peer left")] NoValidPeerLeft, diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index ce9081ec7..1c5961fdc 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -35,7 +35,7 @@ impl PeerList { self.peers.get_mut(&self.primary) } - pub fn secondaries(&self) -> Vec<&Instance> { + pub fn witnesses(&self) -> Vec<&Instance> { self.peers .keys() .filter(|peer_id| peer_id != &&self.primary) @@ -44,7 +44,7 @@ impl PeerList { } #[pre(peer_id != &self.primary)] - pub fn remove_secondary(&mut self, peer_id: &PeerId) { + pub fn remove_witness(&mut self, peer_id: &PeerId) { self.peers.remove(peer_id); } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index dde0fffe9..6b7c197d1 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -115,7 +115,7 @@ impl Supervisor { forked.push(block.provider); } Fork::Faulty(block, _error) => { - self.peers.remove_secondary(&block.provider); + self.peers.remove_witness(&block.provider); // TODO: Log/record the error } } @@ -156,15 +156,13 @@ impl Supervisor { light_block: &LightBlock, trusted_state: &LightBlock, ) -> Result>, Error> { - if self.peers.secondaries().is_empty() { - return Ok(None); + if self.peers.witnesses().is_empty() { + bail!(ErrorKind::NoWitnesses); } - let result = self.fork_detector.detect_forks( - light_block, - &trusted_state, - self.peers.secondaries(), - )?; + let result = + self.fork_detector + .detect_forks(light_block, &trusted_state, self.peers.witnesses())?; match result { ForkDetection::Detected(forks) => Ok(Some(forks)), From 289ca33037cf2ea28dcfd27a43b3e85e65ef111c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 11 Jun 2020 14:41:18 +0200 Subject: [PATCH 31/39] Rename leftover secondaries to witnesses --- light-client/src/fork_detector.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 3b79829a8..bbb996c66 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -26,7 +26,7 @@ pub trait ForkDetector: Send { &self, light_block: &LightBlock, trusted_state: &LightBlock, - secondaries: Vec<&Instance>, + witnesses: Vec<&Instance>, ) -> Result; } @@ -53,26 +53,24 @@ impl ForkDetector for ProdForkDetector { &self, light_block: &LightBlock, trusted_state: &LightBlock, - secondaries: Vec<&Instance>, + witnesses: Vec<&Instance>, ) -> Result { let primary_hash = self.header_hasher.hash(&light_block.signed_header.header); - let mut forks = Vec::with_capacity(secondaries.len()); + let mut forks = Vec::with_capacity(witnesses.len()); - for secondary in secondaries { + for witness in witnesses { let mut state = State::new(MemoryStore::new()); - let secondary_block = secondary + let witness_block = witness .light_client .get_or_fetch_block(light_block.height(), &mut state) .unwrap(); // FIXME: unwrap - let secondary_hash = self - .header_hasher - .hash(&secondary_block.signed_header.header); + let witness_hash = self.header_hasher.hash(&witness_block.signed_header.header); - if primary_hash == secondary_hash { - // Hashes match, continue with next secondary, if any. + if primary_hash == witness_hash { + // Hashes match, continue with next witness, if any. continue; } @@ -82,16 +80,16 @@ impl ForkDetector for ProdForkDetector { state .light_store - .update(secondary_block.clone(), VerifiedStatus::Unverified); + .update(witness_block.clone(), VerifiedStatus::Unverified); - let result = secondary + let result = witness .light_client .verify_to_target(light_block.height(), &mut state); match result { - Ok(_) => forks.push(Fork::Forked(secondary_block)), - Err(e) if e.kind().has_expired() => forks.push(Fork::Forked(secondary_block)), - Err(e) => forks.push(Fork::Faulty(secondary_block, e.kind().clone())), + Ok(_) => forks.push(Fork::Forked(witness_block)), + Err(e) if e.kind().has_expired() => forks.push(Fork::Forked(witness_block)), + Err(e) => forks.push(Fork::Faulty(witness_block, e.kind().clone())), } } From 35f6ee95a35cfd3a2069c56c356e205f6b3a5e08 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 11 Jun 2020 14:45:26 +0200 Subject: [PATCH 32/39] Set clock_drift parameter to 10 seconds to match Go default --- light-client/tests/light_client.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index bddf178c0..7371a368b 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -75,8 +75,9 @@ fn run_test_case(tc: TestCase) { None => false, }; - // FIXME: What should this be, and where should it be configured? - let clock_drift = Duration::from_secs(1); + // In Go, default is 10 sec. + // Once we switch to the proposer based timestamps, it will probably be a consensus parameter + let clock_drift = Duration::from_secs(10); let trusting_period: Duration = tc.initial.trusting_period.into(); let tm_now = tc.initial.now; @@ -169,8 +170,9 @@ fn run_bisection_test(tc: TestBisection) { let trusting_period = tc.trust_options.period; let now = tc.now; - // FIXME: What should this be, and where should it be configured? - let clock_drift = Duration::from_secs(1); + // In Go, default is 10 sec. + // Once we switch to the proposer based timestamps, it will probably be a consensus parameter + let clock_drift = Duration::from_secs(10); let clock = MockClock { now }; From 8fca5c184edbf78c72f9ad5b412e04fe57babe93 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 12 Jun 2020 15:48:52 +0200 Subject: [PATCH 33/39] Add configurable timeouts to Io component --- light-client/examples/light_client.rs | 4 ++- light-client/src/components/io.rs | 44 +++++++++++++++++++-------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 09d99564e..d4886d435 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -78,7 +78,9 @@ fn sync_cmd(opts: SyncOpts) { let mut peer_map = HashMap::new(); peer_map.insert(primary, primary_addr); - let io = ProdIo::new(peer_map); + + let timeout = Duration::from_secs(10); + let io = ProdIo::new(peer_map, Some(timeout)); let db = sled::open(opts.db_path).unwrap_or_else(|e| { println!("[ error ] could not open database: {}", e); diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index 1a2c9f4ff..44688476e 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::time::Duration; use contracts::{contract_trait, post, pre}; use serde::{Deserialize, Serialize}; @@ -17,6 +18,10 @@ pub enum IoError { /// Wrapper for a `tendermint::rpc::Error`. #[error(transparent)] IoError(#[from] rpc::Error), + + /// The request timed out. + #[error("request timed out")] + Timeout, } /// Interface for fetching light blocks from a full node, typically via the RPC client. @@ -45,6 +50,7 @@ where #[derive(Clone, Debug)] pub struct ProdIo { peer_map: HashMap, + timeout: Option, } #[contract_trait] @@ -66,8 +72,11 @@ impl ProdIo { /// Constructs a new ProdIo component. /// /// A peer map which maps peer IDS to their network address must be supplied. - pub fn new(peer_map: HashMap) -> Self { - Self { peer_map } + pub fn new( + peer_map: HashMap, + timeout: Option, + ) -> Self { + Self { peer_map, timeout } } #[pre(self.peer_map.contains_key(&peer))] @@ -75,12 +84,15 @@ impl ProdIo { let height: block::Height = height.into(); let rpc_client = self.rpc_client_for(peer); - let res = block_on(async { - match height.value() { - 0 => rpc_client.latest_commit().await, - _ => rpc_client.commit(height).await, - } - }); + let res = block_on( + async { + match height.value() { + 0 => rpc_client.latest_commit().await, + _ => rpc_client.commit(height).await, + } + }, + self.timeout, + )?; match res { Ok(response) => Ok(response.signed_header), @@ -90,7 +102,7 @@ impl ProdIo { #[pre(self.peer_map.contains_key(&peer))] fn fetch_validator_set(&self, peer: PeerId, height: Height) -> Result { - let res = block_on(self.rpc_client_for(peer).validators(height)); + let res = block_on(self.rpc_client_for(peer).validators(height), self.timeout)?; match res { Ok(response) => Ok(TMValidatorSet::new(response.validators)), @@ -106,11 +118,17 @@ impl ProdIo { } } -fn block_on(f: F) -> F::Output { - tokio::runtime::Builder::new() +fn block_on(f: F, timeout: Option) -> Result { + let mut rt = tokio::runtime::Builder::new() .basic_scheduler() .enable_all() .build() - .unwrap() - .block_on(f) + .unwrap(); + + if let Some(timeout) = timeout { + rt.block_on(async { tokio::time::timeout(timeout, f).await }) + .map_err(|_| IoError::Timeout) + } else { + Ok(rt.block_on(f)) + } } From 7a328559074dc1b4700573992d044fdc38d8c8d0 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 12 Jun 2020 15:49:02 +0200 Subject: [PATCH 34/39] Add the primary node as a witness in the example CLI --- light-client/examples/light_client.rs | 41 ++++++++++++++++++++------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index d4886d435..067f7823a 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -17,7 +17,10 @@ use tendermint_light_client::{ use gumdrop::Options; use std::collections::HashMap; -use std::{path::PathBuf, time::Duration}; +use std::{ + path::{Path, PathBuf}, + time::Duration, +}; #[derive(Debug, Options)] struct CliOptions { @@ -72,17 +75,19 @@ fn main() { } } -fn sync_cmd(opts: SyncOpts) { - let primary_addr = opts.address; - let primary: PeerId = "BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap(); - +fn make_instance( + peer_id: PeerId, + addr: tendermint::net::Address, + db_path: impl AsRef, + opts: &SyncOpts, +) -> Instance { let mut peer_map = HashMap::new(); - peer_map.insert(primary, primary_addr); + peer_map.insert(peer_id, addr); let timeout = Duration::from_secs(10); let io = ProdIo::new(peer_map, Some(timeout)); - let db = sled::open(opts.db_path).unwrap_or_else(|e| { + let db = sled::open(db_path).unwrap_or_else(|e| { println!("[ error ] could not open database: {}", e); std::process::exit(1); }); @@ -90,7 +95,7 @@ fn sync_cmd(opts: SyncOpts) { let mut light_store = SledStore::new(db); if let Some(height) = opts.trusted_height { - let trusted_state = io.fetch_light_block(primary, height).unwrap_or_else(|e| { + let trusted_state = io.fetch_light_block(peer_id, height).unwrap_or_else(|e| { println!("[ error ] could not retrieve trusted header: {}", e); std::process::exit(1); }); @@ -122,13 +127,27 @@ fn sync_cmd(opts: SyncOpts) { let clock = SystemClock; let scheduler = scheduler::basic_bisecting_schedule; - let light_client = LightClient::new(primary, options, clock, scheduler, verifier, io); + let light_client = LightClient::new(peer_id, options, clock, scheduler, verifier, io); + + Instance::new(light_client, state) +} + +fn sync_cmd(opts: SyncOpts) { + let addr = opts.address.clone(); + + let primary: PeerId = "BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap(); + let witness: PeerId = "CEFEEDBADFADAD0C0CEEFACADE0ADEADBEEFC0FF".parse().unwrap(); + + let primary_path = opts.db_path.clone().join(primary.to_string()); + let witness_path = opts.db_path.clone().join(witness.to_string()); - let instance = Instance::new(light_client, state); + let primary_instance = make_instance(primary, addr.clone(), primary_path, &opts); + let witness_instance = make_instance(witness, addr.clone(), witness_path, &opts); let peer_list = PeerList::builder() .primary(primary) - .peer(primary, instance) + .peer(primary, primary_instance) + .peer(witness, witness_instance) .build(); let mut supervisor = Supervisor::new(peer_list, ProdForkDetector::default()); From 56bc022a40bd296c829ec43574105c0b40cf19a5 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Fri, 12 Jun 2020 16:16:19 +0200 Subject: [PATCH 35/39] Remove witness from peers when request times out during fork detection --- light-client/src/components/io.rs | 19 ++++++++++++++----- light-client/src/fork_detector.rs | 3 +-- light-client/src/supervisor.rs | 21 +++++++++++++++------ 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index 44688476e..ebec4118e 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -20,8 +20,8 @@ pub enum IoError { IoError(#[from] rpc::Error), /// The request timed out. - #[error("request timed out")] - Timeout, + #[error("request to peer {0} timed out")] + Timeout(PeerId), } /// Interface for fetching light blocks from a full node, typically via the RPC client. @@ -91,6 +91,7 @@ impl ProdIo { _ => rpc_client.commit(height).await, } }, + peer, self.timeout, )?; @@ -102,7 +103,11 @@ impl ProdIo { #[pre(self.peer_map.contains_key(&peer))] fn fetch_validator_set(&self, peer: PeerId, height: Height) -> Result { - let res = block_on(self.rpc_client_for(peer).validators(height), self.timeout)?; + let res = block_on( + self.rpc_client_for(peer).validators(height), + peer, + self.timeout, + )?; match res { Ok(response) => Ok(TMValidatorSet::new(response.validators)), @@ -118,7 +123,11 @@ impl ProdIo { } } -fn block_on(f: F, timeout: Option) -> Result { +fn block_on( + f: F, + peer: PeerId, + timeout: Option, +) -> Result { let mut rt = tokio::runtime::Builder::new() .basic_scheduler() .enable_all() @@ -127,7 +136,7 @@ fn block_on(f: F, timeout: Option) -> Result Ok(Some(forks)), - ForkDetection::NotDetected => Ok(None), + Ok(ForkDetection::Detected(forks)) => Ok(Some(forks)), + Ok(ForkDetection::NotDetected) => Ok(None), + Err(e) => match e.kind() { + // TODO: Clean this up + ErrorKind::Io(IoError::Timeout(peer)) => { + self.peers.remove_witness(peer); + Err(e) + } + _ => Err(e), + }, } } From d24d1bfffa7caf9108568d4f4fdd76ac5b91bf1a Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 15 Jun 2020 10:51:38 +0200 Subject: [PATCH 36/39] Fix PeerList::swap_primary --- light-client/src/peer_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index 1c5961fdc..c71df397a 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -49,7 +49,7 @@ impl PeerList { } pub fn swap_primary(&mut self) -> Result<(), Error> { - if let Some(peer_id) = self.peers.keys().next() { + while let Some(peer_id) = self.peers.keys().next() { if peer_id != &self.primary { self.primary = *peer_id; return Ok(()); From 5e24ec8e2ecf2af425cec018ff8a13130952f1c9 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 15 Jun 2020 10:53:12 +0200 Subject: [PATCH 37/39] Improve PeerListBuilder API --- light-client/examples/light_client.rs | 5 ++--- light-client/src/peer_list.rs | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 067f7823a..912eef9a5 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -145,9 +145,8 @@ fn sync_cmd(opts: SyncOpts) { let witness_instance = make_instance(witness, addr.clone(), witness_path, &opts); let peer_list = PeerList::builder() - .primary(primary) - .peer(primary, primary_instance) - .peer(witness, witness_instance) + .primary(primary, primary_instance) + .witness(witness, witness_instance) .build(); let mut supervisor = Supervisor::new(peer_list, ProdForkDetector::default()); diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index c71df397a..f9c64d07f 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -67,12 +67,13 @@ pub struct PeerListBuilder { } impl PeerListBuilder { - pub fn primary(mut self, primary: PeerId) -> Self { - self.primary = Some(primary); + pub fn primary(mut self, peer_id: PeerId, instance: Instance) -> Self { + self.primary = Some(peer_id); + self.peers.insert(peer_id, instance); self } - pub fn peer(mut self, peer_id: PeerId, instance: Instance) -> Self { + pub fn witness(mut self, peer_id: PeerId, instance: Instance) -> Self { self.peers.insert(peer_id, instance); self } From 809d394a457cf0a439bbac0087e75b77f83cd25e Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 15 Jun 2020 13:36:23 +0200 Subject: [PATCH 38/39] Add doc comments --- light-client/src/callback.rs | 3 +++ light-client/src/errors.rs | 4 ++-- light-client/src/fork_detector.rs | 23 +++++++++++++++++++++++ light-client/src/peer_list.rs | 27 ++++++++++++++++++++++++++- light-client/src/supervisor.rs | 2 +- 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/light-client/src/callback.rs b/light-client/src/callback.rs index ba1cdcf4f..a6db2d1fd 100644 --- a/light-client/src/callback.rs +++ b/light-client/src/callback.rs @@ -1,5 +1,6 @@ use std::fmt; +/// A boxed `FnOnce(A) -> () + Send`. pub struct Callback { inner: Box () + Send>, } @@ -11,12 +12,14 @@ impl fmt::Debug for Callback { } impl Callback { + /// Box the given closure in a `Callback`. pub fn new(inner: impl FnOnce(A) -> () + Send + 'static) -> Self { Self { inner: Box::new(inner), } } + /// Call the underlying closure on `result`. pub fn call(self, result: A) -> () { (self.inner)(result); } diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index afb2de148..6d87e3ca9 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -24,8 +24,8 @@ pub enum ErrorKind { #[error("no witnesses")] NoWitnesses, - #[error("no valid peer left")] - NoValidPeerLeft, + #[error("no witness left")] + NoWitnessLeft, #[error("fork detected peers={0:?}")] ForkDetected(Vec), diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 1f1e003e5..99760550f 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -9,19 +9,28 @@ use crate::{ types::LightBlock, }; +/// Result of fork detection #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ForkDetection { + /// One or more forks have been detected Detected(Vec), + /// No fork has been detected NotDetected, } +/// Types of fork #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Fork { + /// An actual fork was found for this `LightBlock` Forked(LightBlock), + /// The node has been deemed faulty for this `LightBlock` Faulty(LightBlock, ErrorKind), } +/// Interface for a fork detector pub trait ForkDetector: Send { + /// Detect forks using the given light block, trusted state, + /// and list of witnesses to verify the given light block against. fn detect_forks( &self, light_block: &LightBlock, @@ -30,11 +39,24 @@ pub trait ForkDetector: Send { ) -> Result; } +/// A production-ready fork detector which compares +/// light blocks fetched from the witnesses by hash. +/// If the hashes don't match, this fork detector +/// then attempts to verify the light block pulled from +/// the witness against a light block containing only +/// the given trusted state, and then: +/// +/// - If the verification succeeds, we have a real fork +/// - If verification fails because of lack of trust, +/// we have a potential fork. +/// - If verification fails for any other reason, the +/// witness is deemed faulty. pub struct ProdForkDetector { header_hasher: Box, } impl ProdForkDetector { + /// Construct a new fork detector that will use the given header hasher. pub fn new(header_hasher: impl HeaderHasher + 'static) -> Self { Self { header_hasher: Box::new(header_hasher), @@ -49,6 +71,7 @@ impl Default for ProdForkDetector { } impl ForkDetector for ProdForkDetector { + /// Perform fork detection. See the documentation `ProdForkDetector` for details. fn detect_forks( &self, light_block: &LightBlock, diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index f9c64d07f..7092f39a4 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -8,6 +8,8 @@ use crate::{ use contracts::pre; use std::collections::HashMap; +/// A mapping from PeerIds to Light Client instances. +/// Keeps track of which peer is deemed the primary peer. #[derive(Debug)] pub struct PeerList { peers: HashMap, @@ -15,26 +17,33 @@ pub struct PeerList { } impl PeerList { + /// Returns a builder of `PeerList` pub fn builder() -> PeerListBuilder { PeerListBuilder::default() } + /// Get a reference to the light client instance for the given peer id. pub fn get(&self, peer_id: &PeerId) -> Option<&Instance> { self.peers.get(peer_id) } + /// Get a mutable reference to the light client instance for the given peer id. pub fn get_mut(&mut self, peer_id: &PeerId) -> Option<&mut Instance> { self.peers.get_mut(peer_id) } + /// Get a reference to the current primary instance. pub fn primary(&self) -> Option<&Instance> { self.peers.get(&self.primary) } + /// Get a mutable reference to the current primary instance. pub fn primary_mut(&mut self) -> Option<&mut Instance> { self.peers.get_mut(&self.primary) } + /// Get a list of references to all the witnesses, + /// (ie. all peers which are not the primary). pub fn witnesses(&self) -> Vec<&Instance> { self.peers .keys() @@ -43,11 +52,19 @@ impl PeerList { .collect() } + /// Remove the given peer from the list of witnesses. + /// + /// ## Precondition + /// - The given peer id must not be the primary peer id. #[pre(peer_id != &self.primary)] pub fn remove_witness(&mut self, peer_id: &PeerId) { self.peers.remove(peer_id); } + /// Swap the primary for the next available witness, if any. + /// + /// ## Errors + /// - If there are no witness left, returns `ErrorKind::NoWitnessLeft`. pub fn swap_primary(&mut self) -> Result<(), Error> { while let Some(peer_id) = self.peers.keys().next() { if peer_id != &self.primary { @@ -56,10 +73,11 @@ impl PeerList { } } - bail!(ErrorKind::NoValidPeerLeft) + bail!(ErrorKind::NoWitnessLeft) } } +/// A builder of `PeerList` with a fluent API. #[derive(Default)] pub struct PeerListBuilder { primary: Option, @@ -67,17 +85,24 @@ pub struct PeerListBuilder { } impl PeerListBuilder { + /// Register the given peer id and instance as the primary. + /// Overrides the previous primary if it was already set. pub fn primary(mut self, peer_id: PeerId, instance: Instance) -> Self { self.primary = Some(peer_id); self.peers.insert(peer_id, instance); self } + /// Register the given peer id and instance as a witness. pub fn witness(mut self, peer_id: PeerId, instance: Instance) -> Self { self.peers.insert(peer_id, instance); self } + /// Builds the `PeerList`. + /// + /// ## Precondition + /// - A primary has been set with a call to `PeerListBuilder::primary`. #[pre( self.primary.is_some() && self.peers.contains_key(self.primary.as_ref().unwrap()) )] diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index e052f91d9..0cd879e1c 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -144,7 +144,7 @@ impl Supervisor { } } - bail!(ErrorKind::NoValidPeerLeft) + bail!(ErrorKind::NoWitnessLeft) } fn report_evidence(&mut self, _light_block: &LightBlock) { From b6b50a8a6ad51901fab551a56df54b5cdc192888 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 15 Jun 2020 14:56:33 +0200 Subject: [PATCH 39/39] Use enum instead of 0 to denote latest height --- light-client/examples/light_client.rs | 12 +++-- light-client/src/components/io.rs | 70 ++++++++++++++++++++------- light-client/src/light_client.rs | 4 +- light-client/tests/light_client.rs | 17 +++++-- 4 files changed, 74 insertions(+), 29 deletions(-) diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 912eef9a5..f94f6cb1e 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -1,7 +1,7 @@ use tendermint_light_client::{ components::{ clock::SystemClock, - io::{Io, ProdIo}, + io::{AtHeight, Io, ProdIo}, scheduler, verifier::ProdVerifier, }, @@ -95,10 +95,12 @@ fn make_instance( let mut light_store = SledStore::new(db); if let Some(height) = opts.trusted_height { - let trusted_state = io.fetch_light_block(peer_id, height).unwrap_or_else(|e| { - println!("[ error ] could not retrieve trusted header: {}", e); - std::process::exit(1); - }); + let trusted_state = io + .fetch_light_block(peer_id, AtHeight::At(height)) + .unwrap_or_else(|e| { + println!("[ error ] could not retrieve trusted header: {}", e); + std::process::exit(1); + }); light_store.insert(trusted_state, VerifiedStatus::Verified); } else { diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index ebec4118e..c6074b032 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -3,15 +3,31 @@ use std::time::Duration; use contracts::{contract_trait, post, pre}; use serde::{Deserialize, Serialize}; -use tendermint::{block, rpc}; use thiserror::Error; -use tendermint::block::signed_header::SignedHeader as TMSignedHeader; -use tendermint::validator::Set as TMValidatorSet; +use tendermint::{ + block::signed_header::SignedHeader as TMSignedHeader, rpc, validator::Set as TMValidatorSet, +}; -use crate::types::{Height, LightBlock, PeerId}; +use crate::{ + bail, + types::{Height, LightBlock, PeerId}, +}; -pub const LATEST_HEIGHT: Height = 0; +pub enum AtHeight { + At(Height), + Latest, +} + +impl From for AtHeight { + fn from(height: Height) -> Self { + if height == 0 { + Self::Latest + } else { + Self::At(height) + } + } +} #[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] pub enum IoError { @@ -19,6 +35,10 @@ pub enum IoError { #[error(transparent)] IoError(#[from] rpc::Error), + /// Given height is invalid + #[error("invalid height: {0}")] + InvalidHeight(String), + /// The request timed out. #[error("request to peer {0} timed out")] Timeout(PeerId), @@ -32,15 +52,15 @@ pub trait Io: Send { /// ## Postcondition /// - The provider of the returned light block matches the given peer [LCV-IO-POST-PROVIDER] #[post(ret.as_ref().map(|lb| lb.provider == peer).unwrap_or(true))] - fn fetch_light_block(&self, peer: PeerId, height: Height) -> Result; + fn fetch_light_block(&self, peer: PeerId, height: AtHeight) -> Result; } #[contract_trait] impl Io for F where - F: Fn(PeerId, Height) -> Result, + F: Fn(PeerId, AtHeight) -> Result, { - fn fetch_light_block(&self, peer: PeerId, height: Height) -> Result { + fn fetch_light_block(&self, peer: PeerId, height: AtHeight) -> Result { self(peer, height) } } @@ -55,12 +75,12 @@ pub struct ProdIo { #[contract_trait] impl Io for ProdIo { - fn fetch_light_block(&self, peer: PeerId, height: Height) -> Result { + fn fetch_light_block(&self, peer: PeerId, height: AtHeight) -> Result { let signed_header = self.fetch_signed_header(peer, height)?; - let height = signed_header.header.height.into(); + let height: Height = signed_header.header.height.into(); - let validator_set = self.fetch_validator_set(peer, height)?; - let next_validator_set = self.fetch_validator_set(peer, height + 1)?; + let validator_set = self.fetch_validator_set(peer, height.into())?; + let next_validator_set = self.fetch_validator_set(peer, (height + 1).into())?; let light_block = LightBlock::new(signed_header, validator_set, next_validator_set, peer); @@ -80,15 +100,18 @@ impl ProdIo { } #[pre(self.peer_map.contains_key(&peer))] - fn fetch_signed_header(&self, peer: PeerId, height: Height) -> Result { - let height: block::Height = height.into(); + fn fetch_signed_header( + &self, + peer: PeerId, + height: AtHeight, + ) -> Result { let rpc_client = self.rpc_client_for(peer); let res = block_on( async { - match height.value() { - 0 => rpc_client.latest_commit().await, - _ => rpc_client.commit(height).await, + match height { + AtHeight::Latest => rpc_client.latest_commit().await, + AtHeight::At(height) => rpc_client.commit(height).await, } }, peer, @@ -102,7 +125,18 @@ impl ProdIo { } #[pre(self.peer_map.contains_key(&peer))] - fn fetch_validator_set(&self, peer: PeerId, height: Height) -> Result { + fn fetch_validator_set( + &self, + peer: PeerId, + height: AtHeight, + ) -> Result { + let height = match height { + AtHeight::Latest => bail!(IoError::InvalidHeight( + "given height must be greater than 0".to_string() + )), + AtHeight::At(height) => height, + }; + let res = block_on( self.rpc_client_for(peer).validators(height), peer, diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index c010bf52a..c30e5fa6f 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -94,7 +94,7 @@ impl LightClient { /// /// Note: This functin delegates the actual work to `verify_to_target`. pub fn verify_to_highest(&mut self, state: &mut State) -> Result { - let target_block = match self.io.fetch_light_block(self.peer, LATEST_HEIGHT) { + let target_block = match self.io.fetch_light_block(self.peer, AtHeight::Latest) { Ok(last_block) => last_block, Err(io_error) => bail!(ErrorKind::Io(io_error)), }; @@ -254,7 +254,7 @@ impl LightClient { } self.io - .fetch_light_block(self.peer, current_height) + .fetch_light_block(self.peer, AtHeight::At(current_height)) .map(|current_block| { state .light_store diff --git a/light-client/tests/light_client.rs b/light-client/tests/light_client.rs index 7371a368b..0f02ddb9a 100644 --- a/light-client/tests/light_client.rs +++ b/light-client/tests/light_client.rs @@ -1,7 +1,7 @@ use tendermint_light_client::{ components::{ clock::Clock, - io::{Io, IoError}, + io::{AtHeight, Io, IoError}, scheduler, verifier::{ProdVerifier, Verdict, Verifier}, }, @@ -114,10 +114,13 @@ fn run_test_case(tc: TestCase) { struct MockIo { chain_id: String, light_blocks: HashMap, + latest_height: Height, } impl MockIo { fn new(chain_id: String, light_blocks: Vec) -> Self { + let latest_height = light_blocks.iter().map(|lb| lb.height()).max().unwrap(); + let light_blocks = light_blocks .into_iter() .map(|lb| (lb.height(), lb)) @@ -126,13 +129,19 @@ impl MockIo { Self { chain_id, light_blocks, + latest_height, } } } #[contract_trait] impl Io for MockIo { - fn fetch_light_block(&self, _peer: PeerId, height: Height) -> Result { + fn fetch_light_block(&self, _peer: PeerId, height: AtHeight) -> Result { + let height = match height { + AtHeight::Latest => self.latest_height, + AtHeight::At(height) => height, + }; + self.light_blocks .get(&height) .cloned() @@ -193,7 +202,7 @@ fn run_bisection_test(tc: TestBisection) { let trusted_height = tc.trust_options.height.try_into().unwrap(); let trusted_state = io - .fetch_light_block(primary.clone(), trusted_height) + .fetch_light_block(primary.clone(), AtHeight::At(trusted_height)) .expect("could not 'request' light block"); let mut light_store = MemoryStore::new(); @@ -218,7 +227,7 @@ fn run_bisection_test(tc: TestBisection) { match verify_bisection(untrusted_height, &mut light_client, &mut state) { Ok(new_states) => { let untrusted_light_block = io - .fetch_light_block(primary.clone(), untrusted_height) + .fetch_light_block(primary.clone(), AtHeight::At(untrusted_height)) .expect("header at untrusted height not found"); // TODO: number of bisections started diverting in JSON tests and Rust impl