From 3487b16ce58ef6802a7157bb43483beadc9d6600 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 8 Jun 2019 20:21:50 -0400 Subject: [PATCH] Add `eth2_config` crate, integrate into val client --- Cargo.toml | 1 + beacon_node/Cargo.toml | 1 + beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/client_config.rs | 6 +- beacon_node/client/src/lib.rs | 1 - beacon_node/src/main.rs | 113 +++++-------- eth2/utils/eth2_config/Cargo.toml | 13 ++ .../utils/eth2_config/src/lib.rs | 44 ++++- validator_client/Cargo.toml | 4 + validator_client/eth2_config.toml | 47 ++++++ validator_client/src/config.rs | 43 +++-- validator_client/src/main.rs | 156 ++++++++++++++++-- validator_client/src/service.rs | 65 +++++--- 13 files changed, 366 insertions(+), 129 deletions(-) create mode 100644 eth2/utils/eth2_config/Cargo.toml rename beacon_node/client/src/eth2_config.rs => eth2/utils/eth2_config/src/lib.rs (54%) create mode 100644 validator_client/eth2_config.toml diff --git a/Cargo.toml b/Cargo.toml index e9acb2be4..43c13ac8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "eth2/utils/cached_tree_hash", "eth2/utils/compare_fields", "eth2/utils/compare_fields_derive", + "eth2/utils/eth2_config", "eth2/utils/fixed_len_vec", "eth2/utils/hashing", "eth2/utils/honey-badger-split", diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 77c9844aa..3edbf636d 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] dirs = "1.0.3" +eth2_config = { path = "../eth2/utils/eth2_config" } types = { path = "../eth2/types" } toml = "^0.5" store = { path = "./store" } diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 6da832c33..2b6f44e94 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -14,6 +14,7 @@ fork_choice = { path = "../../eth2/fork_choice" } prometheus = "^0.6" types = { path = "../../eth2/types" } tree_hash = { path = "../../eth2/utils/tree_hash" } +eth2_config = { path = "../../eth2/utils/eth2_config" } slot_clock = { path = "../../eth2/utils/slot_clock" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index b9caff9f8..166725b61 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; /// The core configuration of a Lighthouse beacon node. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClientConfig { - pub data_dir: String, + pub data_dir: PathBuf, pub db_type: String, db_name: String, pub network: network::NetworkConfig, @@ -19,7 +19,7 @@ pub struct ClientConfig { impl Default for ClientConfig { fn default() -> Self { Self { - data_dir: ".lighthouse".to_string(), + data_dir: PathBuf::from(".lighthouse"), db_type: "disk".to_string(), db_name: "chain_db".to_string(), // Note: there are no default bootnodes specified. @@ -51,7 +51,7 @@ impl ClientConfig { /// invalid. pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { if let Some(dir) = args.value_of("datadir") { - self.data_dir = dir.to_string(); + self.data_dir = PathBuf::from(dir); }; if let Some(dir) = args.value_of("db") { diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 67aff3342..92ed6e022 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -3,7 +3,6 @@ extern crate slog; mod beacon_chain_types; mod client_config; pub mod error; -mod eth2_config; pub mod notifier; use beacon_chain::BeaconChain; diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index ef47458c3..20217049d 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -2,12 +2,12 @@ extern crate slog; mod run; -use clap::{App, Arg}; +use clap::{App, Arg, ArgMatches}; use client::{ClientConfig, Eth2Config}; +use eth2_config::{read_from_file, write_to_file}; use slog::{crit, o, Drain}; use std::fs; -use std::fs::File; -use std::io::prelude::*; +use std::path::PathBuf; pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; @@ -105,7 +105,8 @@ fn main() { Arg::with_name("spec-constants") .long("spec-constants") .value_name("TITLE") - .help("The title of the spec constants for chain config..") + .short("s") + .help("The title of the spec constants for chain config.") .takes_value(true) .possible_values(&["mainnet", "minimal"]) .default_value("minimal"), @@ -113,34 +114,44 @@ fn main() { .arg( Arg::with_name("recent-genesis") .long("recent-genesis") + .short("r") .help("When present, genesis will be within 30 minutes prior. Only for testing"), ) .get_matches(); - // Attempt to lead the `ClientConfig` from disk. If it fails, write - let mut client_config = match read_from_file::( - matches.value_of("data_dir"), - CLIENT_CONFIG_FILENAME, - ) { + let data_dir = match get_data_dir(&matches) { + Ok(dir) => dir, + Err(e) => { + crit!(logger, "Failed to initialize data dir"; "error" => format!("{:?}", e)); + return; + } + }; + + let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME); + + // Attempt to lead the `ClientConfig` from disk. + // + // If file doesn't exist, create a new, default one. + let mut client_config = match read_from_file::(client_config_path.clone()) { Ok(Some(c)) => c, Ok(None) => { let default = ClientConfig::default(); - if let Err(e) = write_to_file(matches.value_of("data_dir"), CLIENT_CONFIG_FILENAME, &default) { + if let Err(e) = write_to_file(client_config_path, &default) { crit!(logger, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e)); return; } default - }, + } Err(e) => { crit!(logger, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e)); return; } }; - if let Some(data_dir) = matches.value_of("data_dir") { - client_config.data_dir = data_dir.to_string(); - } + // Ensure the `data_dir` in the config matches that supplied to the CLI. + client_config.data_dir = data_dir.clone(); + // Update the client config with any CLI args. match client_config.apply_cli_args(&matches) { Ok(()) => (), Err(s) => { @@ -149,10 +160,12 @@ fn main() { } }; - let mut eth2_config = match read_from_file::( - matches.value_of("data_dir"), - ETH2_CONFIG_FILENAME, - ) { + let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME); + + // Attempt to load the `Eth2Config` from file. + // + // If the file doesn't exist, create a default one depending on the CLI flags. + let mut eth2_config = match read_from_file::(eth2_config_path.clone()) { Ok(Some(c)) => c, Ok(None) => { let default = match matches.value_of("spec-constants") { @@ -160,7 +173,7 @@ fn main() { Some("minimal") => Eth2Config::minimal(), _ => unreachable!(), // Guarded by slog. }; - if let Err(e) = write_to_file(matches.value_of("data_dir"), ETH2_CONFIG_FILENAME, &default) { + if let Err(e) = write_to_file(eth2_config_path, &default) { crit!(logger, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e)); return; } @@ -172,6 +185,7 @@ fn main() { } }; + // Update the eth2 config with any CLI flags. match eth2_config.apply_cli_args(&matches) { Ok(()) => (), Err(s) => { @@ -186,59 +200,14 @@ fn main() { } } -/// Write a configuration to file. -fn write_to_file(data_dir: Option<&str>, config_filename: &str, config: &T) -> Result<(), String> -where - T: Default + serde::de::DeserializeOwned + serde::Serialize, -{ - let data_dir = data_dir.unwrap_or_else(|| DEFAULT_DATA_DIR); - - let path = dirs::home_dir() - .ok_or_else(|| "Unable to locate home directory")? - .join(&data_dir); - fs::create_dir_all(&path).map_err(|_| "Unable to open data_dir")?; - - if let Ok(mut file) = File::create(path.join(config_filename)) { - let toml_encoded = toml::to_string(&config).map_err(|e| { - format!( - "Failed to write configuration to {}. Error: {:?}", - config_filename, e - ) - })?; - file.write_all(toml_encoded.as_bytes()) - .expect(&format!("Unable to write to {}", config_filename)); - } - - Ok(()) -} - -/// Loads a `ClientConfig` from file. If unable to load from file, generates a default -/// configuration and saves that as a sample file. -fn read_from_file(data_dir: Option<&str>, config_filename: &str) -> Result, String> -where - T: Default + serde::de::DeserializeOwned + serde::Serialize, -{ - let data_dir = data_dir.unwrap_or_else(|| DEFAULT_DATA_DIR); - - let path = dirs::home_dir() - .ok_or_else(|| "Unable to locate home directory")? - .join(&data_dir); - fs::create_dir_all(&path).map_err(|_| "Unable to open data_dir")?; - - if let Ok(mut file) = File::open(path.join(config_filename)) { - let mut contents = String::new(); - file.read_to_string(&mut contents).map_err(|e| { - format!( - "Unable to read existing {}. Error: {:?}", - config_filename, e - ) - })?; - - let config = toml::from_str(&contents) - .map_err(|e| format!("Unable to parse {}: {:?}", config_filename, e))?; - - Ok(Some(config)) +fn get_data_dir(args: &ArgMatches) -> Result { + if let Some(data_dir) = args.value_of("data_dir") { + Ok(PathBuf::from(data_dir)) } else { - Ok(None) + let path = dirs::home_dir() + .ok_or_else(|| "Unable to locate home directory")? + .join(&DEFAULT_DATA_DIR); + fs::create_dir_all(&path).map_err(|_| "Unable to create data_dir")?; + Ok(path) } } diff --git a/eth2/utils/eth2_config/Cargo.toml b/eth2/utils/eth2_config/Cargo.toml new file mode 100644 index 000000000..5af385e2d --- /dev/null +++ b/eth2/utils/eth2_config/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "eth2_config" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +[dependencies] +clap = "2.32.0" +dirs = "1.0.3" +serde = "1.0" +serde_derive = "1.0" +toml = "^0.5" +types = { path = "../../types" } diff --git a/beacon_node/client/src/eth2_config.rs b/eth2/utils/eth2_config/src/lib.rs similarity index 54% rename from beacon_node/client/src/eth2_config.rs rename to eth2/utils/eth2_config/src/lib.rs index b16e8729e..df4229629 100644 --- a/beacon_node/client/src/eth2_config.rs +++ b/eth2/utils/eth2_config/src/lib.rs @@ -1,5 +1,8 @@ use clap::ArgMatches; use serde_derive::{Deserialize, Serialize}; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; use std::time::SystemTime; use types::ChainSpec; @@ -42,7 +45,7 @@ impl Eth2Config { /// Returns an error if arguments are obviously invalid. May succeed even if some values are /// invalid. pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { - if args.is_present("recent_genesis") { + if args.is_present("recent-genesis") { self.spec.genesis_time = recent_genesis_time() } @@ -62,3 +65,42 @@ fn recent_genesis_time() -> u64 { // genesis is now the last 30 minute block. now - secs_after_last_period } + +/// Write a configuration to file. +pub fn write_to_file(path: PathBuf, config: &T) -> Result<(), String> +where + T: Default + serde::de::DeserializeOwned + serde::Serialize, +{ + if let Ok(mut file) = File::create(path.clone()) { + let toml_encoded = toml::to_string(&config).map_err(|e| { + format!( + "Failed to write configuration to {:?}. Error: {:?}", + path, e + ) + })?; + file.write_all(toml_encoded.as_bytes()) + .expect(&format!("Unable to write to {:?}", path)); + } + + Ok(()) +} + +/// Loads a `ClientConfig` from file. If unable to load from file, generates a default +/// configuration and saves that as a sample file. +pub fn read_from_file(path: PathBuf) -> Result, String> +where + T: Default + serde::de::DeserializeOwned + serde::Serialize, +{ + if let Ok(mut file) = File::open(path.clone()) { + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|e| format!("Unable to read {:?}. Error: {:?}", path, e))?; + + let config = toml::from_str(&contents) + .map_err(|e| format!("Unable to parse {:?}: {:?}", path, e))?; + + Ok(Some(config)) + } else { + Ok(None) + } +} diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 559460c8b..06ee02f53 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -15,6 +15,7 @@ path = "src/lib.rs" [dependencies] bls = { path = "../eth2/utils/bls" } ssz = { path = "../eth2/utils/ssz" } +eth2_config = { path = "../eth2/utils/eth2_config" } tree_hash = { path = "../eth2/utils/tree_hash" } clap = "2.32.0" dirs = "1.0.3" @@ -23,11 +24,14 @@ protobuf = "2.0.2" protos = { path = "../protos" } slot_clock = { path = "../eth2/utils/slot_clock" } types = { path = "../eth2/types" } +serde = "1.0" +serde_derive = "1.0" slog = "^2.2.3" slog-term = "^2.4.0" slog-async = "^2.3.0" tokio = "0.1.18" tokio-timer = "0.2.10" +toml = "^0.5" error-chain = "0.12.0" bincode = "^1.1.2" futures = "0.1.25" diff --git a/validator_client/eth2_config.toml b/validator_client/eth2_config.toml new file mode 100644 index 000000000..49d4e1bd3 --- /dev/null +++ b/validator_client/eth2_config.toml @@ -0,0 +1,47 @@ +spec_constants = "minimal" + +[spec] +target_committee_size = 1 +max_indices_per_attestation = 4096 +min_per_epoch_churn_limit = 4 +churn_limit_quotient = 65536 +base_rewards_per_epoch = 5 +shuffle_round_count = 10 +deposit_contract_tree_depth = 32 +min_deposit_amount = 1000000000 +max_effective_balance = 32000000000 +ejection_balance = 16000000000 +effective_balance_increment = 1000000000 +genesis_slot = 0 +zero_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +bls_withdrawal_prefix_byte = "0x00" +genesis_time = 4294967295 +seconds_per_slot = 6 +min_attestation_inclusion_delay = 4 +min_seed_lookahead = 1 +activation_exit_delay = 4 +slots_per_eth1_voting_period = 1024 +slots_per_historical_root = 8192 +min_validator_withdrawability_delay = 256 +persistent_committee_period = 2048 +max_crosslink_epochs = 64 +min_epochs_to_inactivity_penalty = 4 +base_reward_quotient = 32 +whistleblowing_reward_quotient = 512 +proposer_reward_quotient = 8 +inactivity_penalty_quotient = 33554432 +min_slashing_penalty_quotient = 32 +max_proposer_slashings = 16 +max_attester_slashings = 1 +max_attestations = 128 +max_deposits = 16 +max_voluntary_exits = 16 +max_transfers = 0 +domain_beacon_proposer = 0 +domain_randao = 1 +domain_attestation = 2 +domain_deposit = 3 +domain_voluntary_exit = 4 +domain_transfer = 5 +boot_nodes = ["/ip4/127.0.0.1/tcp/9000"] +chain_id = 2 diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index b6e2c5bb5..46ceaaf80 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,22 +1,22 @@ use bincode; use bls::Keypair; use clap::ArgMatches; +use serde_derive::{Deserialize, Serialize}; use slog::{debug, error, info}; use std::fs; use std::fs::File; use std::io::{Error, ErrorKind}; use std::path::PathBuf; -use types::{ChainSpec, EthSpec, MainnetEthSpec, MinimalEthSpec}; +use types::{EthSpec, MainnetEthSpec}; /// Stores the core configuration for this validator instance. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct Config { /// The data directory, which stores all validator databases pub data_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, + /// The number of slots per epoch. pub slots_per_epoch: u64, } @@ -25,25 +25,33 @@ const DEFAULT_PRIVATE_KEY_FILENAME: &str = "private.key"; impl Default for Config { /// Build a new configuration from defaults. fn default() -> Self { - let data_dir = { - let home = dirs::home_dir().expect("Unable to determine home directory."); - home.join(".lighthouse-validator") - }; - - let server = "localhost:5051".to_string(); - - let spec = MainnetEthSpec::default_spec(); - Self { - data_dir, - server, - spec, + data_dir: PathBuf::from(".lighthouse-validator"), + server: "localhost:5051".to_string(), slots_per_epoch: MainnetEthSpec::slots_per_epoch(), } } } impl Config { + /// Apply the following arguments to `self`, replacing values if they are specified in `args`. + /// + /// Returns an error if arguments are obviously invalid. May succeed even if some values are + /// invalid. + pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { + if let Some(datadir) = args.value_of("datadir") { + self.data_dir = PathBuf::from(datadir); + }; + + if let Some(srv) = args.value_of("server") { + self.server = srv.to_string(); + }; + + Ok(()) + // + } + + /* /// Build a new configuration from defaults, which are overrided by arguments provided. pub fn parse_args(args: &ArgMatches, log: &slog::Logger) -> Result { let mut config = Config::default(); @@ -80,12 +88,13 @@ impl Config { Ok(config) } + */ /// Try to load keys from validator_dir, returning None if none are found or an error. #[allow(dead_code)] pub fn fetch_keys(&self, log: &slog::Logger) -> Option> { let key_pairs: Vec = fs::read_dir(&self.data_dir) - .unwrap() + .ok()? .filter_map(|validator_dir| { let validator_dir = validator_dir.ok()?; diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index d755db4d2..44dede100 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -7,11 +7,19 @@ mod service; mod signer; use crate::config::Config as ValidatorClientConfig; +use std::fs; use crate::service::Service as ValidatorService; -use clap::{App, Arg}; +use clap::{App, Arg, ArgMatches}; +use eth2_config::{read_from_file, write_to_file, Eth2Config}; use protos::services_grpc::ValidatorServiceClient; -use slog::{error, info, o, Drain}; -use types::Keypair; +use slog::{crit, error, info, o, Drain}; +use std::path::PathBuf; +use types::{Keypair, MainnetEthSpec, MinimalEthSpec}; + +pub const DEFAULT_SPEC: &str = "minimal"; +pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator"; +pub const CLIENT_CONFIG_FILENAME: &str = "client_config.toml"; +pub const ETH2_CONFIG_FILENAME: &str = "eth2_config.toml"; fn main() { // Logging @@ -30,7 +38,16 @@ fn main() { .long("datadir") .value_name("DIR") .help("Data directory for keys and databases.") - .takes_value(true), + .takes_value(true) + ) + .arg( + Arg::with_name("eth-config") + .long("eth-config") + .short("e") + .value_name("DIR") + .help(&format!("Directory containing {}.", ETH2_CONFIG_FILENAME)) + .takes_value(true) + .default_value(ETH2_CONFIG_FILENAME), ) .arg( Arg::with_name("server") @@ -40,24 +57,139 @@ fn main() { .takes_value(true), ) .arg( - Arg::with_name("spec") - .long("spec") - .value_name("spec") + Arg::with_name("spec-constants") + .long("spec-constants") + .value_name("TITLE") .short("s") - .help("Configuration of Beacon Chain") + .help("The title of the spec constants for chain config.") .takes_value(true) .possible_values(&["mainnet", "minimal"]) .default_value("minimal"), ) .get_matches(); - let config = ValidatorClientConfig::parse_args(&matches, &log) - .expect("Unable to build a configuration for the validator client."); + let data_dir = match get_data_dir(&matches) { + Ok(dir) => dir, + Err(e) => { + crit!(log, "Failed to initialize data dir"; "error" => format!("{:?}", e)); + return + } + }; + + let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME); + + // Attempt to lead the `ClientConfig` from disk. + // + // If file doesn't exist, create a new, default one. + let mut client_config = match read_from_file::( + client_config_path.clone(), + ) { + Ok(Some(c)) => c, + Ok(None) => { + let default = ValidatorClientConfig::default(); + if let Err(e) = write_to_file(client_config_path.clone(), &default) { + crit!(log, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e)); + return; + } + default + } + Err(e) => { + crit!(log, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e)); + return; + } + }; + + // Ensure the `data_dir` in the config matches that supplied to the CLI. + client_config.data_dir = data_dir.clone(); + + // Update the client config with any CLI args. + match client_config.apply_cli_args(&matches) { + Ok(()) => (), + Err(s) => { + crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s); + return; + } + }; + + let eth2_config_path: PathBuf = matches + .value_of("eth-config") + .and_then(|s| Some(PathBuf::from(s))) + .unwrap_or_else(|| data_dir.join(ETH2_CONFIG_FILENAME)); + + // Attempt to load the `Eth2Config` from file. + // + // If the file doesn't exist, create a default one depending on the CLI flags. + let mut eth2_config = match read_from_file::( + eth2_config_path.clone() + ) { + Ok(Some(c)) => c, + Ok(None) => { + let default = match matches.value_of("spec-constants") { + Some("mainnet") => Eth2Config::mainnet(), + Some("minimal") => Eth2Config::minimal(), + _ => unreachable!(), // Guarded by slog. + }; + if let Err(e) = write_to_file(eth2_config_path, &default) { + crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e)); + return; + } + default + } + Err(e) => { + crit!(log, "Failed to instantiate an Eth2Config"; "error" => format!("{:?}", e)); + return; + } + }; + + // Update the eth2 config with any CLI flags. + match eth2_config.apply_cli_args(&matches) { + Ok(()) => (), + Err(s) => { + crit!(log, "Failed to parse Eth2Config CLI arguments"; "error" => s); + return; + } + }; + + info!( + log, + "Starting validator client"; + "datadir" => client_config.data_dir.to_str(), + "spec_constants" => ð2_config.spec_constants, + ); + + let result = match eth2_config.spec_constants.as_str() { + "mainnet" => ValidatorService::::start::( + client_config, + eth2_config, + log.clone(), + ), + "minimal" => ValidatorService::::start::( + client_config, + eth2_config, + log.clone(), + ), + other => { + crit!(log, "Unknown spec constants"; "title" => other); + return; + } + }; // start the validator service. // this specifies the GRPC and signer type to use as the duty manager beacon node. - match ValidatorService::::start(config, log.clone()) { + match result { Ok(_) => info!(log, "Validator client shutdown successfully."), - Err(e) => error!(log, "Validator exited due to: {}", e.to_string()), + Err(e) => crit!(log, "Validator client exited with error"; "error" => e.to_string()), + } +} + +fn get_data_dir(args: &ArgMatches) -> Result { + if let Some(data_dir) = args.value_of("data_dir") { + Ok(PathBuf::from(data_dir)) + } else { + let path = dirs::home_dir() + .ok_or_else(|| "Unable to locate home directory")? + .join(&DEFAULT_DATA_DIR); + fs::create_dir_all(&path).map_err(|_| "Unable to create data_dir")?; + Ok(path) } } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 99455808c..8dbb82b37 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -16,6 +16,7 @@ use crate::error as error_chain; use crate::error::ErrorKind; use crate::signer::Signer; use bls::Keypair; +use eth2_config::Eth2Config; use grpcio::{ChannelBuilder, EnvBuilder}; use protos::services::Empty; use protos::services_grpc::{ @@ -31,7 +32,7 @@ use tokio::prelude::*; use tokio::runtime::Builder; use tokio::timer::Interval; use tokio_timer::clock::Clock; -use types::{ChainSpec, Epoch, Fork, Slot}; +use types::{ChainSpec, Epoch, EthSpec, Fork, Slot}; /// A fixed amount of time after a slot to perform operations. This gives the node time to complete /// per-slot processes. @@ -66,8 +67,9 @@ impl Service { /// /// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients /// and returns an instance of the service. - fn initialize_service( - config: ValidatorConfig, + fn initialize_service( + client_config: ValidatorConfig, + eth2_config: Eth2Config, log: slog::Logger, ) -> error_chain::Result> { // initialise the beacon node client to check for a connection @@ -75,7 +77,7 @@ impl Service { let env = Arc::new(EnvBuilder::new().build()); // Beacon node gRPC beacon node endpoints. let beacon_node_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + let ch = ChannelBuilder::new(env.clone()).connect(&client_config.server); BeaconNodeServiceClient::new(ch) }; @@ -103,12 +105,12 @@ impl Service { return Err("Genesis time in the future".into()); } // verify the node's chain id - if config.spec.chain_id != info.chain_id as u8 { + if eth2_config.spec.chain_id != info.chain_id as u8 { error!( log, "Beacon Node's genesis time is in the future. No work to do.\n Exiting" ); - return Err(format!("Beacon node has the wrong chain id. Expected chain id: {}, node's chain id: {}", config.spec.chain_id, info.chain_id).into()); + return Err(format!("Beacon node has the wrong chain id. Expected chain id: {}, node's chain id: {}", eth2_config.spec.chain_id, info.chain_id).into()); } break info; } @@ -136,7 +138,7 @@ impl Service { // Beacon node gRPC beacon block endpoints. let beacon_block_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + let ch = ChannelBuilder::new(env.clone()).connect(&client_config.server); let beacon_block_service_client = Arc::new(BeaconBlockServiceClient::new(ch)); // a wrapper around the service client to implement the beacon block node trait Arc::new(BeaconBlockGrpcClient::new(beacon_block_service_client)) @@ -144,33 +146,42 @@ impl Service { // Beacon node gRPC validator endpoints. let validator_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + let ch = ChannelBuilder::new(env.clone()).connect(&client_config.server); Arc::new(ValidatorServiceClient::new(ch)) }; //Beacon node gRPC attester endpoints. let attestation_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + let ch = ChannelBuilder::new(env.clone()).connect(&client_config.server); Arc::new(AttestationServiceClient::new(ch)) }; // build the validator slot clock - let slot_clock = - SystemTimeSlotClock::new(genesis_slot, genesis_time, config.spec.seconds_per_slot); + let slot_clock = SystemTimeSlotClock::new( + genesis_slot, + genesis_time, + eth2_config.spec.seconds_per_slot, + ); let current_slot = slot_clock .present_slot() .map_err(ErrorKind::SlotClockError)? - .expect("Genesis must be in the future"); + .ok_or_else::(|| { + "Genesis is not in the past. Exiting.".into() + })?; /* Generate the duties manager */ // Load generated keypairs - let keypairs = match config.fetch_keys(&log) { + let keypairs = match client_config.fetch_keys(&log) { Some(kps) => Arc::new(kps), - None => panic!("No key pairs found, cannot start validator client without at least one. Try running `./account_manager generate` first.") + None => { + return Err("Unable to locate validator key pairs, nothing to do.".into()); + } }; + let slots_per_epoch = T::slots_per_epoch(); + // TODO: keypairs are randomly generated; they should be loaded from a file or generated. // https://github.com/sigp/lighthouse/issues/160 //let keypairs = Arc::new(generate_deterministic_keypairs(8)); @@ -178,7 +189,7 @@ impl Service { // Builds a mapping of Epoch -> Map(PublicKey, EpochDuty) // where EpochDuty contains slot numbers and attestation data that each validator needs to // produce work on. - let duties_map = RwLock::new(EpochDutiesMap::new(config.slots_per_epoch)); + let duties_map = RwLock::new(EpochDutiesMap::new(slots_per_epoch)); // builds a manager which maintains the list of current duties for all known validators // and can check when a validator needs to perform a task. @@ -189,13 +200,13 @@ impl Service { beacon_node: validator_client, }); - let spec = Arc::new(config.spec); + let spec = Arc::new(eth2_config.spec); Ok(Service { fork, slot_clock, current_slot, - slots_per_epoch: config.slots_per_epoch, + slots_per_epoch, spec, duties_manager, beacon_block_client, @@ -206,13 +217,17 @@ impl Service { /// Initialise the service then run the core thread. // TODO: Improve handling of generic BeaconNode types, to stub grpcClient - pub fn start( - config: ValidatorConfig, + pub fn start( + client_config: ValidatorConfig, + eth2_config: Eth2Config, log: slog::Logger, ) -> error_chain::Result<()> { // connect to the node and retrieve its properties and initialize the gRPC clients - let mut service = - Service::::initialize_service(config, log)?; + let mut service = Service::::initialize_service::( + client_config, + eth2_config, + log, + )?; // we have connected to a node and established its parameters. Spin up the core service @@ -227,7 +242,9 @@ impl Service { .slot_clock .duration_to_next_slot() .map_err(|e| format!("System clock error: {:?}", e))? - .expect("Cannot start before genesis"); + .ok_or_else::(|| { + "Genesis is not in the past. Exiting.".into() + })?; // set up the validator work interval - start at next slot and proceed every slot let interval = { @@ -276,7 +293,9 @@ impl Service { error!(self.log, "SystemTimeError {:?}", e); return Err("Could not read system time".into()); } - Ok(slot) => slot.expect("Genesis is in the future"), + Ok(slot) => slot.ok_or_else::(|| { + "Genesis is not in the past. Exiting.".into() + })?, }; let current_epoch = current_slot.epoch(self.slots_per_epoch);