Migrate validator client to clap derive (#6300)

Partially #5900


  Migrate the validator client cli to clap derive
This commit is contained in:
Eitan Seri-Levi
2025-02-03 23:08:31 +03:00
committed by GitHub
parent 95cec45c38
commit 7e4b27c922
18 changed files with 653 additions and 773 deletions

1
Cargo.lock generated
View File

@@ -884,6 +884,7 @@ dependencies = [
name = "beacon_node_fallback" name = "beacon_node_fallback"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap",
"environment", "environment",
"eth2", "eth2",
"futures", "futures",

View File

@@ -2,11 +2,8 @@ mod common;
pub mod validator; pub mod validator;
pub mod wallet; pub mod wallet;
use clap::Arg;
use clap::ArgAction;
use clap::ArgMatches; use clap::ArgMatches;
use clap::Command; use clap::Command;
use clap_utils::FLAG_HEADER;
use environment::Environment; use environment::Environment;
use types::EthSpec; use types::EthSpec;
@@ -21,15 +18,6 @@ pub fn cli_app() -> Command {
.visible_aliases(["a", "am", "account"]) .visible_aliases(["a", "am", "account"])
.about("Utilities for generating and managing Ethereum 2.0 accounts.") .about("Utilities for generating and managing Ethereum 2.0 accounts.")
.display_order(0) .display_order(0)
.arg(
Arg::new("help")
.long("help")
.short('h')
.help("Prints help information")
.action(ArgAction::HelpLong)
.display_order(0)
.help_heading(FLAG_HEADER),
)
.subcommand(wallet::cli_app()) .subcommand(wallet::cli_app())
.subcommand(validator::cli_app()) .subcommand(validator::cli_app())
} }

View File

@@ -8,7 +8,6 @@ pub mod slashing_protection;
use crate::{VALIDATOR_DIR_FLAG, VALIDATOR_DIR_FLAG_ALIAS}; use crate::{VALIDATOR_DIR_FLAG, VALIDATOR_DIR_FLAG_ALIAS};
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{Arg, ArgAction, ArgMatches, Command};
use clap_utils::FLAG_HEADER;
use directory::{parse_path_or_default_with_flag, DEFAULT_VALIDATOR_DIR}; use directory::{parse_path_or_default_with_flag, DEFAULT_VALIDATOR_DIR};
use environment::Environment; use environment::Environment;
use std::path::PathBuf; use std::path::PathBuf;
@@ -20,16 +19,6 @@ pub fn cli_app() -> Command {
Command::new(CMD) Command::new(CMD)
.display_order(0) .display_order(0)
.about("Provides commands for managing Eth2 validators.") .about("Provides commands for managing Eth2 validators.")
.arg(
Arg::new("help")
.long("help")
.short('h')
.help("Prints help information")
.action(ArgAction::HelpLong)
.display_order(0)
.help_heading(FLAG_HEADER)
.global(true),
)
.arg( .arg(
Arg::new(VALIDATOR_DIR_FLAG) Arg::new(VALIDATOR_DIR_FLAG)
.long(VALIDATOR_DIR_FLAG) .long(VALIDATOR_DIR_FLAG)

View File

@@ -4,7 +4,6 @@ pub mod recover;
use crate::WALLETS_DIR_FLAG; use crate::WALLETS_DIR_FLAG;
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{Arg, ArgAction, ArgMatches, Command};
use clap_utils::FLAG_HEADER;
use directory::{parse_path_or_default_with_flag, DEFAULT_WALLET_DIR}; use directory::{parse_path_or_default_with_flag, DEFAULT_WALLET_DIR};
use std::fs::create_dir_all; use std::fs::create_dir_all;
use std::path::PathBuf; use std::path::PathBuf;
@@ -15,16 +14,6 @@ pub fn cli_app() -> Command {
Command::new(CMD) Command::new(CMD)
.about("Manage wallets, from which validator keys can be derived.") .about("Manage wallets, from which validator keys can be derived.")
.display_order(0) .display_order(0)
.arg(
Arg::new("help")
.long("help")
.short('h')
.help("Prints help information")
.action(ArgAction::HelpLong)
.display_order(0)
.help_heading(FLAG_HEADER)
.global(true)
)
.arg( .arg(
Arg::new(WALLETS_DIR_FLAG) Arg::new(WALLETS_DIR_FLAG)
.long(WALLETS_DIR_FLAG) .long(WALLETS_DIR_FLAG)

View File

@@ -18,15 +18,6 @@ pub fn cli_app() -> Command {
/* /*
* Configuration directory locations. * Configuration directory locations.
*/ */
.arg(
Arg::new("help")
.long("help")
.short('h')
.help("Prints help information")
.action(ArgAction::HelpLong)
.display_order(0)
.help_heading(FLAG_HEADER)
)
.arg( .arg(
Arg::new("network-dir") Arg::new("network-dir")
.long("network-dir") .long("network-dir")

View File

@@ -18,16 +18,16 @@ Options:
certificate path. certificate path.
--broadcast <API_TOPICS> --broadcast <API_TOPICS>
Comma-separated list of beacon API topics to broadcast to all beacon Comma-separated list of beacon API topics to broadcast to all beacon
nodes. Possible values are: none, attestations, blocks, subscriptions, nodes. Default (when flag is omitted) is to broadcast subscriptions
sync-committee. Default (when flag is omitted) is to broadcast only. [possible values: none, attestations, blocks, subscriptions,
subscriptions only. sync-committee]
--builder-boost-factor <UINT64> --builder-boost-factor <UINT64>
Defines the boost factor, a percentage multiplier to apply to the Defines the boost factor, a percentage multiplier to apply to the
builder's payload value when choosing between a builder payload header builder's payload value when choosing between a builder payload header
and payload from the local execution node. and payload from the local execution node.
--builder-registration-timestamp-override <builder-registration-timestamp-override> --builder-registration-timestamp-override <UNIX-TIMESTAMP>
This flag takes a unix timestamp value that will be used to override This flag takes a unix timestamp value that will be used to override
the timestamp used in the builder api registration the timestamp used in the builder api registration.
-d, --datadir <DIR> -d, --datadir <DIR>
Used to specify a custom root data directory for lighthouse keys and Used to specify a custom root data directory for lighthouse keys and
databases. Defaults to $HOME/.lighthouse/{network} where network is databases. Defaults to $HOME/.lighthouse/{network} where network is
@@ -41,7 +41,7 @@ Options:
The gas limit to be used in all builder proposals for all validators The gas limit to be used in all builder proposals for all validators
managed by this validator client. Note this will not necessarily be managed by this validator client. Note this will not necessarily be
used if the gas limit set here moves too far from the previous block's used if the gas limit set here moves too far from the previous block's
gas limit. [default: 30,000,000] gas limit. [default: 30000000]
--genesis-state-url <URL> --genesis-state-url <URL>
A URL of a beacon-API compatible server from which to download the A URL of a beacon-API compatible server from which to download the
genesis state. Checkpoint sync server URLs can generally be used with genesis state. Checkpoint sync server URLs can generally be used with
@@ -68,7 +68,8 @@ Options:
is supplied, the CORS allowed origin is set to the listen address of is supplied, the CORS allowed origin is set to the listen address of
this server (e.g., http://localhost:5062). this server (e.g., http://localhost:5062).
--http-port <PORT> --http-port <PORT>
Set the listen TCP port for the RESTful HTTP API server. Set the listen TCP port for the RESTful HTTP API server. [default:
5062]
--http-token-path <HTTP_TOKEN_PATH> --http-token-path <HTTP_TOKEN_PATH>
Path to file containing the HTTP API token for validator client Path to file containing the HTTP API token for validator client
authentication. If not specified, defaults to authentication. If not specified, defaults to
@@ -96,6 +97,7 @@ Options:
set to 0, background file logging is disabled. [default: 200] set to 0, background file logging is disabled. [default: 200]
--metrics-address <ADDRESS> --metrics-address <ADDRESS>
Set the listen address for the Prometheus metrics HTTP server. Set the listen address for the Prometheus metrics HTTP server.
[default: 127.0.0.1]
--metrics-allow-origin <ORIGIN> --metrics-allow-origin <ORIGIN>
Set the value of the Access-Control-Allow-Origin response HTTP header. Set the value of the Access-Control-Allow-Origin response HTTP header.
Use * to allow any origin (not recommended in production). If no value Use * to allow any origin (not recommended in production). If no value
@@ -103,6 +105,7 @@ Options:
this server (e.g., http://localhost:5064). this server (e.g., http://localhost:5064).
--metrics-port <PORT> --metrics-port <PORT>
Set the listen TCP port for the Prometheus metrics HTTP server. Set the listen TCP port for the Prometheus metrics HTTP server.
[default: 5064]
--monitoring-endpoint <ADDRESS> --monitoring-endpoint <ADDRESS>
Enables the monitoring service for sending system metrics to a remote Enables the monitoring service for sending system metrics to a remote
endpoint. This can be used to monitor your setup on certain services endpoint. This can be used to monitor your setup on certain services
@@ -113,7 +116,7 @@ Options:
provide an untrusted URL. provide an untrusted URL.
--monitoring-endpoint-period <SECONDS> --monitoring-endpoint-period <SECONDS>
Defines how many seconds to wait between each message sent to the Defines how many seconds to wait between each message sent to the
monitoring-endpoint. Default: 60s monitoring-endpoint. [default: 60]
--network <network> --network <network>
Name of the Eth2 chain Lighthouse will sync and follow. [possible Name of the Eth2 chain Lighthouse will sync and follow. [possible
values: mainnet, gnosis, chiado, sepolia, holesky] values: mainnet, gnosis, chiado, sepolia, holesky]
@@ -145,8 +148,8 @@ Options:
each validator along with the common slashing protection database and each validator along with the common slashing protection database and
the validator_definitions.yml the validator_definitions.yml
--web3-signer-keep-alive-timeout <MILLIS> --web3-signer-keep-alive-timeout <MILLIS>
Keep-alive timeout for each web3signer connection. Set to 'null' to Keep-alive timeout for each web3signer connection. Set to '0' to never
never timeout [default: 20000] timeout. [default: 20000]
--web3-signer-max-idle-connections <COUNT> --web3-signer-max-idle-connections <COUNT>
Maximum number of idle connections to maintain per web3signer host. Maximum number of idle connections to maintain per web3signer host.
Default is unlimited. Default is unlimited.

View File

@@ -13,15 +13,6 @@ pub fn cli_app() -> Command {
surface compared to a full beacon node.") surface compared to a full beacon node.")
.styles(get_color_style()) .styles(get_color_style())
.display_order(0) .display_order(0)
.arg(
Arg::new("help")
.long("help")
.short('h')
.help("Prints help information")
.action(ArgAction::HelpLong)
.display_order(0)
.help_heading(FLAG_HEADER)
)
.arg( .arg(
Arg::new("enr-address") Arg::new("enr-address")
.long("enr-address") .long("enr-address")

View File

@@ -66,16 +66,6 @@ pub struct DatabaseManager {
)] )]
pub backend: store::config::DatabaseBackend, pub backend: store::config::DatabaseBackend,
#[clap(
long,
global = true,
help = "Prints help information",
action = clap::ArgAction::HelpLong,
display_order = 0,
help_heading = FLAG_HEADER
)]
help: Option<bool>,
#[clap(subcommand)] #[clap(subcommand)]
pub subcommand: DatabaseManagerSubcommand, pub subcommand: DatabaseManagerSubcommand,
} }

View File

@@ -1,9 +1,12 @@
use clap::Parser; use clap::Parser;
use database_manager::cli::DatabaseManager; use database_manager::cli::DatabaseManager;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator_client::cli::ValidatorClient;
#[derive(Parser, Clone, Deserialize, Serialize, Debug)] #[derive(Parser, Clone, Deserialize, Serialize, Debug)]
pub enum LighthouseSubcommands { pub enum LighthouseSubcommands {
#[clap(name = "database_manager")] #[clap(name = "database_manager")]
DatabaseManager(DatabaseManager), DatabaseManager(Box<DatabaseManager>),
#[clap(name = "validator_client")]
ValidatorClient(Box<ValidatorClient>),
} }

View File

@@ -399,10 +399,10 @@ fn main() {
.action(ArgAction::HelpLong) .action(ArgAction::HelpLong)
.display_order(0) .display_order(0)
.help_heading(FLAG_HEADER) .help_heading(FLAG_HEADER)
.global(true)
) )
.subcommand(beacon_node::cli_app()) .subcommand(beacon_node::cli_app())
.subcommand(boot_node::cli_app()) .subcommand(boot_node::cli_app())
.subcommand(validator_client::cli_app())
.subcommand(account_manager::cli_app()) .subcommand(account_manager::cli_app())
.subcommand(validator_manager::cli_app()); .subcommand(validator_manager::cli_app());
@@ -673,12 +673,49 @@ fn run<E: EthSpec>(
return Ok(()); return Ok(());
} }
if let Ok(LighthouseSubcommands::DatabaseManager(db_manager_config)) = match LighthouseSubcommands::from_arg_matches(matches) {
LighthouseSubcommands::from_arg_matches(matches) Ok(LighthouseSubcommands::DatabaseManager(db_manager_config)) => {
{ info!(log, "Running database manager for {} network", network_name);
info!(log, "Running database manager for {} network", network_name); database_manager::run(matches, &db_manager_config, environment)?;
database_manager::run(matches, &db_manager_config, environment)?; return Ok(());
return Ok(()); }
Ok(LighthouseSubcommands::ValidatorClient(validator_client_config)) => {
let context = environment.core_context();
let log = context.log().clone();
let executor = context.executor.clone();
let config = validator_client::Config::from_cli(
matches,
&validator_client_config,
context.log(),
)
.map_err(|e| format!("Unable to initialize validator config: {}", e))?;
// Dump configs if `dump-config` or `dump-chain-config` flags are set
clap_utils::check_dump_configs::<_, E>(matches, &config, &context.eth2_config.spec)?;
let shutdown_flag = matches.get_flag("immediate-shutdown");
if shutdown_flag {
info!(log, "Validator client immediate shutdown triggered.");
return Ok(());
}
executor.clone().spawn(
async move {
if let Err(e) = ProductionValidatorClient::new(context, config)
.and_then(|mut vc| async move { vc.start_service().await })
.await
{
crit!(log, "Failed to start validator client"; "reason" => e);
// Ignore the error since it always occurs during normal operation when
// shutting down.
let _ = executor
.shutdown_sender()
.try_send(ShutdownReason::Failure("Failed to start validator client"));
}
},
"validator_client",
);
}
Err(_) => (),
}; };
info!(log, "Lighthouse started"; "version" => VERSION); info!(log, "Lighthouse started"; "version" => VERSION);
@@ -733,38 +770,9 @@ fn run<E: EthSpec>(
"beacon_node", "beacon_node",
); );
} }
Some(("validator_client", matches)) => { // TODO(clap-derive) delete this once we've fully migrated to clap derive.
let context = environment.core_context(); // Qt the moment this needs to exist so that we dont trigger a crit.
let log = context.log().clone(); Some(("validator_client", _)) => (),
let executor = context.executor.clone();
let config = validator_client::Config::from_cli(matches, context.log())
.map_err(|e| format!("Unable to initialize validator config: {}", e))?;
// Dump configs if `dump-config` or `dump-chain-config` flags are set
clap_utils::check_dump_configs::<_, E>(matches, &config, &context.eth2_config.spec)?;
let shutdown_flag = matches.get_flag("immediate-shutdown");
if shutdown_flag {
info!(log, "Validator client immediate shutdown triggered.");
return Ok(());
}
executor.clone().spawn(
async move {
if let Err(e) = ProductionValidatorClient::new(context, config)
.and_then(|mut vc| async move { vc.start_service().await })
.await
{
crit!(log, "Failed to start validator client"; "reason" => e);
// Ignore the error since it always occurs during normal operation when
// shutting down.
let _ = executor
.shutdown_sender()
.try_send(ShutdownReason::Failure("Failed to start validator client"));
}
},
"validator_client",
);
}
_ => { _ => {
crit!(log, "No subcommand supplied. See --help ."); crit!(log, "No subcommand supplied. See --help .");
return Err("No subcommand supplied.".into()); return Err("No subcommand supplied.".into());

View File

@@ -407,6 +407,13 @@ fn metrics_port_flag() {
.with_config(|config| assert_eq!(config.http_metrics.listen_port, 9090)); .with_config(|config| assert_eq!(config.http_metrics.listen_port, 9090));
} }
#[test] #[test]
fn metrics_port_flag_default() {
CommandLineTest::new()
.flag("metrics", None)
.run()
.with_config(|config| assert_eq!(config.http_metrics.listen_port, 5064));
}
#[test]
fn metrics_allow_origin_flag() { fn metrics_allow_origin_flag() {
CommandLineTest::new() CommandLineTest::new()
.flag("metrics", None) .flag("metrics", None)
@@ -458,7 +465,7 @@ fn no_doppelganger_protection_flag() {
fn no_gas_limit_flag() { fn no_gas_limit_flag() {
CommandLineTest::new() CommandLineTest::new()
.run() .run()
.with_config(|config| assert!(config.validator_store.gas_limit.is_none())); .with_config(|config| assert!(config.validator_store.gas_limit == Some(30_000_000)));
} }
#[test] #[test]
fn gas_limit_flag() { fn gas_limit_flag() {
@@ -560,7 +567,7 @@ fn broadcast_flag() {
}); });
// Other valid variants // Other valid variants
CommandLineTest::new() CommandLineTest::new()
.flag("broadcast", Some("blocks, subscriptions")) .flag("broadcast", Some("blocks,subscriptions"))
.run() .run()
.with_config(|config| { .with_config(|config| {
assert_eq!( assert_eq!(
@@ -605,7 +612,7 @@ fn beacon_nodes_sync_tolerances_flag() {
} }
#[test] #[test]
#[should_panic(expected = "Unknown API topic")] #[should_panic(expected = "invalid value")]
fn wrong_broadcast_flag() { fn wrong_broadcast_flag() {
CommandLineTest::new() CommandLineTest::new()
.flag("broadcast", Some("foo, subscriptions")) .flag("broadcast", Some("foo, subscriptions"))

View File

@@ -9,6 +9,7 @@ name = "beacon_node_fallback"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
clap = { workspace = true }
environment = { workspace = true } environment = { workspace = true }
eth2 = { workspace = true } eth2 = { workspace = true }
futures = { workspace = true } futures = { workspace = true }

View File

@@ -1,11 +1,9 @@
use super::CandidateError; use super::CandidateError;
use eth2::BeaconNodeHttpClient; use eth2::BeaconNodeHttpClient;
use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slog::{warn, Logger}; use slog::{warn, Logger};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use types::Slot; use types::Slot;
/// Sync distances between 0 and DEFAULT_SYNC_TOLERANCE are considered `synced`. /// Sync distances between 0 and DEFAULT_SYNC_TOLERANCE are considered `synced`.
@@ -53,29 +51,6 @@ impl Default for BeaconNodeSyncDistanceTiers {
} }
} }
impl FromStr for BeaconNodeSyncDistanceTiers {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
let values: (u64, u64, u64) = s
.split(',')
.map(|s| {
s.parse()
.map_err(|e| format!("Invalid sync distance modifier: {e:?}"))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.collect_tuple()
.ok_or("Invalid number of sync distance modifiers".to_string())?;
Ok(BeaconNodeSyncDistanceTiers {
synced: Slot::new(values.0),
small: Slot::new(values.0 + values.1),
medium: Slot::new(values.0 + values.1 + values.2),
})
}
}
impl BeaconNodeSyncDistanceTiers { impl BeaconNodeSyncDistanceTiers {
/// Takes a given sync distance and determines its tier based on the `sync_tolerance` defined by /// Takes a given sync distance and determines its tier based on the `sync_tolerance` defined by
/// the CLI. /// the CLI.
@@ -90,6 +65,17 @@ impl BeaconNodeSyncDistanceTiers {
SyncDistanceTier::Large SyncDistanceTier::Large
} }
} }
pub fn from_vec(tiers: &[u64]) -> Result<Self, String> {
if tiers.len() != 3 {
return Err("Invalid number of sync distance modifiers".to_string());
}
Ok(BeaconNodeSyncDistanceTiers {
synced: Slot::new(tiers[0]),
small: Slot::new(tiers[0] + tiers[1]),
medium: Slot::new(tiers[0] + tiers[1] + tiers[2]),
})
}
} }
/// Execution Node health metrics. /// Execution Node health metrics.
@@ -320,7 +306,6 @@ mod tests {
SyncDistanceTier, SyncDistanceTier,
}; };
use crate::Config; use crate::Config;
use std::str::FromStr;
use types::Slot; use types::Slot;
#[test] #[test]
@@ -423,7 +408,7 @@ mod tests {
// medium 9..=12 // medium 9..=12
// large: 13.. // large: 13..
let distance_tiers = BeaconNodeSyncDistanceTiers::from_str("4,4,4").unwrap(); let distance_tiers = BeaconNodeSyncDistanceTiers::from_vec(&[4, 4, 4]).unwrap();
let synced_low = new_distance_tier(0, &distance_tiers); let synced_low = new_distance_tier(0, &distance_tiers);
let synced_high = new_distance_tier(4, &distance_tiers); let synced_high = new_distance_tier(4, &distance_tiers);

View File

@@ -7,6 +7,7 @@ use beacon_node_health::{
check_node_health, BeaconNodeHealth, BeaconNodeSyncDistanceTiers, ExecutionEngineHealth, check_node_health, BeaconNodeHealth, BeaconNodeSyncDistanceTiers, ExecutionEngineHealth,
IsOptimistic, SyncDistanceTier, IsOptimistic, SyncDistanceTier,
}; };
use clap::ValueEnum;
use environment::RuntimeContext; use environment::RuntimeContext;
use eth2::BeaconNodeHttpClient; use eth2::BeaconNodeHttpClient;
use futures::future; use futures::future;
@@ -20,7 +21,8 @@ use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use strum::{EnumString, EnumVariantNames}; use std::vec::Vec;
use strum::EnumVariantNames;
use tokio::{sync::RwLock, time::sleep}; use tokio::{sync::RwLock, time::sleep};
use types::{ChainSpec, Config as ConfigSpec, EthSpec, Slot}; use types::{ChainSpec, Config as ConfigSpec, EthSpec, Slot};
use validator_metrics::{inc_counter_vec, ENDPOINT_ERRORS, ENDPOINT_REQUESTS}; use validator_metrics::{inc_counter_vec, ENDPOINT_ERRORS, ENDPOINT_REQUESTS};
@@ -727,9 +729,10 @@ async fn sort_nodes_by_health<E: EthSpec>(nodes: &mut Vec<CandidateBeaconNode<E>
} }
/// Serves as a cue for `BeaconNodeFallback` to tell which requests need to be broadcasted. /// Serves as a cue for `BeaconNodeFallback` to tell which requests need to be broadcasted.
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, EnumString, EnumVariantNames)] #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, EnumVariantNames, ValueEnum)]
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]
pub enum ApiTopic { pub enum ApiTopic {
None,
Attestations, Attestations,
Blocks, Blocks,
Subscriptions, Subscriptions,
@@ -749,7 +752,6 @@ mod tests {
use crate::beacon_node_health::BeaconNodeHealthTier; use crate::beacon_node_health::BeaconNodeHealthTier;
use eth2::SensitiveUrl; use eth2::SensitiveUrl;
use eth2::Timeouts; use eth2::Timeouts;
use std::str::FromStr;
use strum::VariantNames; use strum::VariantNames;
use types::{MainnetEthSpec, Slot}; use types::{MainnetEthSpec, Slot};
@@ -758,10 +760,13 @@ mod tests {
#[test] #[test]
fn api_topic_all() { fn api_topic_all() {
let all = ApiTopic::all(); let all = ApiTopic::all();
assert_eq!(all.len(), ApiTopic::VARIANTS.len()); // ignore NONE variant
assert!(ApiTopic::VARIANTS let mut variants = ApiTopic::VARIANTS.to_vec();
variants.retain(|s| *s != "none");
assert_eq!(all.len(), variants.len());
assert!(variants
.iter() .iter()
.map(|topic| ApiTopic::from_str(topic).unwrap()) .map(|topic| ApiTopic::from_str(topic, true).unwrap())
.eq(all.into_iter())); .eq(all.into_iter()));
} }

View File

@@ -1,490 +1,478 @@
use clap::{builder::ArgPredicate, Arg, ArgAction, Command}; use beacon_node_fallback::ApiTopic;
use clap_utils::{get_color_style, FLAG_HEADER}; use clap::builder::ArgPredicate;
pub use clap::{FromArgMatches, Parser};
use clap_utils::get_color_style;
use clap_utils::FLAG_HEADER;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use types::Address;
pub fn cli_app() -> Command { #[derive(Parser, Clone, Deserialize, Serialize, Debug)]
Command::new("validator_client") #[clap(
.visible_aliases(["v", "vc", "validator"]) name = "validator_client",
.styles(get_color_style()) visible_aliases = &["v", "vc", "validator"],
.display_order(0) about = "When connected to a beacon node, performs the duties of a staked \
.about(
"When connected to a beacon node, performs the duties of a staked \
validator (e.g., proposing blocks and attestations).", validator (e.g., proposing blocks and attestations).",
) styles = get_color_style(),
.arg( next_line_help = true,
Arg::new("help") term_width = 80,
.long("help") disable_help_flag = true,
.short('h') disable_help_subcommand = true,
.help("Prints help information") display_order = 0,
.action(ArgAction::HelpLong) )]
.display_order(0) pub struct ValidatorClient {
.help_heading(FLAG_HEADER) #[clap(
) long,
.arg( value_name = "NETWORK_ADDRESSES",
Arg::new("beacon-nodes") value_delimiter = ',',
.long("beacon-nodes") help = "Comma-separated addresses to one or more beacon node HTTP APIs. \
.value_name("NETWORK_ADDRESSES") Default is http://localhost:5052.",
.help("Comma-separated addresses to one or more beacon node HTTP APIs. \ display_order = 0
Default is http://localhost:5052." )]
) pub beacon_nodes: Option<Vec<String>>,
.action(ArgAction::Set)
.display_order(0) #[clap(
) long,
.arg( value_name = "NETWORK_ADDRESSES",
Arg::new("proposer-nodes") value_delimiter = ',',
.long("proposer-nodes") help = "Comma-separated addresses to one or more beacon node HTTP APIs. \
.value_name("NETWORK_ADDRESSES") These specify nodes that are used to send beacon block proposals. \
.help("Comma-separated addresses to one or more beacon node HTTP APIs. \ A failure will revert back to the standard beacon nodes specified in --beacon-nodes.",
These specify nodes that are used to send beacon block proposals. A failure will revert back to the standard beacon nodes specified in --beacon-nodes." display_order = 0
) )]
.action(ArgAction::Set) pub proposer_nodes: Option<Vec<String>>,
.display_order(0)
) #[clap(
.arg( long,
Arg::new("broadcast") value_name = "API_TOPICS",
.long("broadcast") value_delimiter = ',',
.value_name("API_TOPICS") help = "Comma-separated list of beacon API topics to broadcast to all beacon nodes. \
.help("Comma-separated list of beacon API topics to broadcast to all beacon nodes. \ Default (when flag is omitted) is to broadcast subscriptions only.",
Possible values are: none, attestations, blocks, subscriptions, \ display_order = 0
sync-committee. Default (when flag is omitted) is to broadcast \ )]
subscriptions only." pub broadcast: Option<Vec<ApiTopic>>,
)
.action(ArgAction::Set) #[clap(
.display_order(0) long,
) alias = "validator-dir",
.arg( value_name = "VALIDATORS_DIR",
Arg::new("validators-dir") conflicts_with = "datadir",
.long("validators-dir") help = "The directory which contains the validator keystores, deposit data for \
.alias("validator-dir") each validator along with the common slashing protection database \
.value_name("VALIDATORS_DIR") and the validator_definitions.yml",
.help( display_order = 0
"The directory which contains the validator keystores, deposit data for \ )]
each validator along with the common slashing protection database \ pub validators_dir: Option<PathBuf>,
and the validator_definitions.yml"
) #[clap(
.action(ArgAction::Set) long,
.conflicts_with("datadir") value_name = "SECRETS_DIRECTORY",
.display_order(0) conflicts_with = "datadir",
) help = "The directory which contains the password to unlock the validator \
.arg( voting keypairs. Each password should be contained in a file where the \
Arg::new("secrets-dir") name is the 0x-prefixed hex representation of the validators voting public \
.long("secrets-dir") key. Defaults to ~/.lighthouse/{network}/secrets.",
.value_name("SECRETS_DIRECTORY") display_order = 0
.help( )]
"The directory which contains the password to unlock the validator \ pub secrets_dir: Option<PathBuf>,
voting keypairs. Each password should be contained in a file where the \
name is the 0x-prefixed hex representation of the validators voting public \ #[clap(
key. Defaults to ~/.lighthouse/{network}/secrets.", long,
) help = "If present, do not require the slashing protection database to exist before \
.action(ArgAction::Set) running. You SHOULD NOT use this flag unless you're certain that a new \
.conflicts_with("datadir") slashing protection database is required. Usually, your database \
.display_order(0) will have been initialized when you imported your validator keys. If you \
) misplace your database and then run with this flag you risk being slashed.",
.arg( display_order = 0,
Arg::new("init-slashing-protection") help_heading = FLAG_HEADER
.long("init-slashing-protection") )]
.action(ArgAction::SetTrue) pub init_slashing_protection: bool,
.help_heading(FLAG_HEADER)
.help( #[clap(
"If present, do not require the slashing protection database to exist before \ long,
running. You SHOULD NOT use this flag unless you're certain that a new \ help = "If present, do not attempt to discover new validators in the validators-dir. Validators \
slashing protection database is required. Usually, your database \ will need to be manually added to the validator_definitions.yml file.",
will have been initialized when you imported your validator keys. If you \ display_order = 0,
misplace your database and then run with this flag you risk being slashed." help_heading = FLAG_HEADER
) )]
.display_order(0) pub disable_auto_discover: bool,
)
.arg( #[clap(
Arg::new("disable-auto-discover") long,
.long("disable-auto-discover") help = "If present, the validator client will use longer timeouts for requests \
.action(ArgAction::SetTrue) made to the beacon node. This flag is generally not recommended, \
.help_heading(FLAG_HEADER) longer timeouts can cause missed duties when fallbacks are used.",
.help( display_order = 0,
"If present, do not attempt to discover new validators in the validators-dir. Validators \ help_heading = FLAG_HEADER
will need to be manually added to the validator_definitions.yml file." )]
) pub use_long_timeouts: bool,
.display_order(0)
) #[clap(
.arg( long,
Arg::new("use-long-timeouts") value_name = "CERTIFICATE-FILES",
.long("use-long-timeouts") value_delimiter = ',',
.action(ArgAction::SetTrue) help = "Comma-separated paths to custom TLS certificates to use when connecting \
.help_heading(FLAG_HEADER) to a beacon node (and/or proposer node). These certificates must be in PEM format and are used \
.help("If present, the validator client will use longer timeouts for requests \ in addition to the OS trust store. Commas must only be used as a \
made to the beacon node. This flag is generally not recommended, \ delimiter, and must not be part of the certificate path.",
longer timeouts can cause missed duties when fallbacks are used.") display_order = 0
.display_order(0) )]
) pub beacon_nodes_tls_certs: Option<Vec<String>>,
.arg(
Arg::new("beacon-nodes-tls-certs") // This overwrites the graffiti configured in the beacon node.
.long("beacon-nodes-tls-certs") #[clap(
.value_name("CERTIFICATE-FILES") long,
.action(ArgAction::Set) value_name = "GRAFFITI",
.help("Comma-separated paths to custom TLS certificates to use when connecting \ help = "Specify your custom graffiti to be included in blocks.",
to a beacon node (and/or proposer node). These certificates must be in PEM format and are used \ display_order = 0
in addition to the OS trust store. Commas must only be used as a \ )]
delimiter, and must not be part of the certificate path.") pub graffiti: Option<String>,
.display_order(0)
) #[clap(
// This overwrites the graffiti configured in the beacon node. long,
.arg( value_name = "GRAFFITI-FILE",
Arg::new("graffiti") conflicts_with = "graffiti",
.long("graffiti") help = "Specify a graffiti file to load validator graffitis from.",
.help("Specify your custom graffiti to be included in blocks.") display_order = 0
.value_name("GRAFFITI") )]
.action(ArgAction::Set) pub graffiti_file: Option<PathBuf>,
.display_order(0)
) #[clap(
.arg( long,
Arg::new("graffiti-file") value_name = "FEE-RECIPIENT",
.long("graffiti-file") help = "Once the merge has happened, this address will receive transaction fees \
.help("Specify a graffiti file to load validator graffitis from.") from blocks proposed by this validator client. If a fee recipient is \
.value_name("GRAFFITI-FILE") configured in the validator definitions it takes priority over this value.",
.action(ArgAction::Set) display_order = 0
.conflicts_with("graffiti") )]
.display_order(0) pub suggested_fee_recipient: Option<Address>,
)
.arg( #[clap(
Arg::new("suggested-fee-recipient") long,
.long("suggested-fee-recipient") help = "Enables functionality required for running the validator in a distributed validator cluster.",
.help("Once the merge has happened, this address will receive transaction fees \ display_order = 0,
from blocks proposed by this validator client. If a fee recipient is \ help_heading = FLAG_HEADER
configured in the validator definitions it takes priority over this value.") )]
.value_name("FEE-RECIPIENT") pub distributed: bool,
.action(ArgAction::Set)
.display_order(0) /* REST API related arguments */
) #[clap(
.arg( long,
Arg::new("distributed") help = "Enable the RESTful HTTP API server. Disabled by default.",
.long("distributed") display_order = 0,
.help("Enables functionality required for running the validator in a distributed validator cluster.") help_heading = FLAG_HEADER
.action(ArgAction::SetTrue) )]
.help_heading(FLAG_HEADER) pub http: bool,
.display_order(0)
) /*
/* REST API related arguments */ * Note: The HTTP server is **not** encrypted (i.e., not HTTPS) and therefore it is
.arg( * unsafe to publish on a public network.
Arg::new("http") *
.long("http") * If the `--http-address` flag is used, the `--unencrypted-http-transport` flag
.help("Enable the RESTful HTTP API server. Disabled by default.") * must also be used in order to make it clear to the user that this is unsafe.
.action(ArgAction::SetTrue) */
.help_heading(FLAG_HEADER) #[clap(
.display_order(0) long,
) value_name = "ADDRESS",
/* requires = "unencrypted_http_transport",
* Note: The HTTP server is **not** encrypted (i.e., not HTTPS) and therefore it is help = "Set the address for the HTTP address. The HTTP server is not encrypted \
* unsafe to publish on a public network. and therefore it is unsafe to publish on a public network. When this \
* flag is used, it additionally requires the explicit use of the \
* If the `--http-address` flag is used, the `--unencrypted-http-transport` flag `--unencrypted-http-transport` flag to ensure the user is aware of the \
* must also be used in order to make it clear to the user that this is unsafe. risks involved. For access via the Internet, users should apply \
*/ transport-layer security like a HTTPS reverse-proxy or SSH tunnelling.",
.arg( display_order = 0
Arg::new("http-address") )]
.long("http-address") pub http_address: Option<String>,
.requires("http")
.value_name("ADDRESS") #[clap(
.help("Set the address for the HTTP address. The HTTP server is not encrypted \ long,
and therefore it is unsafe to publish on a public network. When this \ requires = "http_address",
flag is used, it additionally requires the explicit use of the \ help = "This is a safety flag to ensure that the user is aware that the http \
`--unencrypted-http-transport` flag to ensure the user is aware of the \ transport is unencrypted and using a custom HTTP address is unsafe.",
risks involved. For access via the Internet, users should apply \ display_order = 0,
transport-layer security like a HTTPS reverse-proxy or SSH tunnelling.") help_heading = FLAG_HEADER
.requires("unencrypted-http-transport") )]
.display_order(0) pub unencrypted_http_transport: bool,
)
.arg( #[clap(
Arg::new("unencrypted-http-transport") long,
.long("unencrypted-http-transport") value_name = "PORT",
.help("This is a safety flag to ensure that the user is aware that the http \ default_value_t = 5062,
transport is unencrypted and using a custom HTTP address is unsafe.") help = "Set the listen TCP port for the RESTful HTTP API server.",
.action(ArgAction::SetTrue) display_order = 0
.help_heading(FLAG_HEADER) )]
.requires("http-address") pub http_port: u16,
.display_order(0)
) #[clap(
.arg( long,
Arg::new("http-port") value_name = "ORIGIN",
.long("http-port") help = "Set the value of the Access-Control-Allow-Origin response HTTP header. \
.requires("http")
.value_name("PORT")
.help("Set the listen TCP port for the RESTful HTTP API server.")
.default_value_if("http", ArgPredicate::IsPresent, "5062")
.action(ArgAction::Set)
.display_order(0)
)
.arg(
Arg::new("http-allow-origin")
.long("http-allow-origin")
.requires("http")
.value_name("ORIGIN")
.help("Set the value of the Access-Control-Allow-Origin response HTTP header. \
Use * to allow any origin (not recommended in production). \ Use * to allow any origin (not recommended in production). \
If no value is supplied, the CORS allowed origin is set to the listen \ If no value is supplied, the CORS allowed origin is set to the listen \
address of this server (e.g., http://localhost:5062).") address of this server (e.g., http://localhost:5062).",
.action(ArgAction::Set) display_order = 0
.display_order(0) )]
) pub http_allow_origin: Option<String>,
.arg(
Arg::new("http-allow-keystore-export") #[clap(
.long("http-allow-keystore-export") long,
.requires("http") requires = "http",
.help("If present, allow access to the DELETE /lighthouse/keystores HTTP \ help = "If present, allow access to the DELETE /lighthouse/keystores HTTP \
API method, which allows exporting keystores and passwords to HTTP API \ API method, which allows exporting keystores and passwords to HTTP API \
consumers who have access to the API token. This method is useful for \ consumers who have access to the API token. This method is useful for \
exporting validators, however it should be used with caution since it \ exporting validators, however it should be used with caution since it \
exposes private key data to authorized users.") exposes private key data to authorized users.",
.action(ArgAction::SetTrue) display_order = 0,
.help_heading(FLAG_HEADER) help_heading = FLAG_HEADER
.display_order(0) )]
) pub http_allow_keystore_export: bool,
.arg(
Arg::new("http-store-passwords-in-secrets-dir") #[clap(
.long("http-store-passwords-in-secrets-dir") long,
.requires("http") requires = "http",
.help("If present, any validators created via the HTTP will have keystore \ help = "If present, any validators created via the HTTP will have keystore \
passwords stored in the secrets-dir rather than the validator \ passwords stored in the secrets-dir rather than the validator \
definitions file.") definitions file.",
.action(ArgAction::SetTrue) display_order = 0,
.help_heading(FLAG_HEADER) help_heading = FLAG_HEADER
.display_order(0) )]
) pub http_store_passwords_in_secrets_dir: bool,
.arg(
Arg::new("http-token-path") #[clap(
.long("http-token-path") long,
.requires("http") requires = "http",
.value_name("HTTP_TOKEN_PATH") help = "Path to file containing the HTTP API token for validator client authentication. \
.help( If not specified, defaults to {validators-dir}/api-token.txt.",
"Path to file containing the HTTP API token for validator client authentication. \ display_order = 0
If not specified, defaults to {validators-dir}/api-token.txt." )]
) pub http_token_path: Option<String>,
.action(ArgAction::Set)
.display_order(0) /* Prometheus metrics HTTP server related arguments */
) #[clap(
/* Prometheus metrics HTTP server related arguments */ long,
.arg( help = "Enable the Prometheus metrics HTTP server. Disabled by default.",
Arg::new("metrics") display_order = 0,
.long("metrics") help_heading = FLAG_HEADER
.help("Enable the Prometheus metrics HTTP server. Disabled by default.") )]
.action(ArgAction::SetTrue) pub metrics: bool,
.help_heading(FLAG_HEADER)
.display_order(0) #[clap(
) long,
.arg( value_name = "ADDRESS",
Arg::new("metrics-address") requires = "metrics",
.long("metrics-address") default_value_if("metrics", ArgPredicate::IsPresent, "127.0.0.1"),
.requires("metrics") help = "Set the listen address for the Prometheus metrics HTTP server. [default: 127.0.0.1]",
.value_name("ADDRESS") display_order = 0
.help("Set the listen address for the Prometheus metrics HTTP server.") )]
.default_value_if("metrics", ArgPredicate::IsPresent, "127.0.0.1") pub metrics_address: Option<String>,
.action(ArgAction::Set)
.display_order(0) #[clap(
) long,
.arg( value_name = "PORT",
Arg::new("metrics-port") requires = "metrics",
.long("metrics-port") default_value_t = 5064,
.requires("metrics") help = "Set the listen TCP port for the Prometheus metrics HTTP server.",
.value_name("PORT") display_order = 0
.help("Set the listen TCP port for the Prometheus metrics HTTP server.") )]
.default_value_if("metrics", ArgPredicate::IsPresent, "5064") pub metrics_port: u16,
.action(ArgAction::Set)
.display_order(0) #[clap(
) long,
.arg( value_name = "ORIGIN",
Arg::new("metrics-allow-origin") requires = "metrics",
.long("metrics-allow-origin") help = "Set the value of the Access-Control-Allow-Origin response HTTP header. \
.requires("metrics") Use * to allow any origin (not recommended in production). \
.value_name("ORIGIN") If no value is supplied, the CORS allowed origin is set to the listen \
.help("Set the value of the Access-Control-Allow-Origin response HTTP header. \ address of this server (e.g., http://localhost:5064).",
Use * to allow any origin (not recommended in production). \ display_order = 0
If no value is supplied, the CORS allowed origin is set to the listen \ )]
address of this server (e.g., http://localhost:5064).") pub metrics_allow_origin: Option<String>,
.action(ArgAction::Set)
.display_order(0) #[clap(
) long,
.arg( help = "Enable per validator metrics for > 64 validators. \
Arg::new("enable-high-validator-count-metrics") Note: This flag is automatically enabled for <= 64 validators. \
.long("enable-high-validator-count-metrics") Enabling this metric for higher validator counts will lead to higher volume \
.help("Enable per validator metrics for > 64 validators. \ of prometheus metrics being collected.",
Note: This flag is automatically enabled for <= 64 validators. \ display_order = 0,
Enabling this metric for higher validator counts will lead to higher volume \ help_heading = FLAG_HEADER
of prometheus metrics being collected.") )]
.action(ArgAction::SetTrue) pub enable_high_validator_count_metrics: bool,
.help_heading(FLAG_HEADER)
.display_order(0) /* Explorer metrics */
) #[clap(
/* long,
* Explorer metrics value_name = "ADDRESS",
*/ help = "Enables the monitoring service for sending system metrics to a remote endpoint. \
.arg(
Arg::new("monitoring-endpoint")
.long("monitoring-endpoint")
.value_name("ADDRESS")
.help("Enables the monitoring service for sending system metrics to a remote endpoint. \
This can be used to monitor your setup on certain services (e.g. beaconcha.in). \ This can be used to monitor your setup on certain services (e.g. beaconcha.in). \
This flag sets the endpoint where the beacon node metrics will be sent. \ This flag sets the endpoint where the beacon node metrics will be sent. \
Note: This will send information to a remote sever which may identify and associate your \ Note: This will send information to a remote sever which may identify and associate your \
validators, IP address and other personal information. Always use a HTTPS connection \ validators, IP address and other personal information. Always use a HTTPS connection \
and never provide an untrusted URL.") and never provide an untrusted URL.",
.action(ArgAction::Set) display_order = 0
.display_order(0) )]
) pub monitoring_endpoint: Option<String>,
.arg(
Arg::new("monitoring-endpoint-period") #[clap(
.long("monitoring-endpoint-period") long,
.value_name("SECONDS") value_name = "SECONDS",
.help("Defines how many seconds to wait between each message sent to \ requires = "monitoring_endpoint",
the monitoring-endpoint. Default: 60s") default_value_t = 60,
.requires("monitoring-endpoint") help = "Defines how many seconds to wait between each message sent to \
.action(ArgAction::Set) the monitoring-endpoint.",
.display_order(0) display_order = 0
) )]
.arg( pub monitoring_endpoint_period: u64,
Arg::new("enable-doppelganger-protection")
.long("enable-doppelganger-protection") #[clap(
.value_name("ENABLE_DOPPELGANGER_PROTECTION") long,
.help("If this flag is set, Lighthouse will delay startup for three epochs and \ value_name = "BOOLEAN",
monitor for messages on the network by any of the validators managed by this \ help = "If this flag is set, Lighthouse will delay startup for three epochs and \
client. This will result in three (possibly four) epochs worth of missed \ monitor for messages on the network by any of the validators managed by this \
attestations. If an attestation is detected during this period, it means it is \ client. This will result in three (possibly four) epochs worth of missed \
very likely that you are running a second validator client with the same keys. \ attestations. If an attestation is detected during this period, it means it is \
This validator client will immediately shutdown if this is detected in order \ very likely that you are running a second validator client with the same keys. \
to avoid potentially committing a slashable offense. Use this flag in order to \ This validator client will immediately shutdown if this is detected in order \
ENABLE this functionality, without this flag Lighthouse will begin attesting \ to avoid potentially committing a slashable offense. Use this flag in order to \
immediately.") ENABLE this functionality, without this flag Lighthouse will begin attesting \
.action(ArgAction::SetTrue) immediately.",
.help_heading(FLAG_HEADER) display_order = 0,
.display_order(0) help_heading = FLAG_HEADER
) )]
.arg( pub enable_doppelganger_protection: bool,
Arg::new("builder-proposals")
.long("builder-proposals") #[clap(
.alias("private-tx-proposals") long,
.help("If this flag is set, Lighthouse will query the Beacon Node for only block \ alias = "private-tx-proposals",
headers during proposals and will sign over headers. Useful for outsourcing \ help = "If this flag is set, Lighthouse will query the Beacon Node for only block \
execution payload construction during proposals.") headers during proposals and will sign over headers. Useful for outsourcing \
.action(ArgAction::SetTrue) execution payload construction during proposals.",
.help_heading(FLAG_HEADER) display_order = 0,
.display_order(0) help_heading = FLAG_HEADER
) )]
.arg( pub builder_proposals: bool,
Arg::new("builder-registration-timestamp-override")
.long("builder-registration-timestamp-override") #[clap(
.alias("builder-registration-timestamp-override") long,
.help("This flag takes a unix timestamp value that will be used to override the \ value_name = "UNIX-TIMESTAMP",
timestamp used in the builder api registration") help = "This flag takes a unix timestamp value that will be used to override the \
.action(ArgAction::Set) timestamp used in the builder api registration.",
.display_order(0) display_order = 0
) )]
.arg( pub builder_registration_timestamp_override: Option<u64>,
Arg::new("gas-limit")
.long("gas-limit") #[clap(
.value_name("INTEGER") long,
.action(ArgAction::Set) value_name = "INTEGER",
.help("The gas limit to be used in all builder proposals for all validators managed \ default_value_t = 30_000_000,
by this validator client. Note this will not necessarily be used if the gas limit \ requires = "builder_proposals",
set here moves too far from the previous block's gas limit. [default: 30,000,000]") help = "The gas limit to be used in all builder proposals for all validators managed \
.requires("builder-proposals") by this validator client. Note this will not necessarily be used if the gas limit \
.display_order(0) set here moves too far from the previous block's gas limit.",
) display_order = 0
.arg( )]
Arg::new("disable-latency-measurement-service") pub gas_limit: u64,
.long("disable-latency-measurement-service")
.help("Disables the service that periodically attempts to measure latency to BNs.") #[clap(
.action(ArgAction::SetTrue) long,
.help_heading(FLAG_HEADER) value_name = "BOOLEAN",
.display_order(0) help = "Disables the service that periodically attempts to measure latency to BNs.",
) display_order = 0,
.arg( help_heading = FLAG_HEADER
Arg::new("validator-registration-batch-size") )]
.long("validator-registration-batch-size") pub disable_latency_measurement_service: bool,
.value_name("INTEGER")
.help("Defines the number of validators per \ #[clap(
validator/register_validator request sent to the BN. This value \ long,
can be reduced to avoid timeouts from builders.") value_name = "INTEGER",
.default_value("500") default_value_t = 500,
.action(ArgAction::Set) help = "Defines the number of validators per \
.display_order(0) validator/register_validator request sent to the BN. This value \
) can be reduced to avoid timeouts from builders.",
.arg( display_order = 0
Arg::new("builder-boost-factor") )]
.long("builder-boost-factor") pub validator_registration_batch_size: usize,
.value_name("UINT64")
.help("Defines the boost factor, \ #[clap(
a percentage multiplier to apply to the builder's payload value \ long,
when choosing between a builder payload header and payload from \ value_name = "UINT64",
the local execution node.") help = "Defines the boost factor, \
.conflicts_with("prefer-builder-proposals") a percentage multiplier to apply to the builder's payload value \
.action(ArgAction::Set) when choosing between a builder payload header and payload from \
.display_order(0) the local execution node.",
) conflicts_with = "prefer_builder_proposals",
.arg( display_order = 0
Arg::new("prefer-builder-proposals") )]
.long("prefer-builder-proposals") pub builder_boost_factor: Option<u64>,
.help("If this flag is set, Lighthouse will always prefer blocks \
constructed by builders, regardless of payload value.") #[clap(
.action(ArgAction::SetTrue) long,
.help_heading(FLAG_HEADER) help = "If this flag is set, Lighthouse will always prefer blocks \
.display_order(0) constructed by builders, regardless of payload value.",
) display_order = 0,
.arg( help_heading = FLAG_HEADER
Arg::new("beacon-nodes-sync-tolerances") )]
.long("beacon-nodes-sync-tolerances") pub prefer_builder_proposals: bool,
.value_name("SYNC_TOLERANCES")
.help("A comma-separated list of 3 values which sets the size of each sync distance range when \ #[clap(
determining the health of each connected beacon node. \ long,
The first value determines the `Synced` range. \ help = "A comma-separated list of 3 values which sets the size of each sync distance range when \
If a connected beacon node is synced to within this number of slots it is considered 'Synced'. \ determining the health of each connected beacon node. \
The second value determines the `Small` sync distance range. \ The first value determines the `Synced` range. \
This range starts immediately after the `Synced` range. \ If a connected beacon node is synced to within this number of slots it is considered 'Synced'. \
The third value determines the `Medium` sync distance range. \ The second value determines the `Small` sync distance range. \
This range starts immediately after the `Small` range. \ This range starts immediately after the `Synced` range. \
Any sync distance value beyond that is considered `Large`. \ The third value determines the `Medium` sync distance range. \
For example, a value of `8,8,48` would have ranges like the following: \ This range starts immediately after the `Small` range. \
`Synced`: 0..=8 \ Any sync distance value beyond that is considered `Large`. \
`Small`: 9..=16 \ For example, a value of `8,8,48` would have ranges like the following: \
`Medium`: 17..=64 \ `Synced`: 0..=8 \
`Large`: 65.. \ `Small`: 9..=16 \
These values are used to determine what ordering beacon node fallbacks are used in. \ `Medium`: 17..=64 \
Generally, `Synced` nodes are preferred over `Small` and so on. \ `Large`: 65.. \
Nodes in the `Synced` range will tie-break based on their ordering in `--beacon-nodes`. \ These values are used to determine what ordering beacon node fallbacks are used in. \
This ensures the primary beacon node is prioritised. \ Generally, `Synced` nodes are preferred over `Small` and so on. \
[default: 8,8,48]") Nodes in the `Synced` range will tie-break based on their ordering in `--beacon-nodes`. \
.action(ArgAction::Set) This ensures the primary beacon node is prioritised.",
.help_heading(FLAG_HEADER) display_order = 0,
.display_order(0) value_delimiter = ',',
) default_value = "8,8,48",
.arg( help_heading = FLAG_HEADER,
Arg::new("disable-slashing-protection-web3signer") value_name = "SYNC_TOLERANCES"
.long("disable-slashing-protection-web3signer") )]
.help("Disable Lighthouse's slashing protection for all web3signer keys. This can \ pub beacon_nodes_sync_tolerances: Vec<u64>,
reduce the I/O burden on the VC but is only safe if slashing protection \
is enabled on the remote signer and is implemented correctly. DO NOT ENABLE \ #[clap(
THIS FLAG UNLESS YOU ARE CERTAIN THAT SLASHING PROTECTION IS ENABLED ON \ long,
THE REMOTE SIGNER. YOU WILL GET SLASHED IF YOU USE THIS FLAG WITHOUT \ help = "Disable Lighthouse's slashing protection for all web3signer keys. This can \
ENABLING WEB3SIGNER'S SLASHING PROTECTION.") reduce the I/O burden on the VC but is only safe if slashing protection \
.action(ArgAction::SetTrue) is enabled on the remote signer and is implemented correctly. DO NOT ENABLE \
.help_heading(FLAG_HEADER) THIS FLAG UNLESS YOU ARE CERTAIN THAT SLASHING PROTECTION IS ENABLED ON \
.display_order(0) THE REMOTE SIGNER. YOU WILL GET SLASHED IF YOU USE THIS FLAG WITHOUT \
) ENABLING WEB3SIGNER'S SLASHING PROTECTION.",
/* display_order = 0,
* Experimental/development options. help_heading = FLAG_HEADER
*/ )]
.arg( pub disable_slashing_protection_web3signer: bool,
Arg::new("web3-signer-keep-alive-timeout")
.long("web3-signer-keep-alive-timeout") /* Experimental/development options */
.value_name("MILLIS") #[clap(
.default_value("20000") long,
.help("Keep-alive timeout for each web3signer connection. Set to 'null' to never \ value_name = "MILLIS",
timeout") default_value_t = 20000,
.action(ArgAction::Set) help = "Keep-alive timeout for each web3signer connection. Set to '0' to never \
.display_order(0) timeout.",
) display_order = 0
.arg( )]
Arg::new("web3-signer-max-idle-connections") pub web3_signer_keep_alive_timeout: u64,
.long("web3-signer-max-idle-connections")
.value_name("COUNT") #[clap(
.help("Maximum number of idle connections to maintain per web3signer host. Default \ long,
is unlimited.") value_name = "COUNT",
.action(ArgAction::Set) help = "Maximum number of idle connections to maintain per web3signer host. Default \
.display_order(0) is unlimited.",
) display_order = 0
)]
pub web3_signer_max_idle_connections: Option<usize>,
} }

