Skip to content

Commit

Permalink
Added config options & copied code into new app crate
Browse files Browse the repository at this point in the history
- copied from tendermint-lite/src/main.rs to lite-node/src/command/start.rs
  • Loading branch information
liamsi committed Jan 11, 2020
1 parent f25f0da commit a899c73
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 22 deletions.
145 changes: 139 additions & 6 deletions lite-node/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,24 @@
/// accessors along with logging macros. Customize as you see fit.
use crate::prelude::*;

use core::future::Future;
use tendermint::hash;
use tendermint::lite;
use tendermint::lite::{Error, Header, Requester, SignedHeader, Store, TrustedState};
use tendermint::rpc;
use tendermint::{block::Height, Hash};
use tokio::runtime::Builder;

use tendermint::lite::ValidatorSet as _;

use crate::config::LiteNodeConfig;
use crate::requester::RPCRequester;
use crate::state::State;
use crate::store::MemStore;
use crate::threshold::TrustThresholdOneThird;

use abscissa_core::{config, Command, FrameworkError, Options, Runnable};
use std::time::{Duration, SystemTime};

/// `start` subcommand
///
Expand All @@ -16,17 +32,82 @@ use abscissa_core::{config, Command, FrameworkError, Options, Runnable};
/// <https://docs.rs/gumdrop/>
#[derive(Command, Debug, Options)]
pub struct StartCmd {
/// To whom are we saying hello?
/// RPC address to request headers and validators from.
#[options(free)]
recipient: Vec<String>,
rpc_addr: String,
}

// TODO: this should also somehow be configurable ...
// we can't simply add this as a field in the config because this either would
// be a trait (`TrustThreshold`) or immediately and impl thereof (`TrustThresholdOneThird`).
static THRESHOLD: &TrustThresholdOneThird = &TrustThresholdOneThird {};

impl Runnable for StartCmd {
/// Start the application.
fn run(&self) {
let config = app_config();
// TODO(liamsi): light client loop goes here...
println!("Hello, {}!", &config.hello.recipient);

let client = block_on(rpc::Client::new(&config.rpc_address.parse().unwrap())).unwrap();
let req = RPCRequester::new(client);
let mut store = MemStore::new();

let vals_hash = Hash::from_hex_upper(
hash::Algorithm::Sha256,
&config.subjective_init.validators_hash,
)
.unwrap();

println!("Requesting from {}.", config.rpc_address);

subjective_init(
Height::from(config.subjective_init.height),
vals_hash,
&mut store,
&req,
)
.unwrap();

loop {
let latest = (&req).signed_header(0).unwrap();
let latest_peer_height = latest.header().height();

let latest = store.get(Height::from(0)).unwrap();
let latest_height = latest.last_header().header().height();

// only bisect to higher heights
if latest_peer_height <= latest_height {
std::thread::sleep(Duration::new(1, 0));
continue;
}

println!(
"attempting bisection from height {:?} to height {:?}",
store
.get(Height::from(0))
.unwrap()
.last_header()
.header()
.height(),
latest_peer_height,
);

let now = &SystemTime::now();
lite::verify_and_update_bisection(
latest_peer_height,
THRESHOLD, // TODO
&config.trusting_period,
now,
&req,
&mut store,
)
.unwrap();

println!("Succeeded bisecting!");

// notifications ?

// sleep for a few secs ?
}
}
}

Expand All @@ -38,10 +119,62 @@ impl config::Override<LiteNodeConfig> for StartCmd {
&self,
mut config: LiteNodeConfig,
) -> Result<LiteNodeConfig, FrameworkError> {
if !self.recipient.is_empty() {
config.hello.recipient = self.recipient.join(" ");
if !self.rpc_addr.is_empty() {
config.rpc_address = self.rpc_addr.to_owned();
}

Ok(config)
}
}

/*
* The following is initialization logic that should have a
* function in the lite crate like:
* `subjective_init(height, vals_hash, store, requester) -> Result<(), Error`
* it would fetch the initial header/vals from the requester and populate a
* trusted state and store it in the store ...
* TODO: this should take traits ... but how to deal with the State ?
* TODO: better name ?
*/
fn subjective_init(
height: Height,
vals_hash: Hash,
store: &mut MemStore,
req: &RPCRequester,
) -> Result<(), Error> {
if store.get(height).is_ok() {
// we already have this !
return Ok(());
}

// check that the val hash matches
let vals = req.validator_set(height)?;

if vals.hash() != vals_hash {
// TODO
panic!("vals hash dont match")
}

let signed_header = req.signed_header(height)?;

// TODO: validate signed_header.commit() with the vals ...

let next_vals = req.validator_set(height.increment())?;

// TODO: check next_vals ...

let trusted_state = &State::new(&signed_header, &next_vals);

store.add(trusted_state)?;

Ok(())
}

fn block_on<F: Future>(future: F) -> F::Output {
Builder::new()
.basic_scheduler()
.enable_all()
.build()
.unwrap()
.block_on(future)
}
33 changes: 23 additions & 10 deletions lite-node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@
//! for specifying it.

