mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-06 18:21:45 +00:00
Move lcli out of the tests dir
This commit is contained in:
181
lcli/src/main.rs
Normal file
181
lcli/src/main.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod parse_hex;
|
||||
mod transition_blocks;
|
||||
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use parse_hex::run_parse_hex;
|
||||
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() {
|
||||
simple_logger::init().expect("logger should initialize");
|
||||
|
||||
let matches = App::new("Lighthouse CLI Tool")
|
||||
.version("0.1.0")
|
||||
.author("Paul Hauner <paul@sigmaprime.io>")
|
||||
.about(
|
||||
"Performs various testing-related tasks, modelled after zcli. \
|
||||
by @protolambda.",
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("genesis_yaml")
|
||||
.about("Generates a genesis YAML file")
|
||||
.version("0.1.0")
|
||||
.author("Paul Hauner <paul@sigmaprime.io>")
|
||||
.arg(
|
||||
Arg::with_name("num_validators")
|
||||
.short("n")
|
||||
.value_name("INTEGER")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Number of initial validators."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("genesis_time")
|
||||
.short("g")
|
||||
.value_name("INTEGER")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Eth2 genesis time (seconds since UNIX epoch)."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("spec")
|
||||
.short("s")
|
||||
.value_name("STRING")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.possible_values(&["minimal", "mainnet"])
|
||||
.default_value("minimal")
|
||||
.help("Eth2 genesis time (seconds since UNIX epoch)."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_file")
|
||||
.short("f")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.default_value("./genesis_state.yaml")
|
||||
.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 <paul@sigmaprime.io>")
|
||||
.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."),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("pretty-hex")
|
||||
.about("Parses SSZ encoded as ASCII 0x-prefixed hex")
|
||||
.version("0.1.0")
|
||||
.author("Paul Hauner <paul@sigmaprime.io>")
|
||||
.arg(
|
||||
Arg::with_name("type")
|
||||
.value_name("TYPE")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.possible_values(&["block"])
|
||||
.help("The schema of the supplied SSZ."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("hex_ssz")
|
||||
.value_name("HEX")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("SSZ encoded as 0x-prefixed hex"),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
match matches.subcommand() {
|
||||
("genesis_yaml", Some(matches)) => {
|
||||
let num_validators = matches
|
||||
.value_of("num_validators")
|
||||
.expect("slog requires num_validators")
|
||||
.parse::<usize>()
|
||||
.expect("num_validators must be a valid integer");
|
||||
|
||||
let genesis_time = if let Some(string) = matches.value_of("genesis_time") {
|
||||
string
|
||||
.parse::<u64>()
|
||||
.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::<PathBuf>()
|
||||
.expect("output_file must be a valid path");
|
||||
|
||||
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::<MinimalEthSpec>(num_validators, genesis_time, file),
|
||||
"mainnet" => genesis_yaml::<MainnetEthSpec>(num_validators, genesis_time, file),
|
||||
_ => unreachable!("guarded by slog possible_values"),
|
||||
};
|
||||
|
||||
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)),
|
||||
("pretty-hex", Some(matches)) => {
|
||||
run_parse_hex(matches).unwrap_or_else(|e| error!("Failed to pretty print hex: {}", e))
|
||||
}
|
||||
(other, _) => error!("Unknown subcommand {}. See --help.", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a genesis state and writes it to a YAML file.
|
||||
fn genesis_yaml<T: EthSpec>(validator_count: usize, genesis_time: u64, output: PathBuf) {
|
||||
let spec = &T::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<T> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec);
|
||||
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
state.genesis_time = genesis_time;
|
||||
|
||||
info!("Generated state root: {:?}", state.canonical_root());
|
||||
|
||||
info!("Writing genesis state to {:?}", output);
|
||||
|
||||
let file = File::create(output.clone())
|
||||
.unwrap_or_else(|e| panic!("unable to create file: {:?}. Error: {:?}", output, e));
|
||||
serde_yaml::to_writer(file, &state).expect("should be able to serialize BeaconState");
|
||||
}
|
||||
43
lcli/src/parse_hex.rs
Normal file
43
lcli/src/parse_hex.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use clap::ArgMatches;
|
||||
use serde::Serialize;
|
||||
use ssz::Decode;
|
||||
use types::{BeaconBlock, BeaconState, MinimalEthSpec};
|
||||
|
||||
pub fn run_parse_hex(matches: &ArgMatches) -> Result<(), String> {
|
||||
let type_str = matches
|
||||
.value_of("type")
|
||||
.ok_or_else(|| "No type supplied".to_string())?;
|
||||
let mut hex: String = matches
|
||||
.value_of("hex_ssz")
|
||||
.ok_or_else(|| "No hex ssz supplied".to_string())?
|
||||
.to_string();
|
||||
|
||||
if hex.starts_with("0x") {
|
||||
hex = hex[2..].to_string();
|
||||
}
|
||||
|
||||
let hex = hex::decode(&hex).map_err(|e| format!("Failed to parse hex: {:?}", e))?;
|
||||
|
||||
info!("Using minimal spec");
|
||||
info!("Type: {:?}", type_str);
|
||||
|
||||
match type_str.as_ref() {
|
||||
"block" => decode_and_print::<BeaconBlock<MinimalEthSpec>>(&hex)?,
|
||||
"state" => decode_and_print::<BeaconState<MinimalEthSpec>>(&hex)?,
|
||||
other => return Err(format!("Unknown type: {}", other)),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_and_print<T: Decode + Serialize>(bytes: &[u8]) -> Result<(), String> {
|
||||
let item = T::from_ssz_bytes(&bytes).map_err(|e| format!("Ssz decode failed: {:?}", e))?;
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
serde_yaml::to_string(&item)
|
||||
.map_err(|e| format!("Unable to write object to YAML: {:?}", e))?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
93
lcli/src/transition_blocks.rs
Normal file
93
lcli/src/transition_blocks.rs
Normal file
@@ -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::<PathBuf>()
|
||||
.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::<PathBuf>()
|
||||
.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::<PathBuf>()
|
||||
.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<MinimalEthSpec> = load_from_ssz(pre_state_path)?;
|
||||
let block: BeaconBlock<MinimalEthSpec> = 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<T: EthSpec>(
|
||||
mut pre_state: BeaconState<T>,
|
||||
block: BeaconBlock<T>,
|
||||
) -> Result<BeaconState<T>, 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<T: Decode>(path: PathBuf) -> Result<T, String> {
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user