diff --git a/book/src/validator-stake.md b/book/src/validator-stake.md index 426b6d88d..0d67b0a6b 100644 --- a/book/src/validator-stake.md +++ b/book/src/validator-stake.md @@ -23,7 +23,7 @@ or more for the change to take effect. Stake can be deactivate by running: ```bash -$ solana-wallet deactivate-stake ~/validator-config/stake-keypair.json +$ solana-wallet deactivate-stake ~/validator-config/stake-keypair.json [VOTE PUBKEY] ``` Note that a stake account may only be used once, so after deactivation, use the wallet's `withdraw-stake` command to recover the previously staked lamports. diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 10540bfc4..4d7962731 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -47,7 +47,8 @@ pub enum StakeInstruction { /// /// Expects 2 Accounts: /// 0 - Delegate StakeAccount - /// 1 - Syscall Account that carries epoch + /// 1 - VoteAccount to which the Stake is delegated + /// 2 - Syscall Account that carries epoch Deactivate, } @@ -79,7 +80,7 @@ pub fn create_stake_account_and_delegate_stake( pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction { let account_metas = vec![ AccountMeta::new(*stake_pubkey, false), - AccountMeta::new(*vote_pubkey, false), + AccountMeta::new_credit_only(*vote_pubkey, false), AccountMeta::new(crate::rewards_pools::random_id(), false), AccountMeta::new_credit_only(sysvar::rewards::id(), false), ]; @@ -104,9 +105,10 @@ pub fn withdraw(stake_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Ins Instruction::new(id(), &StakeInstruction::Withdraw(lamports), account_metas) } -pub fn deactivate_stake(stake_pubkey: &Pubkey) -> Instruction { +pub fn deactivate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction { let account_metas = vec![ AccountMeta::new(*stake_pubkey, true), + AccountMeta::new_credit_only(*vote_pubkey, false), AccountMeta::new_credit_only(sysvar::clock::id(), false), ]; Instruction::new(id(), &StakeInstruction::Deactivate, account_metas) @@ -168,12 +170,14 @@ pub fn process_instruction( ) } StakeInstruction::Deactivate => { - if rest.len() != 1 { + if rest.len() != 2 { Err(InstructionError::InvalidInstructionData)?; } - let sysvar = &rest[0]; + let (vote, rest) = rest.split_at_mut(1); + let vote = &mut vote[0]; + let clock = &rest[0]; - me.deactivate_stake(&sysvar::clock::from_keyed_account(&sysvar)?) + me.deactivate_stake(vote, &sysvar::clock::from_keyed_account(&clock)?) } } } @@ -225,7 +229,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&deactivate_stake(&Pubkey::default())), + process_instruction(&deactivate_stake(&Pubkey::default(), &Pubkey::default())), Err(InstructionError::InvalidAccountData), ); } @@ -351,6 +355,7 @@ mod tests { super::process_instruction( &Pubkey::default(), &mut [ + KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), KeyedAccount::new( &sysvar::rewards::id(), @@ -368,7 +373,6 @@ mod tests { super::process_instruction( &Pubkey::default(), &mut [ - KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), KeyedAccount::new( &sysvar::clock::id(), diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index e1586f05b..0fe49abb0 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -180,7 +180,11 @@ pub trait StakeAccount { stake: u64, clock: &sysvar::clock::Clock, ) -> Result<(), InstructionError>; - fn deactivate_stake(&mut self, clock: &sysvar::clock::Clock) -> Result<(), InstructionError>; + fn deactivate_stake( + &mut self, + vote_account: &KeyedAccount, + clock: &sysvar::clock::Clock, + ) -> Result<(), InstructionError>; fn redeem_vote_credits( &mut self, vote_account: &mut KeyedAccount, @@ -225,7 +229,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> { Err(InstructionError::InvalidAccountData) } } - fn deactivate_stake(&mut self, clock: &sysvar::clock::Clock) -> Result<(), InstructionError> { + fn deactivate_stake( + &mut self, + _vote_account: &KeyedAccount, // TODO: used in slashing + clock: &sysvar::clock::Clock, + ) -> Result<(), InstructionError> { if self.signer_key().is_none() { return Err(InstructionError::MissingRequiredSignature); } @@ -466,17 +474,22 @@ mod tests { ..sysvar::clock::Clock::default() }; + let vote_pubkey = Pubkey::new_rand(); + let mut vote_account = + vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); + let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); + // unsigned keyed account let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); assert_eq!( - stake_keyed_account.deactivate_stake(&clock), + stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), Err(InstructionError::MissingRequiredSignature) ); // signed keyed account but not staked yet let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); assert_eq!( - stake_keyed_account.deactivate_stake(&clock), + stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), Err(InstructionError::InvalidAccountData) ); @@ -492,7 +505,10 @@ mod tests { ); // Deactivate after staking - assert_eq!(stake_keyed_account.deactivate_stake(&clock), Ok(())); + assert_eq!( + stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), + Ok(()) + ); } #[test] @@ -570,7 +586,10 @@ mod tests { ); // deactivate the stake before withdrawal - assert_eq!(stake_keyed_account.deactivate_stake(&clock), Ok(())); + assert_eq!( + stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), + Ok(()) + ); // simulate time passing clock.epoch += STAKE_WARMUP_EPOCHS * 2; diff --git a/programs/stake_tests/tests/stake_instruction.rs b/programs/stake_tests/tests/stake_instruction.rs index 245c11761..db19382e6 100644 --- a/programs/stake_tests/tests/stake_instruction.rs +++ b/programs/stake_tests/tests/stake_instruction.rs @@ -175,7 +175,10 @@ fn test_stake_account_delegate() { // Deactivate the stake let message = Message::new_with_payer( - vec![stake_instruction::deactivate_stake(&staker_pubkey)], + vec![stake_instruction::deactivate_stake( + &staker_pubkey, + &vote_pubkey, + )], Some(&mint_pubkey), ); assert!(bank_client diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index 4a685254a..acbbf514d 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -55,7 +55,7 @@ pub enum WalletCommand { ShowVoteAccount(Pubkey), DelegateStake(Keypair, Pubkey, u64, bool), WithdrawStake(Keypair, Pubkey, u64), - DeactivateStake(Keypair), + DeactivateStake(Keypair, Pubkey), RedeemVoteCredits(Pubkey, Pubkey), ShowStakeAccount(Pubkey), CreateReplicatorStorageAccount(Pubkey, Pubkey), @@ -271,7 +271,11 @@ pub fn parse_command( } ("deactivate-stake", Some(matches)) => { let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap(); - Ok(WalletCommand::DeactivateStake(stake_account_keypair)) + let vote_account_pubkey = value_of(matches, "vote_account_pubkey").unwrap(); + Ok(WalletCommand::DeactivateStake( + stake_account_keypair, + vote_account_pubkey, + )) } ("redeem-vote-credits", Some(matches)) => { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); @@ -587,9 +591,11 @@ fn process_deactivate_stake_account( rpc_client: &RpcClient, config: &WalletConfig, stake_account_keypair: &Keypair, + vote_account_pubkey: &Pubkey, ) -> ProcessResult { let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; - let ixs = stake_instruction::deactivate_stake(&stake_account_keypair.pubkey()); + let ixs = + stake_instruction::deactivate_stake(&stake_account_keypair.pubkey(), vote_account_pubkey); let mut tx = Transaction::new_signed_with_payer( vec![ixs], Some(&config.keypair.pubkey()), @@ -1168,8 +1174,13 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { ), // Deactivate stake account - WalletCommand::DeactivateStake(stake_account_keypair) => { - process_deactivate_stake_account(&rpc_client, config, &stake_account_keypair) + WalletCommand::DeactivateStake(stake_account_keypair, vote_account_pubkey) => { + process_deactivate_stake_account( + &rpc_client, + config, + &stake_account_keypair, + &vote_account_pubkey, + ) } WalletCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey) => { @@ -1549,6 +1560,15 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .required(true) .help("Keypair file for the stake account, for signing the delegate transaction."), ) + .arg( + Arg::with_name("vote_account_pubkey") + .index(2) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + .help("The vote account to which the stake is currently delegated"), + ) ) .subcommand( SubCommand::with_name("withdraw-stake") @@ -2057,13 +2077,15 @@ mod tests { let keypair_file = make_tmp_path("keypair_file"); gen_keypair_file(&keypair_file).unwrap(); let keypair = read_keypair(&keypair_file).unwrap(); - let test_deactivate_stake = - test_commands - .clone() - .get_matches_from(vec!["test", "deactivate-stake", &keypair_file]); + let test_deactivate_stake = test_commands.clone().get_matches_from(vec![ + "test", + "deactivate-stake", + &keypair_file, + &pubkey_string, + ]); assert_eq!( parse_command(&pubkey, &test_deactivate_stake).unwrap(), - WalletCommand::DeactivateStake(keypair) + WalletCommand::DeactivateStake(keypair, pubkey) ); // Test Deploy Subcommand @@ -2229,8 +2251,8 @@ mod tests { // DeactivateStake test. /* let bob_keypair = Keypair::new(); - let node_pubkey = Pubkey::new_rand(); - config.command = WalletCommand::DelegateStake(bob_keypair.into(), node_pubkey, 100, true); + let vote_pubkey = Pubkey::new_rand(); + config.command = WalletCommand::DelegateStake(bob_keypair.into(), vote_pubkey, 100, true); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); */ @@ -2242,7 +2264,8 @@ mod tests { assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let bob_keypair = Keypair::new(); - config.command = WalletCommand::DeactivateStake(bob_keypair.into()); + let vote_pubkey = Pubkey::new_rand(); + config.command = WalletCommand::DeactivateStake(bob_keypair.into(), vote_pubkey); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string());