diff --git a/light-client/src/components/scheduler.rs b/light-client/src/components/scheduler.rs index eafe4b53c..df59d1d6f 100644 --- a/light-client/src/components/scheduler.rs +++ b/light-client/src/components/scheduler.rs @@ -1,7 +1,4 @@ -use crate::{ - store::LightStore, - types::{Height, Status}, -}; +use crate::{store::LightStore, types::Height}; use contracts::*; diff --git a/mbt-utils/Cargo.toml b/mbt-utils/Cargo.toml index 63a6bbce8..411c7d9b6 100644 --- a/mbt-utils/Cargo.toml +++ b/mbt-utils/Cargo.toml @@ -20,4 +20,4 @@ simple-error = "0.2.1" [[bin]] name = "mbt-tendermint-produce" -path = "src/tendermint-produce.rs" \ No newline at end of file +path = "bin/tendermint-produce.rs" diff --git a/mbt-utils/bin/tendermint-produce.rs b/mbt-utils/bin/tendermint-produce.rs new file mode 100644 index 000000000..d0e8a1e35 --- /dev/null +++ b/mbt-utils/bin/tendermint-produce.rs @@ -0,0 +1,119 @@ +use gumdrop::Options; + +use tendermint_mbt_utils::commit::Commit; +use tendermint_mbt_utils::header::Header; +use tendermint_mbt_utils::producer::Producer; +use tendermint_mbt_utils::validator::Validator; + +const USAGE: &str = r#" +This is a small utility for producing tendermint datastructures +from minimal input (for testing purposes only). + +For example, a tendermint validator can be produced only from an identifier, +or a tendermint header only from a set of validators. + +To get an idea which input is needed for each datastructure, try '--help CMD': +it will list the required and optional parameters. + +The parameters can be supplied in two ways: + - via STDIN: in that case they are expected to be a valid JSON object, + with each parameter being a field of this object + - via command line arguments to the specific command. + +If a parameter is supplied both via STDIN and CLI, the latter is given preference. + +In case a particular datastructure can be produced from a single parameter +(like validator), there is a shortcut that allows to provide this parameter +directly via STDIN, without wrapping it into JSON object. +E.g., in the validator case, the following are equivalent: + + mbt-tendermint-produce validator --id a --voting-power 3 + echo -n '{"id": "a", "voting_power": 3}' | mbt-tendermint-produce --read-stdin validator + echo -n a | mbt-tendermint-produce --read-stdin validator --voting-power 3 + echo -n '{"id": "a"}' | mbt-tendermint-produce --read-stdin validator --voting-power 3 + echo -n '{"id": "a", "voting_power": 100}' | mbt-tendermint-produce --read-stdin validator --voting-power 3 + +The result is: + { + "address": "730D3D6B2E9F4F0F23879458F2D02E0004F0F241", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "YnT69eNDaRaNU7teDTcyBedSD0B/Ziqx+sejm0wQba0=" + }, + "voting_power": "3", + "proposer_priority": null + } +"#; + +#[derive(Debug, Options)] +struct CliOptions { + #[options(help = "print this help and exit (--help CMD for command-specific help)")] + help: bool, + #[options(help = "provide detailed usage instructions")] + usage: bool, + #[options(help = "read input from STDIN (default: no)")] + read_stdin: bool, + + #[options(command)] + command: Option, +} + +#[derive(Debug, Options)] +enum Command { + #[options(help = "produce validator from identifier and other parameters")] + Validator(Validator), + #[options(help = "produce header from validator array and other parameters")] + Header(Header), + #[options(help = "produce commit from validator array and other parameters")] + Commit(Commit), +} + +fn run_command + Options, T: serde::Serialize>(cli: Opts, read_stdin: bool) { + let res = if read_stdin { + Opts::encode_with_stdin(&cli) + } else { + Opts::encode(&cli) + }; + match res { + Ok(res) => println!("{}", res), + Err(e) => { + println!("Error: {}\n", e); + println!("Supported parameters for this command are: "); + print_params(cli.self_usage()) + } + } +} + +fn print_params(options: &str) { + for line in options.lines().skip(1) { + eprintln!("{}", line); + } +} + +fn main() { + let opts = CliOptions::parse_args_default_or_exit(); + if opts.usage { + eprintln!("{}", USAGE); + std::process::exit(1); + } + match opts.command { + None => { + eprintln!("Produce tendermint datastructures for testing from minimal input\n"); + eprintln!("Please specify a command:"); + eprintln!("{}\n", CliOptions::command_list().unwrap()); + eprintln!("{}\n", CliOptions::usage()); + for cmd in CliOptions::command_list() + .unwrap() + .split('\n') + .map(|s| s.split_whitespace().next().unwrap()) + { + eprintln!("\n{} parameters:", cmd); + print_params(CliOptions::command_usage(cmd).unwrap()) + } + std::process::exit(1); + } + Some(Command::Validator(cli)) => run_command(cli, opts.read_stdin), + Some(Command::Header(cli)) => run_command(cli, opts.read_stdin), + Some(Command::Commit(cli)) => run_command(cli, opts.read_stdin), + } +} diff --git a/mbt-utils/src/commit.rs b/mbt-utils/src/commit.rs new file mode 100644 index 000000000..9797a5459 --- /dev/null +++ b/mbt-utils/src/commit.rs @@ -0,0 +1,115 @@ +use gumdrop::Options; +use serde::Deserialize; +use signatory::ed25519; +use signatory::ed25519::SIGNATURE_SIZE; +use signatory::signature::Signature as _; +use signatory::signature::Signer; +use signatory_dalek::Ed25519Signer; +use simple_error::*; + +use tendermint::signature::Signature; +use tendermint::vote::{Type, Vote}; +use tendermint::{amino_types, block, lite, vote}; + +use crate::header::Header; +use crate::helpers::*; +use crate::producer::Producer; + +#[derive(Debug, Options, Deserialize)] +pub struct Commit { + #[options(help = "header (required)", parse(try_from_str = "parse_as::
"))] + pub header: Option
, + #[options(help = "commit round (default: 1)")] + pub round: Option, +} + +impl Commit { + pub fn new(header: &Header) -> Self { + Commit { + header: Some(header.clone()), + round: None, + } + } + pub fn round(mut self, round: u64) -> Self { + self.round = Some(round); + self + } +} + +impl Producer for Commit { + fn parse_stdin() -> Result { + let commit = match parse_stdin_as::() { + Ok(input) => input, + Err(input) => Commit { + header: match parse_as::
(input.as_str()) { + Ok(header) => Some(header), + Err(e) => bail!("failed to read commit from input: {}", e), + }, + round: None, + }, + }; + Ok(commit) + } + + fn merge_with_default(&self, other: &Self) -> Self { + Commit { + header: choose_from(&self.header, &other.header), + round: choose_from(&self.round, &other.round), + } + } + + fn produce(&self) -> Result { + if self.header.is_none() { + bail!("header is missing") + } + let header = self.header.as_ref().unwrap(); + let block_header = header.produce()?; + let block_id = block::Id::new(lite::Header::hash(&block_header), None); + let sigs: Vec = header + .validators + .as_ref() + .unwrap() + .iter() + .enumerate() + .map(|(i, v)| { + let validator = v.produce().unwrap(); + let signer: Ed25519Signer = v.signer().unwrap(); + let vote = Vote { + vote_type: Type::Precommit, + height: block_header.height, + round: choose_or(self.round, 1), + block_id: Some(block_id.clone()), + timestamp: block_header.time, + validator_address: validator.address, + validator_index: i as u64, + signature: Signature::Ed25519( + ed25519::Signature::from_bytes(&[0_u8; SIGNATURE_SIZE]).unwrap(), + ), + }; + let signed_vote = vote::SignedVote::new( + amino_types::vote::Vote::from(&vote), + block_header.chain_id.as_str(), + validator.address, + Signature::Ed25519( + ed25519::Signature::from_bytes(&[0_u8; SIGNATURE_SIZE]).unwrap(), + ), + ); + let sign_bytes = signed_vote.sign_bytes(); + + block::CommitSig::BlockIDFlagCommit { + validator_address: validator.address, + timestamp: block_header.time, + signature: Signature::Ed25519(signer.try_sign(sign_bytes.as_slice()).unwrap()), + } + }) + .collect(); + + let commit = block::Commit { + height: block_header.height, + round: choose_or(self.round, 1), + block_id, // TODO do we need at least one part? //block::Id::new(hasher.hash_header(&block_header), None), // + signatures: block::CommitSigs::new(sigs), + }; + Ok(commit) + } +} diff --git a/mbt-utils/src/consensus.rs b/mbt-utils/src/consensus.rs new file mode 100644 index 000000000..ad63104bc --- /dev/null +++ b/mbt-utils/src/consensus.rs @@ -0,0 +1,18 @@ +use tendermint::{block, consensus, evidence, public_key::Algorithm}; + +/// Default consensus params modeled after Go code; but it's not clear how to go to a valid hash from here +pub fn default_consensus_params() -> consensus::Params { + consensus::Params { + block: block::Size { + max_bytes: 22020096, + max_gas: -1, // Tendetmint-go also has TimeIotaMs: 1000, // 1s + }, + evidence: evidence::Params { + max_age_num_blocks: 100000, + max_age_duration: evidence::Duration(std::time::Duration::new(48 * 3600, 0)), + }, + validator: consensus::params::ValidatorParams { + pub_key_types: vec![Algorithm::Ed25519], + }, + } +} diff --git a/mbt-utils/src/header.rs b/mbt-utils/src/header.rs new file mode 100644 index 000000000..30f3442cb --- /dev/null +++ b/mbt-utils/src/header.rs @@ -0,0 +1,110 @@ +use std::str::FromStr; + +use gumdrop::Options; +use serde::Deserialize; +use simple_error::*; + +use tendermint::block::header::Version; +use tendermint::lite::ValidatorSet; +use tendermint::{block, chain, validator, Time}; + +use crate::helpers::*; +use crate::producer::Producer; +use crate::validator::{produce_validators, Validator}; + +#[derive(Debug, Options, Deserialize, Clone)] +pub struct Header { + #[options( + help = "validators (required), encoded as array of 'validator' parameters", + parse(try_from_str = "parse_as::>") + )] + pub validators: Option>, + #[options( + help = "next validators (default: same as validators), encoded as array of 'validator' parameters", + parse(try_from_str = "parse_as::>") + )] + pub next_validators: Option>, + #[options(help = "block height (default: 1)")] + pub height: Option, + #[options(help = "time (default: now)")] + pub time: Option