Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testgen: LightBlock generation without cyclic dependency #606

Merged
merged 31 commits into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
06ab006
added functionality to generate light block
Shivani912 Aug 25, 2020
4a8cc4e
Merge branch 'master' into shivani/testgen-light-block
Shivani912 Aug 25, 2020
acd576b
fix failing checks
Shivani912 Aug 25, 2020
a6b2491
Possible dependency loop
Shivani912 Sep 8, 2020
510e6a2
Formatting
romac Sep 8, 2020
28f3086
Remove cyclic dependency between light-client and testgen crates
romac Sep 8, 2020
edfece9
renaming
Shivani912 Sep 11, 2020
12e6519
adopt light block generation to new structure
Shivani912 Sep 15, 2020
5b4ee0c
Merge branch 'master' into Shivani/testgen-light-block
adizere Oct 2, 2020
437a575
fmt fix & bit of cleanup
adizere Oct 2, 2020
7c9aa3c
Todo note for LightBlock dependency.
adizere Oct 2, 2020
7d5f9ac
Merge branch 'Shivani/testgen-light-block' of github.com:informalsyst…
Shivani912 Oct 5, 2020
4f72b5d
implement Generator trait for LightBlock
Shivani912 Oct 5, 2020
917220e
remove validator set generation fn
Shivani912 Oct 5, 2020
115a95c
add support for LightChain features
Shivani912 Oct 6, 2020
a36b737
minimal implementation of LightChain to support ibc-rs testing
Shivani912 Oct 6, 2020
b0410d1
add TODOs and minor fixes
Shivani912 Oct 8, 2020
f72dbd4
renaming LightBlock for better clarity
Shivani912 Oct 8, 2020
5a44955
finalize LightBlock generation
Shivani912 Oct 8, 2020
8ca01d3
cargo fmt
Shivani912 Oct 8, 2020
8ddb68b
remove tests - will be added in a separate PR
Shivani912 Oct 8, 2020
5bd720b
cargo fmt
Shivani912 Oct 8, 2020
5284d96
update lib.rs
Shivani912 Oct 8, 2020
035a335
polish advancing chain for LightChain
Shivani912 Oct 22, 2020
d7d02ce
LightChain fetches block at target_height
Shivani912 Oct 22, 2020
60fe5d9
LightChain fetches latest block
Shivani912 Oct 22, 2020
19d3cad
Merge branch 'master' into Shivani/testgen-light-block
Shivani912 Oct 22, 2020
171edf1
merge aftermath
Shivani912 Oct 22, 2020
7e30c55
add a test for LightBlock::next()
Shivani912 Oct 22, 2020
450997e
cargo fmt
Shivani912 Oct 22, 2020
d767fc8
address Adi's comments
Shivani912 Oct 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions testgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ 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_chain::LightChain;
pub use time::Time;
pub use validator::Validator;
pub use vote::Vote;
Expand Down
208 changes: 208 additions & 0 deletions testgen/src/light_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
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 std::str::FromStr;
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.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TMLightBlock {
adizere marked this conversation as resolved.
Show resolved Hide resolved
/// 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,
}

impl TMLightBlock {
/// Constructs a new light block
pub fn new(
Shivani912 marked this conversation as resolved.
Show resolved Hide resolved
signed_header: SignedHeader,
validators: ValidatorSet,
next_validators: ValidatorSet,
provider: PeerId,
) -> Self {
Self {
signed_header,
validators,
next_validators,
provider,
}
}
}

/// 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: peer_id())")]
pub provider: Option<String>,
}

