From e1f6052d5eb34e837d7d08862291d9cd1829ea18 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 13 Sep 2019 18:49:39 -0400 Subject: [PATCH] Add unfinished pycli integration --- lcli/Cargo.toml | 1 + lcli/src/main.rs | 21 ++++++++ lcli/src/pycli.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 lcli/src/pycli.rs diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index b774d4d125..55bfc16544 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -17,3 +17,4 @@ simple_logger = "1.0" types = { path = "../eth2/types" } state_processing = { path = "../eth2/state_processing" } eth2_ssz = { path = "../eth2/utils/ssz" } +regex = "1.3" diff --git a/lcli/src/main.rs b/lcli/src/main.rs index 63f01c671b..87d670cb96 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -2,16 +2,20 @@ extern crate log; mod parse_hex; +mod pycli; mod transition_blocks; use clap::{App, Arg, SubCommand}; use parse_hex::run_parse_hex; +use pycli::run_pycli; 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}; +type LocalEthSpec = MinimalEthSpec; + fn main() { simple_logger::init().expect("logger should initialize"); @@ -111,6 +115,21 @@ fn main() { .help("SSZ encoded as 0x-prefixed hex"), ), ) + .subcommand( + SubCommand::with_name("pycli") + .about("TODO") + .version("0.1.0") + .author("Paul Hauner ") + .arg( + Arg::with_name("pycli-path") + .long("pycli-path") + .short("p") + .value_name("PATH") + .takes_value(true) + .default_value("../../pycli") + .help("Path to the pycli repository."), + ), + ) .get_matches(); match matches.subcommand() { @@ -157,6 +176,8 @@ fn main() { ("pretty-hex", Some(matches)) => { run_parse_hex(matches).unwrap_or_else(|e| error!("Failed to pretty print hex: {}", e)) } + ("pycli", Some(matches)) => run_pycli::(matches) + .unwrap_or_else(|e| error!("Failed to run pycli: {}", e)), (other, _) => error!("Unknown subcommand {}. See --help.", other), } } diff --git a/lcli/src/pycli.rs b/lcli/src/pycli.rs new file mode 100644 index 0000000000..4b1b328284 --- /dev/null +++ b/lcli/src/pycli.rs @@ -0,0 +1,123 @@ +use clap::ArgMatches; +use ssz::Decode; +use std::fs; +use std::path::PathBuf; +use std::process::Command; +use types::{BeaconState, EthSpec}; + +pub fn run_pycli(matches: &ArgMatches) -> Result<(), String> { + let cmd_path = matches + .value_of("pycli-path") + .ok_or_else(|| "No pycli-path supplied")?; + + let pycli = PyCli::new(cmd_path.to_string())?; + + let block_path = PathBuf::from("/tmp/trinity/block_16.ssz"); + let pre_state_path = PathBuf::from("/tmp/trinity/state_15.ssz"); + + pycli + .transition_blocks::(block_path, pre_state_path) + .map_err(|e| e.to_string())?; + + Ok(()) +} + +/* + * TODO: loading from file. + * +use regex::Regex; +use std::collections::HashMap; +use std::ffi::OsString; + +const BLOCK_PREFIX: &str = "block_"; +const PRE_PREFIX: &str = "state_pre_"; +const POST_PREFIX: &str = "state_post_"; + +struct Case { + pre: Option>, + post: Option>, + block: Option>, +} + +fn get_sets(dir: PathBuf) -> Result<(), String> { + let map: HashMap> = HashMap::new(); + + fs::read_dir(dir) + .map_err(|e| format!("Unable to read source directory: {:?}", e))? + .filter_map(Result::ok) + .map(|f| f.file_name().into_string()) + .filter_map(Result::ok) + .try_for_each(|filename| { + if filename.starts_with(BLOCK_PREFIX) { + let regex = Regex::new(r".*root0x(.........)") + .map_err(|e| format!("Failed to compile block regex: {:?}", e))?; + let captures = regex.captures(&filename). + // block + } else if filename.starts_with(PRE_PREFIX) { + dbg!("pre state"); + } else if filename.starts_with(POST_PREFIX) { + dbg!("post state"); + } else { + dbg!("unknown file"); + } + + Ok(()) + }) +} +*/ + +/// A wrapper around Danny Ryan's `pycli` utility: +/// +/// https://github.com/djrtwo/pycli +/// +/// Provides functions for testing consensus logic against the executable Python spec. +pub struct PyCli { + cmd_path: PathBuf, +} + +impl PyCli { + /// Create a new instance, parsing the given `cmd_path` as a canonical path. + pub fn new(cmd_path: String) -> Result { + Ok(Self { + cmd_path: fs::canonicalize(cmd_path) + .map_err(|e| format!("Failed to canonicalize pycli path: {:?}", e))?, + }) + } + + /// Performs block processing on the state at the given `pre_state_path`, using the block at + /// `block_path`. + /// + /// Returns an SSZ-encoded `BeaconState` on success. + pub fn transition_blocks( + &self, + block_path: PathBuf, + pre_state_path: PathBuf, + ) -> Result, String> { + let output = Command::new("python") + .current_dir(self.cmd_path.clone()) + .arg("pycli.py") + .arg("transition") + .arg("blocks") + .arg(format!("--pre={}", path_string(pre_state_path)?)) + .arg(path_string(block_path)?) + .output() + .map_err(|e| format!("Failed to run command: {:?}", e))?; + + if output.status.success() { + let state = BeaconState::from_ssz_bytes(&output.stdout) + .map_err(|e| format!("Failed to parse SSZ: {:?}", e))?; + Ok(state) + } else { + Err(format!("pycli returned an error: {:?}", output)) + } + } +} + +fn path_string(path: PathBuf) -> Result { + let path = + fs::canonicalize(path).map_err(|e| format!("Unable to canonicalize path: {:?}", e))?; + + path.into_os_string() + .into_string() + .map_err(|p| format!("Unable to stringify path: {:?}", p)) +}