mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 03:31:45 +00:00
Co-Authored-By: hopinheimer <knmanas6@gmail.com> Co-Authored-By: hopinheimer <48147533+hopinheimer@users.noreply.github.com> Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu> Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: Michael Sproul <michaelsproul@users.noreply.github.com>
420 lines
17 KiB
Rust
420 lines
17 KiB
Rust
use crate::cli::ValidatorClient;
|
|
use beacon_node_fallback::ApiTopic;
|
|
use beacon_node_fallback::beacon_node_health::BeaconNodeSyncDistanceTiers;
|
|
use clap::ArgMatches;
|
|
use clap_utils::{flags::DISABLE_MALLOC_TUNING_FLAG, parse_required};
|
|
use directory::{
|
|
DEFAULT_HARDCODED_NETWORK, DEFAULT_ROOT_DIR, DEFAULT_SECRET_DIR, DEFAULT_VALIDATOR_DIR,
|
|
get_network_dir,
|
|
};
|
|
use eth2::types::{Graffiti, GraffitiPolicy};
|
|
use graffiti_file::GraffitiFile;
|
|
use initialized_validators::Config as InitializedValidatorsConfig;
|
|
use lighthouse_validator_store::Config as ValidatorStoreConfig;
|
|
use sensitive_url::SensitiveUrl;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fs;
|
|
use std::net::IpAddr;
|
|
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
use tracing::{info, warn};
|
|
use types::GRAFFITI_BYTES_LEN;
|
|
use validator_http_api::{self, PK_FILENAME};
|
|
use validator_http_metrics;
|
|
|
|
pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/";
|
|
|
|
/// Stores the core configuration for this validator instance.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct Config {
|
|
/// Configuration parameters for the validator store.
|
|
#[serde(flatten)]
|
|
pub validator_store: ValidatorStoreConfig,
|
|
/// 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 endpoints of the beacon node APIs.
|
|
///
|
|
/// Should be similar to `["http://localhost:8080"]`
|
|
pub beacon_nodes: Vec<SensitiveUrl>,
|
|
/// An optional beacon node used for block proposals only.
|
|
pub proposer_nodes: Vec<SensitiveUrl>,
|
|
/// 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, don't scan the validators dir for new keystores.
|
|
pub disable_auto_discover: bool,
|
|
/// If true, re-register existing validators in definitions.yml for slashing protection.
|
|
pub init_slashing_protection: bool,
|
|
/// If true, use longer timeouts for requests made to the beacon node.
|
|
pub use_long_timeouts: bool,
|
|
/// Multiplier to use for long timeouts.
|
|
pub long_timeouts_multiplier: u32,
|
|
/// Graffiti to be inserted everytime we create a block.
|
|
pub graffiti: Option<Graffiti>,
|
|
/// Graffiti file to load per validator graffitis.
|
|
pub graffiti_file: Option<GraffitiFile>,
|
|
/// GraffitiPolicy to append client version info
|
|
pub graffiti_policy: Option<GraffitiPolicy>,
|
|
/// Configuration for the HTTP REST API.
|
|
pub http_api: validator_http_api::Config,
|
|
/// Configuration for the HTTP REST API.
|
|
pub http_metrics: validator_http_metrics::Config,
|
|
/// Configuration for the Beacon Node fallback.
|
|
pub beacon_node_fallback: beacon_node_fallback::Config,
|
|
/// Configuration for sending metrics to a remote explorer endpoint.
|
|
pub monitoring_api: Option<monitoring_api::Config>,
|
|
/// If true, enable functionality that monitors the network for attestations or proposals from
|
|
/// any of the validators managed by this client before starting up.
|
|
pub enable_doppelganger_protection: bool,
|
|
/// If true, then we publish validator specific metrics (e.g next attestation duty slot)
|
|
/// for all our managed validators.
|
|
/// Note: We publish validator specific metrics for low validator counts without this flag
|
|
/// (<= 64 validators)
|
|
pub enable_high_validator_count_metrics: bool,
|
|
/// Enable use of the blinded block endpoints during proposals.
|
|
pub builder_registration_timestamp_override: Option<u64>,
|
|
/// A list of custom certificates that the validator client will additionally use when
|
|
/// connecting to a beacon node over SSL/TLS.
|
|
pub beacon_nodes_tls_certs: Option<Vec<PathBuf>>,
|
|
/// Enables broadcasting of various requests (by topic) to all beacon nodes.
|
|
pub broadcast_topics: Vec<ApiTopic>,
|
|
/// Enables a service which attempts to measure latency between the VC and BNs.
|
|
pub enable_latency_measurement_service: bool,
|
|
/// Enables the beacon head monitor that reacts to head updates from connected beacon nodes.
|
|
pub enable_beacon_head_monitor: bool,
|
|
/// Defines the number of validators per `validator/register_validator` request sent to the BN.
|
|
pub validator_registration_batch_size: usize,
|
|
/// Whether we are running with distributed network support.
|
|
pub distributed: bool,
|
|
/// Configuration for the initialized validators
|
|
#[serde(flatten)]
|
|
pub initialized_validators: InitializedValidatorsConfig,
|
|
pub disable_attesting: bool,
|
|
}
|
|
|
|
impl Default for Config {
|
|
/// Build a new configuration from defaults.
|
|
fn default() -> Self {
|
|
// WARNING: these directory defaults should be always overwritten 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_NETWORK);
|
|
let validator_dir = base_dir.join(DEFAULT_VALIDATOR_DIR);
|
|
let secrets_dir = base_dir.join(DEFAULT_SECRET_DIR);
|
|
|
|
let beacon_nodes = vec![
|
|
SensitiveUrl::parse(DEFAULT_BEACON_NODE)
|
|
.expect("beacon_nodes must always be a valid url."),
|
|
];
|
|
Self {
|
|
validator_store: ValidatorStoreConfig::default(),
|
|
validator_dir,
|
|
secrets_dir,
|
|
beacon_nodes,
|
|
proposer_nodes: Vec::new(),
|
|
allow_unsynced_beacon_node: false,
|
|
disable_auto_discover: false,
|
|
init_slashing_protection: false,
|
|
use_long_timeouts: false,
|
|
long_timeouts_multiplier: 1,
|
|
graffiti: None,
|
|
graffiti_file: None,
|
|
graffiti_policy: None,
|
|
http_api: <_>::default(),
|
|
http_metrics: <_>::default(),
|
|
beacon_node_fallback: <_>::default(),
|
|
monitoring_api: None,
|
|
enable_doppelganger_protection: false,
|
|
enable_high_validator_count_metrics: false,
|
|
beacon_nodes_tls_certs: None,
|
|
builder_registration_timestamp_override: None,
|
|
broadcast_topics: vec![ApiTopic::Subscriptions],
|
|
enable_latency_measurement_service: true,
|
|
enable_beacon_head_monitor: true,
|
|
validator_registration_batch_size: 500,
|
|
distributed: false,
|
|
initialized_validators: <_>::default(),
|
|
disable_attesting: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
/// Returns a `Default` implementation of `Self` with some parameters modified by the supplied
|
|
/// `cli_args`.
|
|
pub fn from_cli(
|
|
cli_args: &ArgMatches,
|
|
validator_client_config: &ValidatorClient,
|
|
) -> 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.get_one::<String>("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 let Some(validator_dir_path) = validator_client_config.validators_dir.as_ref() {
|
|
validator_dir = Some(validator_dir_path.clone());
|
|
}
|
|
if let Some(secrets_dir_path) = validator_client_config.secrets_dir.as_ref() {
|
|
secrets_dir = Some(secrets_dir_path.clone());
|
|
}
|
|
|
|
config.validator_dir = validator_dir.unwrap_or_else(|| {
|
|
default_root_dir
|
|
.join(get_network_dir(cli_args))
|
|
.join(DEFAULT_VALIDATOR_DIR)
|
|
});
|
|
|
|
config.secrets_dir = secrets_dir.unwrap_or_else(|| {
|
|
default_root_dir
|
|
.join(get_network_dir(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_nodes) = validator_client_config.beacon_nodes.as_ref() {
|
|
config.beacon_nodes = beacon_nodes
|
|
.iter()
|
|
.map(|s| SensitiveUrl::parse(s))
|
|
.collect::<Result<_, _>>()
|
|
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?;
|
|
}
|
|
|
|
if let Some(proposer_nodes) = validator_client_config.proposer_nodes.as_ref() {
|
|
config.proposer_nodes = proposer_nodes
|
|
.iter()
|
|
.map(|s| SensitiveUrl::parse(s))
|
|
.collect::<Result<_, _>>()
|
|
.map_err(|e| format!("Unable to parse proposer node URL: {:?}", e))?;
|
|
}
|
|
|
|
config.disable_auto_discover = validator_client_config.disable_auto_discover;
|
|
config.init_slashing_protection = validator_client_config.init_slashing_protection;
|
|
config.use_long_timeouts = validator_client_config.use_long_timeouts;
|
|
config.long_timeouts_multiplier = validator_client_config.long_timeouts_multiplier;
|
|
|
|
if let Some(graffiti_file_path) = validator_client_config.graffiti_file.as_ref() {
|
|
let mut graffiti_file = GraffitiFile::new(graffiti_file_path.into());
|
|
graffiti_file
|
|
.read_graffiti_file()
|
|
.map_err(|e| format!("Error reading graffiti file: {:?}", e))?;
|
|
config.graffiti_file = Some(graffiti_file);
|
|
info!(
|
|
path = graffiti_file_path.to_str(),
|
|
"Successfully loaded graffiti file"
|
|
);
|
|
}
|
|
|
|
if let Some(input_graffiti) = validator_client_config.graffiti.as_ref() {
|
|
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());
|
|
}
|
|
}
|
|
|
|
config.graffiti_policy = if validator_client_config.graffiti_append {
|
|
Some(GraffitiPolicy::AppendClientVersions)
|
|
} else {
|
|
Some(GraffitiPolicy::PreserveUserGraffiti)
|
|
};
|
|
|
|
if let Some(input_fee_recipient) = validator_client_config.suggested_fee_recipient {
|
|
config.validator_store.fee_recipient = Some(input_fee_recipient);
|
|
}
|
|
|
|
if let Some(tls_certs) = validator_client_config.beacon_nodes_tls_certs.as_ref() {
|
|
config.beacon_nodes_tls_certs = Some(tls_certs.iter().map(PathBuf::from).collect());
|
|
}
|
|
|
|
config.distributed = validator_client_config.distributed;
|
|
|
|
if let Some(mut broadcast_topics) = validator_client_config.broadcast.clone() {
|
|
broadcast_topics.retain(|topic| *topic != ApiTopic::None);
|
|
config.broadcast_topics = broadcast_topics;
|
|
}
|
|
|
|
/*
|
|
* Beacon node fallback
|
|
*/
|
|
config.beacon_node_fallback.sync_tolerances = BeaconNodeSyncDistanceTiers::from_vec(
|
|
&validator_client_config.beacon_nodes_sync_tolerances,
|
|
)?;
|
|
|
|
/*
|
|
* Web3 signer
|
|
*/
|
|
if validator_client_config.web3_signer_keep_alive_timeout == 0 {
|
|
config.initialized_validators.web3_signer_keep_alive_timeout = None
|
|
} else {
|
|
config.initialized_validators.web3_signer_keep_alive_timeout = Some(
|
|
Duration::from_millis(validator_client_config.web3_signer_keep_alive_timeout),
|
|
);
|
|
}
|
|
|
|
if let Some(n) = validator_client_config.web3_signer_max_idle_connections {
|
|
config
|
|
.initialized_validators
|
|
.web3_signer_max_idle_connections = Some(n);
|
|
}
|
|
|
|
/*
|
|
* Http API server
|
|
*/
|
|
|
|
config.http_api.enabled = validator_client_config.http;
|
|
|
|
if let Some(address) = &validator_client_config.http_address {
|
|
if validator_client_config.unencrypted_http_transport {
|
|
config.http_api.listen_addr = address
|
|
.parse::<IpAddr>()
|
|
.map_err(|_| "http-address is not a valid IP address.")?;
|
|
} else {
|
|
return Err(
|
|
"While using `--http-address`, you must also use `--unencrypted-http-transport`."
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
|
|
config.http_api.listen_port = validator_client_config.http_port;
|
|
|
|
if let Some(allow_origin) = validator_client_config.http_allow_origin.as_ref() {
|
|
// 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());
|
|
}
|
|
|
|
config.http_api.allow_keystore_export = validator_client_config.http_allow_keystore_export;
|
|
config.http_api.store_passwords_in_secrets_dir =
|
|
validator_client_config.http_store_passwords_in_secrets_dir;
|
|
|
|
if let Some(http_token_path) = &validator_client_config.http_token_path {
|
|
config.http_api.http_token_path = PathBuf::from(http_token_path);
|
|
} else {
|
|
// For backward compatibility, default to the path under the validator dir if not provided.
|
|
config.http_api.http_token_path = config.validator_dir.join(PK_FILENAME);
|
|
}
|
|
|
|
/*
|
|
* Prometheus metrics HTTP server
|
|
*/
|
|
|
|
config.http_metrics.enabled = validator_client_config.metrics;
|
|
config.enable_high_validator_count_metrics =
|
|
validator_client_config.enable_high_validator_count_metrics;
|
|
|
|
if let Some(metrics_address) = &validator_client_config.metrics_address {
|
|
config.http_metrics.listen_addr = metrics_address
|
|
.parse::<IpAddr>()
|
|
.map_err(|_| "metrics-address is not a valid IP address.")?;
|
|
}
|
|
|
|
config.http_metrics.listen_port = validator_client_config.metrics_port;
|
|
|
|
if let Some(allow_origin) = validator_client_config.metrics_allow_origin.as_ref() {
|
|
// 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_metrics.allow_origin = Some(allow_origin.to_string());
|
|
}
|
|
|
|
if cli_args.get_flag(DISABLE_MALLOC_TUNING_FLAG) {
|
|
config.http_metrics.allocator_metrics_enabled = false;
|
|
}
|
|
|
|
/*
|
|
* Explorer metrics
|
|
*/
|
|
if let Some(monitoring_endpoint) = validator_client_config.monitoring_endpoint.as_ref() {
|
|
let update_period_secs = Some(validator_client_config.monitoring_endpoint_period);
|
|
config.monitoring_api = Some(monitoring_api::Config {
|
|
db_path: None,
|
|
freezer_db_path: None,
|
|
update_period_secs,
|
|
monitoring_endpoint: monitoring_endpoint.to_string(),
|
|
});
|
|
}
|
|
|
|
config.enable_doppelganger_protection =
|
|
validator_client_config.enable_doppelganger_protection;
|
|
config.validator_store.builder_proposals = validator_client_config.builder_proposals;
|
|
config.validator_store.prefer_builder_proposals =
|
|
validator_client_config.prefer_builder_proposals;
|
|
config.validator_store.gas_limit = Some(validator_client_config.gas_limit);
|
|
|
|
config.builder_registration_timestamp_override =
|
|
validator_client_config.builder_registration_timestamp_override;
|
|
|
|
config.validator_store.builder_boost_factor = validator_client_config.builder_boost_factor;
|
|
config.enable_latency_measurement_service =
|
|
!validator_client_config.disable_latency_measurement_service;
|
|
config.enable_beacon_head_monitor = !validator_client_config.disable_beacon_head_monitor;
|
|
|
|
config.validator_registration_batch_size =
|
|
validator_client_config.validator_registration_batch_size;
|
|
|
|
if config.validator_registration_batch_size == 0 {
|
|
return Err("validator-registration-batch-size cannot be 0".to_string());
|
|
}
|
|
|
|
config.validator_store.enable_web3signer_slashing_protection =
|
|
if validator_client_config.disable_slashing_protection_web3signer {
|
|
warn!(
|
|
info = "ensure slashing protection on web3signer is enabled or you WILL \
|
|
get slashed",
|
|
"Slashing protection for remote keys disabled"
|
|
);
|
|
false
|
|
} else {
|
|
true
|
|
};
|
|
|
|
config.disable_attesting = validator_client_config.disable_attesting;
|
|
|
|
Ok(config)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
// Ensures the default config does not panic.
|
|
fn default_config() {
|
|
Config::default();
|
|
}
|
|
}
|