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: Compute last_block_id hash when generating a light chain #746

Merged
merged 2 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- `[tendermint, light-client]` Specify the proposer in the validator set of fetched light blocks ([#705])
- `[tendermint-proto]` (Since v0.17.0-rc3) Upgrade protobuf definitions to Tendermint Go v0.34.0 ([#737])
- `[tendermint]` Remove `total_voting_power` parameter from `validator::Set::new` ([#739])
- `[testgen]` Compute `last_block_id` hash when generating a `LightChain` ([#745])

[#425]: https://github.com/informalsystems/tendermint-rs/issues/425
[#646]: https://github.com/informalsystems/tendermint-rs/pull/646
Expand All @@ -22,6 +23,7 @@
[#705]: https://github.com/informalsystems/tendermint-rs/issues/705
[#737]: https://github.com/informalsystems/tendermint-rs/pull/737
[#739]: https://github.com/informalsystems/tendermint-rs/issues/739
[#745]: https://github.com/informalsystems/tendermint-rs/issues/745

## v0.17.0-rc3

Expand Down
20 changes: 18 additions & 2 deletions testgen/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use simple_error::*;
use std::convert::TryFrom;
use std::str::FromStr;
use tendermint::{block, chain, validator, AppHash};
use tendermint::{block, chain, validator, AppHash, Hash};

#[derive(Debug, Options, Serialize, Deserialize, Clone)]
pub struct Header {
Expand All @@ -26,6 +26,8 @@ pub struct Header {
pub time: Option<u64>,
#[options(help = "proposer index (default: 0)")]
pub proposer: Option<usize>,
#[options(help = "last block id hash (default: Hash::None)")]
pub last_block_id_hash: Option<Hash>,
}

impl Header {
Expand All @@ -37,6 +39,7 @@ impl Header {
height: None,
time: None,
proposer: None,
last_block_id_hash: None,
}
}
set_option!(validators, &[Validator], Some(validators.to_vec()));
Expand All @@ -49,20 +52,25 @@ impl Header {
set_option!(height, u64);
set_option!(time, u64);
set_option!(proposer, usize);
set_option!(last_block_id_hash, Hash);

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);

let prev_header = self.generate().unwrap();
let last_block_id_hash = prev_header.hash();

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
last_block_id_hash: Some(last_block_id_hash),
}
}
}
Expand All @@ -87,6 +95,7 @@ impl Generator<block::Header> for Header {
height: self.height.or(default.height),
time: self.time.or(default.time),
proposer: self.proposer.or(default.proposer),
last_block_id_hash: self.last_block_id_hash.or(default.last_block_id_hash),
}
}

Expand Down Expand Up @@ -116,11 +125,18 @@ impl Generator<block::Header> for Header {
Ok(id) => id,
Err(_) => bail!("failed to construct header's chain_id"),
};

let time = if let Some(t) = self.time {
get_time(t)
} else {
tendermint::Time::now()
};

let last_block_id = self.last_block_id_hash.map(|hash| block::Id {
hash,
part_set_header: Default::default(),
});

let header = block::Header {
// block version in Tendermint-go is hardcoded with value 11
// so we do the same with MBT for now for compatibility
Expand All @@ -129,7 +145,7 @@ impl Generator<block::Header> for Header {
height: block::Height::try_from(self.height.unwrap_or(1))
.map_err(|_| SimpleError::new("height out of bounds"))?,
time,
last_block_id: None,
last_block_id,
last_commit_hash: None,
data_hash: None,
validators_hash: valset.hash(),
Expand Down
10 changes: 9 additions & 1 deletion testgen/src/light_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ 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;
use tendermint::{block::signed_header::SignedHeader, Hash};

/// 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.
Expand Down Expand Up @@ -139,6 +139,14 @@ impl LightBlock {
.expect("chain_id is missing")
.to_string()
}

/// returns the last_block_id hash of LightBlock's header
pub fn last_block_id_hash(&self) -> Option<Hash> {
self.header
.as_ref()
.expect("header is missing")
.last_block_id_hash
}
}

impl std::str::FromStr for LightBlock {
Expand Down
122 changes: 92 additions & 30 deletions testgen/src/light_chain.rs
Original file line number Diff line number Diff line change
@@ -1,75 +1,91 @@
use crate::light_block::LightBlock;
use tendermint::block::Height;
use crate::{light_block::LightBlock, Generator};
use tendermint::block::{self, Height};
use tendermint::chain::Info;

use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};

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 testgen_light_block = LightBlock::new_default(1);
let mut light_blocks: Vec<LightBlock> = vec![testgen_light_block.clone()];

for _i in 2..num {
// add "next" light block to the vector
light_blocks.push(testgen_light_block.next());
}
impl Default for LightChain {
fn default() -> Self {
let initial_block = LightBlock::new_default(1);

let id = light_blocks[0].chain_id().parse().unwrap();
let height = Height::try_from(num).expect("failed to convert from u64 to Height");
let id = initial_block.chain_id().parse().unwrap();
let height = initial_block.height().try_into().unwrap();

let info = Info {
id,
height,
// TODO: figure how to add this
// no last block id for the initial block
last_block_id: None,
// TODO: Not sure yet what this time means
time: None,
};
Self::new(info, light_blocks)

Self::new(info, vec![initial_block])
}
}

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 mut light_chain = Self::default();
for _i in 2..=num {
light_chain.advance_chain();
}
light_chain
}

/// expects at least one LightBlock in the Chain
pub fn advance_chain(&mut self) -> LightBlock {
pub fn advance_chain(&mut self) -> &LightBlock {
let last_light_block = self
.light_blocks
.last()
.expect("Cannot find testgen light block");

let new_light_block = last_light_block.next();
self.light_blocks.push(new_light_block.clone());

self.info.height = Height::try_from(new_light_block.height())
.expect("failed to convert from u64 to Height");

new_light_block
let last_block_id_hash = new_light_block
.header
.as_ref()
.expect("missing header in new light block")
.generate()
.expect("failed to generate header")
.hash();

self.info.last_block_id = Some(block::Id {
hash: last_block_id_hash,
part_set_header: Default::default(),
});

self.light_blocks.push(new_light_block);
self.light_blocks.last().unwrap() // safe because of push above
}

/// fetches a block from LightChain at a certain height
/// it returns None if a block does not exist for the target_height
pub fn block(&self, target_height: u64) -> Option<LightBlock> {
pub fn block(&self, target_height: u64) -> Option<&LightBlock> {
self.light_blocks
.clone()
.into_iter()
.iter()
.find(|lb| lb.height() == target_height)
}

/// fetches the latest block from LightChain
pub fn latest_block(&self) -> LightBlock {
pub fn latest_block(&self) -> &LightBlock {
self.light_blocks
.last()
.expect("cannot find last light block")
.clone()
}
}

Expand Down Expand Up @@ -112,4 +128,50 @@ mod tests {
let second_block = light_chain.latest_block();
assert_eq!(2, second_block.height());
}

#[test]
fn test_light_chain_with_length() {
const CHAIN_HEIGHT: u64 = 10;

let chain = LightChain::default_with_length(CHAIN_HEIGHT);

let blocks = chain
.light_blocks
.into_iter()
.flat_map(|lb| lb.generate())
.collect::<Vec<_>>();

// we have as many blocks as the height of the chain
assert_eq!(blocks.len(), chain.info.height.value() as usize);
assert_eq!(blocks.len(), CHAIN_HEIGHT as usize);

let first_block = blocks.first().unwrap();
let last_block = blocks.last().unwrap();

// the first block is at height 1
assert_eq!(first_block.signed_header.header.height.value(), 1);

// the first block does not have a last_block_id
assert!(first_block.signed_header.header.last_block_id.is_none());

// the last block is at the chain height
assert_eq!(last_block.signed_header.header.height, chain.info.height);

for i in 1..blocks.len() {
let prv = &blocks[i - 1];
let cur = &blocks[i];

// the height of the current block is the successor of the previous block
assert_eq!(
cur.signed_header.header.height.value(),
prv.signed_header.header.height.value() + 1
);

// the last_block_id hash is equal to the previous block's hash
assert_eq!(
cur.signed_header.header.last_block_id.map(|lbi| lbi.hash),
Some(prv.signed_header.header.hash())
);
}
}
}