Add eth2_config crate, integrate into val client

This commit is contained in:
Paul Hauner 2019-06-08 20:21:50 -04:00
parent eb23b003b4
commit 3487b16ce5
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
13 changed files with 366 additions and 129 deletions

View File

@ -9,6 +9,7 @@ members = [
"eth2/utils/cached_tree_hash", "eth2/utils/cached_tree_hash",
"eth2/utils/compare_fields", "eth2/utils/compare_fields",
"eth2/utils/compare_fields_derive", "eth2/utils/compare_fields_derive",
"eth2/utils/eth2_config",
"eth2/utils/fixed_len_vec", "eth2/utils/fixed_len_vec",
"eth2/utils/hashing", "eth2/utils/hashing",
"eth2/utils/honey-badger-split", "eth2/utils/honey-badger-split",

View File

@ -6,6 +6,7 @@ edition = "2018"
[dependencies] [dependencies]
dirs = "1.0.3" dirs = "1.0.3"
eth2_config = { path = "../eth2/utils/eth2_config" }
types = { path = "../eth2/types" } types = { path = "../eth2/types" }
toml = "^0.5" toml = "^0.5"
store = { path = "./store" } store = { path = "./store" }

View File

@ -14,6 +14,7 @@ fork_choice = { path = "../../eth2/fork_choice" }
prometheus = "^0.6" prometheus = "^0.6"
types = { path = "../../eth2/types" } types = { path = "../../eth2/types" }
tree_hash = { path = "../../eth2/utils/tree_hash" } tree_hash = { path = "../../eth2/utils/tree_hash" }
eth2_config = { path = "../../eth2/utils/eth2_config" }
slot_clock = { path = "../../eth2/utils/slot_clock" } slot_clock = { path = "../../eth2/utils/slot_clock" }
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View File

@ -8,7 +8,7 @@ use std::path::PathBuf;
/// The core configuration of a Lighthouse beacon node. /// The core configuration of a Lighthouse beacon node.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientConfig { pub struct ClientConfig {
pub data_dir: String, pub data_dir: PathBuf,
pub db_type: String, pub db_type: String,
db_name: String, db_name: String,
pub network: network::NetworkConfig, pub network: network::NetworkConfig,
@ -19,7 +19,7 @@ pub struct ClientConfig {
impl Default for ClientConfig { impl Default for ClientConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
data_dir: ".lighthouse".to_string(), data_dir: PathBuf::from(".lighthouse"),
db_type: "disk".to_string(), db_type: "disk".to_string(),
db_name: "chain_db".to_string(), db_name: "chain_db".to_string(),
// Note: there are no default bootnodes specified. // Note: there are no default bootnodes specified.
@ -51,7 +51,7 @@ impl ClientConfig {
/// invalid. /// invalid.
pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> {
if let Some(dir) = args.value_of("datadir") { 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") { if let Some(dir) = args.value_of("db") {

View File

@ -3,7 +3,6 @@ extern crate slog;
mod beacon_chain_types; mod beacon_chain_types;
mod client_config; mod client_config;
pub mod error; pub mod error;
mod eth2_config;
pub mod notifier; pub mod notifier;
use beacon_chain::BeaconChain; use beacon_chain::BeaconChain;

View File

@ -2,12 +2,12 @@ extern crate slog;
mod run; mod run;
use clap::{App, Arg}; use clap::{App, Arg, ArgMatches};
use client::{ClientConfig, Eth2Config}; use client::{ClientConfig, Eth2Config};
use eth2_config::{read_from_file, write_to_file};
use slog::{crit, o, Drain}; use slog::{crit, o, Drain};
use std::fs; use std::fs;
use std::fs::File; use std::path::PathBuf;
use std::io::prelude::*;
pub const DEFAULT_DATA_DIR: &str = ".lighthouse"; pub const DEFAULT_DATA_DIR: &str = ".lighthouse";
@ -105,7 +105,8 @@ fn main() {
Arg::with_name("spec-constants") Arg::with_name("spec-constants")
.long("spec-constants") .long("spec-constants")
.value_name("TITLE") .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) .takes_value(true)
.possible_values(&["mainnet", "minimal"]) .possible_values(&["mainnet", "minimal"])
.default_value("minimal"), .default_value("minimal"),
@ -113,34 +114,44 @@ fn main() {
.arg( .arg(
Arg::with_name("recent-genesis") Arg::with_name("recent-genesis")
.long("recent-genesis") .long("recent-genesis")
.short("r")
.help("When present, genesis will be within 30 minutes prior. Only for testing"), .help("When present, genesis will be within 30 minutes prior. Only for testing"),
) )
.get_matches(); .get_matches();
// Attempt to lead the `ClientConfig` from disk. If it fails, write let data_dir = match get_data_dir(&matches) {
let mut client_config = match read_from_file::<ClientConfig>( Ok(dir) => dir,
matches.value_of("data_dir"), Err(e) => {
CLIENT_CONFIG_FILENAME, 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::<ClientConfig>(client_config_path.clone()) {
Ok(Some(c)) => c, Ok(Some(c)) => c,
Ok(None) => { Ok(None) => {
let default = ClientConfig::default(); 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)); crit!(logger, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e));
return; return;
} }
default default
}, }
Err(e) => { Err(e) => {
crit!(logger, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e)); crit!(logger, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e));
return; return;
} }
}; };
if let Some(data_dir) = matches.value_of("data_dir") { // Ensure the `data_dir` in the config matches that supplied to the CLI.
client_config.data_dir = data_dir.to_string(); client_config.data_dir = data_dir.clone();
}
// Update the client config with any CLI args.
match client_config.apply_cli_args(&matches) { match client_config.apply_cli_args(&matches) {
Ok(()) => (), Ok(()) => (),
Err(s) => { Err(s) => {
@ -149,10 +160,12 @@ fn main() {
} }
}; };
let mut eth2_config = match read_from_file::<Eth2Config>( let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME);
matches.value_of("data_dir"),
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::<Eth2Config>(eth2_config_path.clone()) {
Ok(Some(c)) => c, Ok(Some(c)) => c,
Ok(None) => { Ok(None) => {
let default = match matches.value_of("spec-constants") { let default = match matches.value_of("spec-constants") {
@ -160,7 +173,7 @@ fn main() {
Some("minimal") => Eth2Config::minimal(), Some("minimal") => Eth2Config::minimal(),
_ => unreachable!(), // Guarded by slog. _ => 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)); crit!(logger, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e));
return; return;
} }
@ -172,6 +185,7 @@ fn main() {
} }
}; };
// Update the eth2 config with any CLI flags.
match eth2_config.apply_cli_args(&matches) { match eth2_config.apply_cli_args(&matches) {
Ok(()) => (), Ok(()) => (),
Err(s) => { Err(s) => {
@ -186,59 +200,14 @@ fn main() {
} }
} }
/// Write a configuration to file. fn get_data_dir(args: &ArgMatches) -> Result<PathBuf, &'static str> {
fn write_to_file<T>(data_dir: Option<&str>, config_filename: &str, config: &T) -> Result<(), String> if let Some(data_dir) = args.value_of("data_dir") {
where Ok(PathBuf::from(data_dir))
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<T>(data_dir: Option<&str>, config_filename: &str) -> Result<Option<T>, 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))
} else { } 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)
} }
} }

