Loose VC lockfile and slashing protection registers (#1314)

This commit is contained in:
Paul Hauner 2020-06-29 21:04:07 +10:00 committed by GitHub
parent d4dd9fae07
commit 916a133043
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 5 deletions

View File

@ -1,7 +1,7 @@
use crate::{Error as ValidatorDirError, ValidatorDir}; use crate::{Error as ValidatorDirError, ValidatorDir};
use bls::Keypair; use bls::Keypair;
use rayon::prelude::*; use rayon::prelude::*;
use slog::{info, Logger}; use slog::{info, warn, Logger};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::read_dir; use std::fs::read_dir;
use std::io; use std::io;
@ -81,6 +81,50 @@ impl Manager {
.collect() .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<Vec<(Keypair, ValidatorDir)>, 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. /// 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. /// If `log.is_some()`, an `info` log will be generated for each decrypted validator.

View File

@ -93,6 +93,37 @@ impl ValidatorDir {
Ok(Self { dir }) 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<P: AsRef<Path>>(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`. /// Returns the `dir` provided to `Self::open`.
pub fn dir(&self) -> &PathBuf { pub fn dir(&self) -> &PathBuf {
&self.dir &self.dir

View File

@ -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 \ 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 \ 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 \ 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(
Arg::with_name("allow-unsynced") Arg::with_name("allow-unsynced")
.long("allow-unsynced") .long("allow-unsynced")

View File

@ -23,6 +23,8 @@ pub struct Config {
/// If true, the validator client will still poll for duties and produce blocks even if the /// If true, the validator client will still poll for duties and produce blocks even if the
/// beacon node is not synced at startup. /// beacon node is not synced at startup.
pub allow_unsynced_beacon_node: bool, 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. /// If true, register new validator keys with the slashing protection database.
pub auto_register: bool, pub auto_register: bool,
} }
@ -42,6 +44,7 @@ impl Default for Config {
http_server: DEFAULT_HTTP_SERVER.to_string(), http_server: DEFAULT_HTTP_SERVER.to_string(),
allow_unsynced_beacon_node: false, allow_unsynced_beacon_node: false,
auto_register: false, auto_register: false,
strict: false,
} }
} }
} }
@ -71,6 +74,12 @@ impl Config {
config.allow_unsynced_beacon_node = cli_args.is_present("allow-unsynced"); config.allow_unsynced_beacon_node = cli_args.is_present("allow-unsynced");
config.auto_register = cli_args.is_present("auto-register"); 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")? { if let Some(secrets_dir) = parse_optional(cli_args, "secrets-dir")? {
config.secrets_dir = secrets_dir; config.secrets_dir = secrets_dir;

View File

@ -76,9 +76,16 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
); );
} }
let validators = ValidatorManager::open(&config.data_dir) let validator_manager = ValidatorManager::open(&config.data_dir)
.map_err(|e| format!("unable to read data_dir: {:?}", e))? .map_err(|e| format!("unable to read data_dir: {:?}", e))?;
.decrypt_all_validators(config.secrets_dir.clone(), Some(&log))
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))?; .map_err(|e| format!("unable to decrypt all validator directories: {:?}", e))?;
info!( info!(