From b8b1e57df4a798e90833e37cc1a39b5244fb30a3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 23:44:51 -0800 Subject: [PATCH] Fix offline stakes payer (#7385) (#7394) automerge --- cli/src/cli.rs | 57 +++++++-- cli/src/cluster_query.rs | 7 +- cli/src/stake.rs | 62 +++++++--- cli/src/storage.rs | 14 ++- cli/src/validator_info.rs | 7 +- cli/src/vote.rs | 14 ++- cli/tests/stake.rs | 252 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 382 insertions(+), 31 deletions(-) create mode 100644 cli/tests/stake.rs diff --git a/cli/src/cli.rs b/cli/src/cli.rs index e3d71f6c88..ce15dfa5bc 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -512,20 +512,20 @@ pub type ProcessResult = Result>; pub fn check_account_for_fee( rpc_client: &RpcClient, - config: &CliConfig, + account_pubkey: &Pubkey, fee_calculator: &FeeCalculator, message: &Message, ) -> Result<(), Box> { - check_account_for_multiple_fees(rpc_client, config, fee_calculator, &[message]) + check_account_for_multiple_fees(rpc_client, account_pubkey, fee_calculator, &[message]) } fn check_account_for_multiple_fees( rpc_client: &RpcClient, - config: &CliConfig, + account_pubkey: &Pubkey, fee_calculator: &FeeCalculator, messages: &[&Message], ) -> Result<(), Box> { - let balance = rpc_client.retry_get_balance(&config.keypair.pubkey(), 5)?; + let balance = rpc_client.retry_get_balance(account_pubkey, 5)?; if let Some(lamports) = balance { if lamports >= messages @@ -744,7 +744,12 @@ fn process_deploy( let mut finalize_tx = Transaction::new(&signers, message, blockhash); messages.push(&finalize_tx.message); - check_account_for_multiple_fees(rpc_client, config, &fee_calculator, &messages)?; + check_account_for_multiple_fees( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &messages, + )?; trace!("Creating program account"); let result = rpc_client.send_and_confirm_transaction(&mut create_account_tx, &signers); @@ -804,7 +809,12 @@ fn process_pay( if sign_only { return_signers(&tx) } else { - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -838,7 +848,12 @@ fn process_pay( if sign_only { return_signers(&tx) } else { - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client .send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]); let signature_str = log_instruction_custom_error::(result)?; @@ -883,7 +898,12 @@ fn process_pay( } else { let result = rpc_client .send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ @@ -905,7 +925,12 @@ fn process_cancel(rpc_client: &RpcClient, config: &CliConfig, pubkey: &Pubkey) - &config.keypair.pubkey(), ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -921,7 +946,12 @@ fn process_time_elapsed( let ix = budget_instruction::apply_timestamp(&config.keypair.pubkey(), pubkey, to, dt); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -936,7 +966,12 @@ fn process_witness( let ix = budget_instruction::apply_signature(&config.keypair.pubkey(), pubkey, to); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index f3993800c8..0872db0f40 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -419,7 +419,12 @@ pub fn process_ping( let transaction = system_transaction::transfer(&config.keypair, &to, lamports, recent_blockhash); - check_account_for_fee(rpc_client, config, &fee_calculator, &transaction.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &transaction.message, + )?; match rpc_client.send_transaction(&transaction) { Ok(signature) => { diff --git a/cli/src/stake.rs b/cli/src/stake.rs index c5786c57ea..5222004e9c 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -344,6 +344,7 @@ pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); + Ok(CliCommandInfo { command: CliCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey), require_keypair: true, @@ -389,6 +391,8 @@ pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result) -> Result(result) @@ -520,7 +529,12 @@ pub fn process_stake_authorize( &[&config.keypair], recent_blockhash, ); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -551,7 +565,12 @@ pub fn process_deactivate_stake_account( if sign_only { return_signers(&tx) } else { - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &tx.message.account_keys[0], + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -579,7 +598,12 @@ pub fn process_withdraw_stake( &[&config.keypair], recent_blockhash, ); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -601,7 +625,12 @@ pub fn process_redeem_vote_credits( &[&config.keypair], recent_blockhash, ); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -791,7 +820,12 @@ pub fn process_delegate_stake( if sign_only { return_signers(&tx) } else { - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &tx.message.account_keys[0], + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } @@ -1008,7 +1042,7 @@ mod tests { signers: None, blockhash: None }, - require_keypair: false + require_keypair: true } ); @@ -1035,7 +1069,7 @@ mod tests { signers: Some(vec![(key1, sig1)]), blockhash: None }, - require_keypair: true + require_keypair: false } ); @@ -1064,7 +1098,7 @@ mod tests { signers: Some(vec![(key1, sig1), (key2, sig2)]), blockhash: None }, - require_keypair: true + require_keypair: false } ); @@ -1143,7 +1177,7 @@ mod tests { signers: None, blockhash: None }, - require_keypair: false + require_keypair: true } ); @@ -1167,7 +1201,7 @@ mod tests { signers: Some(vec![(key1, sig1)]), blockhash: None }, - require_keypair: true + require_keypair: false } ); @@ -1193,7 +1227,7 @@ mod tests { signers: Some(vec![(key1, sig1), (key2, sig2)]), blockhash: None }, - require_keypair: true + require_keypair: false } ); } diff --git a/cli/src/storage.rs b/cli/src/storage.rs index 7720c687be..2dc46c6c76 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -176,7 +176,12 @@ pub fn process_create_storage_account( ixs, recent_blockhash, ); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &storage_account]); log_instruction_custom_error::(result) @@ -196,7 +201,12 @@ pub fn process_claim_storage_reward( let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); let mut tx = Transaction::new(&signers, message, recent_blockhash); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?; Ok(signature_str.to_string()) } diff --git a/cli/src/validator_info.rs b/cli/src/validator_info.rs index 0bd6b2219e..c4aedc4f79 100644 --- a/cli/src/validator_info.rs +++ b/cli/src/validator_info.rs @@ -347,7 +347,12 @@ pub fn process_set_validator_info( // Submit transaction let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let mut tx = Transaction::new(&signers, message, recent_blockhash); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?; println!("Success! Validator info published at: {:?}", info_pubkey); diff --git a/cli/src/vote.rs b/cli/src/vote.rs index dfc64bebd1..5abcb9a14f 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -268,7 +268,12 @@ pub fn process_create_vote_account( ixs, recent_blockhash, ); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, vote_account]); log_instruction_custom_error::(result) } @@ -298,7 +303,12 @@ pub fn process_vote_authorize( &[&config.keypair], recent_blockhash, ); - check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; + check_account_for_fee( + rpc_client, + &config.keypair.pubkey(), + &fee_calculator, + &tx.message, + )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result) } diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs new file mode 100644 index 0000000000..2af1e56dd4 --- /dev/null +++ b/cli/tests/stake.rs @@ -0,0 +1,252 @@ +use serde_json::Value; +use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig}; +use solana_client::rpc_client::RpcClient; +use solana_drone::drone::run_local_drone; +use solana_sdk::{ + hash::Hash, + pubkey::Pubkey, + signature::{read_keypair_file, write_keypair, KeypairUtil, Signature}, +}; +use solana_stake_program::stake_state::Lockup; +use std::fs::remove_dir_all; +use std::str::FromStr; +use std::sync::mpsc::channel; + +#[cfg(test)] +use solana_core::validator::new_validator_for_tests; +use std::thread::sleep; +use std::time::Duration; + +use tempfile::NamedTempFile; + +fn make_tmp_file() -> (String, NamedTempFile) { + let tmp_file = NamedTempFile::new().unwrap(); + (String::from(tmp_file.path().to_str().unwrap()), tmp_file) +} + +fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) { + (0..5).for_each(|tries| { + let balance = client.retry_get_balance(pubkey, 1).unwrap().unwrap(); + if balance == expected_balance { + return; + } + if tries == 4 { + assert_eq!(balance, expected_balance); + } + sleep(Duration::from_millis(500)); + }); +} + +#[test] +fn test_stake_delegation_and_deactivation() { + solana_logger::setup(); + + let (server, leader_data, alice, ledger_path) = new_validator_for_tests(); + let (sender, receiver) = channel(); + run_local_drone(alice, sender, None); + let drone_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_socket(leader_data.rpc); + + let mut config_validator = CliConfig::default(); + config_validator.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + + let mut config_vote = CliConfig::default(); + config_vote.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let (vote_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_vote.keypair, tmp_file.as_file_mut()).unwrap(); + + let mut config_stake = CliConfig::default(); + config_stake.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let (stake_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_stake.keypair, tmp_file.as_file_mut()).unwrap(); + + request_and_confirm_airdrop( + &rpc_client, + &drone_addr, + &config_validator.keypair.pubkey(), + 100_000, + ) + .unwrap(); + check_balance(100_000, &rpc_client, &config_validator.keypair.pubkey()); + + // Create vote account + config_validator.command = CliCommand::CreateVoteAccount { + vote_account: read_keypair_file(&vote_keypair_file).unwrap().into(), + node_pubkey: config_validator.keypair.pubkey(), + authorized_voter: None, + authorized_withdrawer: None, + commission: 0, + }; + process_command(&config_validator).unwrap(); + + // Create stake account + config_validator.command = CliCommand::CreateStakeAccount { + stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + staker: None, + withdrawer: None, + lockup: Lockup { + custodian: Pubkey::default(), + epoch: 0, + }, + lamports: 50_000, + }; + process_command(&config_validator).unwrap(); + + // Delegate stake + config_validator.command = CliCommand::DelegateStake { + stake_account_pubkey: config_stake.keypair.pubkey(), + vote_account_pubkey: config_vote.keypair.pubkey(), + force: true, + sign_only: false, + signers: None, + blockhash: None, + }; + process_command(&config_validator).unwrap(); + + // Deactivate stake + config_validator.command = CliCommand::DeactivateStake { + stake_account_pubkey: config_stake.keypair.pubkey(), + sign_only: false, + signers: None, + blockhash: None, + }; + process_command(&config_validator).unwrap(); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +} + +#[test] +fn test_stake_delegation_and_deactivation_offline() { + solana_logger::setup(); + + let (server, leader_data, alice, ledger_path) = new_validator_for_tests(); + let (sender, receiver) = channel(); + run_local_drone(alice, sender, None); + let drone_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_socket(leader_data.rpc); + + let mut config_validator = CliConfig::default(); + config_validator.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 mut config_vote = CliConfig::default(); + config_vote.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let (vote_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_vote.keypair, tmp_file.as_file_mut()).unwrap(); + + let mut config_stake = CliConfig::default(); + config_stake.json_rpc_url = + format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let (stake_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&config_stake.keypair, tmp_file.as_file_mut()).unwrap(); + + request_and_confirm_airdrop( + &rpc_client, + &drone_addr, + &config_validator.keypair.pubkey(), + 100_000, + ) + .unwrap(); + check_balance(100_000, &rpc_client, &config_validator.keypair.pubkey()); + + // Create vote account + config_validator.command = CliCommand::CreateVoteAccount { + vote_account: read_keypair_file(&vote_keypair_file).unwrap().into(), + node_pubkey: config_validator.keypair.pubkey(), + authorized_voter: None, + authorized_withdrawer: None, + commission: 0, + }; + process_command(&config_validator).unwrap(); + + // Create stake account + config_validator.command = CliCommand::CreateStakeAccount { + stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + staker: None, + withdrawer: None, + lockup: Lockup { + custodian: Pubkey::default(), + epoch: 0, + }, + lamports: 50_000, + }; + process_command(&config_validator).unwrap(); + + // Delegate stake offline + config_validator.command = CliCommand::DelegateStake { + stake_account_pubkey: config_stake.keypair.pubkey(), + vote_account_pubkey: config_vote.keypair.pubkey(), + force: true, + sign_only: true, + signers: None, + blockhash: None, + }; + let sig_response = process_command(&config_validator).unwrap(); + let object: Value = serde_json::from_str(&sig_response).unwrap(); + let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap(); + let signer_strings = object.get("signers").unwrap().as_array().unwrap(); + let signers: Vec<_> = signer_strings + .iter() + .map(|signer_string| { + let mut signer = signer_string.as_str().unwrap().split('='); + let key = Pubkey::from_str(signer.next().unwrap()).unwrap(); + let sig = Signature::from_str(signer.next().unwrap()).unwrap(); + (key, sig) + }) + .collect(); + + // Delegate stake online + config_payer.command = CliCommand::DelegateStake { + stake_account_pubkey: config_stake.keypair.pubkey(), + vote_account_pubkey: config_vote.keypair.pubkey(), + force: true, + sign_only: false, + signers: Some(signers), + blockhash: Some(blockhash_str.parse::().unwrap()), + }; + process_command(&config_payer).unwrap(); + + // Deactivate stake offline + config_validator.command = CliCommand::DeactivateStake { + stake_account_pubkey: config_stake.keypair.pubkey(), + sign_only: true, + signers: None, + blockhash: None, + }; + let sig_response = process_command(&config_validator).unwrap(); + let object: Value = serde_json::from_str(&sig_response).unwrap(); + let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap(); + let signer_strings = object.get("signers").unwrap().as_array().unwrap(); + let signers: Vec<_> = signer_strings + .iter() + .map(|signer_string| { + let mut signer = signer_string.as_str().unwrap().split('='); + let key = Pubkey::from_str(signer.next().unwrap()).unwrap(); + let sig = Signature::from_str(signer.next().unwrap()).unwrap(); + (key, sig) + }) + .collect(); + + // Deactivate stake online + config_payer.command = CliCommand::DeactivateStake { + stake_account_pubkey: config_stake.keypair.pubkey(), + sign_only: false, + signers: Some(signers), + blockhash: Some(blockhash_str.parse::().unwrap()), + }; + process_command(&config_payer).unwrap(); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +}