diff --git a/account_manager/src/validator/mod.rs b/account_manager/src/validator/mod.rs index ea00435d30f..a652603ff6f 100644 --- a/account_manager/src/validator/mod.rs +++ b/account_manager/src/validator/mod.rs @@ -2,6 +2,7 @@ pub mod create; pub mod exit; pub mod import; pub mod list; +pub mod modify; pub mod recover; pub mod slashing_protection; @@ -29,6 +30,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .conflicts_with("datadir"), ) .subcommand(create::cli_app()) + .subcommand(modify::cli_app()) .subcommand(import::cli_app()) .subcommand(list::cli_app()) .subcommand(recover::cli_app()) @@ -47,6 +49,7 @@ pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result< match matches.subcommand() { (create::CMD, Some(matches)) => create::cli_run::(matches, env, validator_base_dir), + (modify::CMD, Some(matches)) => modify::cli_run(matches, validator_base_dir), (import::CMD, Some(matches)) => import::cli_run(matches, validator_base_dir), (list::CMD, Some(_)) => list::cli_run(validator_base_dir), (recover::CMD, Some(matches)) => recover::cli_run(matches, validator_base_dir), diff --git a/account_manager/src/validator/modify.rs b/account_manager/src/validator/modify.rs new file mode 100644 index 00000000000..bd4ae4d8f49 --- /dev/null +++ b/account_manager/src/validator/modify.rs @@ -0,0 +1,100 @@ +use account_utils::validator_definitions::ValidatorDefinitions; +use bls::PublicKey; +use clap::{App, Arg, ArgMatches}; +use std::{collections::HashSet, path::PathBuf}; + +pub const CMD: &str = "modify"; +pub const ENABLE: &str = "enable"; +pub const DISABLE: &str = "disable"; + +pub const PUBKEY_FLAG: &str = "pubkey"; +pub const ALL: &str = "all"; + +pub fn cli_app<'a, 'b>() -> App<'a, 'b> { + App::new(CMD) + .about("Modify validator status in validator_definitions.yml.") + .subcommand( + App::new(ENABLE) + .about("Enable validator(s) in validator_definitions.yml.") + .arg( + Arg::with_name(PUBKEY_FLAG) + .long(PUBKEY_FLAG) + .value_name("PUBKEY") + .help("Validator pubkey to enable") + .takes_value(true), + ) + .arg( + Arg::with_name(ALL) + .long(ALL) + .help("Enable all validators in the validator directory") + .takes_value(false) + .conflicts_with(PUBKEY_FLAG), + ), + ) + .subcommand( + App::new(DISABLE) + .about("Disable validator(s) in validator_definitions.yml.") + .arg( + Arg::with_name(PUBKEY_FLAG) + .long(PUBKEY_FLAG) + .value_name("PUBKEY") + .help("Validator pubkey to disable") + .takes_value(true), + ) + .arg( + Arg::with_name(ALL) + .long(ALL) + .help("Disable all validators in the validator directory") + .takes_value(false) + .conflicts_with(PUBKEY_FLAG), + ), + ) +} + +pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), String> { + // `true` implies we are setting `validator_definition.enabled = true` and + // vice versa. + let (enabled, sub_matches) = match matches.subcommand() { + (ENABLE, Some(sub_matches)) => (true, sub_matches), + (DISABLE, Some(sub_matches)) => (false, sub_matches), + (unknown, _) => { + return Err(format!( + "{} does not have a {} command. See --help", + CMD, unknown + )) + } + }; + let mut defs = ValidatorDefinitions::open(&validator_dir).map_err(|e| { + format!( + "No validator definitions found in {:?}: {:?}", + validator_dir, e + ) + })?; + let pubkeys_to_modify = if sub_matches.is_present(ALL) { + defs.as_slice() + .iter() + .map(|def| def.voting_public_key.clone()) + .collect::>() + } else { + let public_key: PublicKey = clap_utils::parse_required(sub_matches, PUBKEY_FLAG)?; + std::iter::once(public_key).collect::>() + }; + + // Modify required entries from validator_definitions. + for def in defs.as_mut_slice() { + if pubkeys_to_modify.contains(&def.voting_public_key) { + def.enabled = enabled; + eprintln!( + "Validator {} {}", + def.voting_public_key, + if enabled { "enabled" } else { "disabled" } + ); + } + } + + defs.save(&validator_dir) + .map_err(|e| format!("Unable to modify validator definitions: {:?}", e))?; + + eprintln!("\nSuccessfully modified validator_definitions.yml"); + Ok(()) +} diff --git a/lighthouse/tests/account_manager.rs b/lighthouse/tests/account_manager.rs index dd4e8443e79..d985a3d1a77 100644 --- a/lighthouse/tests/account_manager.rs +++ b/lighthouse/tests/account_manager.rs @@ -2,6 +2,7 @@ use account_manager::{ validator::{ create::*, import::{self, CMD as IMPORT_CMD}, + modify::{ALL, CMD as MODIFY_CMD, DISABLE, ENABLE, PUBKEY_FLAG}, CMD as VALIDATOR_CMD, }, wallet::{ @@ -475,10 +476,21 @@ fn validator_import_launchpad() { // Validator should be registered with slashing protection. check_slashing_protection(&dst_dir, std::iter::once(keystore.public_key().unwrap())); + // Disable all the validators in validator_definition. + output_result( + validator_cmd() + .arg(format!("--{}", VALIDATOR_DIR_FLAG)) + .arg(dst_dir.path().as_os_str()) + .arg(MODIFY_CMD) + .arg(DISABLE) + .arg(format!("--{}", ALL)), + ) + .unwrap(); + let defs = ValidatorDefinitions::open(&dst_dir).unwrap(); - let expected_def = ValidatorDefinition { - enabled: true, + let mut expected_def = ValidatorDefinition { + enabled: false, description: "".into(), graffiti: None, voting_public_key: keystore.public_key().unwrap(), @@ -490,7 +502,28 @@ fn validator_import_launchpad() { }; assert!( - defs.as_slice() == &[expected_def], + defs.as_slice() == &[expected_def.clone()], + "validator defs file should be accurate" + ); + + // Enable keystore validator again + output_result( + validator_cmd() + .arg(format!("--{}", VALIDATOR_DIR_FLAG)) + .arg(dst_dir.path().as_os_str()) + .arg(MODIFY_CMD) + .arg(ENABLE) + .arg(format!("--{}", PUBKEY_FLAG)) + .arg(format!("{}", keystore.public_key().unwrap())), + ) + .unwrap(); + + let defs = ValidatorDefinitions::open(&dst_dir).unwrap(); + + expected_def.enabled = true; + + assert!( + defs.as_slice() == &[expected_def.clone()], "validator defs file should be accurate" ); }