Skip to content

Commit

Permalink
Implement builder DSL for light client initialization, and other thin…
Browse files Browse the repository at this point in the history
…gs (#583)

* Add Send + Sync bound on light client Handle

* Remove default implementation on Handle trait

* Whitespace

* Fix light-node code after Handle update

* Remove unused mut qualifier after Supervisor update

* Derive Clone on SupervisorHandle

* Add DSL for building supervisor and light clients

* Refactor Io component

* Refactor Supervisor builder

* Re-add a TODO that got lost in the refactor

* Document builders

* Simplify return types of supervisor builder methods

* Update light client example to use builders

* Perform more validation when doing subjective initialization

* Add option to use trusted state alrady in store to light client builder

* Use builders in light node start command

* Improve API of PeerListBuilder

* Finish adapting start command to use supervisor builder

* Fix WASM build

* Expose a couple functions on the supervisor

* Block on async tasks in a new thread to avoid nesting Tokio runtimes

* Cleanup

* Nest std_ext module under utils

* Feature-guard `block_on` for WASM

* Fix typo in comment

Co-authored-by: Thane Thomson <thane@informal.systems>

* Ensure that at least one witness is provided in the SupervisorBuilder

* Rename SupervisorBuilder::unwrap to inner to express is infallability

* Remove LightClientBuilder::trust_primary_latest since we do not need it and it is quite dangerous

* Make `LightClientBuilder::trust_light_block` method private

* Perform more thorough validation of the trusted light block in the light client builder

* Update light client integration test to use builders

Co-authored-by: Thane Thomson <thane@informal.systems>
  • Loading branch information
romac and thanethomson committed Sep 30, 2020
1 parent 416f514 commit 34d7d02
Show file tree
Hide file tree
Showing 22 changed files with 794 additions and 410 deletions.
118 changes: 42 additions & 76 deletions light-client/examples/light_client.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
use std::collections::HashMap;
use std::{
path::{Path, PathBuf},
time::Duration,
};

use anomaly::BoxError;
use gumdrop::Options;

use tendermint_light_client::supervisor::{Handle as _, Instance, Supervisor};
use tendermint::Hash;
use tendermint_rpc as rpc;

use tendermint_light_client::supervisor::{Handle as _, Instance};
use tendermint_light_client::{
components::{
clock::SystemClock,
io::{AtHeight, Io, ProdIo},
scheduler,
verifier::ProdVerifier,
},
evidence::ProdEvidenceReporter,
fork_detector::ProdForkDetector,
light_client::{self, LightClient},
peer_list::PeerList,
state::State,
store::{sled::SledStore, LightStore},
types::{Height, PeerId, Status, TrustThreshold},
builder::{LightClientBuilder, SupervisorBuilder},
light_client,
store::sled::SledStore,
types::{Height, PeerId, TrustThreshold},
};

#[derive(Debug, Options)]
Expand Down Expand Up @@ -55,6 +49,11 @@ struct SyncOpts {
meta = "HEIGHT"
)]
trusted_height: Option<Height>,
#[options(
help = "hash of the initial trusted state (optional if store already initialized)",
meta = "HASH"
)]
trusted_hash: Option<Hash>,
#[options(
help = "path to the database folder",
meta = "PATH",
Expand All @@ -65,14 +64,18 @@ struct SyncOpts {

fn main() {
let opts = CliOptions::parse_args_default_or_exit();

match opts.command {
None => {
eprintln!("Please specify a command:");
eprintln!("{}\n", CliOptions::command_list().unwrap());
eprintln!("{}\n", CliOptions::usage());
std::process::exit(1);
}
Some(Command::Sync(sync_opts)) => sync_cmd(sync_opts),
Some(Command::Sync(sync_opts)) => sync_cmd(sync_opts).unwrap_or_else(|e| {
eprintln!("Command failed: {}", e);
std::process::exit(1);
}),
}
}

Expand All @@ -81,83 +84,46 @@ fn make_instance(
addr: tendermint::net::Address,
db_path: impl AsRef<Path>,
opts: &SyncOpts,
) -> Instance {
let mut peer_map = HashMap::new();
peer_map.insert(peer_id, addr);

let timeout = Duration::from_secs(10);
let io = ProdIo::new(peer_map, Some(timeout));

let db = sled::open(db_path).unwrap_or_else(|e| {
println!("[ error ] could not open database: {}", e);
std::process::exit(1);
});

let mut light_store = SledStore::new(db);

if let Some(height) = opts.trusted_height {
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, Status::Verified);
} else if light_store.latest(Status::Verified).is_none() {
println!("[ error ] no trusted state in database, please specify a trusted header");
std::process::exit(1);
}

let state = State {
light_store: Box::new(light_store),
verification_trace: HashMap::new(),
};
) -> Result<Instance, BoxError> {
let db = sled::open(db_path)?;

let light_store = SledStore::new(db);
let rpc_client = rpc::HttpClient::new(addr).unwrap();
let options = light_client::Options {
trust_threshold: TrustThreshold {
numerator: 1,
denominator: 3,
},
trust_threshold: TrustThreshold::default(),
trusting_period: Duration::from_secs(36000),
clock_drift: Duration::from_secs(1),
};

let verifier = ProdVerifier::default();
let clock = SystemClock;
let scheduler = scheduler::basic_bisecting_schedule;
let builder =
LightClientBuilder::prod(peer_id, rpc_client, Box::new(light_store), options, None);

let light_client = LightClient::new(peer_id, options, clock, scheduler, verifier, io);
let builder = if let (Some(height), Some(hash)) = (opts.trusted_height, opts.trusted_hash) {
builder.trust_primary_at(height, hash)
} else {
builder.trust_from_store()
}?;

Instance::new(light_client, state)
Ok(builder.build())
}

