diff --git a/cli/src/cli.rs b/cli/src/cli.rs index a2b19cfe6..ef1861734 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -190,7 +190,16 @@ pub enum CliCommand { aggregate: bool, span: Option, }, - VoteAuthorize(Pubkey, Pubkey, VoteAuthorize), + VoteAuthorize { + vote_account_pubkey: Pubkey, + new_authorized_pubkey: Pubkey, + vote_authorize: VoteAuthorize, + }, + VoteUpdateValidator { + vote_account_pubkey: Pubkey, + new_identity_pubkey: Pubkey, + authorized_voter: KeypairEq, + }, // Wallet Commands Address, Airdrop { @@ -371,6 +380,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result parse_vote_create_account(matches), + ("vote-update-validator", Some(matches)) => parse_vote_update_validator(matches), ("vote-authorize-voter", Some(matches)) => { parse_vote_authorize(matches, VoteAuthorize::Voter) } @@ -1265,15 +1275,28 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &vote_account_pubkey, *use_lamports_unit, ), - CliCommand::VoteAuthorize(vote_account_pubkey, new_authorized_pubkey, vote_authorize) => { - process_vote_authorize( - &rpc_client, - config, - &vote_account_pubkey, - &new_authorized_pubkey, - *vote_authorize, - ) - } + CliCommand::VoteAuthorize { + vote_account_pubkey, + new_authorized_pubkey, + vote_authorize, + } => process_vote_authorize( + &rpc_client, + config, + &vote_account_pubkey, + &new_authorized_pubkey, + *vote_authorize, + ), + CliCommand::VoteUpdateValidator { + vote_account_pubkey, + new_identity_pubkey, + authorized_voter, + } => process_vote_update_validator( + &rpc_client, + config, + &vote_account_pubkey, + &new_identity_pubkey, + authorized_voter, + ), CliCommand::Uptime { pubkey: vote_account_pubkey, aggregate, @@ -2254,8 +2277,20 @@ mod tests { assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let new_authorized_pubkey = Pubkey::new_rand(); - config.command = - CliCommand::VoteAuthorize(bob_pubkey, new_authorized_pubkey, VoteAuthorize::Voter); + config.command = CliCommand::VoteAuthorize { + vote_account_pubkey: bob_pubkey, + new_authorized_pubkey, + vote_authorize: VoteAuthorize::Voter, + }; + let signature = process_command(&config); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + + let new_identity_pubkey = Pubkey::new_rand(); + config.command = CliCommand::VoteUpdateValidator { + vote_account_pubkey: bob_pubkey, + new_identity_pubkey, + authorized_voter: Keypair::new().into(), + }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -2438,7 +2473,18 @@ mod tests { }; assert!(process_command(&config).is_err()); - config.command = CliCommand::VoteAuthorize(bob_pubkey, bob_pubkey, VoteAuthorize::Voter); + config.command = CliCommand::VoteAuthorize { + vote_account_pubkey: bob_pubkey, + new_authorized_pubkey: bob_pubkey, + vote_authorize: VoteAuthorize::Voter, + }; + assert!(process_command(&config).is_err()); + + config.command = CliCommand::VoteUpdateValidator { + vote_account_pubkey: bob_pubkey, + new_identity_pubkey: bob_pubkey, + authorized_voter: Keypair::new().into(), + }; assert!(process_command(&config).is_err()); config.command = CliCommand::GetSlot { diff --git a/cli/src/vote.rs b/cli/src/vote.rs index d71960960..d45ef7698 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -70,6 +70,37 @@ impl VoteSubCommands for App<'_, '_> { .help("Public key of the authorized withdrawer (defaults to cli config pubkey)"), ), ) + .subcommand( + SubCommand::with_name("vote-update-validator") + .about("Update the vote account's validator identity") + .arg( + Arg::with_name("vote_account_pubkey") + .index(1) + .value_name("VOTE ACCOUNT PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey_or_keypair) + .help("Vote account to update"), + ) + .arg( + Arg::with_name("new_identity_pubkey") + .index(2) + .value_name("NEW VALIDATOR IDENTITY PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey_or_keypair) + .help("New validator that will vote with this account"), + ) + .arg( + Arg::with_name("authorized_voter") + .index(3) + .value_name("AUTHORIZED VOTER KEYPAIR") + .takes_value(true) + .required(true) + .validator(is_keypair) + .help("Authorized voter keypair"), + ) + ) .subcommand( SubCommand::with_name("vote-authorize-voter") .about("Authorize a new vote signing keypair for the given vote account") @@ -188,11 +219,26 @@ pub fn parse_vote_authorize( let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap(); Ok(CliCommandInfo { - command: CliCommand::VoteAuthorize( + command: CliCommand::VoteAuthorize { vote_account_pubkey, new_authorized_pubkey, vote_authorize, - ), + }, + require_keypair: true, + }) +} + +pub fn parse_vote_update_validator(matches: &ArgMatches<'_>) -> Result { + let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); + let new_identity_pubkey = pubkey_of(matches, "new_identity_pubkey").unwrap(); + let authorized_voter = keypair_of(matches, "authorized_voter").unwrap(); + + Ok(CliCommandInfo { + command: CliCommand::VoteUpdateValidator { + vote_account_pubkey, + new_identity_pubkey, + authorized_voter: authorized_voter.into(), + }, require_keypair: true, }) } @@ -315,6 +361,40 @@ pub fn process_vote_authorize( log_instruction_custom_error::(result) } +pub fn process_vote_update_validator( + rpc_client: &RpcClient, + config: &CliConfig, + vote_account_pubkey: &Pubkey, + new_identity_pubkey: &Pubkey, + authorized_voter: &Keypair, +) -> ProcessResult { + check_unique_pubkeys( + (vote_account_pubkey, "vote_account_pubkey".to_string()), + (new_identity_pubkey, "new_identity_pubkey".to_string()), + )?; + let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; + let ixs = vec![vote_instruction::update_node( + vote_account_pubkey, + &authorized_voter.pubkey(), + new_identity_pubkey, + )]; + + let mut tx = Transaction::new_signed_with_payer( + ixs, + Some(&config.keypair.pubkey()), + &[&config.keypair, authorized_voter], + recent_blockhash, + ); + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + log_instruction_custom_error::(result) +} + fn get_vote_account( rpc_client: &RpcClient, vote_account_pubkey: &Pubkey, @@ -460,17 +540,24 @@ mod tests { let keypair = Keypair::new(); let pubkey = keypair.pubkey(); let pubkey_string = pubkey.to_string(); + let keypair2 = Keypair::new(); + let pubkey2 = keypair2.pubkey(); + let pubkey2_string = pubkey2.to_string(); let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", "vote-authorize-voter", &pubkey_string, - &pubkey_string, + &pubkey2_string, ]); assert_eq!( parse_command(&test_authorize_voter).unwrap(), CliCommandInfo { - command: CliCommand::VoteAuthorize(pubkey, pubkey, VoteAuthorize::Voter), + command: CliCommand::VoteAuthorize { + vote_account_pubkey: pubkey, + new_authorized_pubkey: pubkey2, + vote_authorize: VoteAuthorize::Voter + }, require_keypair: true } ); @@ -581,6 +668,27 @@ mod tests { } ); + let test_update_validator = test_commands.clone().get_matches_from(vec![ + "test", + "vote-update-validator", + &pubkey_string, + &pubkey2_string, + &keypair_file, + ]); + assert_eq!( + parse_command(&test_update_validator).unwrap(), + CliCommandInfo { + command: CliCommand::VoteUpdateValidator { + vote_account_pubkey: pubkey, + new_identity_pubkey: pubkey2, + authorized_voter: solana_sdk::signature::read_keypair_file(&keypair_file) + .unwrap() + .into(), + }, + require_keypair: true + } + ); + // Test Uptime Subcommand let pubkey = Pubkey::new_rand(); let matches = test_commands.clone().get_matches_from(vec![ diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index 6f1dc982e..0e583a60a 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -58,7 +58,7 @@ pub enum VoteInstruction { /// Withdraw some amount of funds Withdraw(u64), - /// Update the vote account's node id + /// Update the vote account's validator identity (node id) UpdateNode(Pubkey), }