From ec9e0bbddfc1896eaa4d5e8466d5a5fb465e3044 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 13:18:02 +1100 Subject: [PATCH] Allow test_harness to load validators from file Also adds a command to test_harness binary to generate validators --- .../beacon_chain/test_harness/.gitignore | 1 + .../beacon_chain/test_harness/Cargo.toml | 2 + .../test_harness/src/beacon_chain_harness.rs | 21 +++++-- .../beacon_chain_harness/generate_deposits.rs | 18 ++++-- .../load_deposits_from_file.rs | 38 ++++++++++++ .../beacon_chain/test_harness/src/bin.rs | 58 +++++++++++++++++++ .../beacon_chain/test_harness/src/prepare.rs | 35 +++++++++++ .../beacon_chain/test_harness/src/run_test.rs | 7 ++- .../test_harness/src/test_case.rs | 5 +- 9 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 beacon_node/beacon_chain/test_harness/.gitignore create mode 100644 beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs create mode 100644 beacon_node/beacon_chain/test_harness/src/prepare.rs diff --git a/beacon_node/beacon_chain/test_harness/.gitignore b/beacon_node/beacon_chain/test_harness/.gitignore new file mode 100644 index 000000000..5f605cba0 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/.gitignore @@ -0,0 +1 @@ +validators/ diff --git a/beacon_node/beacon_chain/test_harness/Cargo.toml b/beacon_node/beacon_chain/test_harness/Cargo.toml index bd7a58270..448934eb3 100644 --- a/beacon_node/beacon_chain/test_harness/Cargo.toml +++ b/beacon_node/beacon_chain/test_harness/Cargo.toml @@ -33,12 +33,14 @@ failure = "0.1" failure_derive = "0.1" fork_choice = { path = "../../../eth2/fork_choice" } hashing = { path = "../../../eth2/utils/hashing" } +int_to_bytes = { path = "../../../eth2/utils/int_to_bytes" } log = "0.4" env_logger = "0.6.0" rayon = "1.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +serde_yaml = "0.8" slot_clock = { path = "../../../eth2/utils/slot_clock" } ssz = { path = "../../../eth2/utils/ssz" } types = { path = "../../../eth2/types" } diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 95899e23b..3eabfcb1f 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -6,16 +6,20 @@ use db::{ MemoryDB, }; use fork_choice::BitwiseLMDGhost; -use generate_deposits::generate_deposits_with_random_keypairs; use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; use std::collections::HashSet; use std::iter::FromIterator; +use std::path::Path; use std::sync::Arc; use types::*; mod generate_deposits; +mod load_deposits_from_file; + +pub use generate_deposits::generate_deposits_with_deterministic_keypairs; +pub use load_deposits_from_file::load_deposits_from_file; /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read @@ -37,7 +41,7 @@ impl BeaconChainHarness { /// /// - A keypair, `BlockProducer` and `Attester` for each validator. /// - A new BeaconChain struct where the given validators are in the genesis. - pub fn new(spec: ChainSpec, validator_count: usize) -> Self { + pub fn new(spec: ChainSpec, validator_count: usize, validators_dir: Option<&Path>) -> Self { let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); @@ -49,8 +53,17 @@ impl BeaconChainHarness { block_hash: Hash256::zero(), }; - let (keypairs, initial_validator_deposits) = - generate_deposits_with_random_keypairs(validator_count, genesis_time, &spec); + let (keypairs, initial_validator_deposits) = if let Some(path) = validators_dir { + let keypairs_path = path.join("keypairs.yaml"); + let deposits_path = path.join("deposits.yaml"); + load_deposits_from_file( + validator_count, + &keypairs_path.as_path(), + &deposits_path.as_path(), + ) + } else { + generate_deposits_with_deterministic_keypairs(validator_count, genesis_time, &spec) + }; // Create the Beacon Chain let beacon_chain = Arc::new( diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs index 39924fb67..b07168df9 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs @@ -1,11 +1,16 @@ use bls::{create_proof_of_possession, get_withdrawal_credentials}; +use int_to_bytes::int_to_bytes48; use log::debug; use rayon::prelude::*; use types::*; -/// Generates `validator_count` deposits using randomly generated keypairs and some default specs -/// for the deposits. -pub fn generate_deposits_with_random_keypairs( +/// Generates `validator_count` deposits using keypairs where the secret key is the index of the +/// validator. +/// +/// For example, the first validator has a secret key of `int_to_bytes48(1)`, the second has +/// `int_to_bytes48(2)` and so on. (We skip `0` as it generates a weird looking public key and is +/// probably invalid). +pub fn generate_deposits_with_deterministic_keypairs( validator_count: usize, genesis_time: u64, spec: &ChainSpec, @@ -18,7 +23,12 @@ pub fn generate_deposits_with_random_keypairs( let keypairs: Vec = (0..validator_count) .collect::>() .par_iter() - .map(|_| Keypair::random()) + .map(|&i| { + let secret = int_to_bytes48(i as u64 + 1); + let sk = SecretKey::from_bytes(&secret).unwrap(); + let pk = PublicKey::from_secret_key(&sk); + Keypair { sk, pk } + }) .collect(); debug!( diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs new file mode 100644 index 000000000..9cba3d3c4 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs @@ -0,0 +1,38 @@ +use log::debug; +use serde_yaml; +use std::fs::File; +use std::path::Path; +use types::*; + +pub fn load_deposits_from_file( + validator_count: usize, + keypairs_path: &Path, + deposits_path: &Path, +) -> (Vec, Vec) { + debug!("Loading keypairs from file..."); + let keypairs_file = File::open(keypairs_path).unwrap(); + let mut keypairs: Vec = serde_yaml::from_reader(&keypairs_file).unwrap(); + + debug!("Loading deposits from file..."); + let deposits_file = File::open(deposits_path).unwrap(); + let mut deposits: Vec = serde_yaml::from_reader(&deposits_file).unwrap(); + + assert!( + keypairs.len() >= validator_count, + "Unable to load {} keypairs from file ({} available)", + validator_count, + keypairs.len() + ); + + assert!( + deposits.len() >= validator_count, + "Unable to load {} deposits from file ({} available)", + validator_count, + deposits.len() + ); + + keypairs.truncate(validator_count); + deposits.truncate(validator_count); + + (keypairs, deposits) +} diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 3769788d8..0a02264a3 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,8 +1,11 @@ use clap::{App, Arg, SubCommand}; use env_logger::{Builder, Env}; +use prepare::prepare; use run_test::run_test; +use types::ChainSpec; mod beacon_chain_harness; +mod prepare; mod run_test; mod test_case; mod validator_harness; @@ -17,12 +20,22 @@ fn main() { .arg( Arg::with_name("log") .long("log-level") + .short("l") .value_name("LOG_LEVEL") .help("Logging level.") .possible_values(&["error", "warn", "info", "debug", "trace"]) .default_value("debug") .required(true), ) + .arg( + Arg::with_name("spec") + .long("spec") + .short("s") + .value_name("SPECIFICATION") + .help("ChainSpec instantiation.") + .possible_values(&["foundation", "few_validators"]) + .default_value("foundation"), + ) .subcommand( SubCommand::with_name("run_test") .about("Executes a YAML test specification") @@ -32,6 +45,41 @@ fn main() { .value_name("FILE") .help("YAML file test_case.") .required(true), + ) + .arg( + Arg::with_name("validators_dir") + .long("validators-dir") + .short("v") + .value_name("VALIDATORS_DIR") + .help("A directory with validator deposits and keypair YAML."), + ), + ) + .subcommand( + SubCommand::with_name("prepare") + .about("Builds validator YAML files for faster tests.") + .arg( + Arg::with_name("validator_count") + .long("validator_count") + .short("n") + .value_name("VALIDATOR_COUNT") + .help("Number of validators to generate.") + .required(true), + ) + .arg( + Arg::with_name("genesis_time") + .long("genesis_time") + .short("t") + .value_name("GENESIS_TIME") + .help("Time for validator deposits.") + .required(true), + ) + .arg( + Arg::with_name("output_dir") + .long("output_dir") + .short("d") + .value_name("GENESIS_TIME") + .help("Output directory for generated YAML.") + .default_value("validators"), ), ) .get_matches(); @@ -40,7 +88,17 @@ fn main() { Builder::from_env(Env::default().default_filter_or(log_level)).init(); } + let spec = match matches.value_of("spec") { + Some("foundation") => ChainSpec::foundation(), + Some("few_validators") => ChainSpec::few_validators(), + _ => unreachable!(), // Has a default value, should always exist. + }; + if let Some(matches) = matches.subcommand_matches("run_test") { run_test(matches); } + + if let Some(matches) = matches.subcommand_matches("prepare") { + prepare(matches, &spec); + } } diff --git a/beacon_node/beacon_chain/test_harness/src/prepare.rs b/beacon_node/beacon_chain/test_harness/src/prepare.rs new file mode 100644 index 000000000..160b0f7ee --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/prepare.rs @@ -0,0 +1,35 @@ +use crate::beacon_chain_harness::generate_deposits_with_deterministic_keypairs; +use clap::{value_t, ArgMatches}; +use log::debug; +use serde_yaml; +use std::path::Path; +use std::{fs, fs::File}; +use types::*; + +const KEYPAIRS_FILE: &str = "keypairs.yaml"; +const DEPOSITS_FILE: &str = "deposits.yaml"; + +pub fn prepare(matches: &ArgMatches, spec: &ChainSpec) { + let validator_count = value_t!(matches.value_of("validator_count"), usize) + .expect("Validator count is required argument"); + let genesis_time = + value_t!(matches.value_of("genesis_time"), u64).expect("Genesis time is required argument"); + let output_dir = matches + .value_of("output_dir") + .expect("Output dir has a default value."); + + let (keypairs, deposits) = + generate_deposits_with_deterministic_keypairs(validator_count, genesis_time, &spec); + + debug!("Created keypairs and deposits, writing to file..."); + + fs::create_dir_all(Path::new(output_dir)).unwrap(); + + let keypairs_path = Path::new(output_dir).join(KEYPAIRS_FILE); + let keypairs_file = File::create(keypairs_path).unwrap(); + serde_yaml::to_writer(keypairs_file, &keypairs).unwrap(); + + let deposits_path = Path::new(output_dir).join(DEPOSITS_FILE); + let deposits_file = File::create(deposits_path).unwrap(); + serde_yaml::to_writer(deposits_file, &deposits).unwrap(); +} diff --git a/beacon_node/beacon_chain/test_harness/src/run_test.rs b/beacon_node/beacon_chain/test_harness/src/run_test.rs index 1a816afe0..51a993bd7 100644 --- a/beacon_node/beacon_chain/test_harness/src/run_test.rs +++ b/beacon_node/beacon_chain/test_harness/src/run_test.rs @@ -1,5 +1,6 @@ use crate::test_case::TestCase; use clap::ArgMatches; +use std::path::Path; use std::{fs::File, io::prelude::*}; use yaml_rust::YamlLoader; @@ -15,6 +16,10 @@ pub fn run_test(matches: &ArgMatches) { }; for doc in &docs { + let validators_dir = matches + .value_of("validators_dir") + .and_then(|dir_str| Some(Path::new(dir_str))); + // For each `test_cases` YAML in the document, build a `TestCase`, execute it and // assert that the execution result matches the test_case description. // @@ -29,7 +34,7 @@ pub fn run_test(matches: &ArgMatches) { // panics with a message. for test_case in doc["test_cases"].as_vec().unwrap() { let test_case = TestCase::from_yaml(test_case); - test_case.assert_result_valid(test_case.execute()) + test_case.assert_result_valid(test_case.execute(validators_dir)) } } } diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index b2709edfc..e7b2defe6 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -6,6 +6,7 @@ use beacon_chain::CheckPoint; use bls::{create_proof_of_possession, get_withdrawal_credentials}; use log::{info, warn}; use ssz::SignedRoot; +use std::path::Path; use types::*; use types::{ @@ -70,7 +71,7 @@ impl TestCase { /// Executes the test case, returning an `ExecutionResult`. #[allow(clippy::cyclomatic_complexity)] - pub fn execute(&self) -> ExecutionResult { + pub fn execute(&self, validators_dir: Option<&Path>) -> ExecutionResult { let spec = self.spec(); let validator_count = self.config.deposits_for_chain_start; let slots = self.config.num_slots; @@ -80,7 +81,7 @@ impl TestCase { validator_count ); - let mut harness = BeaconChainHarness::new(spec, validator_count); + let mut harness = BeaconChainHarness::new(spec, validator_count, validators_dir); info!("Starting simulation across {} slots...", slots);