diff --git a/Cargo.lock b/Cargo.lock index 122c92044..81c95cbb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3324,6 +3324,7 @@ dependencies = [ name = "lcli" version = "1.0.6" dependencies = [ + "account_utils", "bls", "clap", "clap_utils", @@ -3335,6 +3336,7 @@ dependencies = [ "eth2_libp2p", "eth2_network_config", "eth2_ssz", + "eth2_wallet", "futures 0.3.8", "genesis", "hex", diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index 56bcf5dca..7ec20e42c 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -35,4 +35,6 @@ rand = "0.7.3" eth2_keystore = { path = "../crypto/eth2_keystore" } lighthouse_version = { path = "../common/lighthouse_version" } directory = { path = "../common/directory" } +account_utils = { path = "../common/account_utils" } +eth2_wallet = { path = "../crypto/eth2_wallet" } tokio-compat-02 = "0.1" diff --git a/lcli/src/main.rs b/lcli/src/main.rs index f93857e19..d7017c8ca 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -8,6 +8,7 @@ mod insecure_validators; mod interop_genesis; mod new_testnet; mod parse_hex; +mod replace_state_pubkeys; mod skip_slots; mod transition_blocks; @@ -231,6 +232,35 @@ fn main() { .help("The value for state.genesis_time."), ), ) + .subcommand( + SubCommand::with_name("replace-state-pubkeys") + .about( + "Loads a file with an SSZ-encoded BeaconState and replaces \ + all the validator pubkeys with ones derived from the mnemonic \ + such that validator indices correspond to EIP-2334 voting keypair \ + derivation paths.", + ) + .arg( + Arg::with_name("ssz-state") + .index(1) + .value_name("PATH") + .takes_value(true) + .required(true) + .help("The path to the SSZ file"), + ) + .arg( + Arg::with_name("mnemonic") + .index(2) + .value_name("BIP39_MNENMONIC") + .takes_value(true) + .required(true) + .default_value( + "replace nephew blur decorate waste convince soup column \ + orient excite play baby", + ) + .help("The mnemonic for key derivation."), + ), + ) .subcommand( SubCommand::with_name("new-testnet") .about( @@ -516,6 +546,8 @@ fn run( .map_err(|e| format!("Failed to run interop-genesis command: {}", e)), ("change-genesis-time", Some(matches)) => change_genesis_time::run::(matches) .map_err(|e| format!("Failed to run change-genesis-time command: {}", e)), + ("replace-state-pubkeys", Some(matches)) => replace_state_pubkeys::run::(matches) + .map_err(|e| format!("Failed to run replace-state-pubkeys command: {}", e)), ("new-testnet", Some(matches)) => new_testnet::run::(matches) .map_err(|e| format!("Failed to run new_testnet command: {}", e)), ("check-deposit-data", Some(matches)) => check_deposit_data::run::(matches) diff --git a/lcli/src/replace_state_pubkeys.rs b/lcli/src/replace_state_pubkeys.rs new file mode 100644 index 000000000..e1513b4c2 --- /dev/null +++ b/lcli/src/replace_state_pubkeys.rs @@ -0,0 +1,55 @@ +use account_utils::{eth2_keystore::keypair_from_secret, mnemonic_from_phrase}; +use clap::ArgMatches; +use eth2_wallet::bip39::Seed; +use eth2_wallet::{recover_validator_secret_from_mnemonic, KeyType}; +use ssz::{Decode, Encode}; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::PathBuf; +use types::{BeaconState, EthSpec}; + +pub fn run(matches: &ArgMatches) -> Result<(), String> { + let path = matches + .value_of("ssz-state") + .ok_or("ssz-state not specified")? + .parse::() + .map_err(|e| format!("Unable to parse ssz-state: {}", e))?; + + let mnemonic_phrase = matches + .value_of("mnemonic") + .ok_or("mnemonic not specified")?; + + let mut state: BeaconState = { + let mut file = File::open(&path).map_err(|e| format!("Unable to open file: {}", e))?; + + let mut ssz = vec![]; + + file.read_to_end(&mut ssz) + .map_err(|e| format!("Unable to read file: {}", e))?; + + BeaconState::from_ssz_bytes(&ssz).map_err(|e| format!("Unable to decode SSZ: {:?}", e))? + }; + + let mnemonic = mnemonic_from_phrase(mnemonic_phrase)?; + let seed = Seed::new(&mnemonic, ""); + + for (index, validator) in state.validators.iter_mut().enumerate() { + let (secret, _) = + recover_validator_secret_from_mnemonic(seed.as_bytes(), index as u32, KeyType::Voting) + .map_err(|e| format!("Unable to generate validator key: {:?}", e))?; + + let keypair = keypair_from_secret(secret.as_bytes()) + .map_err(|e| format!("Unable build keystore: {:?}", e))?; + + eprintln!("{}: {}", index, keypair.pk); + + validator.pubkey = keypair.pk.into(); + } + + let mut file = File::create(path).map_err(|e| format!("Unable to create file: {}", e))?; + + file.write_all(&state.as_ssz_bytes()) + .map_err(|e| format!("Unable to write to file: {}", e))?; + + Ok(()) +}