From f5c944b52c82998bec933d0a113c6b51226d6b81 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 6 Dec 2021 15:54:50 -0700 Subject: [PATCH 1/2] Add offline and fee-payer utilities to CLI vote module (#21579) * create-vote-account: add offline, nonce, fee_payer capabilities * vote-authorize: add offline, nonce, fee-payer * vote-update-things: add offline, nonce, fee-payer * withdraw-vote: add offline, nonce, fee-payer * close-vote-acct: add fee-payer * Allow WithdrawVoteAccount to empty account, since offline operations cannot perform account state queries as in CloseVoteAccount * Fix lint * Update offline-signing docs * Add some parse unit tests * Add offline integration test (cherry picked from commit 873fe81bc048599b540cf511872380d77be38f56) # Conflicts: # cli/src/cli.rs # cli/src/vote.rs # cli/tests/vote.rs --- cli/src/cli.rs | 154 +++++ cli/src/vote.rs | 1116 ++++++++++++++++++++++++++++++++--- cli/tests/stake.rs | 6 + cli/tests/vote.rs | 330 ++++++++++- docs/src/offline-signing.md | 12 + 5 files changed, 1521 insertions(+), 97 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 201f6331a3d42e..b913ee1c70d9f2 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -298,7 +298,13 @@ pub enum CliCommand { authorized_voter: Option, authorized_withdrawer: Pubkey, commission: u8, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option, + fee_payer: SignerIndex, }, ShowVoteAccount { pubkey: Pubkey, @@ -310,13 +316,35 @@ pub enum CliCommand { destination_account_pubkey: Pubkey, withdraw_authority: SignerIndex, withdraw_amount: SpendAmount, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option, + fee_payer: SignerIndex, }, +<<<<<<< HEAD +======= + CloseVoteAccount { + vote_account_pubkey: Pubkey, + destination_account_pubkey: Pubkey, + withdraw_authority: SignerIndex, + memo: Option, + fee_payer: SignerIndex, + }, +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) VoteAuthorize { vote_account_pubkey: Pubkey, new_authorized_pubkey: Pubkey, vote_authorize: VoteAuthorize, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option, + fee_payer: SignerIndex, authorized: SignerIndex, new_authorized: Option, }, @@ -324,13 +352,25 @@ pub enum CliCommand { vote_account_pubkey: Pubkey, new_identity_account: SignerIndex, withdraw_authority: SignerIndex, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option, + fee_payer: SignerIndex, }, VoteUpdateCommission { vote_account_pubkey: Pubkey, commission: u8, withdraw_authority: SignerIndex, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option, + fee_payer: SignerIndex, }, // Wallet Commands Address, @@ -1375,7 +1415,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { authorized_voter, authorized_withdrawer, commission, + sign_only, + dump_transaction_message, + blockhash_query, + ref nonce_account, + nonce_authority, memo, + fee_payer, } => process_create_vote_account( &rpc_client, config, @@ -1385,7 +1431,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { authorized_voter, *authorized_withdrawer, *commission, + *sign_only, + *dump_transaction_message, + blockhash_query, + nonce_account.as_ref(), + *nonce_authority, memo.as_ref(), + *fee_payer, ), CliCommand::ShowVoteAccount { pubkey: vote_account_pubkey, @@ -1403,7 +1455,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { withdraw_authority, withdraw_amount, destination_account_pubkey, + sign_only, + dump_transaction_message, + blockhash_query, + ref nonce_account, + nonce_authority, memo, + fee_payer, } => process_withdraw_from_vote_account( &rpc_client, config, @@ -1411,13 +1469,43 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { *withdraw_authority, *withdraw_amount, destination_account_pubkey, + *sign_only, + *dump_transaction_message, + blockhash_query, + nonce_account.as_ref(), + *nonce_authority, + memo.as_ref(), + *fee_payer, + ), +<<<<<<< HEAD +======= + CliCommand::CloseVoteAccount { + vote_account_pubkey, + withdraw_authority, + destination_account_pubkey, + memo, + fee_payer, + } => process_close_vote_account( + &rpc_client, + config, + vote_account_pubkey, + *withdraw_authority, + destination_account_pubkey, memo.as_ref(), + *fee_payer, ), +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) CliCommand::VoteAuthorize { vote_account_pubkey, new_authorized_pubkey, vote_authorize, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority, memo, + fee_payer, authorized, new_authorized, } => process_vote_authorize( @@ -1428,33 +1516,63 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { *vote_authorize, *authorized, *new_authorized, + *sign_only, + *dump_transaction_message, + blockhash_query, + *nonce_account, + *nonce_authority, memo.as_ref(), + *fee_payer, ), CliCommand::VoteUpdateValidator { vote_account_pubkey, new_identity_account, withdraw_authority, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority, memo, + fee_payer, } => process_vote_update_validator( &rpc_client, config, vote_account_pubkey, *new_identity_account, *withdraw_authority, + *sign_only, + *dump_transaction_message, + blockhash_query, + *nonce_account, + *nonce_authority, memo.as_ref(), + *fee_payer, ), CliCommand::VoteUpdateCommission { vote_account_pubkey, commission, withdraw_authority, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority, memo, + fee_payer, } => process_vote_update_commission( &rpc_client, config, vote_account_pubkey, *commission, *withdraw_authority, + *sign_only, + *dump_transaction_message, + blockhash_query, + *nonce_account, + *nonce_authority, memo.as_ref(), + *fee_payer, ), // Wallet Commands @@ -1953,7 +2071,13 @@ mod tests { authorized_voter: Some(bob_pubkey), authorized_withdrawer: bob_pubkey, commission: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }; config.signers = vec![&keypair, &bob_keypair, &identity_keypair]; let result = process_command(&config); @@ -1984,7 +2108,13 @@ mod tests { vote_account_pubkey: bob_pubkey, new_authorized_pubkey, vote_authorize: VoteAuthorize::Withdrawer, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 0, new_authorized: None, }; @@ -1997,7 +2127,13 @@ mod tests { vote_account_pubkey: bob_pubkey, new_identity_account: 2, withdraw_authority: 1, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }; let result = process_command(&config); assert!(result.is_ok()); @@ -2173,7 +2309,13 @@ mod tests { authorized_voter: Some(bob_pubkey), authorized_withdrawer: bob_pubkey, commission: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }; config.signers = vec![&keypair, &bob_keypair, &identity_keypair]; assert!(process_command(&config).is_err()); @@ -2182,7 +2324,13 @@ mod tests { vote_account_pubkey: bob_pubkey, new_authorized_pubkey: bob_pubkey, vote_authorize: VoteAuthorize::Voter, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 0, new_authorized: None, }; @@ -2192,7 +2340,13 @@ mod tests { vote_account_pubkey: bob_pubkey, new_identity_account: 1, withdraw_authority: 1, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }; assert!(process_command(&config).is_err()); diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 6f7c25ebab02d5..463ea41e01919d 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -6,18 +6,33 @@ use { ProcessResult, }, memo::WithMemo, - spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount}, + nonce::check_nonce_account, + spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, stake::check_current_authority, }, clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand}, solana_clap_utils::{ + fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, input_parsers::*, input_validators::*, keypair::{DefaultSigner, SignerIndex}, memo::{memo_arg, MEMO_ARG}, + nonce::*, + offline::*, }, + solana_cli_output::{ + return_signers_with_config, CliEpochVotingHistory, CliLockout, CliVoteAccount, + ReturnSignersConfig, + }, + solana_client::{ + blockhash_query::BlockhashQuery, nonce_utils, rpc_client::RpcClient, + rpc_config::RpcGetVoteAccountsConfig, + }, +<<<<<<< HEAD solana_cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount}, solana_client::rpc_client::RpcClient, +======= +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_sdk::{ account::Account, commitment_config::CommitmentConfig, message::Message, @@ -96,6 +111,9 @@ impl VoteSubCommands for App<'_, '_> { .takes_value(true) .help("Seed for address generation; if specified, the resulting account will be at a derived address of the VOTE ACCOUNT pubkey") ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) .arg(memo_arg()) ) .subcommand( @@ -123,6 +141,9 @@ impl VoteSubCommands for App<'_, '_> { .required(true), "New authorized vote signer. "), ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) .arg(memo_arg()) ) .subcommand( @@ -150,6 +171,9 @@ impl VoteSubCommands for App<'_, '_> { .required(true), "New authorized withdrawer. "), ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) .arg(memo_arg()) ) .subcommand( @@ -179,6 +203,9 @@ impl VoteSubCommands for App<'_, '_> { .validator(is_valid_signer) .help("New authorized vote signer."), ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) .arg(memo_arg()) ) .subcommand( @@ -208,6 +235,9 @@ impl VoteSubCommands for App<'_, '_> { .validator(is_valid_signer) .help("New authorized withdrawer."), ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) .arg(memo_arg()) ) .subcommand( @@ -238,6 +268,9 @@ impl VoteSubCommands for App<'_, '_> { .validator(is_valid_signer) .help("Authorized withdrawer keypair"), ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) .arg(memo_arg()) ) .subcommand( @@ -268,6 +301,9 @@ impl VoteSubCommands for App<'_, '_> { .validator(is_valid_signer) .help("Authorized withdrawer keypair"), ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) .arg(memo_arg()) ) .subcommand( @@ -338,7 +374,44 @@ impl VoteSubCommands for App<'_, '_> { .validator(is_valid_signer) .help("Authorized withdrawer [default: cli config keypair]"), ) +<<<<<<< HEAD .arg(memo_arg()) +======= + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) + .arg(memo_arg() + ) + ) + .subcommand( + SubCommand::with_name("close-vote-account") + .about("Close a vote account and withdraw all funds remaining") + .arg( + pubkey!(Arg::with_name("vote_account_pubkey") + .index(1) + .value_name("VOTE_ACCOUNT_ADDRESS") + .required(true), + "Vote account to be closed. "), + ) + .arg( + pubkey!(Arg::with_name("destination_account_pubkey") + .index(2) + .value_name("RECIPIENT_ADDRESS") + .required(true), + "The recipient of all withdrawn SOL. "), + ) + .arg( + Arg::with_name("authorized_withdrawer") + .long("authorized-withdrawer") + .value_name("AUTHORIZED_KEYPAIR") + .takes_value(true) + .validator(is_valid_signer) + .help("Authorized withdrawer [default: cli config keypair]"), + ) + .arg(fee_payer_arg()) + .arg(memo_arg() + ) +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) ) } } @@ -357,7 +430,14 @@ pub fn parse_create_vote_account( let authorized_withdrawer = pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap(); let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer"); + let sign_only = matches.is_present(SIGN_ONLY_ARG.name); + let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); + let blockhash_query = BlockhashQuery::new_from_matches(matches); + let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?; let memo = matches.value_of(MEMO_ARG.name).map(String::from); + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; if !allow_unsafe { if authorized_withdrawer == vote_account_pubkey.unwrap() { @@ -376,12 +456,12 @@ pub fn parse_create_vote_account( } } - let payer_provided = None; - let signer_info = default_signer.generate_unique_signers( - vec![payer_provided, vote_account, identity_account], - matches, - wallet_manager, - )?; + let mut bulk_signers = vec![fee_payer, vote_account, identity_account]; + if nonce_account.is_some() { + bulk_signers.push(nonce_authority); + } + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; Ok(CliCommandInfo { command: CliCommand::CreateVoteAccount { @@ -391,7 +471,13 @@ pub fn parse_create_vote_account( authorized_voter, authorized_withdrawer, commission, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, signers: signer_info.signers, }) @@ -408,27 +494,43 @@ pub fn parse_vote_authorize( pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?; - let payer_provided = None; - let mut signers = vec![payer_provided, authorized]; + let sign_only = matches.is_present(SIGN_ONLY_ARG.name); + let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); + let blockhash_query = BlockhashQuery::new_from_matches(matches); + let nonce_account = pubkey_of(matches, NONCE_ARG.name); + let memo = matches.value_of(MEMO_ARG.name).map(String::from); + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; + + let mut bulk_signers = vec![fee_payer, authorized]; let new_authorized_pubkey = if checked { let (new_authorized_signer, new_authorized_pubkey) = signer_of(matches, "new_authorized", wallet_manager)?; - signers.push(new_authorized_signer); + bulk_signers.push(new_authorized_signer); new_authorized_pubkey.unwrap() } else { pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap() }; - - let signer_info = default_signer.generate_unique_signers(signers, matches, wallet_manager)?; - let memo = matches.value_of(MEMO_ARG.name).map(String::from); + if nonce_account.is_some() { + bulk_signers.push(nonce_authority); + } + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; Ok(CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey, new_authorized_pubkey, vote_authorize, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), authorized: signer_info.index_of(authorized_pubkey).unwrap(), new_authorized: if checked { signer_info.index_of(Some(new_authorized_pubkey)) @@ -452,20 +554,34 @@ pub fn parse_vote_update_validator( let (authorized_withdrawer, authorized_withdrawer_pubkey) = signer_of(matches, "authorized_withdrawer", wallet_manager)?; - let payer_provided = None; - let signer_info = default_signer.generate_unique_signers( - vec![payer_provided, authorized_withdrawer, new_identity_account], - matches, - wallet_manager, - )?; + let sign_only = matches.is_present(SIGN_ONLY_ARG.name); + let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); + let blockhash_query = BlockhashQuery::new_from_matches(matches); + let nonce_account = pubkey_of(matches, NONCE_ARG.name); let memo = matches.value_of(MEMO_ARG.name).map(String::from); + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; + + let mut bulk_signers = vec![fee_payer, authorized_withdrawer, new_identity_account]; + if nonce_account.is_some() { + bulk_signers.push(nonce_authority); + } + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; Ok(CliCommandInfo { command: CliCommand::VoteUpdateValidator { vote_account_pubkey, new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(), withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(), + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, signers: signer_info.signers, }) @@ -482,20 +598,34 @@ pub fn parse_vote_update_commission( signer_of(matches, "authorized_withdrawer", wallet_manager)?; let commission = value_t_or_exit!(matches, "commission", u8); - let payer_provided = None; - let signer_info = default_signer.generate_unique_signers( - vec![payer_provided, authorized_withdrawer], - matches, - wallet_manager, - )?; + let sign_only = matches.is_present(SIGN_ONLY_ARG.name); + let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); + let blockhash_query = BlockhashQuery::new_from_matches(matches); + let nonce_account = pubkey_of(matches, NONCE_ARG.name); let memo = matches.value_of(MEMO_ARG.name).map(String::from); + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; + + let mut bulk_signers = vec![fee_payer, authorized_withdrawer]; + if nonce_account.is_some() { + bulk_signers.push(nonce_authority); + } + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; Ok(CliCommandInfo { command: CliCommand::VoteUpdateCommission { vote_account_pubkey, commission, withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(), + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, signers: signer_info.signers, }) @@ -537,26 +667,77 @@ pub fn parse_withdraw_from_vote_account( let (withdraw_authority, withdraw_authority_pubkey) = signer_of(matches, "authorized_withdrawer", wallet_manager)?; - let payer_provided = None; + let sign_only = matches.is_present(SIGN_ONLY_ARG.name); + let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); + let blockhash_query = BlockhashQuery::new_from_matches(matches); + let nonce_account = pubkey_of(matches, NONCE_ARG.name); + let memo = matches.value_of(MEMO_ARG.name).map(String::from); + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; + + let mut bulk_signers = vec![fee_payer, withdraw_authority]; + if nonce_account.is_some() { + bulk_signers.push(nonce_authority); + } + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + + Ok(CliCommandInfo { + command: CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey, + destination_account_pubkey, + withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(), + withdraw_amount, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), + }, + signers: signer_info.signers, + }) +} + +<<<<<<< HEAD +======= +pub fn parse_close_vote_account( + matches: &ArgMatches<'_>, + default_signer: &DefaultSigner, + wallet_manager: &mut Option>, +) -> Result { + let vote_account_pubkey = + pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); + let destination_account_pubkey = + pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap(); + + let (withdraw_authority, withdraw_authority_pubkey) = + signer_of(matches, "authorized_withdrawer", wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; + let signer_info = default_signer.generate_unique_signers( - vec![payer_provided, withdraw_authority], + vec![fee_payer, withdraw_authority], matches, wallet_manager, )?; let memo = matches.value_of(MEMO_ARG.name).map(String::from); Ok(CliCommandInfo { - command: CliCommand::WithdrawFromVoteAccount { + command: CliCommand::CloseVoteAccount { vote_account_pubkey, destination_account_pubkey, withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(), - withdraw_amount, memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, signers: signer_info.signers, }) } +#[allow(clippy::too_many_arguments)] +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) pub fn process_create_vote_account( rpc_client: &RpcClient, config: &CliConfig, @@ -566,7 +747,13 @@ pub fn process_create_vote_account( authorized_voter: &Option, authorized_withdrawer: Pubkey, commission: u8, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: &BlockhashQuery, + nonce_account: Option<&Pubkey>, + nonce_authority: SignerIndex, memo: Option<&String>, + fee_payer: SignerIndex, ) -> ProcessResult { let vote_account = config.signers[vote_account]; let vote_account_pubkey = vote_account.pubkey(); @@ -592,6 +779,9 @@ pub fn process_create_vote_account( .max(1); let amount = SpendAmount::Some(required_balance); + let fee_payer = config.signers[fee_payer]; + let nonce_authority = config.signers[nonce_authority]; + let build_message = |lamports| { let vote_init = VoteInit { node_pubkey: identity_pubkey, @@ -619,42 +809,92 @@ pub fn process_create_vote_account( ) .with_memo(memo) }; - Message::new(&ixs, Some(&config.signers[0].pubkey())) + if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( + ixs, + Some(&fee_payer.pubkey()), + nonce_account, + &nonce_authority.pubkey(), + ) + } else { + Message::new(&ixs, Some(&fee_payer.pubkey())) + } }; - if let Ok(response) = - rpc_client.get_account_with_commitment(&vote_account_address, config.commitment) - { - if let Some(vote_account) = response.value { - let err_msg = if vote_account.owner == solana_vote_program::id() { - format!("Vote account {} already exists", vote_account_address) - } else { - format!( - "Account {} already exists and is not a vote account", - vote_account_address - ) - }; - return Err(CliError::BadParameter(err_msg).into()); - } - } + let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; +<<<<<<< HEAD let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let (message, _) = resolve_spend_tx_and_check_account_balance( +======= + let (message, _) = resolve_spend_tx_and_check_account_balances( +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) rpc_client, - false, + sign_only, amount, +<<<<<<< HEAD &fee_calculator, +======= + &recent_blockhash, +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) &config.signers[0].pubkey(), + &fee_payer.pubkey(), build_message, config.commitment, )?; + + if !sign_only { + if let Ok(response) = + rpc_client.get_account_with_commitment(&vote_account_address, config.commitment) + { + if let Some(vote_account) = response.value { + let err_msg = if vote_account.owner == solana_vote_program::id() { + format!("Vote account {} already exists", vote_account_address) + } else { + format!( + "Account {} already exists and is not a vote account", + vote_account_address + ) + }; + return Err(CliError::BadParameter(err_msg).into()); + } + } + + if let Some(nonce_account) = &nonce_account { + let nonce_account = nonce_utils::get_account_with_commitment( + rpc_client, + nonce_account, + config.commitment, + )?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; + } + } + let mut tx = Transaction::new_unsigned(message); +<<<<<<< HEAD tx.try_sign(&config.signers, recent_blockhash)?; let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) +======= + if sign_only { + tx.try_partial_sign(&config.signers, recent_blockhash)?; + return_signers_with_config( + &tx, + &config.output_format, + &ReturnSignersConfig { + dump_transaction_message, + }, + ) + } else { + tx.try_sign(&config.signers, recent_blockhash)?; + let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); + log_instruction_custom_error::(result, config) + } +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } +#[allow(clippy::too_many_arguments)] pub fn process_vote_authorize( rpc_client: &RpcClient, config: &CliConfig, @@ -663,30 +903,42 @@ pub fn process_vote_authorize( vote_authorize: VoteAuthorize, authorized: SignerIndex, new_authorized: Option, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: &BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option<&String>, + fee_payer: SignerIndex, ) -> ProcessResult { let authorized = config.signers[authorized]; let new_authorized_signer = new_authorized.map(|index| config.signers[index]); - let (_, vote_state) = get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?; + let vote_state = if !sign_only { + Some(get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?.1) + } else { + None + }; match vote_authorize { VoteAuthorize::Voter => { - let current_epoch = rpc_client.get_epoch_info()?.epoch; - let current_authorized_voter = vote_state - .authorized_voters() - .get_authorized_voter(current_epoch) - .ok_or_else(|| { - CliError::RpcRequestError( - "Invalid vote account state; no authorized voters found".to_string(), - ) - })?; - check_current_authority(¤t_authorized_voter, &authorized.pubkey())?; - if let Some(signer) = new_authorized_signer { - if signer.is_interactive() { - return Err(CliError::BadParameter(format!( - "invalid new authorized vote signer {:?}. Interactive vote signers not supported", - new_authorized_pubkey - )).into()); + if let Some(vote_state) = vote_state { + let current_epoch = rpc_client.get_epoch_info()?.epoch; + let current_authorized_voter = vote_state + .authorized_voters() + .get_authorized_voter(current_epoch) + .ok_or_else(|| { + CliError::RpcRequestError( + "Invalid vote account state; no authorized voters found".to_string(), + ) + })?; + check_current_authority(¤t_authorized_voter, &authorized.pubkey())?; + if let Some(signer) = new_authorized_signer { + if signer.is_interactive() { + return Err(CliError::BadParameter(format!( + "invalid new authorized vote signer {:?}. Interactive vote signers not supported", + new_authorized_pubkey + )).into()); + } } } } @@ -695,11 +947,16 @@ pub fn process_vote_authorize( (&authorized.pubkey(), "authorized_account".to_string()), (new_authorized_pubkey, "new_authorized_pubkey".to_string()), )?; - check_current_authority(&vote_state.authorized_withdrawer, &authorized.pubkey())? + if let Some(vote_state) = vote_state { + check_current_authority(&vote_state.authorized_withdrawer, &authorized.pubkey())? + } } } +<<<<<<< HEAD let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; +======= +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) let vote_ix = if new_authorized_signer.is_some() { vote_instruction::authorize_checked( vote_account_pubkey, // vote account to update @@ -717,8 +974,23 @@ pub fn process_vote_authorize( }; let ixs = vec![vote_ix].with_memo(memo); - let message = Message::new(&ixs, Some(&config.signers[0].pubkey())); + let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; + + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( + ixs, + Some(&fee_payer.pubkey()), + nonce_account, + &nonce_authority.pubkey(), + ) + } else { + Message::new(&ixs, Some(&fee_payer.pubkey())) + }; let mut tx = Transaction::new_unsigned(message); +<<<<<<< HEAD tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee_with_commitment( rpc_client, @@ -729,15 +1001,53 @@ pub fn process_vote_authorize( )?; let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) +======= + + if sign_only { + tx.try_partial_sign(&config.signers, recent_blockhash)?; + return_signers_with_config( + &tx, + &config.output_format, + &ReturnSignersConfig { + dump_transaction_message, + }, + ) + } else { + tx.try_sign(&config.signers, recent_blockhash)?; + if let Some(nonce_account) = &nonce_account { + let nonce_account = nonce_utils::get_account_with_commitment( + rpc_client, + nonce_account, + config.commitment, + )?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; + } + check_account_for_fee_with_commitment( + rpc_client, + &config.signers[0].pubkey(), + &tx.message, + config.commitment, + )?; + let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); + log_instruction_custom_error::(result, config) + } +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } +#[allow(clippy::too_many_arguments)] pub fn process_vote_update_validator( rpc_client: &RpcClient, config: &CliConfig, vote_account_pubkey: &Pubkey, new_identity_account: SignerIndex, withdraw_authority: SignerIndex, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: &BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option<&String>, + fee_payer: SignerIndex, ) -> ProcessResult { let authorized_withdrawer = config.signers[withdraw_authority]; let new_identity_account = config.signers[new_identity_account]; @@ -746,16 +1056,32 @@ pub fn process_vote_update_validator( (vote_account_pubkey, "vote_account_pubkey".to_string()), (&new_identity_pubkey, "new_identity_account".to_string()), )?; +<<<<<<< HEAD let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; +======= + let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) let ixs = vec![vote_instruction::update_validator_identity( vote_account_pubkey, &authorized_withdrawer.pubkey(), &new_identity_pubkey, )] .with_memo(memo); - - let message = Message::new(&ixs, Some(&config.signers[0].pubkey())); + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( + ixs, + Some(&fee_payer.pubkey()), + nonce_account, + &nonce_authority.pubkey(), + ) + } else { + Message::new(&ixs, Some(&fee_payer.pubkey())) + }; let mut tx = Transaction::new_unsigned(message); +<<<<<<< HEAD tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee_with_commitment( rpc_client, @@ -766,27 +1092,81 @@ pub fn process_vote_update_validator( )?; let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) +======= + + if sign_only { + tx.try_partial_sign(&config.signers, recent_blockhash)?; + return_signers_with_config( + &tx, + &config.output_format, + &ReturnSignersConfig { + dump_transaction_message, + }, + ) + } else { + tx.try_sign(&config.signers, recent_blockhash)?; + if let Some(nonce_account) = &nonce_account { + let nonce_account = nonce_utils::get_account_with_commitment( + rpc_client, + nonce_account, + config.commitment, + )?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; + } + check_account_for_fee_with_commitment( + rpc_client, + &config.signers[0].pubkey(), + &tx.message, + config.commitment, + )?; + let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); + log_instruction_custom_error::(result, config) + } +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } +#[allow(clippy::too_many_arguments)] pub fn process_vote_update_commission( rpc_client: &RpcClient, config: &CliConfig, vote_account_pubkey: &Pubkey, commission: u8, withdraw_authority: SignerIndex, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: &BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, memo: Option<&String>, + fee_payer: SignerIndex, ) -> ProcessResult { let authorized_withdrawer = config.signers[withdraw_authority]; +<<<<<<< HEAD let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; +======= + let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) let ixs = vec![vote_instruction::update_commission( vote_account_pubkey, &authorized_withdrawer.pubkey(), commission, )] .with_memo(memo); - - let message = Message::new(&ixs, Some(&config.signers[0].pubkey())); + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( + ixs, + Some(&fee_payer.pubkey()), + nonce_account, + &nonce_authority.pubkey(), + ) + } else { + Message::new(&ixs, Some(&fee_payer.pubkey())) + }; let mut tx = Transaction::new_unsigned(message); +<<<<<<< HEAD tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee_with_commitment( rpc_client, @@ -797,6 +1177,36 @@ pub fn process_vote_update_commission( )?; let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) +======= + if sign_only { + tx.try_partial_sign(&config.signers, recent_blockhash)?; + return_signers_with_config( + &tx, + &config.output_format, + &ReturnSignersConfig { + dump_transaction_message, + }, + ) + } else { + tx.try_sign(&config.signers, recent_blockhash)?; + if let Some(nonce_account) = &nonce_account { + let nonce_account = nonce_utils::get_account_with_commitment( + rpc_client, + nonce_account, + config.commitment, + )?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; + } + check_account_for_fee_with_commitment( + rpc_client, + &config.signers[0].pubkey(), + &tx.message, + config.commitment, + )?; + let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); + log_instruction_custom_error::(result, config) + } +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } fn get_vote_account( @@ -887,6 +1297,7 @@ pub fn process_show_vote_account( Ok(config.output_format.formatted_string(&vote_account_data)) } +#[allow(clippy::too_many_arguments)] pub fn process_withdraw_from_vote_account( rpc_client: &RpcClient, config: &CliConfig, @@ -894,35 +1305,74 @@ pub fn process_withdraw_from_vote_account( withdraw_authority: SignerIndex, withdraw_amount: SpendAmount, destination_account_pubkey: &Pubkey, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: &BlockhashQuery, + nonce_account: Option<&Pubkey>, + nonce_authority: SignerIndex, memo: Option<&String>, + fee_payer: SignerIndex, ) -> ProcessResult { +<<<<<<< HEAD let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; +======= +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) let withdraw_authority = config.signers[withdraw_authority]; + let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; - let current_balance = rpc_client.get_balance(vote_account_pubkey)?; - let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?; + let fee_payer = config.signers[fee_payer]; + let nonce_authority = config.signers[nonce_authority]; + + let build_message = |lamports| { + let ixs = vec![withdraw( + vote_account_pubkey, + &withdraw_authority.pubkey(), + lamports, + destination_account_pubkey, + )] + .with_memo(memo); + + if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( + ixs, + Some(&fee_payer.pubkey()), + nonce_account, + &nonce_authority.pubkey(), + ) + } else { + Message::new(&ixs, Some(&fee_payer.pubkey())) + } + }; + + let (message, _) = resolve_spend_tx_and_check_account_balances( + rpc_client, + sign_only, + withdraw_amount, + &recent_blockhash, + vote_account_pubkey, + &fee_payer.pubkey(), + build_message, + config.commitment, + )?; - let lamports = match withdraw_amount { - SpendAmount::All => current_balance.saturating_sub(minimum_balance), - SpendAmount::Some(withdraw_amount) => { - if current_balance.saturating_sub(withdraw_amount) < minimum_balance { + if !sign_only { + let current_balance = rpc_client.get_balance(vote_account_pubkey)?; + let minimum_balance = + rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?; + if let SpendAmount::Some(withdraw_amount) = withdraw_amount { + let balance_remaining = current_balance.saturating_sub(withdraw_amount); + if balance_remaining < minimum_balance && balance_remaining != 0 { return Err(CliError::BadParameter(format!( "Withdraw amount too large. The vote account balance must be at least {} SOL to remain rent exempt", lamports_to_sol(minimum_balance) )) .into()); } - withdraw_amount } - }; + } - let ixs = vec![withdraw( - vote_account_pubkey, - &withdraw_authority.pubkey(), - lamports, - destination_account_pubkey, - )] - .with_memo(memo); + let mut tx = Transaction::new_unsigned(message); +<<<<<<< HEAD let message = Message::new(&ixs, Some(&config.signers[0].pubkey())); let mut transaction = Transaction::new_unsigned(message); transaction.try_sign(&config.signers, recent_blockhash)?; @@ -931,9 +1381,92 @@ pub fn process_withdraw_from_vote_account( &config.signers[0].pubkey(), &fee_calculator, &transaction.message, +======= + if sign_only { + tx.try_partial_sign(&config.signers, recent_blockhash)?; + return_signers_with_config( + &tx, + &config.output_format, + &ReturnSignersConfig { + dump_transaction_message, + }, + ) + } else { + tx.try_sign(&config.signers, recent_blockhash)?; + if let Some(nonce_account) = &nonce_account { + let nonce_account = nonce_utils::get_account_with_commitment( + rpc_client, + nonce_account, + config.commitment, + )?; + check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?; + } + check_account_for_fee_with_commitment( + rpc_client, + &tx.message.account_keys[0], + &tx.message, + config.commitment, + )?; + let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); + log_instruction_custom_error::(result, config) + } +} + +pub fn process_close_vote_account( + rpc_client: &RpcClient, + config: &CliConfig, + vote_account_pubkey: &Pubkey, + withdraw_authority: SignerIndex, + destination_account_pubkey: &Pubkey, + memo: Option<&String>, + fee_payer: SignerIndex, +) -> ProcessResult { + let vote_account_status = + rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig { + vote_pubkey: Some(vote_account_pubkey.to_string()), + ..RpcGetVoteAccountsConfig::default() + })?; + + if let Some(vote_account) = vote_account_status + .current + .into_iter() + .chain(vote_account_status.delinquent.into_iter()) + .next() + { + if vote_account.activated_stake != 0 { + return Err(format!( + "Cannot close a vote account with active stake: {}", + vote_account_pubkey + ) + .into()); + } + } + + let latest_blockhash = rpc_client.get_latest_blockhash()?; + let withdraw_authority = config.signers[withdraw_authority]; + let fee_payer = config.signers[fee_payer]; + + let current_balance = rpc_client.get_balance(vote_account_pubkey)?; + + let ixs = vec![withdraw( + vote_account_pubkey, + &withdraw_authority.pubkey(), + current_balance, + destination_account_pubkey, + )] + .with_memo(memo); + + let message = Message::new(&ixs, Some(&fee_payer.pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, latest_blockhash)?; + check_account_for_fee_with_commitment( + rpc_client, + &tx.message.account_keys[0], + &tx.message, +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) config.commitment, )?; - let result = rpc_client.send_and_confirm_transaction_with_spinner(&transaction); + let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) } @@ -942,7 +1475,12 @@ mod tests { use { super::*, crate::{clap_app::get_clap_app, cli::parse_command}, - solana_sdk::signature::{read_keypair_file, write_keypair, Keypair, Signer}, + solana_client::blockhash_query, + solana_sdk::{ + hash::Hash, + signature::{read_keypair_file, write_keypair, Keypair, Signer}, + signer::presigner::Presigner, + }, tempfile::NamedTempFile, }; @@ -960,12 +1498,19 @@ mod tests { let keypair2 = Keypair::new(); let pubkey2 = keypair2.pubkey(); let pubkey2_string = pubkey2.to_string(); + let sig2 = keypair2.sign_message(&[0u8]); + let signer2 = format!("{}={}", keypair2.pubkey(), sig2); let default_keypair = Keypair::new(); let (default_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); let default_signer = DefaultSigner::new("", &default_keypair_file); + let blockhash = Hash::default(); + let blockhash_string = format!("{}", blockhash); + let nonce_account = Pubkey::new_unique(); + + // Test VoteAuthorize SubCommand let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", "vote-authorize-voter", @@ -980,7 +1525,13 @@ mod tests { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, vote_authorize: VoteAuthorize::Voter, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 0, new_authorized: None, }, @@ -1006,7 +1557,47 @@ mod tests { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, vote_authorize: VoteAuthorize::Voter, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + authorized: 1, + new_authorized: None, + }, + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&authorized_keypair_file).unwrap().into(), + ], + } + ); + + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair_file, + &pubkey2_string, + "--blockhash", + &blockhash_string, + "--sign-only", + ]); + assert_eq!( + parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::VoteAuthorize { + vote_account_pubkey: pubkey, + new_authorized_pubkey: pubkey2, + vote_authorize: VoteAuthorize::Voter, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::None(blockhash), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 1, new_authorized: None, }, @@ -1017,6 +1608,55 @@ mod tests { } ); + let authorized_sig = authorized_keypair.sign_message(&[0u8]); + let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig); + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair.pubkey().to_string(), + &pubkey2_string, + "--blockhash", + &blockhash_string, + "--signer", + &authorized_signer, + "--signer", + &signer2, + "--fee-payer", + &pubkey2_string, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]); + assert_eq!( + parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::VoteAuthorize { + vote_account_pubkey: pubkey, + new_authorized_pubkey: pubkey2, + vote_authorize: VoteAuthorize::Voter, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::FeeCalculator( + blockhash_query::Source::NonceAccount(nonce_account), + blockhash + ), + nonce_account: Some(nonce_account), + nonce_authority: 0, + memo: None, + fee_payer: 0, + authorized: 1, + new_authorized: None, + }, + signers: vec![ + Presigner::new(&pubkey2, &sig2).into(), + Presigner::new(&authorized_keypair.pubkey(), &authorized_sig).into(), + ], + } + ); + + // Test checked VoteAuthorize SubCommand let (voter_keypair_file, mut tmp_file) = make_tmp_file(); let voter_keypair = Keypair::new(); write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap(); @@ -1035,7 +1675,13 @@ mod tests { vote_account_pubkey: pubkey, new_authorized_pubkey: voter_keypair.pubkey(), vote_authorize: VoteAuthorize::Voter, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 0, new_authorized: Some(1), }, @@ -1060,7 +1706,13 @@ mod tests { vote_account_pubkey: pubkey, new_authorized_pubkey: voter_keypair.pubkey(), vote_authorize: VoteAuthorize::Voter, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 1, new_authorized: Some(2), }, @@ -1081,14 +1733,15 @@ mod tests { ]); assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err()); - let (keypair_file, mut tmp_file) = make_tmp_file(); - let keypair = Keypair::new(); - write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); // Test CreateVoteAccount SubCommand let (identity_keypair_file, mut tmp_file) = make_tmp_file(); let identity_keypair = Keypair::new(); let authorized_withdrawer = Keypair::new().pubkey(); write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap(); + let (keypair_file, mut tmp_file) = make_tmp_file(); + let keypair = Keypair::new(); + write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); + let test_create_vote_account = test_commands.clone().get_matches_from(vec![ "test", "create-vote-account", @@ -1108,20 +1761,22 @@ mod tests { authorized_voter: None, authorized_withdrawer, commission: 10, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), - Box::new(keypair), + read_keypair_file(&keypair_file).unwrap().into(), read_keypair_file(&identity_keypair_file).unwrap().into(), ], } ); - let (keypair_file, mut tmp_file) = make_tmp_file(); - let keypair = Keypair::new(); - write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); - let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![ "test", "create-vote-account", @@ -1139,16 +1794,115 @@ mod tests { authorized_voter: None, authorized_withdrawer, commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), - Box::new(keypair), + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&identity_keypair_file).unwrap().into(), + ], + } + ); + + let test_create_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--commission", + "10", + "--blockhash", + &blockhash_string, + "--sign-only", + "--fee-payer", + &default_keypair.pubkey().to_string(), + ]); + assert_eq!( + parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::None(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }, + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&keypair_file).unwrap().into(), read_keypair_file(&identity_keypair_file).unwrap().into(), ], } ); + let identity_sig = identity_keypair.sign_message(&[0u8]); + let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig); + let test_create_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair.pubkey().to_string(), + &authorized_withdrawer.to_string(), + "--commission", + "10", + "--blockhash", + &blockhash_string, + "--signer", + &identity_signer, + "--signer", + &signer2, + "--fee-payer", + &default_keypair_file, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]); + assert_eq!( + parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::FeeCalculator( + blockhash_query::Source::NonceAccount(nonce_account), + blockhash + ), + nonce_account: Some(nonce_account), + nonce_authority: 3, + memo: None, + fee_payer: 0, + }, + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&keypair_file).unwrap().into(), + Presigner::new(&identity_keypair.pubkey(), &identity_sig).into(), + Presigner::new(&pubkey2, &sig2).into(), + ], + } + ); + // test init with an authed voter let authed = solana_sdk::pubkey::new_rand(); let (keypair_file, mut tmp_file) = make_tmp_file(); @@ -1174,7 +1928,13 @@ mod tests { authorized_voter: Some(authed), authorized_withdrawer, commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), @@ -1206,11 +1966,17 @@ mod tests { authorized_voter: None, authorized_withdrawer: identity_keypair.pubkey(), commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), - Box::new(keypair), + read_keypair_file(&keypair_file).unwrap().into(), read_keypair_file(&identity_keypair_file).unwrap().into(), ], } @@ -1230,7 +1996,13 @@ mod tests { vote_account_pubkey: pubkey, new_identity_account: 2, withdraw_authority: 1, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), @@ -1254,7 +2026,13 @@ mod tests { vote_account_pubkey: pubkey, commission: 42, withdraw_authority: 1, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), @@ -1279,7 +2057,13 @@ mod tests { destination_account_pubkey: pubkey, withdraw_authority: 0, withdraw_amount: SpendAmount::Some(42_000_000_000), + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } @@ -1301,7 +2085,13 @@ mod tests { destination_account_pubkey: pubkey, withdraw_authority: 0, withdraw_amount: SpendAmount::All, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } @@ -1328,7 +2118,140 @@ mod tests { destination_account_pubkey: pubkey, withdraw_authority: 1, withdraw_amount: SpendAmount::Some(42_000_000_000), + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }, + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&withdraw_authority_file).unwrap().into() + ], + } + ); +<<<<<<< HEAD +======= + + // Test WithdrawFromVoteAccount subcommand with offline authority + let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "withdraw-from-vote-account", + &keypair.pubkey().to_string(), + &pubkey_string, + "42", + "--authorized-withdrawer", + &withdraw_authority_file, + "--blockhash", + &blockhash_string, + "--sign-only", + "--fee-payer", + &withdraw_authority_file, + ]); + assert_eq!( + parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey: keypair.pubkey(), + destination_account_pubkey: pubkey, + withdraw_authority: 0, + withdraw_amount: SpendAmount::Some(42_000_000_000), + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::None(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }, + signers: vec![read_keypair_file(&withdraw_authority_file).unwrap().into()], + } + ); + + let authorized_sig = withdraw_authority.sign_message(&[0u8]); + let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig); + let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "withdraw-from-vote-account", + &keypair.pubkey().to_string(), + &pubkey_string, + "42", + "--authorized-withdrawer", + &withdraw_authority.pubkey().to_string(), + "--blockhash", + &blockhash_string, + "--signer", + &authorized_signer, + "--fee-payer", + &withdraw_authority.pubkey().to_string(), + ]); + assert_eq!( + parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey: keypair.pubkey(), + destination_account_pubkey: pubkey, + withdraw_authority: 0, + withdraw_amount: SpendAmount::Some(42_000_000_000), + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::FeeCalculator( + blockhash_query::Source::Cluster, + blockhash + ), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }, + signers: vec![Presigner::new(&withdraw_authority.pubkey(), &authorized_sig).into(),], + } + ); + + // Test CloseVoteAccount subcommand + let test_close_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "close-vote-account", + &keypair_file, + &pubkey_string, + ]); + assert_eq!( + parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::CloseVoteAccount { + vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(), + destination_account_pubkey: pubkey, + withdraw_authority: 0, + memo: None, + fee_payer: 0, + }, + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], + } + ); + + // Test CloseVoteAccount subcommand with authority + let withdraw_authority = Keypair::new(); + let (withdraw_authority_file, mut tmp_file) = make_tmp_file(); + write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap(); + let test_close_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "close-vote-account", + &keypair_file, + &pubkey_string, + "--authorized-withdrawer", + &withdraw_authority_file, + ]); + assert_eq!( + parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::CloseVoteAccount { + vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(), + destination_account_pubkey: pubkey, + withdraw_authority: 1, memo: None, + fee_payer: 0, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), @@ -1336,5 +2259,6 @@ mod tests { ], } ); +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } } diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index e9d039ad407427..6d4632cc7d99f5 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -58,7 +58,13 @@ fn test_stake_delegation_force() { authorized_voter: None, authorized_withdrawer, commission: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }; process_command(&config).unwrap(); diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index c0e6cb51cd6a41..91565673a3b8f1 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -4,6 +4,7 @@ use { spend_utils::SpendAmount, test_utils::check_recent_balance, }, + solana_cli_output::{parse_sign_only_reply_string, OutputFormat}, solana_client::{ blockhash_query::{self, BlockhashQuery}, rpc_client::RpcClient, @@ -13,7 +14,7 @@ use { solana_sdk::{ account_utils::StateMut, commitment_config::CommitmentConfig, - signature::{Keypair, Signer}, + signature::{Keypair, NullSigner, Signer}, }, solana_streamer::socket::SocketAddrSpace, solana_vote_program::vote_state::{VoteAuthorize, VoteState, VoteStateVersions}, @@ -49,7 +50,13 @@ fn test_vote_authorize_and_withdraw() { authorized_voter: None, authorized_withdrawer: config.signers[0].pubkey(), commission: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }; process_command(&config).unwrap(); let vote_account = rpc_client @@ -93,7 +100,13 @@ fn test_vote_authorize_and_withdraw() { vote_account_pubkey, new_authorized_pubkey: first_withdraw_authority.pubkey(), vote_authorize: VoteAuthorize::Withdrawer, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 0, new_authorized: None, }; @@ -112,7 +125,13 @@ fn test_vote_authorize_and_withdraw() { vote_account_pubkey, new_authorized_pubkey: withdraw_authority.pubkey(), vote_authorize: VoteAuthorize::Withdrawer, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 1, new_authorized: Some(1), }; @@ -126,7 +145,13 @@ fn test_vote_authorize_and_withdraw() { vote_account_pubkey, new_authorized_pubkey: withdraw_authority.pubkey(), vote_authorize: VoteAuthorize::Withdrawer, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, authorized: 1, new_authorized: Some(2), }; @@ -146,7 +171,13 @@ fn test_vote_authorize_and_withdraw() { withdraw_authority: 1, withdraw_amount: SpendAmount::Some(100), destination_account_pubkey: destination_account, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, memo: None, + fee_payer: 0, }; process_command(&config).unwrap(); check_recent_balance(expected_balance - 100, &rpc_client, &vote_account_pubkey); @@ -159,7 +190,304 @@ fn test_vote_authorize_and_withdraw() { vote_account_pubkey, new_identity_account: 2, withdraw_authority: 1, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + process_command(&config).unwrap(); +<<<<<<< HEAD +======= + + // Close vote account + let destination_account = solana_sdk::pubkey::new_rand(); // Send withdrawal to new account to make balance check easy + config.signers = vec![&default_signer, &withdraw_authority]; + config.command = CliCommand::CloseVoteAccount { + vote_account_pubkey, + withdraw_authority: 1, + destination_account_pubkey: destination_account, memo: None, + fee_payer: 0, }; process_command(&config).unwrap(); + check_recent_balance(0, &rpc_client, &vote_account_pubkey); + check_recent_balance(expected_balance, &rpc_client, &destination_account); +>>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) +} + +#[test] +fn test_offline_vote_authorize_and_withdraw() { + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let faucet_addr = run_local_faucet(mint_keypair, None); + let test_validator = + TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified); + + let rpc_client = + RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed()); + let default_signer = Keypair::new(); + + let mut config_payer = CliConfig::recent_for_tests(); + config_payer.json_rpc_url = test_validator.rpc_url(); + config_payer.signers = vec![&default_signer]; + + let mut config_offline = CliConfig::recent_for_tests(); + config_offline.json_rpc_url = String::default(); + config_offline.command = CliCommand::ClusterVersion; + let offline_keypair = Keypair::new(); + config_offline.signers = vec![&offline_keypair]; + // Verify that we cannot reach the cluster + process_command(&config_offline).unwrap_err(); + + request_and_confirm_airdrop( + &rpc_client, + &config_payer, + &config_payer.signers[0].pubkey(), + 100_000, + ) + .unwrap(); + check_recent_balance(100_000, &rpc_client, &config_payer.signers[0].pubkey()); + + request_and_confirm_airdrop( + &rpc_client, + &config_offline, + &config_offline.signers[0].pubkey(), + 100_000, + ) + .unwrap(); + check_recent_balance(100_000, &rpc_client, &config_offline.signers[0].pubkey()); + + // Create vote account with specific withdrawer + let vote_account_keypair = Keypair::new(); + let vote_account_pubkey = vote_account_keypair.pubkey(); + config_payer.signers = vec![&default_signer, &vote_account_keypair]; + config_payer.command = CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 0, + authorized_voter: None, + authorized_withdrawer: offline_keypair.pubkey(), + commission: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + process_command(&config_payer).unwrap(); + let vote_account = rpc_client + .get_account(&vote_account_keypair.pubkey()) + .unwrap(); + let vote_state: VoteStateVersions = vote_account.state().unwrap(); + let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer; + assert_eq!(authorized_withdrawer, offline_keypair.pubkey()); + let expected_balance = rpc_client + .get_minimum_balance_for_rent_exemption(VoteState::size_of()) + .unwrap() + .max(1); + check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey); + + // Transfer in some more SOL + config_payer.signers = vec![&default_signer]; + config_payer.command = CliCommand::Transfer { + amount: SpendAmount::Some(1_000), + to: vote_account_pubkey, + from: 0, + sign_only: false, + dump_transaction_message: false, + allow_unfunded_recipient: true, + no_wait: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, + }; + process_command(&config_payer).unwrap(); + let expected_balance = expected_balance + 1_000; + check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey); + + // Authorize vote account withdrawal to another signer, offline + let withdraw_authority = Keypair::new(); + let blockhash = rpc_client.get_latest_blockhash().unwrap(); + config_offline.command = CliCommand::VoteAuthorize { + vote_account_pubkey, + new_authorized_pubkey: withdraw_authority.pubkey(), + vote_authorize: VoteAuthorize::Withdrawer, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::None(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + authorized: 0, + new_authorized: None, + }; + config_offline.output_format = OutputFormat::JsonCompact; + let sig_response = process_command(&config_offline).unwrap(); + let sign_only = parse_sign_only_reply_string(&sig_response); + assert!(sign_only.has_all_signers()); + let offline_presigner = sign_only + .presigner_of(&config_offline.signers[0].pubkey()) + .unwrap(); + config_payer.signers = vec![&offline_presigner]; + config_payer.command = CliCommand::VoteAuthorize { + vote_account_pubkey, + new_authorized_pubkey: withdraw_authority.pubkey(), + vote_authorize: VoteAuthorize::Withdrawer, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + authorized: 0, + new_authorized: None, + }; + process_command(&config_payer).unwrap(); + let vote_account = rpc_client + .get_account(&vote_account_keypair.pubkey()) + .unwrap(); + let vote_state: VoteStateVersions = vote_account.state().unwrap(); + let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer; + assert_eq!(authorized_withdrawer, withdraw_authority.pubkey()); + + // Withdraw from vote account offline + let destination_account = solana_sdk::pubkey::new_rand(); // Send withdrawal to new account to make balance check easy + let blockhash = rpc_client.get_latest_blockhash().unwrap(); + let fee_payer_null_signer = NullSigner::new(&default_signer.pubkey()); + config_offline.signers = vec![&fee_payer_null_signer, &withdraw_authority]; + config_offline.command = CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey, + withdraw_authority: 1, + withdraw_amount: SpendAmount::Some(100), + destination_account_pubkey: destination_account, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::None(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + config_offline.output_format = OutputFormat::JsonCompact; + let sig_response = process_command(&config_offline).unwrap(); + let sign_only = parse_sign_only_reply_string(&sig_response); + let offline_presigner = sign_only + .presigner_of(&config_offline.signers[1].pubkey()) + .unwrap(); + config_payer.signers = vec![&default_signer, &offline_presigner]; + config_payer.command = CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey, + withdraw_authority: 1, + withdraw_amount: SpendAmount::Some(100), + destination_account_pubkey: destination_account, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + process_command(&config_payer).unwrap(); + let expected_balance = expected_balance - 100; + check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey); + check_recent_balance(100, &rpc_client, &destination_account); + + // Re-assign validator identity offline + let blockhash = rpc_client.get_latest_blockhash().unwrap(); + let new_identity_keypair = Keypair::new(); + let new_identity_null_signer = NullSigner::new(&new_identity_keypair.pubkey()); + config_offline.signers = vec![ + &fee_payer_null_signer, + &withdraw_authority, + &new_identity_null_signer, + ]; + config_offline.command = CliCommand::VoteUpdateValidator { + vote_account_pubkey, + new_identity_account: 2, + withdraw_authority: 1, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::None(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + process_command(&config_offline).unwrap(); + config_offline.output_format = OutputFormat::JsonCompact; + let sig_response = process_command(&config_offline).unwrap(); + let sign_only = parse_sign_only_reply_string(&sig_response); + let offline_presigner = sign_only + .presigner_of(&config_offline.signers[1].pubkey()) + .unwrap(); + config_payer.signers = vec![&default_signer, &offline_presigner, &new_identity_keypair]; + config_payer.command = CliCommand::VoteUpdateValidator { + vote_account_pubkey, + new_identity_account: 2, + withdraw_authority: 1, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + process_command(&config_payer).unwrap(); + + // Close vote account offline. Must use WithdrawFromVoteAccount and specify amount, since + // CloseVoteAccount requires RpcClient + let destination_account = solana_sdk::pubkey::new_rand(); // Send withdrawal to new account to make balance check easy + config_offline.signers = vec![&fee_payer_null_signer, &withdraw_authority]; + config_offline.command = CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey, + withdraw_authority: 1, + withdraw_amount: SpendAmount::Some(expected_balance), + destination_account_pubkey: destination_account, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::None(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + process_command(&config_offline).unwrap(); + config_offline.output_format = OutputFormat::JsonCompact; + let sig_response = process_command(&config_offline).unwrap(); + let sign_only = parse_sign_only_reply_string(&sig_response); + let offline_presigner = sign_only + .presigner_of(&config_offline.signers[1].pubkey()) + .unwrap(); + config_payer.signers = vec![&default_signer, &offline_presigner]; + config_payer.command = CliCommand::WithdrawFromVoteAccount { + vote_account_pubkey, + withdraw_authority: 1, + withdraw_amount: SpendAmount::Some(expected_balance), + destination_account_pubkey: destination_account, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + }; + let result = process_command(&config_payer).unwrap(); + println!("{:?}", result); + check_recent_balance(0, &rpc_client, &vote_account_pubkey); + println!("what"); + check_recent_balance(expected_balance, &rpc_client, &destination_account); } diff --git a/docs/src/offline-signing.md b/docs/src/offline-signing.md index 1b4075552d080a..afdf2651ef72f3 100644 --- a/docs/src/offline-signing.md +++ b/docs/src/offline-signing.md @@ -19,14 +19,26 @@ transaction. At present, the following commands support offline signing: - [`create-stake-account`](cli/usage.md#solana-create-stake-account) +- [`create-stake-account-checked`](cli/usage.md#solana-create-stake-account-checked) - [`deactivate-stake`](cli/usage.md#solana-deactivate-stake) - [`delegate-stake`](cli/usage.md#solana-delegate-stake) - [`split-stake`](cli/usage.md#solana-split-stake) - [`stake-authorize`](cli/usage.md#solana-stake-authorize) +- [`stake-authorize-checked`](cli/usage.md#solana-stake-authorize-checked) - [`stake-set-lockup`](cli/usage.md#solana-stake-set-lockup) +- [`stake-set-lockup-checked`](cli/usage.md#solana-stake-set-lockup-checked) - [`transfer`](cli/usage.md#solana-transfer) - [`withdraw-stake`](cli/usage.md#solana-withdraw-stake) +- [`create-vote-account`](cli/usage.md#solana-create-vote-account) +- [`vote-authorize-voter`](cli/usage.md#solana-vote-authorize-voter) +- [`vote-authorize-voter-checked`](cli/usage.md#solana-vote-authorize-voter-checked) +- [`vote-authorize-withdrawer`](cli/usage.md#solana-vote-authorize-withdrawer) +- [`vote-authorize-withdrawer-checked`](cli/usage.md#solana-vote-authorize-withdrawer-checked) +- [`vote-update-commission`](cli/usage.md#solana-vote-update-commission) +- [`vote-update-validator`](cli/usage.md#solana-vote-update-validator) +- [`withdraw-from-vote-account`](cli/usage.md#solana-withdraw-from-vote-account) + ## Signing Transactions Offline To sign a transaction offline, pass the following arguments on the command line From df81cd79d1a3d2f605f1860ac9b357c3d31cc239 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 6 Dec 2021 17:01:38 -0700 Subject: [PATCH 2/2] Fix conflicts --- cli/src/cli.rs | 28 ----- cli/src/vote.rs | 292 +++------------------------------------------- cli/tests/vote.rs | 23 +--- 3 files changed, 19 insertions(+), 324 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index b913ee1c70d9f2..0bea8252a92e92 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -324,16 +324,6 @@ pub enum CliCommand { memo: Option, fee_payer: SignerIndex, }, -<<<<<<< HEAD -======= - CloseVoteAccount { - vote_account_pubkey: Pubkey, - destination_account_pubkey: Pubkey, - withdraw_authority: SignerIndex, - memo: Option, - fee_payer: SignerIndex, - }, ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) VoteAuthorize { vote_account_pubkey: Pubkey, new_authorized_pubkey: Pubkey, @@ -1477,24 +1467,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { memo.as_ref(), *fee_payer, ), -<<<<<<< HEAD -======= - CliCommand::CloseVoteAccount { - vote_account_pubkey, - withdraw_authority, - destination_account_pubkey, - memo, - fee_payer, - } => process_close_vote_account( - &rpc_client, - config, - vote_account_pubkey, - *withdraw_authority, - destination_account_pubkey, - memo.as_ref(), - *fee_payer, - ), ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) CliCommand::VoteAuthorize { vote_account_pubkey, new_authorized_pubkey, diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 463ea41e01919d..7f8edca235af2f 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -24,15 +24,7 @@ use { return_signers_with_config, CliEpochVotingHistory, CliLockout, CliVoteAccount, ReturnSignersConfig, }, - solana_client::{ - blockhash_query::BlockhashQuery, nonce_utils, rpc_client::RpcClient, - rpc_config::RpcGetVoteAccountsConfig, - }, -<<<<<<< HEAD - solana_cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount}, - solana_client::rpc_client::RpcClient, -======= ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) + solana_client::{blockhash_query::BlockhashQuery, nonce_utils, rpc_client::RpcClient}, solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_sdk::{ account::Account, commitment_config::CommitmentConfig, message::Message, @@ -374,45 +366,12 @@ impl VoteSubCommands for App<'_, '_> { .validator(is_valid_signer) .help("Authorized withdrawer [default: cli config keypair]"), ) -<<<<<<< HEAD - .arg(memo_arg()) -======= .offline_args() .nonce_args(false) .arg(fee_payer_arg()) .arg(memo_arg() ) ) - .subcommand( - SubCommand::with_name("close-vote-account") - .about("Close a vote account and withdraw all funds remaining") - .arg( - pubkey!(Arg::with_name("vote_account_pubkey") - .index(1) - .value_name("VOTE_ACCOUNT_ADDRESS") - .required(true), - "Vote account to be closed. "), - ) - .arg( - pubkey!(Arg::with_name("destination_account_pubkey") - .index(2) - .value_name("RECIPIENT_ADDRESS") - .required(true), - "The recipient of all withdrawn SOL. "), - ) - .arg( - Arg::with_name("authorized_withdrawer") - .long("authorized-withdrawer") - .value_name("AUTHORIZED_KEYPAIR") - .takes_value(true) - .validator(is_valid_signer) - .help("Authorized withdrawer [default: cli config keypair]"), - ) - .arg(fee_payer_arg()) - .arg(memo_arg() - ) ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) - ) } } @@ -701,43 +660,7 @@ pub fn parse_withdraw_from_vote_account( }) } -<<<<<<< HEAD -======= -pub fn parse_close_vote_account( - matches: &ArgMatches<'_>, - default_signer: &DefaultSigner, - wallet_manager: &mut Option>, -) -> Result { - let vote_account_pubkey = - pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); - let destination_account_pubkey = - pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap(); - - let (withdraw_authority, withdraw_authority_pubkey) = - signer_of(matches, "authorized_withdrawer", wallet_manager)?; - let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; - - let signer_info = default_signer.generate_unique_signers( - vec![fee_payer, withdraw_authority], - matches, - wallet_manager, - )?; - let memo = matches.value_of(MEMO_ARG.name).map(String::from); - - Ok(CliCommandInfo { - command: CliCommand::CloseVoteAccount { - vote_account_pubkey, - destination_account_pubkey, - withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(), - memo, - fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), - }, - signers: signer_info.signers, - }) -} - #[allow(clippy::too_many_arguments)] ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) pub fn process_create_vote_account( rpc_client: &RpcClient, config: &CliConfig, @@ -821,23 +744,14 @@ pub fn process_create_vote_account( } }; - let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; + let (recent_blockhash, fee_calculator) = + blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?; -<<<<<<< HEAD - let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - - let (message, _) = resolve_spend_tx_and_check_account_balance( -======= let (message, _) = resolve_spend_tx_and_check_account_balances( ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) rpc_client, sign_only, amount, -<<<<<<< HEAD &fee_calculator, -======= - &recent_blockhash, ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) &config.signers[0].pubkey(), &fee_payer.pubkey(), build_message, @@ -872,11 +786,6 @@ pub fn process_create_vote_account( } let mut tx = Transaction::new_unsigned(message); -<<<<<<< HEAD - tx.try_sign(&config.signers, recent_blockhash)?; - let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); - log_instruction_custom_error::(result, config) -======= if sign_only { tx.try_partial_sign(&config.signers, recent_blockhash)?; return_signers_with_config( @@ -891,7 +800,6 @@ pub fn process_create_vote_account( let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) } ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } #[allow(clippy::too_many_arguments)] @@ -953,10 +861,6 @@ pub fn process_vote_authorize( } } -<<<<<<< HEAD - let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; -======= ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) let vote_ix = if new_authorized_signer.is_some() { vote_instruction::authorize_checked( vote_account_pubkey, // vote account to update @@ -974,7 +878,8 @@ pub fn process_vote_authorize( }; let ixs = vec![vote_ix].with_memo(memo); - let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; + let (recent_blockhash, fee_calculator) = + blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?; let nonce_authority = config.signers[nonce_authority]; let fee_payer = config.signers[fee_payer]; @@ -990,18 +895,6 @@ pub fn process_vote_authorize( Message::new(&ixs, Some(&fee_payer.pubkey())) }; let mut tx = Transaction::new_unsigned(message); -<<<<<<< HEAD - tx.try_sign(&config.signers, recent_blockhash)?; - check_account_for_fee_with_commitment( - rpc_client, - &config.signers[0].pubkey(), - &fee_calculator, - &tx.message, - config.commitment, - )?; - let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); - log_instruction_custom_error::(result, config) -======= if sign_only { tx.try_partial_sign(&config.signers, recent_blockhash)?; @@ -1025,13 +918,13 @@ pub fn process_vote_authorize( check_account_for_fee_with_commitment( rpc_client, &config.signers[0].pubkey(), + &fee_calculator, &tx.message, config.commitment, )?; let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) } ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } #[allow(clippy::too_many_arguments)] @@ -1056,11 +949,8 @@ pub fn process_vote_update_validator( (vote_account_pubkey, "vote_account_pubkey".to_string()), (&new_identity_pubkey, "new_identity_account".to_string()), )?; -<<<<<<< HEAD - let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; -======= - let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) + let (recent_blockhash, fee_calculator) = + blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?; let ixs = vec![vote_instruction::update_validator_identity( vote_account_pubkey, &authorized_withdrawer.pubkey(), @@ -1081,18 +971,6 @@ pub fn process_vote_update_validator( Message::new(&ixs, Some(&fee_payer.pubkey())) }; let mut tx = Transaction::new_unsigned(message); -<<<<<<< HEAD - tx.try_sign(&config.signers, recent_blockhash)?; - check_account_for_fee_with_commitment( - rpc_client, - &config.signers[0].pubkey(), - &fee_calculator, - &tx.message, - config.commitment, - )?; - let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); - log_instruction_custom_error::(result, config) -======= if sign_only { tx.try_partial_sign(&config.signers, recent_blockhash)?; @@ -1116,13 +994,13 @@ pub fn process_vote_update_validator( check_account_for_fee_with_commitment( rpc_client, &config.signers[0].pubkey(), + &fee_calculator, &tx.message, config.commitment, )?; let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) } ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } #[allow(clippy::too_many_arguments)] @@ -1141,11 +1019,8 @@ pub fn process_vote_update_commission( fee_payer: SignerIndex, ) -> ProcessResult { let authorized_withdrawer = config.signers[withdraw_authority]; -<<<<<<< HEAD - let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; -======= - let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) + let (recent_blockhash, fee_calculator) = + blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?; let ixs = vec![vote_instruction::update_commission( vote_account_pubkey, &authorized_withdrawer.pubkey(), @@ -1166,18 +1041,6 @@ pub fn process_vote_update_commission( Message::new(&ixs, Some(&fee_payer.pubkey())) }; let mut tx = Transaction::new_unsigned(message); -<<<<<<< HEAD - tx.try_sign(&config.signers, recent_blockhash)?; - check_account_for_fee_with_commitment( - rpc_client, - &config.signers[0].pubkey(), - &fee_calculator, - &tx.message, - config.commitment, - )?; - let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); - log_instruction_custom_error::(result, config) -======= if sign_only { tx.try_partial_sign(&config.signers, recent_blockhash)?; return_signers_with_config( @@ -1200,13 +1063,13 @@ pub fn process_vote_update_commission( check_account_for_fee_with_commitment( rpc_client, &config.signers[0].pubkey(), + &fee_calculator, &tx.message, config.commitment, )?; let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); log_instruction_custom_error::(result, config) } ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } fn get_vote_account( @@ -1313,12 +1176,9 @@ pub fn process_withdraw_from_vote_account( memo: Option<&String>, fee_payer: SignerIndex, ) -> ProcessResult { -<<<<<<< HEAD - let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; -======= ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) let withdraw_authority = config.signers[withdraw_authority]; - let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?; + let (recent_blockhash, fee_calculator) = + blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?; let fee_payer = config.signers[fee_payer]; let nonce_authority = config.signers[nonce_authority]; @@ -1348,7 +1208,7 @@ pub fn process_withdraw_from_vote_account( rpc_client, sign_only, withdraw_amount, - &recent_blockhash, + &fee_calculator, vote_account_pubkey, &fee_payer.pubkey(), build_message, @@ -1372,16 +1232,6 @@ pub fn process_withdraw_from_vote_account( let mut tx = Transaction::new_unsigned(message); -<<<<<<< HEAD - let message = Message::new(&ixs, Some(&config.signers[0].pubkey())); - let mut transaction = Transaction::new_unsigned(message); - transaction.try_sign(&config.signers, recent_blockhash)?; - check_account_for_fee_with_commitment( - rpc_client, - &config.signers[0].pubkey(), - &fee_calculator, - &transaction.message, -======= if sign_only { tx.try_partial_sign(&config.signers, recent_blockhash)?; return_signers_with_config( @@ -1404,6 +1254,7 @@ pub fn process_withdraw_from_vote_account( check_account_for_fee_with_commitment( rpc_client, &tx.message.account_keys[0], + &fee_calculator, &tx.message, config.commitment, )?; @@ -1412,64 +1263,6 @@ pub fn process_withdraw_from_vote_account( } } -pub fn process_close_vote_account( - rpc_client: &RpcClient, - config: &CliConfig, - vote_account_pubkey: &Pubkey, - withdraw_authority: SignerIndex, - destination_account_pubkey: &Pubkey, - memo: Option<&String>, - fee_payer: SignerIndex, -) -> ProcessResult { - let vote_account_status = - rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig { - vote_pubkey: Some(vote_account_pubkey.to_string()), - ..RpcGetVoteAccountsConfig::default() - })?; - - if let Some(vote_account) = vote_account_status - .current - .into_iter() - .chain(vote_account_status.delinquent.into_iter()) - .next() - { - if vote_account.activated_stake != 0 { - return Err(format!( - "Cannot close a vote account with active stake: {}", - vote_account_pubkey - ) - .into()); - } - } - - let latest_blockhash = rpc_client.get_latest_blockhash()?; - let withdraw_authority = config.signers[withdraw_authority]; - let fee_payer = config.signers[fee_payer]; - - let current_balance = rpc_client.get_balance(vote_account_pubkey)?; - - let ixs = vec![withdraw( - vote_account_pubkey, - &withdraw_authority.pubkey(), - current_balance, - destination_account_pubkey, - )] - .with_memo(memo); - - let message = Message::new(&ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&config.signers, latest_blockhash)?; - check_account_for_fee_with_commitment( - rpc_client, - &tx.message.account_keys[0], - &tx.message, ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) - config.commitment, - )?; - let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); - log_instruction_custom_error::(result, config) -} - #[cfg(test)] mod tests { use { @@ -2132,8 +1925,6 @@ mod tests { ], } ); -<<<<<<< HEAD -======= // Test WithdrawFromVoteAccount subcommand with offline authority let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![ @@ -2209,56 +2000,5 @@ mod tests { signers: vec![Presigner::new(&withdraw_authority.pubkey(), &authorized_sig).into(),], } ); - - // Test CloseVoteAccount subcommand - let test_close_vote_account = test_commands.clone().get_matches_from(vec![ - "test", - "close-vote-account", - &keypair_file, - &pubkey_string, - ]); - assert_eq!( - parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(), - CliCommandInfo { - command: CliCommand::CloseVoteAccount { - vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(), - destination_account_pubkey: pubkey, - withdraw_authority: 0, - memo: None, - fee_payer: 0, - }, - signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], - } - ); - - // Test CloseVoteAccount subcommand with authority - let withdraw_authority = Keypair::new(); - let (withdraw_authority_file, mut tmp_file) = make_tmp_file(); - write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap(); - let test_close_vote_account = test_commands.clone().get_matches_from(vec![ - "test", - "close-vote-account", - &keypair_file, - &pubkey_string, - "--authorized-withdrawer", - &withdraw_authority_file, - ]); - assert_eq!( - parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(), - CliCommandInfo { - command: CliCommand::CloseVoteAccount { - vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(), - destination_account_pubkey: pubkey, - withdraw_authority: 1, - memo: None, - fee_payer: 0, - }, - signers: vec![ - read_keypair_file(&default_keypair_file).unwrap().into(), - read_keypair_file(&withdraw_authority_file).unwrap().into() - ], - } - ); ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } } diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index 91565673a3b8f1..d71d2b9d367ca4 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -199,23 +199,6 @@ fn test_vote_authorize_and_withdraw() { fee_payer: 0, }; process_command(&config).unwrap(); -<<<<<<< HEAD -======= - - // Close vote account - let destination_account = solana_sdk::pubkey::new_rand(); // Send withdrawal to new account to make balance check easy - config.signers = vec![&default_signer, &withdraw_authority]; - config.command = CliCommand::CloseVoteAccount { - vote_account_pubkey, - withdraw_authority: 1, - destination_account_pubkey: destination_account, - memo: None, - fee_payer: 0, - }; - process_command(&config).unwrap(); - check_recent_balance(0, &rpc_client, &vote_account_pubkey); - check_recent_balance(expected_balance, &rpc_client, &destination_account); ->>>>>>> 873fe81bc (Add offline and fee-payer utilities to CLI vote module (#21579)) } #[test] @@ -316,7 +299,7 @@ fn test_offline_vote_authorize_and_withdraw() { // Authorize vote account withdrawal to another signer, offline let withdraw_authority = Keypair::new(); - let blockhash = rpc_client.get_latest_blockhash().unwrap(); + let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); config_offline.command = CliCommand::VoteAuthorize { vote_account_pubkey, new_authorized_pubkey: withdraw_authority.pubkey(), @@ -363,7 +346,7 @@ fn test_offline_vote_authorize_and_withdraw() { // Withdraw from vote account offline let destination_account = solana_sdk::pubkey::new_rand(); // Send withdrawal to new account to make balance check easy - let blockhash = rpc_client.get_latest_blockhash().unwrap(); + let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); let fee_payer_null_signer = NullSigner::new(&default_signer.pubkey()); config_offline.signers = vec![&fee_payer_null_signer, &withdraw_authority]; config_offline.command = CliCommand::WithdrawFromVoteAccount { @@ -405,7 +388,7 @@ fn test_offline_vote_authorize_and_withdraw() { check_recent_balance(100, &rpc_client, &destination_account); // Re-assign validator identity offline - let blockhash = rpc_client.get_latest_blockhash().unwrap(); + let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); let new_identity_keypair = Keypair::new(); let new_identity_null_signer = NullSigner::new(&new_identity_keypair.pubkey()); config_offline.signers = vec![