From ff3a025f7e5896c30ba569f0f26ade5e15d22f90 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 16 Aug 2022 17:17:42 +1000 Subject: [PATCH] Add progress --- common/account_utils/src/lib.rs | 12 +++ validator_manager/src/validator/create.rs | 109 +++++++++++++++++----- 2 files changed, 100 insertions(+), 21 deletions(-) diff --git a/common/account_utils/src/lib.rs b/common/account_utils/src/lib.rs index be3b900838..e566d7cdda 100644 --- a/common/account_utils/src/lib.rs +++ b/common/account_utils/src/lib.rs @@ -64,6 +64,18 @@ pub fn read_password>(path: P) -> Result { fs::read(path).map(strip_off_newlines).map(Into::into) } +/// Reads a password file into a `ZeroizeString` struct, with new-lines removed. +pub fn read_password_string>(path: P) -> Result { + fs::read(path) + .map_err(|e| format!("Error opening file: {:?}", e)) + .map(strip_off_newlines) + .and_then(|bytes| { + String::from_utf8(bytes) + .map_err(|e| format!("Error decoding utf8: {:?}", e)) + .map(Into::into) + }) +} + /// Write a file atomically by using a temporary file as an intermediate. /// /// Care is taken to preserve the permissions of the file at `file_path` being written. diff --git a/validator_manager/src/validator/create.rs b/validator_manager/src/validator/create.rs index 324a5bc3a7..02087c7be5 100644 --- a/validator_manager/src/validator/create.rs +++ b/validator_manager/src/validator/create.rs @@ -1,7 +1,13 @@ -use account_utils::{random_password, read_mnemonic_from_cli, read_password}; +use account_utils::{random_password_string, read_mnemonic_from_cli, read_password_string}; use clap::{App, Arg, ArgMatches}; use environment::Environment; -use eth2::{lighthouse_vc::http_client::ValidatorClientHttpClient, SensitiveUrl}; +use eth2::{ + lighthouse_vc::{ + http_client::ValidatorClientHttpClient, + std_types::{ImportKeystoresRequest, KeystoreJsonStr}, + }, + SensitiveUrl, +}; use eth2_wallet::WalletBuilder; use serde::Serialize; use std::fs; @@ -210,7 +216,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .value_name("INTEGER") .help("The number of keystores to be submitted to the VC per request.") .takes_value(true) - .default_value("16"), + .default_value("4"), ) } @@ -240,6 +246,10 @@ pub async fn cli_run<'a, T: EthSpec>( let keystore_upload_batch_size: usize = clap_utils::parse_required(matches, KEYSTORE_UPLOAD_BATCH_SIZE)?; + let num_batches = (count + (count - 1)) + .checked_div(keystore_upload_batch_size as u32) + .ok_or_else(|| format!("--{} cannot be zero", KEYSTORE_UPLOAD_BATCH_SIZE))?; + let http_client = match (dry_run, vc_url, vc_token_path) { (false, Some(vc_url), Some(vc_token_path)) => { let token_bytes = fs::read(&vc_token_path) @@ -278,19 +288,19 @@ pub async fn cli_run<'a, T: EthSpec>( let mnemonic = read_mnemonic_from_cli(mnemonic_path, stdin_inputs)?; // A random password is always appropriate for the wallet since it is ephemeral. - let wallet_password = random_password(); + let wallet_password = random_password_string(); let voting_keystore_password = if let Some(path) = wallet_password_path { - read_password(&path) + read_password_string(&path) .map_err(|e| format!("Failed to read password from {:?}: {:?}", path, e))? } else { - random_password() + random_password_string() }; // A random password is always appropriate for the withdrawal keystore since we don't ever store // it anywhere. - let withdrawal_keystore_password = random_password(); + let withdrawal_keystore_password = random_password_string(); let mut wallet = - WalletBuilder::from_mnemonic(&mnemonic, wallet_password.as_bytes(), "".to_string()) + WalletBuilder::from_mnemonic(&mnemonic, wallet_password.as_ref(), "".to_string()) .map_err(|e| format!("Unable create seed from mnemonic: {:?}", e))? .build() .map_err(|e| format!("Unable to create wallet: {:?}", e))?; @@ -307,14 +317,14 @@ pub async fn cli_run<'a, T: EthSpec>( for i in 0..count { let keystores = wallet .next_validator( - wallet_password.as_bytes(), - voting_keystore_password.as_bytes(), - withdrawal_keystore_password.as_bytes(), + wallet_password.as_ref(), + voting_keystore_password.as_ref(), + withdrawal_keystore_password.as_ref(), ) .map_err(|e| format!("Failed to derive keystore {}: {:?}", i, e))?; let voting_keystore = keystores.voting; let voting_keypair = voting_keystore - .decrypt_keypair(voting_keystore_password.as_bytes()) + .decrypt_keypair(voting_keystore_password.as_ref()) .map_err(|e| format!("Failed to decrypt voting keystore {}: {:?}", i, e))?; let voting_pubkey_bytes = voting_keypair.pk.clone().into(); @@ -352,7 +362,7 @@ pub async fn cli_run<'a, T: EthSpec>( } else { let withdrawal_keypair = keystores .withdrawal - .decrypt_keypair(withdrawal_keystore_password.as_bytes()) + .decrypt_keypair(withdrawal_keystore_password.as_ref()) .map_err(|e| format!("Failed to decrypt withdrawal keystore {}: {:?}", i, e))?; WithdrawalCredentials::bls(&withdrawal_keypair.pk, &spec) }; @@ -378,15 +388,72 @@ pub async fn cli_run<'a, T: EthSpec>( voting_keystores.push(voting_keystore); } - eprintln!( - "Generated {} keystores. Starting to submit keystores to VC, \ - each keystore may take several seconds", - count - ); + if let Some(http_client) = http_client { + eprintln!( + "Generated {} keystores. Starting to submit keystores to VC, \ + each keystore may take several seconds", + count + ); - for voting_keystore_chunk in voting_keystores.chunks(keystore_upload_batch_size) { - todo!("submit to VC") + for (i, chunk) in voting_keystores + .chunks(keystore_upload_batch_size) + .enumerate() + { + let keystores = chunk + .iter() + .cloned() + .map(KeystoreJsonStr) + .collect::>(); + let passwords = keystores + .iter() + .map(|_| voting_keystore_password.clone()) + .collect(); + let request = ImportKeystoresRequest { + keystores, + passwords, + // New validators have no slashing protection history. + slashing_protection: None, + }; + + if let Err(e) = http_client.post_keystores(&request).await { + eprintln!( + "Failed to upload batch {}. Some keys were imported whilst \ + others may not have been imported. A potential solution is to use the \ + --{} flag, however care should be taken to ensure that there are no \ + duplicate deposits submitted.", + i, IGNORE_DUPLICATES_FLAG + ); + // Return here *without* writing the deposit JSON file. This might help prevent + // users from submitting duplicate deposits or deposits for validators that weren't + // initialized on a VC. + // + // Next the the user runs with the --ignore-duplicates flag there should be a new, + // complete deposit JSON file created. + return Err(format!("Key upload failed: {:?}", e)); + } + + eprintln!( + "Uploaded keystore batch {} of {} to the VC", + i + 1, + num_batches + ); + } } - todo!(); + // If configured, create a single JSON file which contains deposit data information for all + // validators. + if let Some(json_deposit_data_path) = json_deposit_data_path { + let json_deposits = json_deposits.ok_or("Internal error: JSON deposit data is None")?; + + let mut file = fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(&json_deposit_data_path) + .map_err(|e| format!("Unable to create {:?}: {:?}", json_deposit_data_path, e))?; + + serde_json::to_writer(&mut file, &json_deposits) + .map_err(|e| format!("Unable write JSON to {:?}: {:?}", json_deposit_data_path, e))?; + } + + Ok(()) }