Skip to content

Commit

Permalink
Testgen: LightBlock generation without cyclic dependency (#606)
Browse files Browse the repository at this point in the history
* added functionality to generate light block
* Possible dependency loop
* Remove cyclic dependency between light-client and testgen crates
* adopt light block generation to new structure
* Todo note for LightBlock dependency.
* implement Generator trait for LightBlock
* remove validator set generation fn
* add support for LightChain features
* minimal implementation of LightChain to support ibc-rs testing
* renaming LightBlock for better clarity
* finalize LightBlock generation
* remove tests - will be added in a separate PR
* update lib.rs
* polish advancing chain for LightChain
* LightChain fetches block at target_height
* LightChain fetches latest block
* add a test for LightBlock::next()
* address Adi's comments

Co-authored-by: Shivani Joshi <joshi.shivani912@gmail.com>
Co-authored-by: Romain Ruetschi <romain@informal.systems>
  • Loading branch information
3 people committed Oct 23, 2020
1 parent 6be5ebf commit 3298d63
Show file tree
Hide file tree
Showing 5 changed files with 409 additions and 0 deletions.
16 changes: 16 additions & 0 deletions testgen/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ impl Header {
set_option!(height, u64);
set_option!(time, u64);
set_option!(proposer, usize);

pub fn next(&self) -> Self {
let height = self.height.expect("Missing previous header's height");
let time = self.time.unwrap_or(height); // if no time is found, then we simple correspond it to the header height
let validators = self.validators.clone().expect("Missing validators");
let next_validators = self.next_validators.clone().unwrap_or(validators);

Self {
validators: Some(next_validators.clone()),
next_validators: Some(next_validators),
chain_id: self.chain_id.clone(),
height: Some(height + 1),
time: Some(time + 1),
proposer: self.proposer, // TODO: proposer must be incremented
}
}
}

impl std::str::FromStr for Header {
Expand Down
4 changes: 4 additions & 0 deletions testgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ pub mod commit;
pub mod consensus;
pub mod generator;
pub mod header;
pub mod light_block;
pub mod light_chain;
pub mod time;
pub mod validator;
pub mod vote;

pub use commit::Commit;
pub use generator::Generator;
pub use header::Header;
pub use light_block::LightBlock;
pub use light_chain::LightChain;
pub use time::Time;
pub use validator::Validator;
pub use vote::Vote;
Expand Down
273 changes: 273 additions & 0 deletions testgen/src/light_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
use gumdrop::Options;
use serde::{Deserialize, Serialize};
use simple_error::*;

use crate::helpers::parse_as;
use crate::validator::generate_validators;
use crate::{Commit, Generator, Header, Validator};
use tendermint::block::signed_header::SignedHeader;
use tendermint::node::Id as PeerId;
use tendermint::validator;
use tendermint::validator::Set as ValidatorSet;

/// A light block is the core data structure used by the light client.
/// It records everything the light client needs to know about a block.
/// NOTE: This struct & associated `impl` below are a copy of light-client's `LightBlock`.
/// The copy is necessary here to avoid a circular dependency.
/// Cf. https://github.com/informalsystems/tendermint-rs/issues/605
/// TODO: fix redundant code without introducing cyclic dependency.
///
/// To convert `TMLightBlock` to the Domain type `LightBlock` used in light-client crate
/// You'll need to implement the `From` trait like below:
///
/// impl From<TMLightBlock> for LightBlock {
/// fn from(tm_lb: TMLightBlock) -> Self {
/// Self {
/// signed_header: tm_lb.signed_header,
/// validators: tm_lb.validators,
/// next_validators: tm_lb.next_validators,
/// provider: tm_lb.provider,
/// }
/// }
/// }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TMLightBlock {
/// Header and commit of this block
pub signed_header: SignedHeader,
/// Validator set at the block height
pub validators: ValidatorSet,
/// Validator set at the next block height
pub next_validators: ValidatorSet,
/// The peer ID of the node that provided this block
pub provider: PeerId,
}

/// We use this data structure as a simplistic representation of LightClient's LightBlock
#[derive(Debug, Options, Deserialize, Clone)]
pub struct LightBlock {
#[options(help = "header (required)", parse(try_from_str = "parse_as::<Header>"))]
pub header: Option<Header>,
#[options(help = "commit (required)", parse(try_from_str = "parse_as::<Commit>"))]
pub commit: Option<Commit>,
#[options(
help = "validators (required), encoded as array of 'validator' parameters",
parse(try_from_str = "parse_as::<Vec<Validator>>")
)]
pub validators: Option<Vec<Validator>>,
#[options(
help = "next validators (default: same as validators), encoded as array of 'validator' parameters",
parse(try_from_str = "parse_as::<Vec<Validator>>")
)]
pub next_validators: Option<Vec<Validator>>,
#[options(help = "peer id (default: default_peer_id())")]
pub provider: Option<PeerId>,
}

