From cce76f0bd2df2416827425518023634438a0a8c4 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 9 Sep 2019 01:55:14 -0400 Subject: [PATCH] Add block transition to cli_util --- tests/cli_util/Cargo.toml | 2 + tests/cli_util/src/main.rs | 102 ++++++++++++++++-------- tests/cli_util/src/transition_blocks.rs | 93 +++++++++++++++++++++ 3 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 tests/cli_util/src/transition_blocks.rs diff --git a/tests/cli_util/Cargo.toml b/tests/cli_util/Cargo.toml index 7690d5a878..b868f1541a 100644 --- a/tests/cli_util/Cargo.toml +++ b/tests/cli_util/Cargo.toml @@ -13,3 +13,5 @@ serde = "1.0" serde_yaml = "0.8" simple_logger = "1.0" types = { path = "../../eth2/types" } +state_processing = { path = "../../eth2/state_processing" } +eth2_ssz = { path = "../../eth2/utils/ssz" } diff --git a/tests/cli_util/src/main.rs b/tests/cli_util/src/main.rs index 330a0d171a..e03febca6d 100644 --- a/tests/cli_util/src/main.rs +++ b/tests/cli_util/src/main.rs @@ -1,10 +1,13 @@ #[macro_use] extern crate log; +mod transition_blocks; + use clap::{App, Arg, SubCommand}; use std::fs::File; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; +use transition_blocks::run_transition_blocks; use types::{test_utils::TestingBeaconStateBuilder, EthSpec, MainnetEthSpec, MinimalEthSpec}; fn main() { @@ -54,47 +57,78 @@ fn main() { .help("Output file for generated state."), ), ) + .subcommand( + SubCommand::with_name("transition-blocks") + .about("Performs a state transition given a pre-state and block") + .version("0.1.0") + .author("Paul Hauner ") + .arg( + Arg::with_name("pre-state") + .value_name("BEACON_STATE") + .takes_value(true) + .required(true) + .help("Path to a SSZ file of the pre-state."), + ) + .arg( + Arg::with_name("block") + .value_name("BEACON_BLOCK") + .takes_value(true) + .required(true) + .help("Path to a SSZ file of the block to apply to pre-state."), + ) + .arg( + Arg::with_name("output") + .value_name("SSZ_FILE") + .takes_value(true) + .required(true) + .default_value("./output.ssz") + .help("Path to output a SSZ file."), + ), + ) .get_matches(); - if let Some(matches) = matches.subcommand_matches("genesis_yaml") { - let num_validators = matches - .value_of("num_validators") - .expect("slog requires num_validators") - .parse::() - .expect("num_validators must be a valid integer"); + match matches.subcommand() { + ("genesis_yaml", Some(matches)) => { + let num_validators = matches + .value_of("num_validators") + .expect("slog requires num_validators") + .parse::() + .expect("num_validators must be a valid integer"); - let genesis_time = if let Some(string) = matches.value_of("genesis_time") { - string - .parse::() - .expect("genesis_time must be a valid integer") - } else { - warn!("No genesis time supplied via CLI, using the current time."); - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("should obtain time since unix epoch") - .as_secs() - }; + let genesis_time = if let Some(string) = matches.value_of("genesis_time") { + string + .parse::() + .expect("genesis_time must be a valid integer") + } else { + warn!("No genesis time supplied via CLI, using the current time."); + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("should obtain time since unix epoch") + .as_secs() + }; - let file = matches - .value_of("output_file") - .expect("slog requires output file") - .parse::() - .expect("output_file must be a valid path"); + let file = matches + .value_of("output_file") + .expect("slog requires output file") + .parse::() + .expect("output_file must be a valid path"); - info!( - "Creating genesis state with {} validators and genesis time {}.", - num_validators, genesis_time - ); + info!( + "Creating genesis state with {} validators and genesis time {}.", + num_validators, genesis_time + ); - match matches.value_of("spec").expect("spec is required by slog") { - "minimal" => genesis_yaml::(num_validators, genesis_time, file), - "mainnet" => genesis_yaml::(num_validators, genesis_time, file), - _ => unreachable!("guarded by slog possible_values"), - }; + match matches.value_of("spec").expect("spec is required by slog") { + "minimal" => genesis_yaml::(num_validators, genesis_time, file), + "mainnet" => genesis_yaml::(num_validators, genesis_time, file), + _ => unreachable!("guarded by slog possible_values"), + }; - info!("Genesis state YAML file created. Exiting successfully."); - } else { - error!("No subcommand supplied.") + info!("Genesis state YAML file created. Exiting successfully."); + } + ("transition-blocks", Some(matches)) => run_transition_blocks(matches) + .unwrap_or_else(|e| error!("Failed to transition blocks: {}", e)), + (other, _) => error!("Unknown subcommand supplied: {}", other), } } diff --git a/tests/cli_util/src/transition_blocks.rs b/tests/cli_util/src/transition_blocks.rs new file mode 100644 index 0000000000..d8b0974b44 --- /dev/null +++ b/tests/cli_util/src/transition_blocks.rs @@ -0,0 +1,93 @@ +use clap::ArgMatches; +use ssz::{Decode, Encode}; +use state_processing::{per_block_processing, per_slot_processing, BlockSignatureStrategy}; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; +use types::{BeaconBlock, BeaconState, EthSpec, MinimalEthSpec}; + +pub fn run_transition_blocks(matches: &ArgMatches) -> Result<(), String> { + let pre_state_path = matches + .value_of("pre-state") + .ok_or_else(|| "No pre-state file supplied".to_string())? + .parse::() + .map_err(|e| format!("Failed to parse pre-state path: {}", e))?; + + let block_path = matches + .value_of("block") + .ok_or_else(|| "No block file supplied".to_string())? + .parse::() + .map_err(|e| format!("Failed to parse block path: {}", e))?; + + let output_path = matches + .value_of("output") + .ok_or_else(|| "No output file supplied".to_string())? + .parse::() + .map_err(|e| format!("Failed to parse output path: {}", e))?; + + info!("Using minimal spec"); + info!("Pre-state path: {:?}", pre_state_path); + info!("Block path: {:?}", block_path); + + let pre_state: BeaconState = load_from_ssz(pre_state_path)?; + let block: BeaconBlock = load_from_ssz(block_path)?; + + let post_state = do_transition(pre_state, block)?; + + let mut output_file = File::create(output_path.clone()) + .map_err(|e| format!("Unable to create output file: {:?}", e))?; + + output_file + .write_all(&post_state.as_ssz_bytes()) + .map_err(|e| format!("Unable to write to output file: {:?}", e))?; + + /* + println!( + "{}", + serde_yaml::to_string(&post_state).expect("Should serialize state") + ); + */ + + Ok(()) +} + +fn do_transition( + mut pre_state: BeaconState, + block: BeaconBlock, +) -> Result, String> { + let spec = &T::default_spec(); + + pre_state + .build_all_caches(spec) + .map_err(|e| format!("Unable to build caches: {:?}", e))?; + + // Transition the parent state to the block slot. + for i in pre_state.slot.as_u64()..block.slot.as_u64() { + per_slot_processing(&mut pre_state, spec) + .map_err(|e| format!("Failed to advance slot on iteration {}: {:?}", i, e))?; + } + + pre_state + .build_all_caches(spec) + .map_err(|e| format!("Unable to build caches: {:?}", e))?; + + per_block_processing( + &mut pre_state, + &block, + None, + BlockSignatureStrategy::VerifyIndividual, + spec, + ) + .map_err(|e| format!("State transition failed: {:?}", e))?; + + Ok(pre_state) +} + +fn load_from_ssz(path: PathBuf) -> Result { + let mut file = + File::open(path.clone()).map_err(|e| format!("Unable to open file {:?}: {:?}", path, e))?; + let mut bytes = vec![]; + file.read_to_end(&mut bytes) + .map_err(|e| format!("Unable to read from file {:?}: {:?}", path, e))?; + T::from_ssz_bytes(&bytes).map_err(|e| format!("Ssz decode failed: {:?}", e)) +}