diff --git a/cli/src/cli.rs b/cli/src/cli.rs index baeebea557..79dc2c35ec 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -14,7 +14,7 @@ use log::*; use num_traits::FromPrimitive; use serde_json::{self, json, Value}; use solana_budget_program::budget_instruction::{self, BudgetError}; -use solana_clap_utils::{input_parsers::*, input_validators::*}; +use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant}; use solana_client::{client_error::ClientError, rpc_client::RpcClient}; #[cfg(not(test))] use solana_faucet::faucet::request_airdrop_transaction; @@ -51,6 +51,23 @@ use std::{ const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE +pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant { + name: "fee_payer", + long: "fee-payer", + help: "Specify the fee-payer account. This may be a keypair file, the ASK keyword \n\ + or the pubkey of an offline signer, provided an appropriate --signer argument \n\ + is also passed. Defaults to the client keypair.", +}; + +pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name(FEE_PAYER_ARG.name) + .long(FEE_PAYER_ARG.long) + .takes_value(true) + .value_name("KEYPAIR or PUBKEY") + .validator(is_pubkey_or_keypair_or_ask_keyword) + .help(FEE_PAYER_ARG.help) +} + #[derive(Debug)] pub struct KeypairEq(Keypair); @@ -264,6 +281,7 @@ pub enum CliCommand { blockhash_query: BlockhashQuery, nonce_account: Option, nonce_authority: Option, + fee_payer: Option, }, DelegateStake { stake_account_pubkey: Pubkey, @@ -275,6 +293,7 @@ pub enum CliCommand { blockhash_query: BlockhashQuery, nonce_account: Option, nonce_authority: Option, + fee_payer: Option, }, SplitStake { stake_account_pubkey: Pubkey, @@ -305,6 +324,7 @@ pub enum CliCommand { blockhash_query: BlockhashQuery, nonce_account: Option, nonce_authority: Option, + fee_payer: Option, }, WithdrawStake { stake_account_pubkey: Pubkey, @@ -1397,6 +1417,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { blockhash_query, nonce_account, ref nonce_authority, + ref fee_payer, } => process_deactivate_stake_account( &rpc_client, config, @@ -1407,6 +1428,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { blockhash_query, *nonce_account, nonce_authority.as_ref(), + fee_payer.as_ref(), ), CliCommand::DelegateStake { stake_account_pubkey, @@ -1418,6 +1440,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { blockhash_query, nonce_account, ref nonce_authority, + ref fee_payer, } => process_delegate_stake( &rpc_client, config, @@ -1430,6 +1453,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { blockhash_query, *nonce_account, nonce_authority.as_ref(), + fee_payer.as_ref(), ), CliCommand::SplitStake { stake_account_pubkey, @@ -1478,6 +1502,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { blockhash_query, nonce_account, ref nonce_authority, + ref fee_payer, } => process_stake_authorize( &rpc_client, config, @@ -1490,6 +1515,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { blockhash_query, *nonce_account, nonce_authority.as_ref(), + fee_payer.as_ref(), ), CliCommand::WithdrawStake { @@ -2797,6 +2823,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 770987d360..a94fd31f72 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1,9 +1,9 @@ use crate::{ cli::{ - build_balance_message, check_account_for_fee, check_unique_pubkeys, + build_balance_message, check_account_for_fee, check_unique_pubkeys, fee_payer_arg, log_instruction_custom_error, nonce_authority_arg, replace_signatures, required_lamports_from, return_signers, CliCommand, CliCommandInfo, CliConfig, CliError, - ProcessResult, SigningAuthority, + ProcessResult, SigningAuthority, FEE_PAYER_ARG, }, nonce::{check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG}, offline::*, @@ -175,6 +175,7 @@ impl StakeSubCommands for App<'_, '_> { .offline_args() .arg(nonce_arg()) .arg(nonce_authority_arg()) + .arg(fee_payer_arg()) ) .subcommand( SubCommand::with_name("stake-authorize-staker") @@ -201,6 +202,7 @@ impl StakeSubCommands for App<'_, '_> { .offline_args() .arg(nonce_arg()) .arg(nonce_authority_arg()) + .arg(fee_payer_arg()) ) .subcommand( SubCommand::with_name("stake-authorize-withdrawer") @@ -227,6 +229,7 @@ impl StakeSubCommands for App<'_, '_> { .offline_args() .arg(nonce_arg()) .arg(nonce_authority_arg()) + .arg(fee_payer_arg()) ) .subcommand( SubCommand::with_name("deactivate-stake") @@ -243,6 +246,7 @@ impl StakeSubCommands for App<'_, '_> { .offline_args() .arg(nonce_arg()) .arg(nonce_authority_arg()) + .arg(fee_payer_arg()) ) .subcommand( SubCommand::with_name("split-stake") @@ -407,6 +411,8 @@ pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result) -> Result, nonce_authority: Option<&SigningAuthority>, + fee_payer: Option<&SigningAuthority>, ) -> ProcessResult { check_unique_pubkeys( (stake_account_pubkey, "stake_account_pubkey".to_string()), @@ -679,11 +693,12 @@ pub fn process_stake_authorize( let (nonce_authority, nonce_authority_pubkey) = nonce_authority .map(|a| (a.keypair(), a.pubkey())) .unwrap_or((&config.keypair, config.keypair.pubkey())); + let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair); let mut tx = if let Some(nonce_account) = &nonce_account { Transaction::new_signed_with_nonce( ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority, authority], + Some(&fee_payer.pubkey()), + &[fee_payer, nonce_authority, authority], nonce_account, &nonce_authority.pubkey(), recent_blockhash, @@ -691,8 +706,8 @@ pub fn process_stake_authorize( } else { Transaction::new_signed_with_payer( ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair, authority], + Some(&fee_payer.pubkey()), + &[fee_payer, authority], recent_blockhash, ) }; @@ -717,6 +732,7 @@ pub fn process_stake_authorize( } } +#[allow(clippy::too_many_arguments)] pub fn process_deactivate_stake_account( rpc_client: &RpcClient, config: &CliConfig, @@ -727,6 +743,7 @@ pub fn process_deactivate_stake_account( blockhash_query: &BlockhashQuery, nonce_account: Option, nonce_authority: Option<&SigningAuthority>, + fee_payer: Option<&SigningAuthority>, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; @@ -740,11 +757,12 @@ pub fn process_deactivate_stake_account( let (nonce_authority, nonce_authority_pubkey) = nonce_authority .map(|a| (a.keypair(), a.pubkey())) .unwrap_or((&config.keypair, config.keypair.pubkey())); + let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair); let mut tx = if let Some(nonce_account) = &nonce_account { Transaction::new_signed_with_nonce( ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority, stake_authority], + Some(&fee_payer.pubkey()), + &[fee_payer, nonce_authority, stake_authority], nonce_account, &nonce_authority.pubkey(), recent_blockhash, @@ -752,8 +770,8 @@ pub fn process_deactivate_stake_account( } else { Transaction::new_signed_with_payer( ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair, stake_authority], + Some(&fee_payer.pubkey()), + &[fee_payer, stake_authority], recent_blockhash, ) }; @@ -1090,6 +1108,7 @@ pub fn process_delegate_stake( blockhash_query: &BlockhashQuery, nonce_account: Option, nonce_authority: Option<&SigningAuthority>, + fee_payer: Option<&SigningAuthority>, ) -> ProcessResult { check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), @@ -1150,11 +1169,12 @@ pub fn process_delegate_stake( let (nonce_authority, nonce_authority_pubkey) = nonce_authority .map(|a| (a.keypair(), a.pubkey())) .unwrap_or((&config.keypair, config.keypair.pubkey())); + let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair); let mut tx = if let Some(nonce_account) = &nonce_account { Transaction::new_signed_with_nonce( ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority, stake_authority], + Some(&fee_payer.pubkey()), + &[fee_payer, nonce_authority, stake_authority], nonce_account, &nonce_authority.pubkey(), recent_blockhash, @@ -1162,8 +1182,8 @@ pub fn process_delegate_stake( } else { Transaction::new_signed_with_payer( ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair, stake_authority], + Some(&fee_payer.pubkey()), + &[fee_payer, stake_authority], recent_blockhash, ) }; @@ -1237,6 +1257,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1263,6 +1284,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1292,6 +1314,7 @@ mod tests { blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1323,6 +1346,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1356,6 +1380,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1382,6 +1407,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1417,6 +1443,72 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account_pubkey), nonce_authority: Some(nonce_authority_keypair.into()), + fee_payer: None, + }, + require_keypair: true + } + ); + // Test Authorize Subcommand w/ fee-payer + let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file(); + let fee_payer_keypair = Keypair::new(); + write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap(); + let fee_payer_pubkey = fee_payer_keypair.pubkey(); + let fee_payer_string = fee_payer_pubkey.to_string(); + let test_authorize = test_commands.clone().get_matches_from(vec![ + "test", + &subcommand, + &stake_account_string, + &stake_account_string, + "--fee-payer", + &fee_payer_keypair_file, + ]); + assert_eq!( + parse_command(&test_authorize).unwrap(), + CliCommandInfo { + command: CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: stake_account_pubkey, + stake_authorize, + authority: None, + sign_only: false, + signers: None, + blockhash_query: BlockhashQuery::All, + nonce_account: None, + nonce_authority: None, + fee_payer: Some(read_keypair_file(&fee_payer_keypair_file).unwrap().into()), + }, + require_keypair: true + } + ); + // Test Authorize Subcommand w/ absentee fee-payer + let sig = fee_payer_keypair.sign_message(&[0u8]); + let signer = format!("{}={}", fee_payer_string, sig); + let test_authorize = test_commands.clone().get_matches_from(vec![ + "test", + &subcommand, + &stake_account_string, + &stake_account_string, + "--fee-payer", + &fee_payer_string, + "--blockhash", + &blockhash_string, + "--signer", + &signer, + ]); + assert_eq!( + parse_command(&test_authorize).unwrap(), + CliCommandInfo { + command: CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: stake_account_pubkey, + stake_authorize, + authority: None, + sign_only: false, + signers: Some(vec![(fee_payer_pubkey, sig)]), + blockhash_query: BlockhashQuery::FeeCalculator(blockhash), + nonce_account: None, + nonce_authority: None, + fee_payer: Some(fee_payer_pubkey.into()), }, require_keypair: true } @@ -1537,6 +1629,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1570,6 +1663,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1596,6 +1690,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1625,6 +1720,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1652,6 +1748,7 @@ mod tests { blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1684,6 +1781,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: false } @@ -1718,6 +1816,74 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, + }, + require_keypair: false + } + ); + + // Test Delegate Subcommand w/ fee-payer + let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file(); + let fee_payer_keypair = Keypair::new(); + write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap(); + let fee_payer_pubkey = fee_payer_keypair.pubkey(); + let fee_payer_string = fee_payer_pubkey.to_string(); + let test_delegate_stake = test_commands.clone().get_matches_from(vec![ + "test", + "delegate-stake", + &stake_account_string, + &vote_account_string, + "--fee-payer", + &fee_payer_keypair_file, + ]); + assert_eq!( + parse_command(&test_delegate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DelegateStake { + stake_account_pubkey, + vote_account_pubkey, + stake_authority: None, + force: false, + sign_only: false, + signers: None, + blockhash_query: BlockhashQuery::All, + nonce_account: None, + nonce_authority: None, + fee_payer: Some(read_keypair_file(&fee_payer_keypair_file).unwrap().into()), + }, + require_keypair: true + } + ); + + // Test Delegate Subcommand w/ absentee fee-payer + let sig = fee_payer_keypair.sign_message(&[0u8]); + let signer = format!("{}={}", fee_payer_string, sig); + let test_delegate_stake = test_commands.clone().get_matches_from(vec![ + "test", + "delegate-stake", + &stake_account_string, + &vote_account_string, + "--fee-payer", + &fee_payer_string, + "--blockhash", + &blockhash_string, + "--signer", + &signer, + ]); + assert_eq!( + parse_command(&test_delegate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DelegateStake { + stake_account_pubkey, + vote_account_pubkey, + stake_authority: None, + force: false, + sign_only: false, + signers: Some(vec![(fee_payer_pubkey, sig)]), + blockhash_query: BlockhashQuery::FeeCalculator(blockhash), + nonce_account: None, + nonce_authority: None, + fee_payer: Some(fee_payer_pubkey.into()), }, require_keypair: false } @@ -1792,6 +1958,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1820,6 +1987,7 @@ mod tests { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1846,6 +2014,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1870,6 +2039,7 @@ mod tests { blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: true } @@ -1899,6 +2069,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }, require_keypair: false } @@ -1930,6 +2101,63 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, + }, + require_keypair: false + } + ); + + // Test Deactivate Subcommand w/ fee-payer + let test_deactivate_stake = test_commands.clone().get_matches_from(vec![ + "test", + "deactivate-stake", + &stake_account_string, + "--fee-payer", + &fee_payer_keypair_file, + ]); + assert_eq!( + parse_command(&test_deactivate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DeactivateStake { + stake_account_pubkey, + stake_authority: None, + sign_only: false, + signers: None, + blockhash_query: BlockhashQuery::All, + nonce_account: None, + nonce_authority: None, + fee_payer: Some(read_keypair_file(&fee_payer_keypair_file).unwrap().into()), + }, + require_keypair: true + } + ); + + // Test Deactivate Subcommand w/ absentee fee-payer + let sig = fee_payer_keypair.sign_message(&[0u8]); + let signer = format!("{}={}", fee_payer_string, sig); + let test_deactivate_stake = test_commands.clone().get_matches_from(vec![ + "test", + "deactivate-stake", + &stake_account_string, + "--fee-payer", + &fee_payer_string, + "--blockhash", + &blockhash_string, + "--signer", + &signer, + ]); + assert_eq!( + parse_command(&test_deactivate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DeactivateStake { + stake_account_pubkey, + stake_authority: None, + sign_only: false, + signers: Some(vec![(fee_payer_pubkey, sig)]), + blockhash_query: BlockhashQuery::FeeCalculator(blockhash), + nonce_account: None, + nonce_authority: None, + fee_payer: Some(fee_payer_pubkey.into()), }, require_keypair: false } diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index d9d0477641..88790b1ec4 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -17,7 +17,7 @@ use std::fs::remove_dir_all; use std::sync::mpsc::channel; #[cfg(test)] -use solana_core::validator::new_validator_for_tests; +use solana_core::validator::{new_validator_for_tests, new_validator_for_tests_ex}; use std::thread::sleep; use std::time::Duration; @@ -119,6 +119,7 @@ fn test_seed_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config_validator).unwrap(); @@ -131,6 +132,7 @@ fn test_seed_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config_validator).unwrap(); @@ -207,6 +209,7 @@ fn test_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config_validator).unwrap(); @@ -219,6 +222,7 @@ fn test_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config_validator).unwrap(); @@ -300,6 +304,7 @@ fn test_offline_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, + fee_payer: None, }; let sig_response = process_command(&config_validator).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); @@ -315,6 +320,7 @@ fn test_offline_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config_payer).unwrap(); @@ -328,6 +334,7 @@ fn test_offline_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, + fee_payer: None, }; let sig_response = process_command(&config_validator).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); @@ -341,6 +348,7 @@ fn test_offline_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config_payer).unwrap(); @@ -428,6 +436,7 @@ fn test_nonced_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), nonce_authority: None, + fee_payer: None, }; process_command(&config).unwrap(); @@ -449,6 +458,7 @@ fn test_nonced_stake_delegation_and_deactivation() { blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account.pubkey()), nonce_authority: Some(config_keypair.into()), + fee_payer: None, }; process_command(&config).unwrap(); @@ -503,6 +513,7 @@ fn test_stake_authorize() { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -528,6 +539,7 @@ fn test_stake_authorize() { blockhash_query: BlockhashQuery::default(), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -554,6 +566,7 @@ fn test_stake_authorize() { blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, nonce_authority: None, + fee_payer: None, }; let sign_reply = process_command(&config).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); @@ -567,6 +580,7 @@ fn test_stake_authorize() { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, nonce_authority: None, + fee_payer: None, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -615,6 +629,7 @@ fn test_stake_authorize() { blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), nonce_authority: None, + fee_payer: None, }; let sign_reply = process_command(&config).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); @@ -629,6 +644,7 @@ fn test_stake_authorize() { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account.pubkey()), nonce_authority: None, + fee_payer: None, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -645,6 +661,143 @@ fn test_stake_authorize() { _ => panic!("Nonce is not initialized"), }; assert_ne!(nonce_hash, new_nonce_hash); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +} + +#[test] +fn test_stake_authorize_with_fee_payer() { + solana_logger::setup(); + const SIG_FEE: u64 = 42; + + let (server, leader_data, alice, ledger_path) = new_validator_for_tests_ex(SIG_FEE, 42_000); + let (sender, receiver) = channel(); + run_local_faucet(alice, sender, None); + let faucet_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_socket(leader_data.rpc); + + let mut config = CliConfig::default(); + config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + + let mut config_payer = CliConfig::default(); + config_payer.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let payer_pubkey = config_payer.keypair.pubkey(); + let (payer_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_payer.keypair, tmp_file.as_file_mut()).unwrap(); + + let mut config_offline = CliConfig::default(); + let offline_pubkey = config_offline.keypair.pubkey(); + let (_offline_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap(); + // Verify we're offline + config_offline.command = CliCommand::ClusterVersion; + process_command(&config_offline).unwrap_err(); + + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 100_000) + .unwrap(); + check_balance(100_000, &rpc_client, &config.keypair.pubkey()); + + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000).unwrap(); + check_balance(100_000, &rpc_client, &payer_pubkey); + + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap(); + check_balance(100_000, &rpc_client, &offline_pubkey); + + // Create stake account, identity is authority + let stake_keypair = Keypair::new(); + let stake_account_pubkey = stake_keypair.pubkey(); + let (stake_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config.command = CliCommand::CreateStakeAccount { + stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + seed: None, + staker: None, + withdrawer: None, + lockup: Lockup::default(), + lamports: 50_000, + }; + process_command(&config).unwrap(); + // `config` balance should be 50,000 - 1 stake account sig - 1 fee sig + check_balance( + 50_000 - SIG_FEE - SIG_FEE, + &rpc_client, + &config.keypair.pubkey(), + ); + + // Assign authority with separate fee payer + config.command = CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: offline_pubkey, + stake_authorize: StakeAuthorize::Staker, + authority: None, + sign_only: false, + signers: None, + blockhash_query: BlockhashQuery::All, + nonce_account: None, + nonce_authority: None, + fee_payer: Some(read_keypair_file(&payer_keypair_file).unwrap().into()), + }; + process_command(&config).unwrap(); + // `config` balance has not changed, despite submitting the TX + check_balance( + 50_000 - SIG_FEE - SIG_FEE, + &rpc_client, + &config.keypair.pubkey(), + ); + // `config_payer` however has paid `config`'s authority sig + // and `config_payer`'s fee sig + check_balance( + 100_000 - SIG_FEE - SIG_FEE, + &rpc_client, + &config_payer.keypair.pubkey(), + ); + + // Assign authority with offline fee payer + let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); + config_offline.command = CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: payer_pubkey, + stake_authorize: StakeAuthorize::Staker, + authority: None, + sign_only: true, + signers: None, + blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), + nonce_account: None, + nonce_authority: None, + fee_payer: None, + }; + let sign_reply = process_command(&config_offline).unwrap(); + let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); + config.command = CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: payer_pubkey, + stake_authorize: StakeAuthorize::Staker, + authority: Some(offline_pubkey.into()), + sign_only: false, + signers: Some(signers), + blockhash_query: BlockhashQuery::FeeCalculator(blockhash), + nonce_account: None, + nonce_authority: None, + fee_payer: Some(offline_pubkey.into()), + }; + process_command(&config).unwrap(); + // `config`'s balance again has not changed + check_balance( + 50_000 - SIG_FEE - SIG_FEE, + &rpc_client, + &config.keypair.pubkey(), + ); + // `config_offline` however has paid 1 sig due to being both authority + // and fee payer + check_balance( + 100_000 - SIG_FEE, + &rpc_client, + &config_offline.keypair.pubkey(), + ); + server.close().unwrap(); remove_dir_all(ledger_path).unwrap(); } diff --git a/core/src/validator.rs b/core/src/validator.rs index c2f84bc871..e362e01186 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -581,7 +581,16 @@ fn wait_for_supermajority( } pub fn new_validator_for_tests() -> (Validator, ContactInfo, Keypair, PathBuf) { - use crate::genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}; + use crate::genesis_utils::BOOTSTRAP_VALIDATOR_LAMPORTS; + new_validator_for_tests_ex(0, BOOTSTRAP_VALIDATOR_LAMPORTS) +} + +pub fn new_validator_for_tests_ex( + fees: u64, + bootstrap_validator_lamports: u64, +) -> (Validator, ContactInfo, Keypair, PathBuf) { + use crate::genesis_utils::{create_genesis_config_with_leader_ex, GenesisConfigInfo}; + use solana_sdk::fee_calculator::FeeCalculator; let node_keypair = Arc::new(Keypair::new()); let node = Node::new_localhost_with_pubkey(&node_keypair.pubkey()); @@ -591,13 +600,19 @@ pub fn new_validator_for_tests() -> (Validator, ContactInfo, Keypair, PathBuf) { mut genesis_config, mint_keypair, voting_keypair, - } = create_genesis_config_with_leader(1_000_000, &contact_info.id, 42); + } = create_genesis_config_with_leader_ex( + 1_000_000, + &contact_info.id, + 42, + bootstrap_validator_lamports, + ); genesis_config .native_instruction_processors .push(solana_budget_program!()); genesis_config.rent.lamports_per_byte_year = 1; genesis_config.rent.exemption_threshold = 1.0; + genesis_config.fee_calculator = FeeCalculator::new(fees, 0); let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config); diff --git a/ledger/src/genesis_utils.rs b/ledger/src/genesis_utils.rs index 9bd546188a..cdee96c890 100644 --- a/ledger/src/genesis_utils.rs +++ b/ledger/src/genesis_utils.rs @@ -1,5 +1,6 @@ pub use solana_runtime::genesis_utils::{ - create_genesis_config_with_leader, GenesisConfigInfo, BOOTSTRAP_VALIDATOR_LAMPORTS, + create_genesis_config_with_leader, create_genesis_config_with_leader_ex, GenesisConfigInfo, + BOOTSTRAP_VALIDATOR_LAMPORTS, }; use solana_sdk::pubkey::Pubkey; diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index a26e1adf3c..ed975c2d4f 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -27,6 +27,20 @@ pub fn create_genesis_config_with_leader( mint_lamports: u64, bootstrap_validator_pubkey: &Pubkey, bootstrap_validator_stake_lamports: u64, +) -> GenesisConfigInfo { + create_genesis_config_with_leader_ex( + mint_lamports, + bootstrap_validator_pubkey, + bootstrap_validator_stake_lamports, + BOOTSTRAP_VALIDATOR_LAMPORTS, + ) +} + +pub fn create_genesis_config_with_leader_ex( + mint_lamports: u64, + bootstrap_validator_pubkey: &Pubkey, + bootstrap_validator_stake_lamports: u64, + bootstrap_validator_lamports: u64, ) -> GenesisConfigInfo { let mint_keypair = Keypair::new(); let bootstrap_validator_voting_keypair = Keypair::new(); @@ -56,7 +70,7 @@ pub fn create_genesis_config_with_leader( ), ( *bootstrap_validator_pubkey, - Account::new(BOOTSTRAP_VALIDATOR_LAMPORTS, 0, &system_program::id()), + Account::new(bootstrap_validator_lamports, 0, &system_program::id()), ), ( bootstrap_validator_voting_keypair.pubkey(),