use serde::{Deserialize, Serialize};
use std::time::Duration;

use tendermint::lite::TrustThreshold;

/// LiteNode Configuration
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LiteNodeConfig {
/// An example configuration section
pub hello: ExampleSection,
/// RPC address to request headers and validators from.
pub rpc_address: String,
/// The duration until we consider a trusted state as expired.
pub trusting_period: Duration,
/// Subjective initialization.
pub subjective_init: SubjectiveInit,
}

/// Default configuration settings.
Expand All @@ -21,25 +28,31 @@ pub struct LiteNodeConfig {
impl Default for LiteNodeConfig {
fn default() -> Self {
Self {
hello: ExampleSection::default(),
rpc_address: "localhost:26657".to_owned(),
trusting_period: Duration::new(6000, 0),
subjective_init: SubjectiveInit::default(),
}
}
}

/// Example configuration section.
/// Configuration for subjective initialization.
///
/// Delete this and replace it with your actual configuration structs.
/// Contains the subjective height and validators hash (as a string formatted as hex).
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ExampleSection {
/// Example configuration value
pub recipient: String,
pub struct SubjectiveInit {
/// Subjective height.
pub height: u64,
/// Subjective validators hash.
pub validators_hash: String,
}

impl Default for ExampleSection {
impl Default for SubjectiveInit {
fn default() -> Self {
Self {
recipient: "world".to_owned(),
height: 1,
validators_hash: "A5A7DEA707ADE6156F8A981777CA093F178FC790475F6EC659B6617E704871DD"
.to_owned(),
}
}
}
5 changes: 5 additions & 0 deletions lite-node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ pub mod commands;
pub mod config;
pub mod error;
pub mod prelude;

pub mod requester;
pub mod state;
pub mod store;
pub mod threshold;
83 changes: 83 additions & 0 deletions lite-node/src/requester.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use tendermint::block;
use tendermint::lite;
use tendermint::rpc;
use tendermint::validator;

use core::future::Future;
use tokio::runtime::Builder;

/// RPCRequester wraps the Tendermint rpc::Client.
pub struct RPCRequester {
client: rpc::Client,
}

impl RPCRequester {
pub fn new(client: rpc::Client) -> Self {
RPCRequester { client }
}
}

impl lite::types::Requester for RPCRequester {
type SignedHeader = block::signed_header::SignedHeader;
type ValidatorSet = validator::Set;

/// Request the signed header at height h.
/// If h==0, request the latest signed header.
/// TODO: use an enum instead of h==0.
fn signed_header<H>(&self, h: H) -> Result<Self::SignedHeader, lite::Error>
where
H: Into<block::Height>,
{
let height: block::Height = h.into();
let r = match height.value() {
0 => block_on(self.client.latest_commit()),
_ => block_on(self.client.commit(height)),
};
match r {
Ok(response) => Ok(response.signed_header),
Err(_error) => Err(lite::Error::RequestFailed),
}
}

/// Request the validator set at height h.
fn validator_set<H>(&self, h: H) -> Result<Self::ValidatorSet, lite::Error>
where
H: Into<block::Height>,
{
let r = block_on(self.client.validators(h));
match r {
Ok(response) => Ok(validator::Set::new(response.validators)),
Err(_error) => Err(lite::Error::RequestFailed),
}
}
}

pub fn block_on<F: Future>(future: F) -> F::Output {
Builder::new()
.basic_scheduler()
.enable_all()
.build()
.unwrap()
.block_on(future)
}

#[cfg(test)]
mod tests {
use super::*;
use tendermint::lite::types::Header as LiteHeader;
use tendermint::lite::types::Requester as LiteRequester;
use tendermint::lite::types::SignedHeader as LiteSignedHeader;
use tendermint::lite::types::ValidatorSet as LiteValSet;
use tendermint::rpc;

// TODO: integration test
#[test]
#[ignore]
fn test_val_set() {
let client = block_on(rpc::Client::new(&"localhost:26657".parse().unwrap())).unwrap();
let req = RPCRequester::new(client);
let r1 = req.validator_set(5).unwrap();
let r2 = req.signed_header(5).unwrap();
assert_eq!(r1.hash(), r2.header().validators_hash());
}
}
30 changes: 30 additions & 0 deletions lite-node/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use tendermint::lite::TrustedState;
use tendermint::{block::signed_header::SignedHeader, validator::Set};

#[derive(Clone)]
pub struct State {
last_header: SignedHeader,
vals: Set,
}

impl TrustedState for State {
type LastHeader = SignedHeader;
type ValidatorSet = Set;

fn new(last_header: &Self::LastHeader, vals: &Self::ValidatorSet) -> Self {
State {
last_header: last_header.clone(),
vals: vals.clone(),
}
}

// height H-1
fn last_header(&self) -> &Self::LastHeader {
&self.last_header
}

// height H
fn validators(&self) -> &Self::ValidatorSet {
&self.vals
}
}
Loading

0 comments on commit a899c73

Please sign in to comment.