From 49f6e7ac659f7daa70a6939f99332976e9bac2af Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Wed, 20 Mar 2019 16:23:33 +1100 Subject: [PATCH] Moved configuration around for validator client. - Custom datadir/server argument logic moved into configuration, out of main. - Updated the validator config directory structure, as per issue #253 suggestions - Removed the 'generate 3 random keys' function - Updated the README to reflect new structure - Just exit if there are no keys, don't generate any (this is for accounts_manager, in a separate commit). - Created a lib.rs file, so that the validator client configuration can be included by external crates. --- validator_client/Cargo.toml | 9 +++ validator_client/README.md | 29 +++++++-- validator_client/src/config.rs | 107 +++++++++++++++++++++++++-------- validator_client/src/lib.rs | 3 + validator_client/src/main.rs | 72 +++++----------------- 5 files changed, 136 insertions(+), 84 deletions(-) create mode 100644 validator_client/src/lib.rs diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index e9d66f6d3..327fab22b 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -4,6 +4,15 @@ version = "0.1.0" authors = ["Paul Hauner "] edition = "2018" +[[bin]] +name = "validator_client" +path = "src/main.rs" + +[lib] +name = "validator_client" +path = "src/lib.rs" + + [dependencies] block_proposer = { path = "../eth2/block_proposer" } bls = { path = "../eth2/utils/bls" } diff --git a/validator_client/README.md b/validator_client/README.md index aa84fe013..109d9f317 100644 --- a/validator_client/README.md +++ b/validator_client/README.md @@ -57,10 +57,31 @@ complete and return a block from the BN. ### Configuration -Presently the validator specifics (pubkey, etc.) are randomly generated and the -chain specification (slot length, BLS domain, etc.) are fixed to foundation -parameters. This is temporary and will be upgrade so these parameters can be -read from file (or initialized on first-boot). +Validator configurations are stored in a separate data directory from the main Beacon Node +binary. The validator data directory defaults to: +`$HOME/.lighthouse-validator`, however an alternative can be specified on the command line +with `--datadir`. + +The configuration directory structure looks like: +``` +~/.lighthouse-validator +└── validators + ├── 3cf4210d58ec + │   └── private.key + ├── 9b5d8b5be4e7 + │   └── private.key + └── cf6e07188f48 + └── private.key +``` + +Where the hex value of the directory is a portion of the validator public key. + +Validator keys must be generated using the separate `accounts_manager` binary, which will +place the keys into this directory structure in a format compatible with the validator client. + +The chain specification (slot length, BLS domain, etc.) defaults to foundation +parameters, however is temporary and an upgrade will allow these parameters to be +read from a file (or initialized on first-boot). ## BN Communication diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index d9d30c64d..2e109c69d 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,72 +1,131 @@ use bls::Keypair; +use clap::ArgMatches; use std::fs; use std::fs::File; use std::io::{Error, ErrorKind}; use std::path::PathBuf; use types::ChainSpec; +use bincode; /// Stores the core configuration for this validator instance. #[derive(Clone)] -pub struct ClientConfig { +pub struct ValidatorClientConfig { + /// The data directory, which stores all validator databases pub data_dir: PathBuf, - pub key_dir: PathBuf, + /// The directory where the individual validator configuration directories are stored. + pub validator_dir: PathBuf, + /// The server at which the Beacon Node can be contacted pub server: String, + /// The chain specification that we are connecting to pub spec: ChainSpec, } -const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse-validators"; -const DEFAULT_KEYSTORE_SUBDIR: &str = "keystore"; +const DEFAULT_VALIDATOR_DATADIR: &str = ".lighthouse-validator"; +const DEFAULT_VALIDATORS_SUBDIR: &str = "validators"; +const DEFAULT_PRIVATE_KEY_FILENAME: &str = "private.key"; -impl ClientConfig { - /// Build a new configuration from defaults. - pub fn default() -> Result { - let data_dir = { - let home = dirs::home_dir() - .ok_or(Error::new(ErrorKind::NotFound, "Unable to determine home directory."))?; - home.join(DEFAULT_LIGHTHOUSE_DIR) +impl ValidatorClientConfig { + /// Build a new configuration from defaults, which are overrided by arguments provided. + pub fn build_config(arguments: &ArgMatches) -> Result { + // Use the specified datadir, or default in the home directory + let data_dir: PathBuf = match arguments.value_of("datadir") { + Some(path) => PathBuf::from(path.to_string()), + None => { + let home = dirs::home_dir().ok_or_else(|| Error::new( + ErrorKind::NotFound, + "Unable to determine home directory.", + ))?; + home.join(DEFAULT_VALIDATOR_DATADIR) + } }; fs::create_dir_all(&data_dir)?; - let key_dir = data_dir.join(DEFAULT_KEYSTORE_SUBDIR); - fs::create_dir_all(&key_dir)?; + let validator_dir = data_dir.join(DEFAULT_VALIDATORS_SUBDIR); + fs::create_dir_all(&validator_dir)?; + + let server: String = match arguments.value_of("server") { + Some(srv) => { + //TODO: I don't think this parses correctly a server & port combo + srv.parse::() + .map_err(|e| Error::new(ErrorKind::InvalidInput, e))? + .to_string() + } + None => "localhost:50051".to_string(), + }; + + // TODO: Permit loading a custom spec from file. + let spec: ChainSpec = match arguments.value_of("spec") { + Some(spec_str) => { + match spec_str { + "foundation" => ChainSpec::foundation(), + "few_validators" => ChainSpec::few_validators(), + // Should be impossible due to clap's `possible_values(..)` function. + _ => unreachable!(), + } + } + None => ChainSpec::foundation(), + }; - let server = "localhost:50051".to_string(); - let spec = ChainSpec::foundation(); Ok(Self { data_dir, - key_dir, + validator_dir, server, spec, }) } - /// Try to load keys from key_dir, returning None if none are found or an error. + /// Try to load keys from validator_dir, returning None if none are found or an error. pub fn fetch_keys(&self) -> Result>, Error> { - let mut key_files = fs::read_dir(&self.key_dir)?.peekable(); + let mut validator_dirs = fs::read_dir(&self.validator_dir)?.peekable(); - if key_files.peek().is_none() { + // There are no validator directories. + if validator_dirs.peek().is_none() { return Ok(None); } let mut key_pairs: Vec = Vec::new(); - for key_filename in key_files { - let mut key_file = File::open(key_filename?.path())?; + for validator_dir_result in validator_dirs { + let validator_dir = validator_dir_result?; + + // Try to open the key file directly + // TODO skip keyfiles that are not found, and log the error instead of returning it. + let mut key_file = File::open(validator_dir.path().join(DEFAULT_PRIVATE_KEY_FILENAME))?; let key: Keypair = bincode::deserialize_from(&mut key_file) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + // TODO skip keyfile if it's not matched, and log the error instead of returning it. + let validator_directory_name = validator_dir.file_name().into_string().map_err(|_| { + Error::new( + ErrorKind::InvalidData, + "The filename cannot be parsed to a string.", + ) + })?; + if key.identifier() != validator_directory_name { + return Err(Error::new( + ErrorKind::InvalidData, + "The validator directory ID did not match the key found inside.", + )); + } + key_pairs.push(key); } Ok(Some(key_pairs)) } - pub fn save_key(&self, key: &Keypair) -> Result<(), Error> { - let key_path = self.key_dir.join(key.identifier() + ".key"); + /// Saves a keypair to a file inside the appropriate validator directory. Returns the saved path filename. + pub fn save_key(&self, key: &Keypair) -> Result { + let validator_config_path = self.validator_dir.join(key.identifier()); + let key_path = validator_config_path.join(DEFAULT_PRIVATE_KEY_FILENAME); + + fs::create_dir_all(&validator_config_path)?; + let mut key_file = File::create(&key_path)?; + bincode::serialize_into(&mut key_file, &key) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - Ok(()) + Ok(key_path) } } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs new file mode 100644 index 000000000..133ccd932 --- /dev/null +++ b/validator_client/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; + +pub use crate::config::ValidatorClientConfig; \ No newline at end of file diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 2f2903707..99523ef9f 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,24 +1,19 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; -use crate::config::ClientConfig; +use crate::config::ValidatorClientConfig; use block_proposer::{test_utils::LocalSigner, BlockProducer}; -use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; use protos::services_grpc::{BeaconBlockServiceClient, ValidatorServiceClient}; -use slog::{debug, error, info, o, Drain}; +use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; -use std::path::PathBuf; use std::sync::Arc; -use std::thread; -use types::ChainSpec; +use std::{thread, process, time}; mod block_producer_service; mod config; mod duties; -const NUMBER_OF_VALIDATOR_TEST_KEYS: u16 = 3; - fn main() { // Logging let decorator = slog_term::TermDecorator::new().build(); @@ -57,36 +52,11 @@ fn main() { ) .get_matches(); - let mut config = ClientConfig::default().expect("Unable to create a default configuration."); - - // Custom datadir - if let Some(dir) = matches.value_of("datadir") { - config.data_dir = PathBuf::from(dir.to_string()); - } - - // Custom server port - if let Some(server_str) = matches.value_of("server") { - if let Ok(addr) = server_str.parse::() { - config.server = addr.to_string(); - } else { - error!(log, "Invalid address"; "server" => server_str); - return; - } - } - - // TODO: Permit loading a custom spec from file. - // Custom spec - if let Some(spec_str) = matches.value_of("spec") { - match spec_str { - "foundation" => config.spec = ChainSpec::foundation(), - "few_validators" => config.spec = ChainSpec::few_validators(), - // Should be impossible due to clap's `possible_values(..)` function. - _ => unreachable!(), - }; - } + let config = ValidatorClientConfig::build_config(&matches) + .expect("Unable to build a configuration for the validator client."); // Log configuration - info!(log, ""; + info!(log, "Configuration parameters:"; "data_dir" => &config.data_dir.to_str(), "server" => &config.server); @@ -121,31 +91,21 @@ fn main() { let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); + let keypairs = config + .fetch_keys() + .expect("Encountered an error while fetching saved keys.") + .unwrap_or_else(|| { + error!(log, "No key pairs found in configuration, they must first be generated with: account_manager generate."); + // give the logger a chance to flush the error before exiting. + thread::sleep(time::Duration::from_millis(500)); + process::exit(1) + }); + /* * Start threads. */ let mut threads = vec![]; - let keypairs = config - .fetch_keys() - .expect("Encountered an error while fetching saved keys.") - .unwrap_or_else(|| { - // TODO: Key generation should occur in a separate binary - let mut k = Vec::new(); - info!( - log, - "No key pairs found, generating and saving 3 random key pairs." - ); - for _n in 0..NUMBER_OF_VALIDATOR_TEST_KEYS { - let keypair = Keypair::random(); - config - .save_key(&keypair) - .expect("Unable to save newly generated private key."); - debug!(log, "Keypair generated {:?}", keypair.identifier()); - k.push(keypair); - } - k - }); for keypair in keypairs { info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id());