impl LightBlock {
/// Constructs a new Testgen-specific light block
pub fn new(header: Header, commit: Commit) -> Self {
Self {
header: Some(header),
commit: Some(commit),
validators: None,
next_validators: None,
provider: None,
}
}
set_option!(validators, &[Validator], Some(validators.to_vec()));
set_option!(
next_validators,
&[Validator],
Some(next_validators.to_vec())
);

pub fn new_default(height: u64) -> Self {
let validators = [Validator::new("1"), Validator::new("2")];
let header = Header::new(&validators)
.height(height)
.chain_id("test-chain")
.next_validators(&validators)
.time(height); // just wanted to initialize time with some value

let commit = Commit::new(header.clone(), 1);

Self {
header: Some(header),
commit: Some(commit),
validators: Some(validators.to_vec()),
next_validators: Some(validators.to_vec()),
provider: Some(default_peer_id()),
}
}

/// Produces a subsequent, i.e. at (height+1), light block to the supplied one
// TODO: figure how to represent the currently ignored details in header
// TODO: and commit like last_block_id and other hashes
pub fn next(&self) -> Self {
let header = self.header.as_ref().expect("header is missing").next();

let commit = Commit::new(header.clone(), 1);

Self {
header: Some(header),
commit: Some(commit),
validators: self.next_validators.clone(),
next_validators: self.next_validators.clone(),
provider: self.provider,
}
}

/// returns the height of LightBlock's header
pub fn height(&self) -> u64 {
self.header
.as_ref()
.expect("header is missing")
.height
.expect("header height is missing")
}

/// returns the chain_id of LightBlock's header
pub fn chain_id(&self) -> String {
self.header
.as_ref()
.expect("header is missing")
.chain_id
.as_ref()
.expect("chain_id is missing")
.to_string()
}
}

impl std::str::FromStr for LightBlock {
type Err = SimpleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let light_block = match parse_as::<LightBlock>(s) {
Ok(input) => input,
Err(_) => LightBlock::new(parse_as::<Header>(s)?, Commit::from_str(s)?),
};
Ok(light_block)
}
}

impl Generator<TMLightBlock> for LightBlock {
fn merge_with_default(self, default: Self) -> Self {
Self {
header: self.header.or(default.header),
commit: self.commit.or(default.commit),
validators: self.validators.or(default.validators),
next_validators: self.next_validators.or(default.next_validators),
provider: self.provider.or(default.provider),
}
}

fn generate(&self) -> Result<TMLightBlock, SimpleError> {
let header = match &self.header {
None => bail!("header is missing"),
Some(h) => h,
};
let commit = match &self.commit {
None => bail!("commit is missing"),
Some(c) => c,
};
let signed_header =
generate_signed_header(header, commit).expect("Could not generate signed header");

let validators = match &self.validators {
None => validator::Set::new(generate_validators(
header
.validators
.as_ref()
.expect("missing validators in header"),
)?),
Some(vals) => validator::Set::new(generate_validators(vals)?),
};

let next_validators = match &self.next_validators {
Some(next_vals) => validator::Set::new(generate_validators(next_vals)?),
None => validators.clone(),
};

let provider = default_peer_id();

let light_block = TMLightBlock {
signed_header,
validators,
next_validators,
provider,
};

Ok(light_block)
}
}

/// A helper function to generate SignedHeader used by TMLightBlock
pub fn generate_signed_header(
raw_header: &Header,
raw_commit: &Commit,
) -> Result<SignedHeader, SimpleError> {
let header = match raw_header.generate() {
Err(e) => bail!("Failed to generate header with error: {}", e),
Ok(h) => h,
};

let commit = match raw_commit.generate() {
Err(e) => bail!("Failed to generate commit with error: {}", e),
Ok(c) => c,
};

Ok(SignedHeader { header, commit })
}

pub fn default_peer_id() -> PeerId {
"BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_light_block() {
let light_block_1 = LightBlock::new_default(1);
let light_block_2 = LightBlock::new_default(1);

assert_eq!(light_block_1.generate(), light_block_2.generate());

let validators = [Validator::new("10"), Validator::new("20")];

let light_block_3 = LightBlock::new_default(1).validators(&validators);

assert_ne!(light_block_2.generate(), light_block_3.generate());

let light_block_4 = LightBlock::new_default(4).validators(&validators);

assert_ne!(light_block_3.generate(), light_block_4.generate());

let light_block_5 = light_block_3.next();
let lb_5_height: u64 = light_block_5
.generate()
.unwrap()
.signed_header
.header
.height
.into();

assert_eq!(2, lb_5_height);

let header_6 = Header::new(&validators)
.next_validators(&validators)
.height(10)
.time(10)
.chain_id("test-chain");
let commit_6 = Commit::new(header_6.clone(), 1);
let light_block_6 = LightBlock::new(header_6.clone(), commit_6);

let header_7 = header_6.next();
let commit_7 = Commit::new(header_7.clone(), 1);
let light_block_7 = LightBlock::new(header_7, commit_7);

assert_eq!(light_block_7.height(), 11);
assert_eq!(light_block_7.chain_id(), "test-chain");
assert_ne!(light_block_6.generate(), light_block_7.generate());
}
}
Loading

0 comments on commit 3298d63

Please sign in to comment.