impl LightBlock {
/// Constructs a new Testgen-specific light block
pub fn new(
header: Header,
commit: Commit,
validators: Vec<Validator>,
next_validators: Vec<Validator>,
provider: &str,
) -> Self {
Self {
header: Some(header),
commit: Some(commit),
validators: Some(validators),
next_validators: Some(next_validators),
provider: Some(provider.to_string()),
}
}

pub fn new_default(validators: &[Validator], height: u64) -> Self {
Shivani912 marked this conversation as resolved.
Show resolved Hide resolved
let header = Header::new(validators)
.height(height)
.chain_id("test-chain");
let commit = Commit::new(header.clone(), 1);

Self {
header: Some(header),
commit: Some(commit),
validators: Some(validators.to_vec()),
next_validators: None,
provider: Some("peer-1".to_string()),
}
}
set_option!(
next_validators,
&[Validator],
Some(next_validators.to_vec())
);
set_option!(provider, String);

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

impl std::str::FromStr for LightBlock {
type Err = SimpleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let testgen_light_block = match parse_as::<LightBlock>(s) {
Ok(input) => input,
Err(_) => LightBlock::new_default(parse_as::<Vec<Validator>>(s)?.as_ref(), 1),
};
Ok(testgen_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 => bail!("validator array is missing"),
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 = match PeerId::from_str(
self.provider
.clone()
.unwrap_or_else(|| "peer-1".to_string())
.as_str(),
) {
Ok(id) => id,
Err(_) => bail!("failed to construct light block's 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 })
}
98 changes: 98 additions & 0 deletions testgen/src/light_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::light_block::LightBlock;
use crate::Validator;
use tendermint::block::Height;
use tendermint::chain::Info;

pub struct LightChain {
pub info: Info,
pub light_blocks: Vec<LightBlock>,
}

impl LightChain {
pub fn new(info: Info, light_blocks: Vec<LightBlock>) -> Self {
LightChain { info, light_blocks }
}

// TODO: make this fn more usable
// TODO: like how does someone generate a chain with different validators at each height
pub fn default_with_length(num: u64) -> Self {
let vals = Validator::new("val-1");
let testgen_light_block = LightBlock::new_default(&[vals], 1);
let mut light_blocks = Vec::new();

for _i in 2..num {
// add "next" light block to the vector
light_blocks.push(testgen_light_block.next());
}

let info = Info {
id: light_blocks[0]
.header
.as_ref()
.unwrap()
.chain_id
.as_ref()
.expect("missing chain id")
.parse()
.unwrap(),
height: Height::from(num),
// TODO: figure how to add this
last_block_id: None,
// TODO: Not sure yet what this time means
time: None,
};
Self::new(info, light_blocks)
}

pub fn advance_chain(&mut self) -> Self {
let new_light_block = self
.light_blocks
.last()
.expect("Cannot find testgen light block")
.next();
let advanced_light_blocks = &mut self.light_blocks;
advanced_light_blocks.push(new_light_block);

let height = self.info.height.value() + 1;

let info = Info {
id: self.info.id,
height: Height::from(height),
// TODO: figure how to add this
last_block_id: None,
// TODO: Not sure yet what this time means
time: None,
};

Self::new(info, advanced_light_blocks.to_owned())
}
}

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

#[test]
fn test_advance_chain() {
let vals = Validator::new("val-1");
let light_blocks = vec![LightBlock::new_default(&[vals], 1)];
let info = Info {
id: light_blocks[0]
.header
.as_ref()
.unwrap()
.chain_id
.as_ref()
.expect("missing chain id")
.parse()
.unwrap(),
height: Height::from(1 as u32),
last_block_id: None,
time: None,
};
let advanced_light_chain = LightChain::new(info, light_blocks).advance_chain();

assert_eq!(2, advanced_light_chain.info.height.value());
}
}
1 change: 1 addition & 0 deletions testgen/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl Validator {
proposer_priority: None,
}
}
// Question: Why do we need this option since we're already initializing id with fn new()??
Shivani912 marked this conversation as resolved.
Show resolved Hide resolved
set_option!(id, &str, Some(id.to_string()));
set_option!(voting_power, u64);
set_option!(proposer_priority, i64);
Expand Down