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

Turn mbt-utils crate into a library #411

Merged
merged 6 commits into from
Jul 6, 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
5 changes: 1 addition & 4 deletions light-client/src/components/scheduler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
store::LightStore,
types::{Height, Status},
};
use crate::{store::LightStore, types::Height};

use contracts::*;

Expand Down
2 changes: 1 addition & 1 deletion mbt-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ simple-error = "0.2.1"

[[bin]]
name = "mbt-tendermint-produce"
path = "src/tendermint-produce.rs"
path = "bin/tendermint-produce.rs"
119 changes: 119 additions & 0 deletions mbt-utils/bin/tendermint-produce.rs
Original file line number Diff line number Diff line change
@@ -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<Command>,
}

#[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<Opts: Producer<T> + 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),
}
}
115 changes: 115 additions & 0 deletions mbt-utils/src/commit.rs
Original file line number Diff line number Diff line change
@@ -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::<Header>"))]
pub header: Option<Header>,
#[options(help = "commit round (default: 1)")]
pub round: Option<u64>,
}

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<block::Commit> for Commit {
fn parse_stdin() -> Result<Self, SimpleError> {
let commit = match parse_stdin_as::<Commit>() {
Ok(input) => input,
Err(input) => Commit {
header: match parse_as::<Header>(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<block::Commit, SimpleError> {
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<block::CommitSig> = 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)
}
}
18 changes: 18 additions & 0 deletions mbt-utils/src/consensus.rs
Original file line number Diff line number Diff line change
@@ -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],
},
}
}
110 changes: 110 additions & 0 deletions mbt-utils/src/header.rs
Original file line number Diff line number Diff line change
@@ -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::<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 = "block height (default: 1)")]
pub height: Option<u64>,
#[options(help = "time (default: now)")]
pub time: Option<Time>,
}

impl Header {
pub fn new(validators: &[Validator]) -> Self {
Header {
validators: Some(validators.to_vec()),
next_validators: None,
height: None,
time: None,
}
}
pub fn next_validators(mut self, vals: &[Validator]) -> Self {
self.next_validators = Some(vals.to_vec());
self
}
pub fn height(mut self, height: u64) -> Self {
self.height = Some(height);
self
}
pub fn time(mut self, time: Time) -> Self {
self.time = Some(time);
self
}
}

impl Producer<block::Header> for Header {
fn parse_stdin() -> Result<Self, SimpleError> {
let header = match parse_stdin_as::<Header>() {
Ok(input) => input,
Err(input) => Header {
validators: match parse_as::<Vec<Validator>>(input.as_str()) {
Ok(vals) => Some(vals),
Err(e) => bail!("failed to read header from input: {}", e),
},
next_validators: None,
height: None,
time: None,
},
};
Ok(header)
}

fn merge_with_default(&self, other: &Self) -> Self {
Header {
validators: choose_from(&self.validators, &other.validators),
next_validators: choose_from(&self.next_validators, &other.next_validators),
height: choose_from(&self.height, &other.height),
time: choose_from(&self.time, &other.time),
}
}

fn produce(&self) -> Result<block::Header, SimpleError> {
if self.validators.is_none() {
bail!("validator array is missing")
}
let vals = produce_validators(&self.validators.as_ref().unwrap())?;
let valset = validator::Set::new(vals.clone());
let next_valset = match &self.next_validators {
Some(next_vals) => validator::Set::new(produce_validators(next_vals)?),
None => valset.clone(),
};
let header = block::Header {
version: Version { block: 0, app: 0 },
chain_id: chain::Id::from_str("test-chain-01").unwrap(),
height: block::Height(choose_or(self.height, 1)),
time: choose_or(self.time, Time::now()),
last_block_id: None,
last_commit_hash: None,
data_hash: None,
validators_hash: valset.hash(),
next_validators_hash: next_valset.hash(), // hasher.hash_validator_set(&next_valset), // next_valset.hash(),
consensus_hash: valset.hash(), //hasher.hash_validator_set(&valset), // TODO: currently not clear how to produce a valid hash
app_hash: vec![],
last_results_hash: None,
evidence_hash: None,
proposer_address: vals[0].address,
};
Ok(header)
}
}
Loading