diff --git a/Cargo.lock b/Cargo.lock index b98e096718a..53592b11bd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "filesystem", "safe_arith", "sensitive_url", + "serde_json", "slashing_protection", "slot_clock", "tempfile", diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index a7752d621ff..071e2681dd1 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -22,6 +22,7 @@ eth2_wallet_manager = { path = "../common/eth2_wallet_manager" } filesystem = { workspace = true } safe_arith = { workspace = true } sensitive_url = { workspace = true } +serde_json = { workspace = true } slashing_protection = { workspace = true } slot_clock = { workspace = true } tokio = { workspace = true } diff --git a/account_manager/src/validator/exit.rs b/account_manager/src/validator/exit.rs index 8a2cdb8400a..1393d0f1526 100644 --- a/account_manager/src/validator/exit.rs +++ b/account_manager/src/validator/exit.rs @@ -11,6 +11,7 @@ use eth2_keystore::Keystore; use eth2_network_config::Eth2NetworkConfig; use safe_arith::SafeArith; use sensitive_url::SensitiveUrl; +use serde_json; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -24,6 +25,7 @@ pub const BEACON_SERVER_FLAG: &str = "beacon-node"; pub const NO_WAIT: &str = "no-wait"; pub const NO_CONFIRMATION: &str = "no-confirmation"; pub const PASSWORD_PROMPT: &str = "Enter the keystore password"; +pub const PRESIGN: &str = "presign"; pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/"; pub const CONFIRMATION_PHRASE: &str = "Exit my validator"; @@ -74,6 +76,15 @@ pub fn cli_app() -> Command { .action(ArgAction::SetTrue) .help_heading(FLAG_HEADER) ) + .arg( + Arg::new(PRESIGN) + .long(PRESIGN) + .help("Only presign the voluntary exit message without publishing it") + .default_value("false") + .action(ArgAction::SetTrue) + .help_heading(FLAG_HEADER) + .display_order(0) + ) } pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result<(), String> { @@ -84,6 +95,7 @@ pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result< let stdin_inputs = cfg!(windows) || matches.get_flag(STDIN_INPUTS_FLAG); let no_wait = matches.get_flag(NO_WAIT); let no_confirmation = matches.get_flag(NO_CONFIRMATION); + let presign = matches.get_flag(PRESIGN); let spec = env.eth2_config().spec.clone(); let server_url: String = clap_utils::parse_required(matches, BEACON_SERVER_FLAG)?; @@ -107,6 +119,7 @@ pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result< ð2_network_config, no_wait, no_confirmation, + presign, ))?; Ok(()) @@ -123,6 +136,7 @@ async fn publish_voluntary_exit( eth2_network_config: &Eth2NetworkConfig, no_wait: bool, no_confirmation: bool, + presign: bool, ) -> Result<(), String> { let genesis_data = get_geneisis_data(client).await?; let testnet_genesis_root = eth2_network_config @@ -154,6 +168,23 @@ async fn publish_voluntary_exit( validator_index, }; + // Sign the voluntary exit. We sign ahead of the prompt as that step is only important for the broadcast + let signed_voluntary_exit = + voluntary_exit.sign(&keypair.sk, genesis_data.genesis_validators_root, spec); + if presign { + eprintln!( + "Successfully pre-signed voluntary exit for validator {}. Not publishing.", + keypair.pk + ); + + // Convert to JSON and print + let string_output = serde_json::to_string_pretty(&signed_voluntary_exit) + .map_err(|e| format!("Unable to convert to JSON: {}", e))?; + + println!("{}", string_output); + return Ok(()); + } + eprintln!( "Publishing a voluntary exit for validator: {} \n", keypair.pk @@ -174,9 +205,7 @@ async fn publish_voluntary_exit( }; if confirmation == CONFIRMATION_PHRASE { - // Sign and publish the voluntary exit to network - let signed_voluntary_exit = - voluntary_exit.sign(&keypair.sk, genesis_data.genesis_validators_root, spec); + // Publish the voluntary exit to network client .post_beacon_pool_voluntary_exits(&signed_voluntary_exit) .await