Skip to content

Commit

Permalink
testgen: Compute last_block_id hash when generating a light chain (#…
Browse files Browse the repository at this point in the history
…746)

* Compute last_block_id hash when generating a light chain

* Update changelog
  • Loading branch information
romac authored Dec 16, 2020
1 parent 4225d7f commit 462eafc
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 33 deletions.
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())
);
}
}
}

0 comments on commit 462eafc

Please sign in to comment.