Add testing keypairs to validator client
This commit is contained in:
parent
4a69d01a37
commit
fe2cd2fc27
@ -2,13 +2,13 @@ use bincode;
|
|||||||
use bls::Keypair;
|
use bls::Keypair;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use slog::{debug, error, info, o, Drain};
|
use slog::{error, info, o, warn, Drain};
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, File, OpenOptions};
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use types::{EthSpec, MainnetEthSpec};
|
use types::{test_utils::generate_deterministic_keypair, EthSpec, MainnetEthSpec};
|
||||||
|
|
||||||
pub const DEFAULT_SERVER: &str = "localhost";
|
pub const DEFAULT_SERVER: &str = "localhost";
|
||||||
pub const DEFAULT_SERVER_GRPC_PORT: &str = "5051";
|
pub const DEFAULT_SERVER_GRPC_PORT: &str = "5051";
|
||||||
@ -182,69 +182,65 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to load keys from validator_dir, returning None if none are found or an error.
|
pub fn fetch_keys_from_disk(&self, log: &slog::Logger) -> Result<Vec<Keypair>, String> {
|
||||||
#[allow(dead_code)]
|
Ok(
|
||||||
pub fn fetch_keys(&self, log: &slog::Logger) -> Option<Vec<Keypair>> {
|
|
||||||
let key_pairs: Vec<Keypair> =
|
|
||||||
fs::read_dir(&self.full_data_dir().expect("Data dir must exist"))
|
fs::read_dir(&self.full_data_dir().expect("Data dir must exist"))
|
||||||
.ok()?
|
.map_err(|e| format!("Failed to read datadir: {:?}", e))?
|
||||||
.filter_map(|validator_dir| {
|
.filter_map(|validator_dir| {
|
||||||
let validator_dir = validator_dir.ok()?;
|
let path = validator_dir.ok()?.path();
|
||||||
|
|
||||||
if !(validator_dir.file_type().ok()?.is_dir()) {
|
if path.is_dir() {
|
||||||
// Skip non-directories (i.e. no files/symlinks)
|
match self.read_keypair_file(path.clone()) {
|
||||||
return None;
|
Ok(keypair) => Some(keypair),
|
||||||
}
|
Err(e) => {
|
||||||
|
error!(
|
||||||
let key_filename = validator_dir.path().join(DEFAULT_PRIVATE_KEY_FILENAME);
|
log,
|
||||||
|
"Failed to parse a validator keypair";
|
||||||
if !(key_filename.is_file()) {
|
"error" => e,
|
||||||
info!(
|
"path" => path.to_str(),
|
||||||
log,
|
);
|
||||||
"Private key is not a file: {:?}",
|
None
|
||||||
key_filename.to_str()
|
}
|
||||||
);
|
}
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
log,
|
|
||||||
"Deserializing private key from file: {:?}",
|
|
||||||
key_filename.to_str()
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut key_file = File::open(key_filename.clone()).ok()?;
|
|
||||||
|
|
||||||
let key: Keypair = if let Ok(key_ok) = bincode::deserialize_from(&mut key_file)
|
|
||||||
{
|
|
||||||
key_ok
|
|
||||||
} else {
|
} else {
|
||||||
error!(
|
None
|
||||||
log,
|
|
||||||
"Unable to deserialize the private key file: {:?}", key_filename
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let ki = key.identifier();
|
|
||||||
if ki != validator_dir.file_name().into_string().ok()? {
|
|
||||||
error!(
|
|
||||||
log,
|
|
||||||
"The validator key ({:?}) did not match the directory filename {:?}.",
|
|
||||||
ki,
|
|
||||||
&validator_dir.path().to_string_lossy()
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
Some(key)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_testing_keypairs(
|
||||||
|
&self,
|
||||||
|
range: std::ops::Range<usize>,
|
||||||
|
) -> Result<Vec<Keypair>, String> {
|
||||||
|
Ok(range
|
||||||
|
.into_iter()
|
||||||
|
.map(generate_deterministic_keypair)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the keypairs according to `self.key_source`. Will return one or more keypairs, or an
|
||||||
|
/// error.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn fetch_keys(&self, log: &slog::Logger) -> Result<Vec<Keypair>, String> {
|
||||||
|
let keypairs = match &self.key_source {
|
||||||
|
KeySource::Disk => self.fetch_keys_from_disk(log)?,
|
||||||
|
KeySource::TestingKeypairRange(range) => {
|
||||||
|
warn!(log, "Using insecure private keys");
|
||||||
|
self.fetch_testing_keypairs(range.clone())?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Check if it's an empty vector, and return none.
|
// Check if it's an empty vector, and return none.
|
||||||
if key_pairs.is_empty() {
|
if keypairs.is_empty() {
|
||||||
None
|
Err(
|
||||||
|
"No validator keypairs were found, unable to proceed. To generate \
|
||||||
|
testing keypairs, see 'testnet range --help'."
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Some(key_pairs)
|
Ok(keypairs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,12 +12,10 @@ use crate::config::{
|
|||||||
};
|
};
|
||||||
use crate::service::Service as ValidatorService;
|
use crate::service::Service as ValidatorService;
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use eth2_config::{read_from_file, write_to_file, Eth2Config};
|
use eth2_config::Eth2Config;
|
||||||
use lighthouse_bootstrap::Bootstrapper;
|
use lighthouse_bootstrap::Bootstrapper;
|
||||||
use protos::services_grpc::ValidatorServiceClient;
|
use protos::services_grpc::ValidatorServiceClient;
|
||||||
use slog::{crit, error, info, o, warn, Drain, Level, Logger};
|
use slog::{crit, error, info, o, Drain, Level, Logger};
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use types::{InteropEthSpec, Keypair, MainnetEthSpec, MinimalEthSpec};
|
use types::{InteropEthSpec, Keypair, MainnetEthSpec, MinimalEthSpec};
|
||||||
|
|
||||||
pub const DEFAULT_SPEC: &str = "minimal";
|
pub const DEFAULT_SPEC: &str = "minimal";
|
||||||
@ -54,6 +52,17 @@ fn main() {
|
|||||||
.help("File path where output will be written.")
|
.help("File path where output will be written.")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("spec")
|
||||||
|
.short("s")
|
||||||
|
.long("spec")
|
||||||
|
.value_name("TITLE")
|
||||||
|
.help("Specifies the default eth2 spec type.")
|
||||||
|
.takes_value(true)
|
||||||
|
.possible_values(&["mainnet", "minimal", "interop"])
|
||||||
|
.conflicts_with("eth2-config")
|
||||||
|
.global(true)
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("eth2-config")
|
Arg::with_name("eth2-config")
|
||||||
.long("eth2-config")
|
.long("eth2-config")
|
||||||
@ -135,135 +144,6 @@ fn main() {
|
|||||||
_ => unreachable!("guarded by clap"),
|
_ => unreachable!("guarded by clap"),
|
||||||
};
|
};
|
||||||
let log = slog::Logger::root(drain.fuse(), o!());
|
let log = slog::Logger::root(drain.fuse(), o!());
|
||||||
|
|
||||||
/*
|
|
||||||
let data_dir = match matches
|
|
||||||
.value_of("datadir")
|
|
||||||
.and_then(|v| Some(PathBuf::from(v)))
|
|
||||||
{
|
|
||||||
Some(v) => v,
|
|
||||||
None => {
|
|
||||||
// use the default
|
|
||||||
let mut default_dir = match dirs::home_dir() {
|
|
||||||
Some(v) => v,
|
|
||||||
None => {
|
|
||||||
crit!(log, "Failed to find a home directory");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
default_dir.push(DEFAULT_DATA_DIR);
|
|
||||||
default_dir
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// create the directory if needed
|
|
||||||
match fs::create_dir_all(&data_dir) {
|
|
||||||
Ok(_) => {}
|
|
||||||
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 load 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(None) => {
|
|
||||||
let default = ClientConfig::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, &mut log) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(s) => {
|
|
||||||
crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let eth2_config_path: PathBuf = matches
|
|
||||||
.value_of("eth2-spec")
|
|
||||||
.and_then(|s| Some(PathBuf::from(s)))
|
|
||||||
.unwrap_or_else(|| data_dir.join(ETH2_CONFIG_FILENAME));
|
|
||||||
|
|
||||||
// Initialise the `Eth2Config`.
|
|
||||||
//
|
|
||||||
// If a CLI parameter is set, overwrite any config file present.
|
|
||||||
// If a parameter is not set, use either the config file present or default to minimal.
|
|
||||||
let cli_config = match matches.value_of("default-spec") {
|
|
||||||
Some("mainnet") => Some(Eth2Config::mainnet()),
|
|
||||||
Some("minimal") => Some(Eth2Config::minimal()),
|
|
||||||
Some("interop") => Some(Eth2Config::interop()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
// if a CLI flag is specified, write the new config if it doesn't exist,
|
|
||||||
// otherwise notify the user that the file will not be written.
|
|
||||||
let eth2_config_from_file = match read_from_file::<Eth2Config>(eth2_config_path.clone()) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(e) => {
|
|
||||||
crit!(log, "Failed to read the Eth2Config from file"; "error" => format!("{:?}", e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut eth2_config = {
|
|
||||||
if let Some(cli_config) = cli_config {
|
|
||||||
if eth2_config_from_file.is_none() {
|
|
||||||
// write to file if one doesn't exist
|
|
||||||
if let Err(e) = write_to_file(eth2_config_path, &cli_config) {
|
|
||||||
crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
log,
|
|
||||||
"Eth2Config file exists. Configuration file is ignored, using default"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
cli_config
|
|
||||||
} else {
|
|
||||||
// CLI config not specified, read from disk
|
|
||||||
match eth2_config_from_file {
|
|
||||||
Some(config) => config,
|
|
||||||
None => {
|
|
||||||
// set default to minimal
|
|
||||||
let eth2_config = Eth2Config::minimal();
|
|
||||||
if let Err(e) = write_to_file(eth2_config_path, ð2_config) {
|
|
||||||
crit!(log, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
eth2_config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
let (client_config, eth2_config) = match get_configs(&matches, &log) {
|
let (client_config, eth2_config) = match get_configs(&matches, &log) {
|
||||||
Ok(tuple) => tuple,
|
Ok(tuple) => tuple,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -353,12 +233,13 @@ pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<(ClientConfig,
|
|||||||
}
|
}
|
||||||
process_testnet_subcommand(sub_cli_args, client_config, log)
|
process_testnet_subcommand(sub_cli_args, client_config, log)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => return Err("You must use the testnet command. See '--help'.".into()),
|
||||||
unimplemented!("Resuming (not starting a testnet)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses the `testnet` CLI subcommand.
|
||||||
|
///
|
||||||
|
/// This is not a pure function, it reads from disk and may contact network servers.
|
||||||
fn process_testnet_subcommand(
|
fn process_testnet_subcommand(
|
||||||
cli_args: &ArgMatches,
|
cli_args: &ArgMatches,
|
||||||
mut client_config: ClientConfig,
|
mut client_config: ClientConfig,
|
||||||
@ -381,7 +262,12 @@ fn process_testnet_subcommand(
|
|||||||
|
|
||||||
eth2_config
|
eth2_config
|
||||||
} else {
|
} else {
|
||||||
return Err("Starting without bootstrap is not implemented".into());
|
match cli_args.value_of("spec") {
|
||||||
|
Some("mainnet") => Eth2Config::mainnet(),
|
||||||
|
Some("minimal") => Eth2Config::minimal(),
|
||||||
|
Some("interop") => Eth2Config::interop(),
|
||||||
|
_ => return Err("No --spec flag provided. See '--help'.".into()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
client_config.key_source = match cli_args.subcommand() {
|
client_config.key_source = match cli_args.subcommand() {
|
||||||
|
@ -188,12 +188,7 @@ impl<B: BeaconNodeDuties + 'static, S: Signer + 'static, E: EthSpec> Service<B,
|
|||||||
/* Generate the duties manager */
|
/* Generate the duties manager */
|
||||||
|
|
||||||
// Load generated keypairs
|
// Load generated keypairs
|
||||||
let keypairs = match client_config.fetch_keys(&log) {
|
let keypairs = Arc::new(client_config.fetch_keys(&log)?);
|
||||||
Some(kps) => Arc::new(kps),
|
|
||||||
None => {
|
|
||||||
return Err("Unable to locate validator key pairs, nothing to do.".into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let slots_per_epoch = E::slots_per_epoch();
|
let slots_per_epoch = E::slots_per_epoch();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user