View File

@ -0,0 +1,13 @@
[package]
name = "eth2_config"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
clap = "2.32.0"
dirs = "1.0.3"
serde = "1.0"
serde_derive = "1.0"
toml = "^0.5"
types = { path = "../../types" }

View File

@ -1,5 +1,8 @@
use clap::ArgMatches; use clap::ArgMatches;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
use types::ChainSpec; use types::ChainSpec;
@ -42,7 +45,7 @@ impl Eth2Config {
/// Returns an error if arguments are obviously invalid. May succeed even if some values are /// Returns an error if arguments are obviously invalid. May succeed even if some values are
/// invalid. /// invalid.
pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { 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() self.spec.genesis_time = recent_genesis_time()
} }
@ -62,3 +65,42 @@ fn recent_genesis_time() -> u64 {
// genesis is now the last 30 minute block. // genesis is now the last 30 minute block.
now - secs_after_last_period now - secs_after_last_period
} }
/// Write a configuration to file.
pub fn write_to_file<T>(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<T>(path: PathBuf) -> Result<Option<T>, 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)
}
}

View File

@ -15,6 +15,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
bls = { path = "../eth2/utils/bls" } bls = { path = "../eth2/utils/bls" }
ssz = { path = "../eth2/utils/ssz" } ssz = { path = "../eth2/utils/ssz" }
eth2_config = { path = "../eth2/utils/eth2_config" }
tree_hash = { path = "../eth2/utils/tree_hash" } tree_hash = { path = "../eth2/utils/tree_hash" }
clap = "2.32.0" clap = "2.32.0"
dirs = "1.0.3" dirs = "1.0.3"
@ -23,11 +24,14 @@ protobuf = "2.0.2"
protos = { path = "../protos" } protos = { path = "../protos" }
slot_clock = { path = "../eth2/utils/slot_clock" } slot_clock = { path = "../eth2/utils/slot_clock" }
types = { path = "../eth2/types" } types = { path = "../eth2/types" }
serde = "1.0"
serde_derive = "1.0"
slog = "^2.2.3" slog = "^2.2.3"
slog-term = "^2.4.0" slog-term = "^2.4.0"
slog-async = "^2.3.0" slog-async = "^2.3.0"
tokio = "0.1.18" tokio = "0.1.18"
tokio-timer = "0.2.10" tokio-timer = "0.2.10"
toml = "^0.5"
error-chain = "0.12.0" error-chain = "0.12.0"
bincode = "^1.1.2" bincode = "^1.1.2"
futures = "0.1.25" futures = "0.1.25"