View File

@@ -1,6 +1,8 @@
use beacon_node_fallback::{beacon_node_health::BeaconNodeSyncDistanceTiers, ApiTopic}; use crate::cli::ValidatorClient;
use beacon_node_fallback::beacon_node_health::BeaconNodeSyncDistanceTiers;
use beacon_node_fallback::ApiTopic;
use clap::ArgMatches; use clap::ArgMatches;
use clap_utils::{flags::DISABLE_MALLOC_TUNING_FLAG, parse_optional, parse_required}; use clap_utils::{flags::DISABLE_MALLOC_TUNING_FLAG, parse_required};
use directory::{ use directory::{
get_network_dir, DEFAULT_HARDCODED_NETWORK, DEFAULT_ROOT_DIR, DEFAULT_SECRET_DIR, get_network_dir, DEFAULT_HARDCODED_NETWORK, DEFAULT_ROOT_DIR, DEFAULT_SECRET_DIR,
DEFAULT_VALIDATOR_DIR, DEFAULT_VALIDATOR_DIR,
@@ -14,9 +16,8 @@ use slog::{info, warn, Logger};
use std::fs; use std::fs;
use std::net::IpAddr; use std::net::IpAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use types::{Address, GRAFFITI_BYTES_LEN}; use types::GRAFFITI_BYTES_LEN;
use validator_http_api::{self, PK_FILENAME}; use validator_http_api::{self, PK_FILENAME};
use validator_http_metrics; use validator_http_metrics;
use validator_store::Config as ValidatorStoreConfig; use validator_store::Config as ValidatorStoreConfig;
@@ -132,7 +133,11 @@ impl Default for Config {
impl Config { impl Config {
/// Returns a `Default` implementation of `Self` with some parameters modified by the supplied /// Returns a `Default` implementation of `Self` with some parameters modified by the supplied
/// `cli_args`. /// `cli_args`.
pub fn from_cli(cli_args: &ArgMatches, log: &Logger) -> Result<Config, String> { pub fn from_cli(
cli_args: &ArgMatches,
validator_client_config: &ValidatorClient,
log: &Logger,
) -> Result<Config, String> {
let mut config = Config::default(); let mut config = Config::default();
let default_root_dir = dirs::home_dir() let default_root_dir = dirs::home_dir()
@@ -145,11 +150,12 @@ impl Config {
validator_dir = Some(base_dir.join(DEFAULT_VALIDATOR_DIR)); validator_dir = Some(base_dir.join(DEFAULT_VALIDATOR_DIR));
secrets_dir = Some(base_dir.join(DEFAULT_SECRET_DIR)); secrets_dir = Some(base_dir.join(DEFAULT_SECRET_DIR));
} }
if cli_args.get_one::<String>("validators-dir").is_some() {
validator_dir = Some(parse_required(cli_args, "validators-dir")?); if let Some(validator_dir_path) = validator_client_config.validators_dir.as_ref() {
validator_dir = Some(validator_dir_path.clone());
} }
if cli_args.get_one::<String>("secrets-dir").is_some() { if let Some(secrets_dir_path) = validator_client_config.secrets_dir.as_ref() {
secrets_dir = Some(parse_required(cli_args, "secrets-dir")?); secrets_dir = Some(secrets_dir_path.clone());
} }
config.validator_dir = validator_dir.unwrap_or_else(|| { config.validator_dir = validator_dir.unwrap_or_else(|| {
@@ -169,35 +175,36 @@ impl Config {
.map_err(|e| format!("Failed to create {:?}: {:?}", config.validator_dir, e))?; .map_err(|e| format!("Failed to create {:?}: {:?}", config.validator_dir, e))?;
} }
if let Some(beacon_nodes) = parse_optional::<String>(cli_args, "beacon-nodes")? { if let Some(beacon_nodes) = validator_client_config.beacon_nodes.as_ref() {
config.beacon_nodes = beacon_nodes config.beacon_nodes = beacon_nodes
.split(',') .iter()
.map(SensitiveUrl::parse) .map(|s| SensitiveUrl::parse(s))
.collect::<Result<_, _>>() .collect::<Result<_, _>>()
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?; .map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?;
} }
if let Some(proposer_nodes) = parse_optional::<String>(cli_args, "proposer-nodes")? {
if let Some(proposer_nodes) = validator_client_config.proposer_nodes.as_ref() {
config.proposer_nodes = proposer_nodes config.proposer_nodes = proposer_nodes
.split(',') .iter()
.map(SensitiveUrl::parse) .map(|s| SensitiveUrl::parse(s))
.collect::<Result<_, _>>() .collect::<Result<_, _>>()
.map_err(|e| format!("Unable to parse proposer node URL: {:?}", e))?; .map_err(|e| format!("Unable to parse proposer node URL: {:?}", e))?;
} }
config.disable_auto_discover = cli_args.get_flag("disable-auto-discover"); config.disable_auto_discover = validator_client_config.disable_auto_discover;
config.init_slashing_protection = cli_args.get_flag("init-slashing-protection"); config.init_slashing_protection = validator_client_config.init_slashing_protection;
config.use_long_timeouts = cli_args.get_flag("use-long-timeouts"); config.use_long_timeouts = validator_client_config.use_long_timeouts;
if let Some(graffiti_file_path) = cli_args.get_one::<String>("graffiti-file") { if let Some(graffiti_file_path) = validator_client_config.graffiti_file.as_ref() {
let mut graffiti_file = GraffitiFile::new(graffiti_file_path.into()); let mut graffiti_file = GraffitiFile::new(graffiti_file_path.into());
graffiti_file graffiti_file
.read_graffiti_file() .read_graffiti_file()
.map_err(|e| format!("Error reading graffiti file: {:?}", e))?; .map_err(|e| format!("Error reading graffiti file: {:?}", e))?;
config.graffiti_file = Some(graffiti_file); config.graffiti_file = Some(graffiti_file);
info!(log, "Successfully loaded graffiti file"; "path" => graffiti_file_path); info!(log, "Successfully loaded graffiti file"; "path" => graffiti_file_path.to_str());
} }
if let Some(input_graffiti) = cli_args.get_one::<String>("graffiti") { if let Some(input_graffiti) = validator_client_config.graffiti.as_ref() {
let graffiti_bytes = input_graffiti.as_bytes(); let graffiti_bytes = input_graffiti.as_bytes();
if graffiti_bytes.len() > GRAFFITI_BYTES_LEN { if graffiti_bytes.len() > GRAFFITI_BYTES_LEN {
return Err(format!( return Err(format!(
@@ -216,55 +223,40 @@ impl Config {
} }
} }
if let Some(input_fee_recipient) = if let Some(input_fee_recipient) = validator_client_config.suggested_fee_recipient {
parse_optional::<Address>(cli_args, "suggested-fee-recipient")?
{
config.validator_store.fee_recipient = Some(input_fee_recipient); config.validator_store.fee_recipient = Some(input_fee_recipient);
} }
if let Some(tls_certs) = parse_optional::<String>(cli_args, "beacon-nodes-tls-certs")? { if let Some(tls_certs) = validator_client_config.beacon_nodes_tls_certs.as_ref() {
config.beacon_nodes_tls_certs = Some(tls_certs.split(',').map(PathBuf::from).collect()); config.beacon_nodes_tls_certs = Some(tls_certs.iter().map(PathBuf::from).collect());
} }
if cli_args.get_flag("distributed") { config.distributed = validator_client_config.distributed;
config.distributed = true;
}
if let Some(broadcast_topics) = cli_args.get_one::<String>("broadcast") { if let Some(mut broadcast_topics) = validator_client_config.broadcast.clone() {
config.broadcast_topics = broadcast_topics broadcast_topics.retain(|topic| *topic != ApiTopic::None);
.split(',') config.broadcast_topics = broadcast_topics;
.filter(|t| *t != "none")
.map(|t| {
t.trim()
.parse::<ApiTopic>()
.map_err(|_| format!("Unknown API topic to broadcast: {t}"))
})
.collect::<Result<_, _>>()?;
} }
/* /*
* Beacon node fallback * Beacon node fallback
*/ */
if let Some(sync_tolerance) = cli_args.get_one::<String>("beacon-nodes-sync-tolerances") { config.beacon_node_fallback.sync_tolerances = BeaconNodeSyncDistanceTiers::from_vec(
config.beacon_node_fallback.sync_tolerances = &validator_client_config.beacon_nodes_sync_tolerances,
BeaconNodeSyncDistanceTiers::from_str(sync_tolerance)?; )?;
} else {
config.beacon_node_fallback.sync_tolerances = BeaconNodeSyncDistanceTiers::default();
}
/* /*
* Web3 signer * Web3 signer
*/ */
if let Some(s) = parse_optional::<String>(cli_args, "web3-signer-keep-alive-timeout")? { if validator_client_config.web3_signer_keep_alive_timeout == 0 {
config.initialized_validators.web3_signer_keep_alive_timeout = if s == "null" { config.initialized_validators.web3_signer_keep_alive_timeout = None
None } else {
} else { config.initialized_validators.web3_signer_keep_alive_timeout = Some(
Some(Duration::from_millis( Duration::from_millis(validator_client_config.web3_signer_keep_alive_timeout),
s.parse().map_err(|_| "invalid timeout value".to_string())?, );
))
}
} }
if let Some(n) = parse_optional::<usize>(cli_args, "web3-signer-max-idle-connections")? {
if let Some(n) = validator_client_config.web3_signer_max_idle_connections {
config config
.initialized_validators .initialized_validators
.web3_signer_max_idle_connections = Some(n); .web3_signer_max_idle_connections = Some(n);
@@ -274,12 +266,10 @@ impl Config {
* Http API server * Http API server
*/ */
if cli_args.get_flag("http") { config.http_api.enabled = validator_client_config.http;
config.http_api.enabled = true;
}
if let Some(address) = cli_args.get_one::<String>("http-address") { if let Some(address) = &validator_client_config.http_address {
if cli_args.get_flag("unencrypted-http-transport") { if validator_client_config.unencrypted_http_transport {
config.http_api.listen_addr = address config.http_api.listen_addr = address
.parse::<IpAddr>() .parse::<IpAddr>()
.map_err(|_| "http-address is not a valid IP address.")?; .map_err(|_| "http-address is not a valid IP address.")?;
@@ -291,13 +281,9 @@ impl Config {
} }
} }
if let Some(port) = cli_args.get_one::<String>("http-port") { config.http_api.listen_port = validator_client_config.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.get_one::<String>("http-allow-origin") { 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 // 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. // as late as when the first API response is produced.
hyper::header::HeaderValue::from_str(allow_origin) hyper::header::HeaderValue::from_str(allow_origin)
@@ -306,15 +292,11 @@ impl Config {
config.http_api.allow_origin = Some(allow_origin.to_string()); config.http_api.allow_origin = Some(allow_origin.to_string());
} }
if cli_args.get_flag("http-allow-keystore-export") { config.http_api.allow_keystore_export = validator_client_config.http_allow_keystore_export;
config.http_api.allow_keystore_export = true; config.http_api.store_passwords_in_secrets_dir =
} validator_client_config.http_store_passwords_in_secrets_dir;
if cli_args.get_flag("http-store-passwords-in-secrets-dir") { if let Some(http_token_path) = &validator_client_config.http_token_path {
config.http_api.store_passwords_in_secrets_dir = true;
}
if let Some(http_token_path) = cli_args.get_one::<String>("http-token-path") {
config.http_api.http_token_path = PathBuf::from(http_token_path); config.http_api.http_token_path = PathBuf::from(http_token_path);
} else { } else {
// For backward compatibility, default to the path under the validator dir if not provided. // For backward compatibility, default to the path under the validator dir if not provided.
@@ -325,27 +307,19 @@ impl Config {
* Prometheus metrics HTTP server * Prometheus metrics HTTP server
*/ */
if cli_args.get_flag("metrics") { config.http_metrics.enabled = validator_client_config.metrics;
config.http_metrics.enabled = true; config.enable_high_validator_count_metrics =
} validator_client_config.enable_high_validator_count_metrics;
if cli_args.get_flag("enable-high-validator-count-metrics") { if let Some(metrics_address) = &validator_client_config.metrics_address {
config.enable_high_validator_count_metrics = true; config.http_metrics.listen_addr = metrics_address
}
if let Some(address) = cli_args.get_one::<String>("metrics-address") {
config.http_metrics.listen_addr = address
.parse::<IpAddr>() .parse::<IpAddr>()
.map_err(|_| "metrics-address is not a valid IP address.")?; .map_err(|_| "metrics-address is not a valid IP address.")?;
} }
if let Some(port) = cli_args.get_one::<String>("metrics-port") { config.http_metrics.listen_port = validator_client_config.metrics_port;
config.http_metrics.listen_port = port
.parse::<u16>()
.map_err(|_| "metrics-port is not a valid u16.")?;
}
if let Some(allow_origin) = cli_args.get_one::<String>("metrics-allow-origin") { 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 // 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. // as late as when the first API response is produced.
hyper::header::HeaderValue::from_str(allow_origin) hyper::header::HeaderValue::from_str(allow_origin)
@@ -361,9 +335,8 @@ impl Config {
/* /*
* Explorer metrics * Explorer metrics
*/ */
if let Some(monitoring_endpoint) = cli_args.get_one::<String>("monitoring-endpoint") { if let Some(monitoring_endpoint) = validator_client_config.monitoring_endpoint.as_ref() {
let update_period_secs = let update_period_secs = Some(validator_client_config.monitoring_endpoint_period);
clap_utils::parse_optional(cli_args, "monitoring-endpoint-period")?;
config.monitoring_api = Some(monitoring_api::Config { config.monitoring_api = Some(monitoring_api::Config {
db_path: None, db_path: None,
freezer_db_path: None, freezer_db_path: None,
@@ -372,56 +345,34 @@ impl Config {
}); });
} }
if cli_args.get_flag("enable-doppelganger-protection") { config.enable_doppelganger_protection =
config.enable_doppelganger_protection = true; 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);
if cli_args.get_flag("builder-proposals") { config.builder_registration_timestamp_override =
config.validator_store.builder_proposals = true; validator_client_config.builder_registration_timestamp_override;
}
if cli_args.get_flag("prefer-builder-proposals") {
config.validator_store.prefer_builder_proposals = true;
}
config.validator_store.gas_limit = cli_args
.get_one::<String>("gas-limit")
.map(|gas_limit| {
gas_limit
.parse::<u64>()
.map_err(|_| "gas-limit is not a valid u64.")
})
.transpose()?;
if let Some(registration_timestamp_override) =
cli_args.get_one::<String>("builder-registration-timestamp-override")
{
config.builder_registration_timestamp_override = Some(
registration_timestamp_override
.parse::<u64>()
.map_err(|_| "builder-registration-timestamp-override is not a valid u64.")?,
);
}
config.validator_store.builder_boost_factor =
parse_optional(cli_args, "builder-boost-factor")?;
config.validator_store.builder_boost_factor = validator_client_config.builder_boost_factor;
config.enable_latency_measurement_service = config.enable_latency_measurement_service =
!cli_args.get_flag("disable-latency-measurement-service"); !validator_client_config.disable_latency_measurement_service;
config.validator_registration_batch_size = config.validator_registration_batch_size =
parse_required(cli_args, "validator-registration-batch-size")?; validator_client_config.validator_registration_batch_size;
if config.validator_registration_batch_size == 0 { if config.validator_registration_batch_size == 0 {
return Err("validator-registration-batch-size cannot be 0".to_string()); return Err("validator-registration-batch-size cannot be 0".to_string());
} }
config.validator_store.enable_web3signer_slashing_protection = config.validator_store.enable_web3signer_slashing_protection =
if cli_args.get_flag("disable-slashing-protection-web3signer") { if validator_client_config.disable_slashing_protection_web3signer {
warn!( warn!(
log, log,
"Slashing protection for remote keys disabled"; "Slashing protection for remote keys disabled";
"info" => "ensure slashing protection on web3signer is enabled or you WILL \ "info" => "ensure slashing protection on web3signer is enabled or you WILL \
get slashed" get slashed"
); );
false false
} else { } else {

View File

@@ -1,9 +1,9 @@
mod cli; pub mod cli;
pub mod config; pub mod config;
mod latency; mod latency;
mod notifier; mod notifier;
pub use cli::cli_app; use crate::cli::ValidatorClient;
pub use config::Config; pub use config::Config;
use initialized_validators::InitializedValidators; use initialized_validators::InitializedValidators;
use metrics::set_gauge; use metrics::set_gauge;
@@ -11,11 +11,10 @@ use monitoring_api::{MonitoringHttpClient, ProcessType};
use sensitive_url::SensitiveUrl; use sensitive_url::SensitiveUrl;
use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME}; use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME};
use account_utils::validator_definitions::ValidatorDefinitions;
use beacon_node_fallback::{ use beacon_node_fallback::{
start_fallback_updater_service, BeaconNodeFallback, CandidateBeaconNode, start_fallback_updater_service, BeaconNodeFallback, CandidateBeaconNode,
}; };
use account_utils::validator_definitions::ValidatorDefinitions;
use clap::ArgMatches; use clap::ArgMatches;
use doppelganger_service::DoppelgangerService; use doppelganger_service::DoppelgangerService;
use environment::RuntimeContext; use environment::RuntimeContext;
@@ -96,8 +95,9 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
pub async fn new_from_cli( pub async fn new_from_cli(
context: RuntimeContext<E>, context: RuntimeContext<E>,
cli_args: &ArgMatches, cli_args: &ArgMatches,
validator_client_config: &ValidatorClient,
) -> Result<Self, String> { ) -> Result<Self, String> {
let config = Config::from_cli(cli_args, context.log()) let config = Config::from_cli(cli_args, validator_client_config, context.log())
.map_err(|e| format!("Unable to initialize config: {}", e))?; .map_err(|e| format!("Unable to initialize config: {}", e))?;
Self::new(context, config).await Self::new(context, config).await
} }

View File

@@ -1,5 +1,5 @@
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{ArgMatches, Command};
use clap_utils::{get_color_style, FLAG_HEADER}; use clap_utils::get_color_style;
use common::write_to_json_file; use common::write_to_json_file;
use environment::Environment; use environment::Environment;
use serde::Serialize; use serde::Serialize;
@@ -46,16 +46,6 @@ pub fn cli_app() -> Command {
.display_order(0) .display_order(0)
.styles(get_color_style()) .styles(get_color_style())
.about("Utilities for managing a Lighthouse validator client via the HTTP API.") .about("Utilities for managing a Lighthouse validator client via the HTTP API.")
.arg(
Arg::new("help")
.long("help")
.short('h')
.help("Prints help information")
.action(ArgAction::HelpLong)
.display_order(0)
.help_heading(FLAG_HEADER)
.global(true),
)
.subcommand(create_validators::cli_app()) .subcommand(create_validators::cli_app())
.subcommand(import_validators::cli_app()) .subcommand(import_validators::cli_app())
.subcommand(move_validators::cli_app()) .subcommand(move_validators::cli_app())