From 57b0a6d36ba7c3275e81156591b9ce0b42a7f835 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 15 Dec 2020 19:45:47 +0100 Subject: [PATCH 1/2] Compute last_block_id hash when generating a light chain --- testgen/src/header.rs | 20 +++++- testgen/src/light_block.rs | 10 ++- testgen/src/light_chain.rs | 122 ++++++++++++++++++++++++++++--------- 3 files changed, 119 insertions(+), 33 deletions(-) diff --git a/testgen/src/header.rs b/testgen/src/header.rs index 4285d0172..0ff9490ad 100644 --- a/testgen/src/header.rs +++ b/testgen/src/header.rs @@ -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 { @@ -26,6 +26,8 @@ pub struct Header { pub time: Option, #[options(help = "proposer index (default: 0)")] pub proposer: Option, + #[options(help = "last block id hash (default: Hash::None)")] + pub last_block_id_hash: Option, } impl Header { @@ -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())); @@ -49,6 +52,7 @@ 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"); @@ -56,6 +60,9 @@ impl Header { 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), @@ -63,6 +70,7 @@ impl Header { 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), } } } @@ -87,6 +95,7 @@ impl Generator 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), } } @@ -116,11 +125,18 @@ impl Generator 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 @@ -129,7 +145,7 @@ impl Generator 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(), diff --git a/testgen/src/light_block.rs b/testgen/src/light_block.rs index 0d0b4b128..d737f406d 100644 --- a/testgen/src/light_block.rs +++ b/testgen/src/light_block.rs @@ -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. @@ -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 { + self.header + .as_ref() + .expect("header is missing") + .last_block_id_hash + } } impl std::str::FromStr for LightBlock { diff --git a/testgen/src/light_chain.rs b/testgen/src/light_chain.rs index 0f8f4bffc..7b21af03d 100644 --- a/testgen/src/light_chain.rs +++ b/testgen/src/light_chain.rs @@ -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, } -impl LightChain { - pub fn new(info: Info, light_blocks: Vec) -> 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 = 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) -> 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 { + 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() } } @@ -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::>(); + + // 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()) + ); + } + } } From cd1bb93a0a4b314eaf6ae17ff3d84bfd8e7d5656 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 16 Dec 2020 11:44:45 +0100 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31bca8b3c..76109a012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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