View File

@ -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

View File

@ -1,22 +1,22 @@
use bincode; use bincode;
use bls::Keypair; use bls::Keypair;
use clap::ArgMatches; use clap::ArgMatches;
use serde_derive::{Deserialize, Serialize};
use slog::{debug, error, info}; use slog::{debug, error, info};
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use std::path::PathBuf; use std::path::PathBuf;
use types::{ChainSpec, EthSpec, MainnetEthSpec, MinimalEthSpec}; use types::{EthSpec, MainnetEthSpec};
/// Stores the core configuration for this validator instance. /// Stores the core configuration for this validator instance.
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct Config { pub struct Config {
/// The data directory, which stores all validator databases /// The data directory, which stores all validator databases
pub data_dir: PathBuf, pub data_dir: PathBuf,
/// The server at which the Beacon Node can be contacted /// The server at which the Beacon Node can be contacted
pub server: String, pub server: String,
/// The chain specification that we are connecting to /// The number of slots per epoch.
pub spec: ChainSpec,
pub slots_per_epoch: u64, pub slots_per_epoch: u64,
} }
@ -25,25 +25,33 @@ const DEFAULT_PRIVATE_KEY_FILENAME: &str = "private.key";
impl Default for Config { impl Default for Config {
/// Build a new configuration from defaults. /// Build a new configuration from defaults.
fn default() -> Self { 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 { Self {
data_dir, data_dir: PathBuf::from(".lighthouse-validator"),
server, server: "localhost:5051".to_string(),
spec,
slots_per_epoch: MainnetEthSpec::slots_per_epoch(), slots_per_epoch: MainnetEthSpec::slots_per_epoch(),
} }
} }
} }
impl Config { 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. /// Build a new configuration from defaults, which are overrided by arguments provided.
pub fn parse_args(args: &ArgMatches, log: &slog::Logger) -> Result<Self, Error> { pub fn parse_args(args: &ArgMatches, log: &slog::Logger) -> Result<Self, Error> {
let mut config = Config::default(); let mut config = Config::default();
@ -80,12 +88,13 @@ impl Config {
Ok(config) Ok(config)
} }
*/
/// Try to load keys from validator_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.
#[allow(dead_code)] #[allow(dead_code)]
pub fn fetch_keys(&self, log: &slog::Logger) -> Option<Vec<Keypair>> { pub fn fetch_keys(&self, log: &slog::Logger) -> Option<Vec<Keypair>> {
let key_pairs: Vec<Keypair> = fs::read_dir(&self.data_dir) let key_pairs: Vec<Keypair> = fs::read_dir(&self.data_dir)
.unwrap() .ok()?
.filter_map(|validator_dir| { .filter_map(|validator_dir| {
let validator_dir = validator_dir.ok()?; let validator_dir = validator_dir.ok()?;

View File

@ -7,11 +7,19 @@ mod service;
mod signer; mod signer;
use crate::config::Config as ValidatorClientConfig; use crate::config::Config as ValidatorClientConfig;
use std::fs;
use crate::service::Service as ValidatorService; 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 protos::services_grpc::ValidatorServiceClient;
use slog::{error, info, o, Drain}; use slog::{crit, error, info, o, Drain};
use types::Keypair; 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() { fn main() {
// Logging // Logging
@ -30,7 +38,16 @@ fn main() {
.long("datadir") .long("datadir")
.value_name("DIR") .value_name("DIR")
.help("Data directory for keys and databases.") .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(
Arg::with_name("server") Arg::with_name("server")
@ -40,24 +57,139 @@ fn main() {
.takes_value(true), .takes_value(true),
) )
.arg( .arg(
Arg::with_name("spec") Arg::with_name("spec-constants")
.long("spec") .long("spec-constants")
.value_name("spec") .value_name("TITLE")
.short("s") .short("s")
.help("Configuration of Beacon Chain") .help("The title of the spec constants for chain config.")
.takes_value(true) .takes_value(true)
.possible_values(&["mainnet", "minimal"]) .possible_values(&["mainnet", "minimal"])
.default_value("minimal"), .default_value("minimal"),
) )
.get_matches(); .get_matches();
let config = ValidatorClientConfig::parse_args(&matches, &log) let data_dir = match get_data_dir(&matches) {
.expect("Unable to build a configuration for the validator client."); 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::<ValidatorClientConfig>(
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::<Eth2Config>(
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" => &eth2_config.spec_constants,
);
let result = match eth2_config.spec_constants.as_str() {
"mainnet" => ValidatorService::<ValidatorServiceClient, Keypair>::start::<MainnetEthSpec>(
client_config,
eth2_config,
log.clone(),
),
"minimal" => ValidatorService::<ValidatorServiceClient, Keypair>::start::<MinimalEthSpec>(
client_config,
eth2_config,
log.clone(),
),
other => {
crit!(log, "Unknown spec constants"; "title" => other);
return;
}
};
// start the validator service. // start the validator service.
// this specifies the GRPC and signer type to use as the duty manager beacon node. // this specifies the GRPC and signer type to use as the duty manager beacon node.
match ValidatorService::<ValidatorServiceClient, Keypair>::start(config, log.clone()) { match result {
Ok(_) => info!(log, "Validator client shutdown successfully."), 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<PathBuf, &'static str> {
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)
} }
} }

View File

@ -16,6 +16,7 @@ use crate::error as error_chain;
use crate::error::ErrorKind; use crate::error::ErrorKind;
use crate::signer::Signer; use crate::signer::Signer;
use bls::Keypair; use bls::Keypair;
use eth2_config::Eth2Config;
use grpcio::{ChannelBuilder, EnvBuilder}; use grpcio::{ChannelBuilder, EnvBuilder};
use protos::services::Empty; use protos::services::Empty;
use protos::services_grpc::{ use protos::services_grpc::{
@ -31,7 +32,7 @@ use tokio::prelude::*;
use tokio::runtime::Builder; use tokio::runtime::Builder;
use tokio::timer::Interval; use tokio::timer::Interval;
use tokio_timer::clock::Clock; 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 /// A fixed amount of time after a slot to perform operations. This gives the node time to complete
/// per-slot processes. /// per-slot processes.
@ -66,8 +67,9 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
/// ///
/// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients /// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients
/// and returns an instance of the service. /// and returns an instance of the service.
fn initialize_service( fn initialize_service<T: EthSpec>(
config: ValidatorConfig, client_config: ValidatorConfig,
eth2_config: Eth2Config,
log: slog::Logger, log: slog::Logger,
) -> error_chain::Result<Service<ValidatorServiceClient, Keypair>> { ) -> error_chain::Result<Service<ValidatorServiceClient, Keypair>> {
// initialise the beacon node client to check for a connection // initialise the beacon node client to check for a connection
@ -75,7 +77,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
let env = Arc::new(EnvBuilder::new().build()); let env = Arc::new(EnvBuilder::new().build());
// Beacon node gRPC beacon node endpoints. // Beacon node gRPC beacon node endpoints.
let beacon_node_client = { 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) BeaconNodeServiceClient::new(ch)
}; };
@ -103,12 +105,12 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
return Err("Genesis time in the future".into()); return Err("Genesis time in the future".into());
} }
// verify the node's chain id // 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!( error!(
log, log,
"Beacon Node's genesis time is in the future. No work to do.\n Exiting" "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; break info;
} }
@ -136,7 +138,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
// Beacon node gRPC beacon block endpoints. // Beacon node gRPC beacon block endpoints.
let beacon_block_client = { 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)); let beacon_block_service_client = Arc::new(BeaconBlockServiceClient::new(ch));
// a wrapper around the service client to implement the beacon block node trait // a wrapper around the service client to implement the beacon block node trait
Arc::new(BeaconBlockGrpcClient::new(beacon_block_service_client)) Arc::new(BeaconBlockGrpcClient::new(beacon_block_service_client))
@ -144,33 +146,42 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
// Beacon node gRPC validator endpoints. // Beacon node gRPC validator endpoints.
let validator_client = { 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)) Arc::new(ValidatorServiceClient::new(ch))
}; };
//Beacon node gRPC attester endpoints. //Beacon node gRPC attester endpoints.
let attestation_client = { 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)) Arc::new(AttestationServiceClient::new(ch))
}; };
// build the validator slot clock // build the validator slot clock
let slot_clock = let slot_clock = SystemTimeSlotClock::new(
SystemTimeSlotClock::new(genesis_slot, genesis_time, config.spec.seconds_per_slot); genesis_slot,
genesis_time,
eth2_config.spec.seconds_per_slot,
);
let current_slot = slot_clock let current_slot = slot_clock
.present_slot() .present_slot()
.map_err(ErrorKind::SlotClockError)? .map_err(ErrorKind::SlotClockError)?
.expect("Genesis must be in the future"); .ok_or_else::<error_chain::Error, _>(|| {
"Genesis is not in the past. Exiting.".into()
})?;
/* Generate the duties manager */ /* Generate the duties manager */
// Load generated keypairs // Load generated keypairs
let keypairs = match config.fetch_keys(&log) { let keypairs = match client_config.fetch_keys(&log) {
Some(kps) => Arc::new(kps), 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. // TODO: keypairs are randomly generated; they should be loaded from a file or generated.
// https://github.com/sigp/lighthouse/issues/160 // https://github.com/sigp/lighthouse/issues/160
//let keypairs = Arc::new(generate_deterministic_keypairs(8)); //let keypairs = Arc::new(generate_deterministic_keypairs(8));
@ -178,7 +189,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
// Builds a mapping of Epoch -> Map(PublicKey, EpochDuty) // Builds a mapping of Epoch -> Map(PublicKey, EpochDuty)
// where EpochDuty contains slot numbers and attestation data that each validator needs to // where EpochDuty contains slot numbers and attestation data that each validator needs to
// produce work on. // 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 // 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. // and can check when a validator needs to perform a task.
@ -189,13 +200,13 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
beacon_node: validator_client, beacon_node: validator_client,
}); });
let spec = Arc::new(config.spec); let spec = Arc::new(eth2_config.spec);
Ok(Service { Ok(Service {
fork, fork,
slot_clock, slot_clock,
current_slot, current_slot,
slots_per_epoch: config.slots_per_epoch, slots_per_epoch,
spec, spec,
duties_manager, duties_manager,
beacon_block_client, beacon_block_client,
@ -206,13 +217,17 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
/// Initialise the service then run the core thread. /// Initialise the service then run the core thread.
// TODO: Improve handling of generic BeaconNode types, to stub grpcClient // TODO: Improve handling of generic BeaconNode types, to stub grpcClient
pub fn start( pub fn start<T: EthSpec>(
config: ValidatorConfig, client_config: ValidatorConfig,
eth2_config: Eth2Config,
log: slog::Logger, log: slog::Logger,
) -> error_chain::Result<()> { ) -> error_chain::Result<()> {
// connect to the node and retrieve its properties and initialize the gRPC clients // connect to the node and retrieve its properties and initialize the gRPC clients
let mut service = let mut service = Service::<ValidatorServiceClient, Keypair>::initialize_service::<T>(
Service::<ValidatorServiceClient, Keypair>::initialize_service(config, log)?; client_config,
eth2_config,
log,
)?;
// we have connected to a node and established its parameters. Spin up the core service // we have connected to a node and established its parameters. Spin up the core service
@ -227,7 +242,9 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
.slot_clock .slot_clock
.duration_to_next_slot() .duration_to_next_slot()
.map_err(|e| format!("System clock error: {:?}", e))? .map_err(|e| format!("System clock error: {:?}", e))?
.expect("Cannot start before genesis"); .ok_or_else::<error_chain::Error, _>(|| {
"Genesis is not in the past. Exiting.".into()
})?;
// set up the validator work interval - start at next slot and proceed every slot // set up the validator work interval - start at next slot and proceed every slot
let interval = { let interval = {
@ -276,7 +293,9 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static> Service<B, S> {
error!(self.log, "SystemTimeError {:?}", e); error!(self.log, "SystemTimeError {:?}", e);
return Err("Could not read system time".into()); return Err("Could not read system time".into());
} }
Ok(slot) => slot.expect("Genesis is in the future"), Ok(slot) => slot.ok_or_else::<error_chain::Error, _>(|| {
"Genesis is not in the past. Exiting.".into()
})?,
}; };
let current_epoch = current_slot.epoch(self.slots_per_epoch); let current_epoch = current_slot.epoch(self.slots_per_epoch);