fn sync_cmd(opts: SyncOpts) {
let addr = opts.address.clone();

fn sync_cmd(opts: SyncOpts) -> Result<(), BoxError> {
let primary: PeerId = "BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap();
let witness: PeerId = "CEFEEDBADFADAD0C0CEEFACADE0ADEADBEEFC0FF".parse().unwrap();

let primary_addr = opts.address.clone();
let witness_addr = opts.address.clone();

let primary_path = opts.db_path.join(primary.to_string());
let witness_path = opts.db_path.join(witness.to_string());

let primary_instance = make_instance(primary, addr.clone(), primary_path, &opts);
let witness_instance = make_instance(witness, addr.clone(), witness_path, &opts);

let mut peer_addr = HashMap::new();
peer_addr.insert(primary, addr.clone());
peer_addr.insert(witness, addr);

let peer_list = PeerList::builder()
.primary(primary, primary_instance)
.witness(witness, witness_instance)
.build();
let primary_instance = make_instance(primary, primary_addr.clone(), primary_path, &opts)?;
let witness_instance = make_instance(witness, witness_addr.clone(), witness_path, &opts)?;

let mut supervisor = Supervisor::new(
peer_list,
ProdForkDetector::default(),
ProdEvidenceReporter::new(peer_addr),
);
let supervisor = SupervisorBuilder::new()
.primary(primary, primary_addr, primary_instance)
.witness(witness, witness_addr, witness_instance)
.build_prod();

let handle = supervisor.handle();

Expand Down
9 changes: 9 additions & 0 deletions light-client/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! DSL for building light clients and supervisor

mod light_client;
pub use light_client::LightClientBuilder;

mod supervisor;
pub use supervisor::SupervisorBuilder;

pub mod error;
58 changes: 58 additions & 0 deletions light-client/src/builder/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Errors raised by the builder DSL

use anomaly::BoxError;
use anomaly::Context;
use tendermint::block::Height;
use tendermint::Hash;
use thiserror::Error;

use crate::components::io::IoError;

/// An error raised by the builder
pub type Error = anomaly::Error<Kind>;

/// The various error kinds raised by the builder
#[derive(Debug, Clone, Error, PartialEq)]
pub enum Kind {
/// I/O error
#[error("I/O error: {0}")]
Io(#[from] IoError),

/// Height mismatch
#[error("height mismatch: given = {given}, found = {found}")]
HeightMismatch {
/// Height of trusted header
given: Height,
/// Height of fetched header
found: Height,
},

/// Hash mismatch
#[error("hash mismatch: given = {given}, found = {found}")]
HashMismatch {
/// Hash of trusted header
given: Hash,
/// hash of fetched header
found: Hash,
},

/// Invalid light block
#[error("invalid light block")]
InvalidLightBlock,

/// No trusted state as found in the store
#[error("no trusted state in store")]
NoTrustedStateInStore,

/// An empty witness list was given
#[error("empty witness list")]
EmptyWitnessList,
}

impl Kind {
/// Add additional context (i.e. include a source error and capture a backtrace).
/// You can convert the resulting `Context` into an `Error` by calling `.into()`.
pub fn context(self, source: impl Into<BoxError>) -> Context<Self> {
Context::new(self, Some(source.into()))
}
}
Loading

0 comments on commit 34d7d02

Please sign in to comment.