6ea3bc5e52
## Issue Addressed NA ## Proposed Changes - Implements a HTTP API for the validator client. - Creates EIP-2335 keystores with an empty `description` field, instead of a missing `description` field. Adds option to set name. - Be more graceful with setups without any validators (yet) - Remove an error log when there are no validators. - Create the `validator` dir if it doesn't exist. - Allow building a `ValidatorDir` without a withdrawal keystore (required for the API method where we only post a voting keystore). - Add optional `description` field to `validator_definitions.yml` ## TODO - [x] Signature header, as per https://github.com/sigp/lighthouse/issues/1269#issuecomment-649879855 - [x] Return validator descriptions - [x] Return deposit data - [x] Respect the mnemonic offset - [x] Check that mnemonic can derive returned keys - [x] Be strict about non-localhost - [x] Allow graceful start without any validators (+ create validator dir) - [x] Docs final pass - [x] Swap to EIP-2335 description field. - [x] Fix Zerioze TODO in VC api types. - [x] Zeroize secp256k1 key ## Endpoints - [x] `GET /lighthouse/version` - [x] `GET /lighthouse/health` - [x] `GET /lighthouse/validators` - [x] `POST /lighthouse/validators/hd` - [x] `POST /lighthouse/validators/keystore` - [x] `PATCH /lighthouse/validators/:validator_pubkey` - [ ] ~~`POST /lighthouse/validators/:validator_pubkey/exit/:epoch`~~ Future works ## Additional Info TBC
172 lines
6.4 KiB
Rust
172 lines
6.4 KiB
Rust
use crate::http_api;
|
|
use clap::ArgMatches;
|
|
use clap_utils::{parse_optional, parse_required};
|
|
use directory::{
|
|
get_testnet_name, DEFAULT_HARDCODED_TESTNET, DEFAULT_ROOT_DIR, DEFAULT_SECRET_DIR,
|
|
DEFAULT_VALIDATOR_DIR,
|
|
};
|
|
use eth2::types::Graffiti;
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use slog::{warn, Logger};
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use types::GRAFFITI_BYTES_LEN;
|
|
|
|
pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/";
|
|
|
|
/// Stores the core configuration for this validator instance.
|
|
#[derive(Clone, Serialize, Deserialize)]
|
|
pub struct Config {
|
|
/// The data directory, which stores all validator databases
|
|
pub validator_dir: PathBuf,
|
|
/// The directory containing the passwords to unlock validator keystores.
|
|
pub secrets_dir: PathBuf,
|
|
/// The http endpoint of the beacon node API.
|
|
///
|
|
/// Should be similar to `http://localhost:8080`
|
|
pub beacon_node: String,
|
|
/// 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, delete any validator keystore lockfiles that would prevent starting.
|
|
pub delete_lockfiles: bool,
|
|
/// If true, don't scan the validators dir for new keystores.
|
|
pub disable_auto_discover: bool,
|
|
/// If true, don't re-register existing validators in definitions.yml for slashing protection.
|
|
pub strict_slashing_protection: bool,
|
|
/// Graffiti to be inserted everytime we create a block.
|
|
pub graffiti: Option<Graffiti>,
|
|
/// Configuration for the HTTP REST API.
|
|
pub http_api: http_api::Config,
|
|
}
|
|
|
|
impl Default for Config {
|
|
/// Build a new configuration from defaults.
|
|
fn default() -> Self {
|
|
// WARNING: these directory defaults should be always overrided with parameters
|
|
// from cli for specific networks.
|
|
let base_dir = dirs::home_dir()
|
|
.unwrap_or_else(|| PathBuf::from("."))
|
|
.join(DEFAULT_ROOT_DIR)
|
|
.join(DEFAULT_HARDCODED_TESTNET);
|
|
let validator_dir = base_dir.join(DEFAULT_VALIDATOR_DIR);
|
|
let secrets_dir = base_dir.join(DEFAULT_SECRET_DIR);
|
|
Self {
|
|
validator_dir,
|
|
secrets_dir,
|
|
beacon_node: DEFAULT_BEACON_NODE.to_string(),
|
|
allow_unsynced_beacon_node: false,
|
|
delete_lockfiles: false,
|
|
disable_auto_discover: false,
|
|
strict_slashing_protection: false,
|
|
graffiti: None,
|
|
http_api: <_>::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
/// Returns a `Default` implementation of `Self` with some parameters modified by the supplied
|
|
/// `cli_args`.
|
|
pub fn from_cli(cli_args: &ArgMatches, log: &Logger) -> Result<Config, String> {
|
|
let mut config = Config::default();
|
|
|
|
let default_root_dir = dirs::home_dir()
|
|
.map(|home| home.join(DEFAULT_ROOT_DIR))
|
|
.unwrap_or_else(|| PathBuf::from("."));
|
|
|
|
let (mut validator_dir, mut secrets_dir) = (None, None);
|
|
if cli_args.value_of("datadir").is_some() {
|
|
let base_dir: PathBuf = parse_required(cli_args, "datadir")?;
|
|
validator_dir = Some(base_dir.join(DEFAULT_VALIDATOR_DIR));
|
|
secrets_dir = Some(base_dir.join(DEFAULT_SECRET_DIR));
|
|
}
|
|
if cli_args.value_of("validators-dir").is_some()
|
|
&& cli_args.value_of("secrets-dir").is_some()
|
|
{
|
|
validator_dir = Some(parse_required(cli_args, "validators-dir")?);
|
|
secrets_dir = Some(parse_required(cli_args, "secrets-dir")?);
|
|
}
|
|
|
|
config.validator_dir = validator_dir.unwrap_or_else(|| {
|
|
default_root_dir
|
|
.join(get_testnet_name(cli_args))
|
|
.join(DEFAULT_VALIDATOR_DIR)
|
|
});
|
|
|
|
config.secrets_dir = secrets_dir.unwrap_or_else(|| {
|
|
default_root_dir
|
|
.join(get_testnet_name(cli_args))
|
|
.join(DEFAULT_SECRET_DIR)
|
|
});
|
|
|
|
if !config.validator_dir.exists() {
|
|
fs::create_dir_all(&config.validator_dir)
|
|
.map_err(|e| format!("Failed to create {:?}: {:?}", config.validator_dir, e))?;
|
|
}
|
|
|
|
if let Some(beacon_node) = parse_optional(cli_args, "beacon-node")? {
|
|
config.beacon_node = beacon_node;
|
|
}
|
|
|
|
// To be deprecated.
|
|
if let Some(server) = parse_optional(cli_args, "server")? {
|
|
warn!(
|
|
log,
|
|
"The --server flag is deprecated";
|
|
"msg" => "please use --beacon-node instead"
|
|
);
|
|
config.beacon_node = server;
|
|
}
|
|
|
|
config.allow_unsynced_beacon_node = cli_args.is_present("allow-unsynced");
|
|
config.delete_lockfiles = cli_args.is_present("delete-lockfiles");
|
|
config.disable_auto_discover = cli_args.is_present("disable-auto-discover");
|
|
config.strict_slashing_protection = cli_args.is_present("strict-slashing-protection");
|
|
|
|
if let Some(input_graffiti) = cli_args.value_of("graffiti") {
|
|
let graffiti_bytes = input_graffiti.as_bytes();
|
|
if graffiti_bytes.len() > GRAFFITI_BYTES_LEN {
|
|
return Err(format!(
|
|
"Your graffiti is too long! {} bytes maximum!",
|
|
GRAFFITI_BYTES_LEN
|
|
));
|
|
} else {
|
|
let mut graffiti = [0; 32];
|
|
|
|
// Copy the provided bytes over.
|
|
//
|
|
// Panic-free because `graffiti_bytes.len()` <= `GRAFFITI_BYTES_LEN`.
|
|
graffiti[..graffiti_bytes.len()].copy_from_slice(&graffiti_bytes);
|
|
|
|
config.graffiti = Some(graffiti.into());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Http API server
|
|
*/
|
|
|
|
if cli_args.is_present("http") {
|
|
config.http_api.enabled = true;
|
|
}
|
|
|
|
if let Some(port) = cli_args.value_of("http-port") {
|
|
config.http_api.listen_port = port
|
|
.parse::<u16>()
|
|
.map_err(|_| "http-port is not a valid u16.")?;
|
|
}
|
|
|
|
if let Some(allow_origin) = cli_args.value_of("http-allow-origin") {
|
|
// Pre-validate the config value to give feedback to the user on node startup, instead of
|
|
// as late as when the first API response is produced.
|
|
hyper::header::HeaderValue::from_str(allow_origin)
|
|
.map_err(|_| "Invalid allow-origin value")?;
|
|
|
|
config.http_api.allow_origin = Some(allow_origin.to_string());
|
|
}
|
|
|
|
Ok(config)
|
|
}
|
|
}
|