diff --git a/common/validator_dir/src/manager.rs b/common/validator_dir/src/manager.rs index dcd4f80b0..51f96c36f 100644 --- a/common/validator_dir/src/manager.rs +++ b/common/validator_dir/src/manager.rs @@ -1,7 +1,7 @@ use crate::{Error as ValidatorDirError, ValidatorDir}; use bls::Keypair; use rayon::prelude::*; -use slog::{info, Logger}; +use slog::{info, warn, Logger}; use std::collections::HashMap; use std::fs::read_dir; use std::io; @@ -81,6 +81,50 @@ impl Manager { .collect() } + /// Opens all the validator directories in `self` and decrypts the validator keypairs, + /// regardless if a lockfile exists or not. + /// + /// If `log.is_some()`, an `info` log will be generated for each decrypted validator. + /// Additionally, a warning log will be created if a lockfile existed already. + /// + /// ## Errors + /// + /// Returns an error if any of the directories is unable to be opened. + pub fn force_decrypt_all_validators( + &self, + secrets_dir: PathBuf, + log_opt: Option<&Logger>, + ) -> Result, Error> { + self.iter_dir()? + .into_par_iter() + .map(|path| { + ValidatorDir::force_open(path) + .and_then(|(v, existed)| { + v.voting_keypair(&secrets_dir).map(|kp| (kp, v, existed)) + }) + .map(|(kp, v, lockfile_existed)| { + if let Some(log) = log_opt { + info!( + log, + "Decrypted validator keystore"; + "voting_pubkey" => kp.pk.as_hex_string() + ); + if lockfile_existed { + warn!( + log, + "Lockfile already existed"; + "msg" => "ensure no other validator client is running on this host", + "voting_pubkey" => kp.pk.as_hex_string() + ); + } + } + (kp, v) + }) + .map_err(Error::ValidatorDirError) + }) + .collect() + } + /// Opens all the validator directories in `self` and decrypts the validator keypairs. /// /// If `log.is_some()`, an `info` log will be generated for each decrypted validator. diff --git a/common/validator_dir/src/validator_dir.rs b/common/validator_dir/src/validator_dir.rs index fd3720916..e24e25cbe 100644 --- a/common/validator_dir/src/validator_dir.rs +++ b/common/validator_dir/src/validator_dir.rs @@ -93,6 +93,37 @@ impl ValidatorDir { Ok(Self { dir }) } + /// Open `dir`, regardless or not if a lockfile exists. + /// + /// Returns `(validator_dir, lockfile_existed)`, where `lockfile_existed == true` if a lockfile + /// was already present before opening. Creates a lockfile if one did not already exist. + /// + /// ## Errors + /// + /// If there is a filesystem error. + pub fn force_open>(dir: P) -> Result<(Self, bool), Error> { + let dir: &Path = dir.as_ref(); + let dir: PathBuf = dir.into(); + + if !dir.exists() { + return Err(Error::DirectoryDoesNotExist(dir)); + } + + let lockfile = dir.join(LOCK_FILE); + + let lockfile_exists = lockfile.exists(); + + if !lockfile_exists { + OpenOptions::new() + .write(true) + .create_new(true) + .open(lockfile) + .map_err(Error::UnableToCreateLockfile)?; + } + + Ok((Self { dir }, lockfile_exists)) + } + /// Returns the `dir` provided to `Self::open`. pub fn dir(&self) -> &PathBuf { &self.dir diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 8e10f522b..7684e9616 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -34,8 +34,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { enabling the same signing key on multiple validator clients WILL lead to \ that validator getting slashed. Only use this flag the first time you run \ the validator client, or if you're certain there are no other \ - nodes using the same key.", + nodes using the same key. Automatically enabled unless `--strict` is specified", )) + .arg( + Arg::with_name("strict") + .long("strict") + .help( + "If present, require that validator keypairs are unlocked and that auto-register \ + is explicit before new validators are allowed to be used." + ) + ) .arg( Arg::with_name("allow-unsynced") .long("allow-unsynced") diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 12258ae7a..75ec02a7f 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -23,6 +23,8 @@ pub struct Config { /// If true, the validator client will still poll for duties and produce blocks even if the /// beacon node is not synced at startup. pub allow_unsynced_beacon_node: bool, + /// If true, we will be strict about concurrency and validator registration. + pub strict: bool, /// If true, register new validator keys with the slashing protection database. pub auto_register: bool, } @@ -42,6 +44,7 @@ impl Default for Config { http_server: DEFAULT_HTTP_SERVER.to_string(), allow_unsynced_beacon_node: false, auto_register: false, + strict: false, } } } @@ -71,6 +74,12 @@ impl Config { config.allow_unsynced_beacon_node = cli_args.is_present("allow-unsynced"); config.auto_register = cli_args.is_present("auto-register"); + config.strict = cli_args.is_present("strict"); + + if !config.strict { + // Do not require an explicit `--auto-register` if `--strict` is disabled. + config.auto_register = true + } if let Some(secrets_dir) = parse_optional(cli_args, "secrets-dir")? { config.secrets_dir = secrets_dir; diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index e09ce1568..e70dd069d 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -76,9 +76,16 @@ impl ProductionValidatorClient { ); } - let validators = ValidatorManager::open(&config.data_dir) - .map_err(|e| format!("unable to read data_dir: {:?}", e))? - .decrypt_all_validators(config.secrets_dir.clone(), Some(&log)) + let validator_manager = ValidatorManager::open(&config.data_dir) + .map_err(|e| format!("unable to read data_dir: {:?}", e))?; + + let validators_result = if config.strict { + validator_manager.decrypt_all_validators(config.secrets_dir.clone(), Some(&log)) + } else { + validator_manager.force_decrypt_all_validators(config.secrets_dir.clone(), Some(&log)) + }; + + let validators = validators_result .map_err(|e| format!("unable to decrypt all validator directories: {:?}", e))?; info!(