From a964570b1a0c2f9cd59707fa6ca8b0c471a0cbb3 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Thu, 26 Sep 2019 13:29:29 -0700 Subject: [PATCH] add authorities to stake init (#6104) * add authorities to stake init * fixups * code review --- cli/src/wallet.rs | 51 ++- core/src/confidence.rs | 10 +- core/src/staking_utils.rs | 6 +- genesis/src/main.rs | 1 + local_cluster/src/local_cluster.rs | 6 +- programs/stake_api/src/stake_instruction.rs | 93 +++-- programs/stake_api/src/stake_state.rs | 363 +++++++++++------- .../stake_tests/tests/stake_instruction.rs | 49 ++- runtime/src/genesis_utils.rs | 1 + runtime/src/stakes.rs | 4 +- 10 files changed, 371 insertions(+), 213 deletions(-) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 00918518f6..fb2519ee1f 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -29,7 +29,10 @@ use solana_sdk::{ system_transaction, transaction::{Transaction, TransactionError}, }; -use solana_stake_api::stake_instruction::{self, StakeError}; +use solana_stake_api::{ + stake_instruction::{self, StakeError}, + stake_state::{Authorized, Lockup}, +}; use solana_storage_api::storage_instruction; use solana_vote_api::vote_state::{VoteAuthorize, VoteInit, VoteState}; use std::{ @@ -80,7 +83,7 @@ pub enum WalletCommand { aggregate: bool, span: Option, }, - DelegateStake(Keypair, Pubkey, u64, bool), + DelegateStake(Keypair, Pubkey, u64, Authorized, bool), WithdrawStake(Keypair, Pubkey, u64), DeactivateStake(Keypair, Pubkey), RedeemVoteCredits(Pubkey, Pubkey), @@ -257,11 +260,13 @@ pub fn parse_command( matches.value_of("amount").unwrap(), matches.value_of("unit"), )?; + let authorized = Authorized::auto(&stake_account_keypair.pubkey()); let force = matches.is_present("force"); Ok(WalletCommand::DelegateStake( stake_account_keypair, vote_account_pubkey, lamports, + authorized, force, )) } @@ -607,6 +612,7 @@ fn process_delegate_stake( stake_account_keypair: &Keypair, vote_account_pubkey: &Pubkey, lamports: u64, + authorized: &Authorized, force: bool, ) -> ProcessResult { check_unique_pubkeys( @@ -623,6 +629,7 @@ fn process_delegate_stake( &stake_account_keypair.pubkey(), vote_account_pubkey, lamports, + authorized, ); // Sanity check the vote account to ensure it is attached to a validator that has recently @@ -740,8 +747,16 @@ fn process_show_stake_account( format!("{:?} is not a stake account", stake_account_pubkey).to_string(), ))?; } + fn show_authorized(authorized: &Authorized) { + println!("authorized staker: {}", authorized.staker); + println!("authorized withdrawer: {}", authorized.staker); + } + fn show_lockup(lockup: &Lockup) { + println!("lockup slot: {}", lockup.slot); + println!("lockup custodian: {}", lockup.custodian); + } match stake_account.state() { - Ok(StakeState::Stake(stake)) => { + Ok(StakeState::Stake(authorized, lockup, stake)) => { println!( "total stake: {}", build_balance_message(stake_account.lamports, use_lamports_unit) @@ -764,11 +779,17 @@ fn process_show_stake_account( stake.deactivation_epoch ); } + show_authorized(&authorized); + show_lockup(&lockup); Ok("".to_string()) } Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()), - Ok(StakeState::Uninitialized) | Ok(StakeState::Lockup(_)) => { - Ok("Stake account is uninitialized".to_string()) + Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()), + Ok(StakeState::Initialized(authorized, lockup)) => { + println!("Stake account is undelegated"); + show_authorized(&authorized); + show_lockup(&lockup); + Ok("".to_string()) } Err(err) => Err(WalletError::RpcRequestError(format!( "Account data could not be deserialized to stake state: {:?}", @@ -1347,6 +1368,7 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { stake_account_keypair, vote_account_pubkey, lamports, + authorized, force, ) => process_delegate_stake( &rpc_client, @@ -1354,6 +1376,7 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { &stake_account_keypair, &vote_account_pubkey, *lamports, + &authorized, *force, ), @@ -2477,9 +2500,16 @@ mod tests { "42", "lamports", ]); + let stake_pubkey = keypair.pubkey(); assert_eq!( parse_command(&pubkey, &test_delegate_stake).unwrap(), - WalletCommand::DelegateStake(keypair, pubkey, 42, false) + WalletCommand::DelegateStake( + keypair, + pubkey, + 42, + Authorized::auto(&stake_pubkey), + false, + ) ); let keypair = read_keypair(&keypair_file).unwrap(); @@ -2492,9 +2522,16 @@ mod tests { "42", "lamports", ]); + let stake_pubkey = keypair.pubkey(); assert_eq!( parse_command(&pubkey, &test_delegate_stake).unwrap(), - WalletCommand::DelegateStake(keypair, pubkey, 42, true) + WalletCommand::DelegateStake( + keypair, + pubkey, + 42, + Authorized::auto(&stake_pubkey), + true + ) ); // Test WithdrawStake Subcommand diff --git a/core/src/confidence.rs b/core/src/confidence.rs index 8a10effa67..50c7b6c702 100644 --- a/core/src/confidence.rs +++ b/core/src/confidence.rs @@ -319,18 +319,20 @@ mod tests { mut genesis_block, .. } = create_genesis_block(10_000); + let sk1 = Pubkey::new_rand(); let pk1 = Pubkey::new_rand(); let mut vote_account1 = vote_state::create_account(&pk1, &Pubkey::new_rand(), 0, 100); - let stake_account1 = stake_state::create_account(&pk1, &vote_account1, 100); + let stake_account1 = stake_state::create_account(&sk1, &pk1, &vote_account1, 100); + let sk2 = Pubkey::new_rand(); let pk2 = Pubkey::new_rand(); let mut vote_account2 = vote_state::create_account(&pk2, &Pubkey::new_rand(), 0, 50); - let stake_account2 = stake_state::create_account(&pk2, &vote_account2, 50); + let stake_account2 = stake_state::create_account(&sk2, &pk2, &vote_account2, 50); genesis_block.accounts.extend(vec![ (pk1, vote_account1.clone()), - (Pubkey::new_rand(), stake_account1), + (sk1, stake_account1), (pk2, vote_account2.clone()), - (Pubkey::new_rand(), stake_account2), + (sk2, stake_account2), ]); // Create bank diff --git a/core/src/staking_utils.rs b/core/src/staking_utils.rs index 108552d5f3..49773a4a80 100644 --- a/core/src/staking_utils.rs +++ b/core/src/staking_utils.rs @@ -104,7 +104,10 @@ pub(crate) mod tests { sysvar::stake_history::{self, StakeHistory}, transaction::Transaction, }; - use solana_stake_api::{stake_instruction, stake_state::Stake}; + use solana_stake_api::{ + stake_instruction, + stake_state::{Authorized, Stake}, + }; use solana_vote_api::{vote_instruction, vote_state::VoteInit}; use std::sync::Arc; @@ -160,6 +163,7 @@ pub(crate) mod tests { &stake_account_pubkey, vote_pubkey, amount, + &Authorized::auto(&stake_account_pubkey), ), ); } diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 0e4ffe1e19..01b64ba0d1 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -315,6 +315,7 @@ fn main() -> Result<(), Box> { 1, ); let stake_account = stake_state::create_account( + &bootstrap_stake_keypair.pubkey(), &bootstrap_vote_keypair.pubkey(), &vote_account, bootstrap_leader_stake_lamports, diff --git a/local_cluster/src/local_cluster.rs b/local_cluster/src/local_cluster.rs index e1086d9947..db7edc8626 100644 --- a/local_cluster/src/local_cluster.rs +++ b/local_cluster/src/local_cluster.rs @@ -21,7 +21,10 @@ use solana_sdk::{ system_transaction, transaction::Transaction, }; -use solana_stake_api::{config as stake_config, stake_instruction, stake_state::StakeState}; +use solana_stake_api::{ + config as stake_config, stake_instruction, + stake_state::{Authorized as StakeAuthorized, StakeState}, +}; use solana_storage_api::{storage_contract, storage_instruction}; use solana_vote_api::{ vote_instruction, @@ -462,6 +465,7 @@ impl LocalCluster { &stake_account_pubkey, &vote_account_pubkey, amount, + &StakeAuthorized::auto(&stake_account_pubkey), ), client.get_recent_blockhash().unwrap().0, ); diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 12985076fc..635925aed0 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -1,6 +1,6 @@ use crate::{ config, id, - stake_state::{StakeAccount, StakeState}, + stake_state::{Authorized, Lockup, StakeAccount, StakeAuthorize, StakeState}, }; use bincode::deserialize; use log::*; @@ -8,7 +8,6 @@ use num_derive::{FromPrimitive, ToPrimitive}; use serde_derive::{Deserialize, Serialize}; use solana_sdk::{ account::KeyedAccount, - clock::Slot, instruction::{AccountMeta, Instruction, InstructionError}, instruction_processor_utils::DecodeError, pubkey::Pubkey, @@ -36,30 +35,33 @@ impl std::fmt::Display for StakeError { } impl std::error::Error for StakeError {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum StakeInstruction { - /// `Lockup` a stake until the specified slot + /// `Initialize` a stake with Lockup and Authorized information /// /// Expects 1 Account: - /// 0 - Uninitialized StakeAccount to be lockup'd + /// 0 - Uninitialized StakeAccount /// - /// The Slot parameter denotes slot height at which this stake - /// will allow withdrawal from the stake account. - /// The Pubkey parameter denotes a "custodian" account, the only - /// account to which this stake will honor a withdrawal *before* - // lockup expires. + /// Authorized carries pubkeys that must sign staker transactions + /// and withdrawer transactions. + /// Lockup carries information about withdrawal restrictions /// - Lockup((Slot, Pubkey)), + Initialize(Authorized, Lockup), - /// Authorize a system account to manage stake + /// Authorize a key to manage stake or withdrawal + /// requires Authorized::staker or Authorized::withdrawer + /// signature, depending on which key's being updated /// /// Expects 1 Account: - /// 0 - Locked-up or delegated StakeAccount to be updated with authorized staker - Authorize(Pubkey), + /// 0 - StakeAccount to be updated with the Pubkey for + /// authorization + Authorize(Pubkey, StakeAuthorize), + /// `Delegate` a stake to a particular vote account + /// requires Authorized::staker signature /// /// Expects 4 Accounts: - /// 0 - Lockup'd StakeAccount to be delegated <= transaction must have this signature + /// 0 - Initialized StakeAccount to be delegated /// 1 - VoteAccount to which this Stake will be delegated /// 2 - Clock sysvar Account that carries clock bank epoch /// 3 - Config Account that carries stake config @@ -71,9 +73,10 @@ pub enum StakeInstruction { DelegateStake, /// Redeem credits in the stake account + /// requires Authorized::staker signature /// /// Expects 5 Accounts: - /// 0 - Delegate StakeAccount to be updated with rewards + /// 0 - StakeAccount to be updated with rewards /// 1 - VoteAccount to which the Stake is delegated, /// 2 - RewardsPool Stake Account from which to redeem credits /// 3 - Rewards sysvar Account that carries points values @@ -81,21 +84,23 @@ pub enum StakeInstruction { RedeemVoteCredits, /// Withdraw unstaked lamports from the stake account + /// requires Authorized::withdrawer signature /// /// Expects 4 Accounts: - /// 0 - Delegate StakeAccount <= transaction must have this signature + /// 0 - StakeAccount from which to withdraw /// 1 - System account to which the lamports will be transferred, /// 2 - Syscall Account that carries epoch /// 3 - StakeHistory sysvar that carries stake warmup/cooldown history /// /// The u64 is the portion of the Stake account balance to be withdrawn, - /// must be <= StakeAccount.lamports - staked lamports + /// must be <= StakeAccount.lamports - staked lamports. Withdraw(u64), /// Deactivates the stake in the account + /// requires Authorized::staker signature /// /// Expects 3 Accounts: - /// 0 - Delegate StakeAccount <= transaction must have this signature + /// 0 - Delegate StakeAccount /// 1 - VoteAccount to which the Stake is delegated /// 2 - Syscall Account that carries epoch /// @@ -106,8 +111,8 @@ pub fn create_stake_account_with_lockup( from_pubkey: &Pubkey, stake_pubkey: &Pubkey, lamports: u64, - lockup: Slot, - custodian: &Pubkey, + authorized: &Authorized, + lockup: &Lockup, ) -> Vec { vec![ system_instruction::create_account( @@ -119,7 +124,7 @@ pub fn create_stake_account_with_lockup( ), Instruction::new( id(), - &StakeInstruction::Lockup((lockup, *custodian)), + &StakeInstruction::Initialize(*authorized, *lockup), vec![AccountMeta::new(*stake_pubkey, false)], ), ] @@ -129,8 +134,15 @@ pub fn create_stake_account( from_pubkey: &Pubkey, stake_pubkey: &Pubkey, lamports: u64, + authorized: &Authorized, ) -> Vec { - create_stake_account_with_lockup(from_pubkey, stake_pubkey, lamports, 0, &Pubkey::default()) + create_stake_account_with_lockup( + from_pubkey, + stake_pubkey, + lamports, + authorized, + &Lockup::default(), + ) } pub fn create_stake_account_and_delegate_stake( @@ -138,21 +150,23 @@ pub fn create_stake_account_and_delegate_stake( stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, lamports: u64, + authorized: &Authorized, ) -> Vec { - let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports); + let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports, authorized); instructions.push(delegate_stake(stake_pubkey, vote_pubkey)); instructions } -fn metas_for_authorized_staker( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, // currently authorized +// for instructions that whose authorized signer may differ from the account's pubkey +fn metas_for_authorized_signer( + account_pubkey: &Pubkey, + authorized_signer: &Pubkey, // currently authorized other_params: &[AccountMeta], ) -> Vec { - let is_own_signer = authorized_pubkey == stake_pubkey; + let is_own_signer = authorized_signer == account_pubkey; - // stake account - let mut account_metas = vec![AccountMeta::new(*stake_pubkey, is_own_signer)]; + // vote account + let mut account_metas = vec![AccountMeta::new(*account_pubkey, is_own_signer)]; for meta in other_params { account_metas.push(meta.clone()); @@ -160,7 +174,7 @@ fn metas_for_authorized_staker( // append signer at the end if !is_own_signer { - account_metas.push(AccountMeta::new_credit_only(*authorized_pubkey, true)) // signer + account_metas.push(AccountMeta::new_credit_only(*authorized_signer, true)) // signer } account_metas @@ -170,12 +184,13 @@ pub fn authorize( stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey, + stake_authorize: StakeAuthorize, ) -> Instruction { - let account_metas = metas_for_authorized_staker(stake_pubkey, authorized_pubkey, &[]); + let account_metas = metas_for_authorized_signer(stake_pubkey, authorized_pubkey, &[]); Instruction::new( id(), - &StakeInstruction::Authorize(*new_authorized_pubkey), + &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize), account_metas, ) } @@ -239,8 +254,10 @@ pub fn process_instruction( // TODO: data-driven unpack and dispatch of KeyedAccounts match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { - StakeInstruction::Lockup((lockup, custodian)) => me.lockup(lockup, &custodian), - StakeInstruction::Authorize(authorized_pubkey) => me.authorize(&authorized_pubkey, &rest), + StakeInstruction::Initialize(authorized, lockup) => me.initialize(&authorized, &lockup), + StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => { + me.authorize(&authorized_pubkey, stake_authorize, &rest) + } StakeInstruction::DelegateStake => { if rest.len() < 3 { Err(InstructionError::InvalidInstructionData)?; @@ -366,7 +383,11 @@ mod tests { super::process_instruction( &Pubkey::default(), &mut [], - &serialize(&StakeInstruction::Lockup((0, Pubkey::default()))).unwrap(), + &serialize(&StakeInstruction::Initialize( + Authorized::default(), + Lockup::default() + )) + .unwrap(), ), Err(InstructionError::InvalidInstructionData), ); diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 84a609e892..41c7153c32 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -18,12 +18,12 @@ use solana_sdk::{ }; use solana_vote_api::vote_state::VoteState; -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] #[allow(clippy::large_enum_variant)] pub enum StakeState { Uninitialized, - Lockup(Lockup), - Stake(Stake), + Initialized(Authorized, Lockup), + Stake(Authorized, Lockup, Stake), RewardsPool, } @@ -43,26 +43,48 @@ impl StakeState { Self::from(account).and_then(|state: Self| state.stake()) } + pub fn authorized_from(account: &Account) -> Option { + Self::from(account).and_then(|state: Self| state.authorized()) + } + pub fn stake(&self) -> Option { match self { - StakeState::Stake(stake) => Some(stake.clone()), + StakeState::Stake(_authorized, _lockup, stake) => Some(*stake), + _ => None, + } + } + pub fn authorized(&self) -> Option { + match self { + StakeState::Stake(authorized, _lockup, _stake) => Some(*authorized), _ => None, } } } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] -pub struct Lockup { - /// slot height at which this stake will allow withdrawal, unless to the custodian - pub slot: Slot, - /// custodian account, the only account to which this stake will honor a - /// withdrawal *before* lockup expires - pub custodian: Pubkey, - /// alternate signer that is enabled to act on the Stake account - pub authority: Pubkey, +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] +pub enum StakeAuthorize { + Staker, + Withdrawer, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] +pub struct Lockup { + /// slot height at which this stake will allow withdrawal, unless + /// to the custodian + pub slot: Slot, + /// custodian account, the only account to which this stake will honor a + /// withdrawal before lockup expires. After lockup expires, custodian + /// is irrelevant + pub custodian: Pubkey, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] +pub struct Authorized { + pub staker: Pubkey, + pub withdrawer: Pubkey, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] pub struct Stake { /// most recently delegated vote account pubkey pub voter_pubkey: Pubkey, @@ -78,8 +100,6 @@ pub struct Stake { pub deactivation_epoch: Epoch, /// stake config (warmup, etc.) pub config: Config, - /// the Lockup information, see above - pub lockup: Lockup, /// history of prior delegates and the epoch ranges for which /// they were set, circular buffer pub prior_delegates: [(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES], @@ -92,7 +112,6 @@ const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exp impl Default for Stake { fn default() -> Self { Self { - lockup: Lockup::default(), voter_pubkey: Pubkey::default(), voter_pubkey_epoch: 0, credits_observed: 0, @@ -106,20 +125,54 @@ impl Default for Stake { } } +impl Authorized { + pub fn auto(authorized: &Pubkey) -> Self { + Self { + staker: *authorized, + withdrawer: *authorized, + } + } + pub fn check( + &self, + stake_signer: Option<&Pubkey>, + other_signers: &[KeyedAccount], + stake_authorize: StakeAuthorize, + ) -> Result<(), InstructionError> { + let authorized = match stake_authorize { + StakeAuthorize::Staker => Some(&self.staker), + StakeAuthorize::Withdrawer => Some(&self.withdrawer), + }; + if stake_signer != authorized + && other_signers + .iter() + .all(|account| account.signer_key() != authorized) + { + Err(InstructionError::MissingRequiredSignature) + } else { + Ok(()) + } + } + pub fn authorize( + &mut self, + stake_signer: Option<&Pubkey>, + other_signers: &[KeyedAccount], + new_authorized: &Pubkey, + stake_authorize: StakeAuthorize, + ) -> Result<(), InstructionError> { + self.check(stake_signer, other_signers, stake_authorize)?; + match stake_authorize { + StakeAuthorize::Staker => self.staker = *new_authorized, + StakeAuthorize::Withdrawer => self.withdrawer = *new_authorized, + } + Ok(()) + } +} + impl Stake { fn is_bootstrap(&self) -> bool { self.activation_epoch == std::u64::MAX } - fn check_authorized( - &self, - stake_pubkey_signer: Option<&Pubkey>, - other_signers: &[KeyedAccount], - ) -> Result<(), InstructionError> { - self.lockup - .check_authorized(stake_pubkey_signer, other_signers) - } - pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { self.stake_activating_and_deactivating(epoch, history).0 } @@ -310,7 +363,6 @@ impl Stake { vote_state, std::u64::MAX, &Config::default(), - &Lockup::default(), ) } @@ -340,7 +392,6 @@ impl Stake { vote_state: &VoteState, activation_epoch: Epoch, config: &Config, - lockup: &Lockup, ) -> Self { Self { stake, @@ -349,7 +400,6 @@ impl Stake { voter_pubkey_epoch: activation_epoch, credits_observed: vote_state.credits(), config: *config, - lockup: *lockup, ..Stake::default() } } @@ -359,29 +409,16 @@ impl Stake { } } -impl Lockup { - fn check_authorized( - &self, - stake_pubkey_signer: Option<&Pubkey>, - other_signers: &[KeyedAccount], - ) -> Result<(), InstructionError> { - let authorized = Some(&self.authority); - if stake_pubkey_signer != authorized - && other_signers - .iter() - .all(|account| account.signer_key() != authorized) - { - return Err(InstructionError::MissingRequiredSignature); - } - Ok(()) - } -} - pub trait StakeAccount { - fn lockup(&mut self, slot: Slot, custodian: &Pubkey) -> Result<(), InstructionError>; + fn initialize( + &mut self, + authorized: &Authorized, + lockup: &Lockup, + ) -> Result<(), InstructionError>; fn authorize( &mut self, - authorized_pubkey: &Pubkey, + authority: &Pubkey, + stake_authorize: StakeAuthorize, other_signers: &[KeyedAccount], ) -> Result<(), InstructionError>; fn delegate_stake( @@ -415,13 +452,13 @@ pub trait StakeAccount { } impl<'a> StakeAccount for KeyedAccount<'a> { - fn lockup(&mut self, slot: Slot, custodian: &Pubkey) -> Result<(), InstructionError> { + fn initialize( + &mut self, + authorized: &Authorized, + lockup: &Lockup, + ) -> Result<(), InstructionError> { if let StakeState::Uninitialized = self.state()? { - self.set_state(&StakeState::Lockup(Lockup { - slot, - custodian: *custodian, - authority: *self.unsigned_key(), - })) + self.set_state(&StakeState::Initialized(*authorized, *lockup)) } else { Err(InstructionError::InvalidAccountData) } @@ -432,17 +469,17 @@ impl<'a> StakeAccount for KeyedAccount<'a> { fn authorize( &mut self, authority: &Pubkey, + stake_authorize: StakeAuthorize, other_signers: &[KeyedAccount], ) -> Result<(), InstructionError> { let stake_state = self.state()?; - if let StakeState::Stake(mut stake) = stake_state { - stake.check_authorized(self.signer_key(), other_signers)?; - stake.lockup.authority = *authority; - self.set_state(&StakeState::Stake(stake)) - } else if let StakeState::Lockup(mut lockup) = stake_state { - lockup.check_authorized(self.signer_key(), other_signers)?; - lockup.authority = *authority; - self.set_state(&StakeState::Lockup(lockup)) + + if let StakeState::Stake(mut authorized, lockup, stake) = stake_state { + authorized.authorize(self.signer_key(), other_signers, authority, stake_authorize)?; + self.set_state(&StakeState::Stake(authorized, lockup, stake)) + } else if let StakeState::Initialized(mut authorized, lockup) = stake_state { + authorized.authorize(self.signer_key(), other_signers, authority, stake_authorize)?; + self.set_state(&StakeState::Initialized(authorized, lockup)) } else { Err(InstructionError::InvalidAccountData) } @@ -454,26 +491,25 @@ impl<'a> StakeAccount for KeyedAccount<'a> { config: &Config, other_signers: &[KeyedAccount], ) -> Result<(), InstructionError> { - if let StakeState::Lockup(lockup) = self.state()? { - lockup.check_authorized(self.signer_key(), other_signers)?; + if let StakeState::Initialized(authorized, lockup) = self.state()? { + authorized.check(self.signer_key(), other_signers, StakeAuthorize::Staker)?; let stake = Stake::new( self.account.lamports, vote_account.unsigned_key(), &vote_account.state()?, clock.epoch, config, - &lockup, ); - self.set_state(&StakeState::Stake(stake)) - } else if let StakeState::Stake(mut stake) = self.state()? { - stake.check_authorized(self.signer_key(), other_signers)?; + self.set_state(&StakeState::Stake(authorized, lockup, stake)) + } else if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? { + authorized.check(self.signer_key(), other_signers, StakeAuthorize::Staker)?; stake.redelegate( vote_account.unsigned_key(), &vote_account.state()?, clock.epoch, )?; - self.set_state(&StakeState::Stake(stake)) + self.set_state(&StakeState::Stake(authorized, lockup, stake)) } else { Err(InstructionError::InvalidAccountData) } @@ -484,11 +520,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> { clock: &sysvar::clock::Clock, other_signers: &[KeyedAccount], ) -> Result<(), InstructionError> { - if let StakeState::Stake(mut stake) = self.state()? { - stake.check_authorized(self.signer_key(), other_signers)?; + if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? { + authorized.check(self.signer_key(), other_signers, StakeAuthorize::Staker)?; stake.deactivate(clock.epoch); - self.set_state(&StakeState::Stake(stake)) + self.set_state(&StakeState::Stake(authorized, lockup, stake)) } else { Err(InstructionError::InvalidAccountData) } @@ -500,7 +536,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { rewards: &sysvar::rewards::Rewards, stake_history: &sysvar::stake_history::StakeHistory, ) -> Result<(), InstructionError> { - if let (StakeState::Stake(mut stake), StakeState::RewardsPool) = + if let (StakeState::Stake(authorized, lockup, mut stake), StakeState::RewardsPool) = (self.state()?, rewards_account.state()?) { let vote_state: VoteState = vote_account.state()?; @@ -528,7 +564,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { stake.credits_observed = credits_observed; - self.set_state(&StakeState::Stake(stake)) + self.set_state(&StakeState::Stake(authorized, lockup, stake)) } else { // not worth collecting Err(StakeError::NoCreditsToRedeem.into()) @@ -546,8 +582,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> { other_signers: &[KeyedAccount], ) -> Result<(), InstructionError> { let lockup = match self.state()? { - StakeState::Stake(stake) => { - stake.check_authorized(self.signer_key(), other_signers)?; + StakeState::Stake(authorized, lockup, stake) => { + authorized.check(self.signer_key(), other_signers, StakeAuthorize::Withdrawer)?; // if we have a deactivation epoch and we're in cooldown let staked = if clock.epoch >= stake.deactivation_epoch { stake.stake(clock.epoch, Some(stake_history)) @@ -561,10 +597,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> { if lamports > self.account.lamports.saturating_sub(staked) { return Err(InstructionError::InsufficientFunds); } - stake.lockup + lockup } - StakeState::Lockup(lockup) => { - lockup.check_authorized(self.signer_key(), other_signers)?; + StakeState::Initialized(authorized, lockup) => { + authorized.check(self.signer_key(), other_signers, StakeAuthorize::Withdrawer)?; lockup } StakeState::Uninitialized => { @@ -615,17 +651,25 @@ where } // utility function, used by Bank, tests, genesis -pub fn create_account(voter_pubkey: &Pubkey, vote_account: &Account, lamports: u64) -> Account { +pub fn create_account( + authorized: &Pubkey, + voter_pubkey: &Pubkey, + vote_account: &Account, + lamports: u64, +) -> Account { let mut stake_account = Account::new(lamports, std::mem::size_of::(), &id()); let vote_state = VoteState::from(vote_account).expect("vote_state"); stake_account - .set_state(&StakeState::Stake(Stake::new_bootstrap( - lamports, - voter_pubkey, - &vote_state, - ))) + .set_state(&StakeState::Stake( + Authorized { + staker: *authorized, + withdrawer: *authorized, + }, + Lockup::default(), + Stake::new_bootstrap(lamports, voter_pubkey, &vote_state), + )) .expect("set_state"); stake_account @@ -691,10 +735,13 @@ mod tests { let stake_lamports = 42; let mut stake_account = Account::new_data_with_space( stake_lamports, - &StakeState::Lockup(Lockup { - authority: stake_pubkey, - ..Lockup::default() - }), + &StakeState::Initialized( + Authorized { + staker: stake_pubkey, + withdrawer: stake_pubkey, + }, + Lockup::default(), + ), std::mem::size_of::(), &id(), ) @@ -707,10 +754,13 @@ mod tests { let stake_state: StakeState = stake_keyed_account.state().unwrap(); assert_eq!( stake_state, - StakeState::Lockup(Lockup { - authority: stake_pubkey, - ..Lockup::default() - }) + StakeState::Initialized( + Authorized { + staker: stake_pubkey, + withdrawer: stake_pubkey, + }, + Lockup::default(), + ) ); } @@ -741,10 +791,6 @@ mod tests { stake: stake_lamports, activation_epoch: clock.epoch, deactivation_epoch: std::u64::MAX, - lockup: Lockup { - authority: stake_pubkey, - ..Lockup::default() - }, ..Stake::default() } ); @@ -1062,21 +1108,32 @@ mod tests { // unsigned keyed account let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); let custodian = Pubkey::new_rand(); - assert_eq!(stake_keyed_account.lockup(1, &custodian), Ok(())); + assert_eq!( + stake_keyed_account.initialize( + &Authorized { + staker: stake_pubkey, + withdrawer: stake_pubkey + }, + &Lockup { slot: 1, custodian } + ), + Ok(()) + ); // first time works, as is uninit assert_eq!( StakeState::from(&stake_keyed_account.account).unwrap(), - StakeState::Lockup(Lockup { - slot: 1, - authority: stake_pubkey, - custodian - }) + StakeState::Initialized( + Authorized { + staker: stake_pubkey, + withdrawer: stake_pubkey + }, + Lockup { slot: 1, custodian } + ) ); // 2nd time fails, can't move it from anything other than uninit->lockup assert_eq!( - stake_keyed_account.lockup(1, &Pubkey::default()), + stake_keyed_account.initialize(&Authorized::default(), &Lockup::default()), Err(InstructionError::InvalidAccountData) ); } @@ -1087,10 +1144,7 @@ mod tests { let stake_lamports = 42; let mut stake_account = Account::new_data_with_space( stake_lamports, - &StakeState::Lockup(Lockup { - authority: stake_pubkey, - ..Lockup::default() - }), + &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), std::mem::size_of::(), &id(), ) @@ -1195,7 +1249,12 @@ mod tests { // lockup let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let custodian = Pubkey::new_rand(); - stake_keyed_account.lockup(0, &custodian).unwrap(); + stake_keyed_account + .initialize( + &Authorized::auto(&stake_pubkey), + &Lockup { slot: 0, custodian }, + ) + .unwrap(); // signed keyed account and locked up, more than available should fail let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); @@ -1297,10 +1356,7 @@ mod tests { let stake_lamports = 42; let mut stake_account = Account::new_data_with_space( total_lamports, - &StakeState::Lockup(Lockup { - authority: stake_pubkey, - ..Lockup::default() - }), + &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), std::mem::size_of::(), &id(), ) @@ -1381,17 +1437,16 @@ mod tests { } #[test] - fn test_withdraw_lockout() { + fn test_withdraw_lockup() { let stake_pubkey = Pubkey::new_rand(); let custodian = Pubkey::new_rand(); let total_lamports = 100; let mut stake_account = Account::new_data_with_space( total_lamports, - &StakeState::Lockup(Lockup { - slot: 1, - authority: stake_pubkey, - custodian, - }), + &StakeState::Initialized( + Authorized::auto(&stake_pubkey), + Lockup { slot: 1, custodian }, + ), std::mem::size_of::(), &id(), ) @@ -1542,10 +1597,7 @@ mod tests { let stake_lamports = 100; let mut stake_account = Account::new_data_with_space( stake_lamports, - &StakeState::Lockup(Lockup { - authority: stake_pubkey, - ..Lockup::default() - }), + &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), std::mem::size_of::(), &id(), ) @@ -1673,10 +1725,7 @@ mod tests { let stake_lamports = 42; let mut stake_account = Account::new_data_with_space( stake_lamports, - &StakeState::Lockup(Lockup { - authority: stake_pubkey, - ..Lockup::default() - }), + &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), std::mem::size_of::(), &id(), ) @@ -1690,16 +1739,27 @@ mod tests { let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let stake_pubkey0 = Pubkey::new_rand(); - assert_eq!(stake_keyed_account.authorize(&stake_pubkey0, &[]), Ok(())); - if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap() + assert_eq!( + stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Staker, &[]), + Ok(()) + ); + assert_eq!( + stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Withdrawer, &[]), + Ok(()) + ); + if let StakeState::Initialized(authorized, _lockup) = + StakeState::from(&stake_keyed_account.account).unwrap() { - assert_eq!(lockup.authority, stake_pubkey0); + assert_eq!(authorized.staker, stake_pubkey0); + assert_eq!(authorized.withdrawer, stake_pubkey0); + } else { + assert!(false); } // A second authorization signed by the stake_keyed_account should fail let stake_pubkey1 = Pubkey::new_rand(); assert_eq!( - stake_keyed_account.authorize(&stake_pubkey1, &[]), + stake_keyed_account.authorize(&stake_pubkey1, StakeAuthorize::Staker, &[]), Err(InstructionError::MissingRequiredSignature) ); @@ -1709,18 +1769,38 @@ mod tests { // Test a second authorization by the newly authorized pubkey let stake_pubkey2 = Pubkey::new_rand(); assert_eq!( - stake_keyed_account.authorize(&stake_pubkey2, &[staker_keyed_account0]), + stake_keyed_account.authorize( + &stake_pubkey2, + StakeAuthorize::Staker, + &[staker_keyed_account0] + ), Ok(()) ); - if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap() + if let StakeState::Initialized(authorized, _lockup) = + StakeState::from(&stake_keyed_account.account).unwrap() { - assert_eq!(lockup.authority, stake_pubkey2); + assert_eq!(authorized.staker, stake_pubkey2); + } + + let staker_keyed_account0 = KeyedAccount::new(&stake_pubkey0, true, &mut staker_account0); + assert_eq!( + stake_keyed_account.authorize( + &stake_pubkey2, + StakeAuthorize::Withdrawer, + &[staker_keyed_account0] + ), + Ok(()) + ); + if let StakeState::Initialized(authorized, _lockup) = + StakeState::from(&stake_keyed_account.account).unwrap() + { + assert_eq!(authorized.staker, stake_pubkey2); } let mut staker_account2 = Account::new(1, 0, &system_program::id()); let staker_keyed_account2 = KeyedAccount::new(&stake_pubkey2, true, &mut staker_account2); - // Test an action by the currently authorized pubkey + // Test an action by the currently authorized withdrawer assert_eq!( stake_keyed_account.withdraw( stake_lamports, @@ -1739,10 +1819,7 @@ mod tests { let stake_lamports = 42; let mut stake_account = Account::new_data_with_space( stake_lamports, - &StakeState::Lockup(Lockup { - authority: stake_pubkey, - ..Lockup::default() - }), + &StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), std::mem::size_of::(), &id(), ) @@ -1762,11 +1839,11 @@ mod tests { let new_staker_pubkey = Pubkey::new_rand(); assert_eq!( - stake_keyed_account.authorize(&new_staker_pubkey, &[]), + stake_keyed_account.authorize(&new_staker_pubkey, StakeAuthorize::Staker, &[]), Ok(()) ); - let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap(); - assert_eq!(stake.lockup.authority, new_staker_pubkey); + let authorized = StakeState::authorized_from(&stake_keyed_account.account).unwrap(); + assert_eq!(authorized.staker, new_staker_pubkey); let other_pubkey = Pubkey::new_rand(); let mut other_account = Account::new(1, 0, &system_program::id()); diff --git a/programs/stake_tests/tests/stake_instruction.rs b/programs/stake_tests/tests/stake_instruction.rs index bb2cbc73e8..7ba23b5af2 100644 --- a/programs/stake_tests/tests/stake_instruction.rs +++ b/programs/stake_tests/tests/stake_instruction.rs @@ -1,20 +1,27 @@ use assert_matches::assert_matches; -use solana_runtime::bank::Bank; -use solana_runtime::bank_client::BankClient; -use solana_runtime::genesis_utils::{create_genesis_block_with_leader, GenesisBlockInfo}; -use solana_sdk::account_utils::State; -use solana_sdk::client::SyncClient; -use solana_sdk::message::Message; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, KeypairUtil}; -use solana_sdk::sysvar; -use solana_sdk::sysvar::rewards::Rewards; -use solana_stake_api::id; -use solana_stake_api::stake_instruction; -use solana_stake_api::stake_instruction::process_instruction; -use solana_stake_api::stake_state::StakeState; -use solana_vote_api::vote_instruction; -use solana_vote_api::vote_state::{Vote, VoteInit, VoteState}; +use solana_runtime::{ + bank::Bank, + bank_client::BankClient, + genesis_utils::{create_genesis_block_with_leader, GenesisBlockInfo}, +}; +use solana_sdk::{ + account_utils::State, + client::SyncClient, + message::Message, + pubkey::Pubkey, + signature::{Keypair, KeypairUtil}, + sysvar, + sysvar::rewards::Rewards, +}; +use solana_stake_api::{ + id, + stake_instruction::{self, process_instruction}, + stake_state::{self, StakeState}, +}; +use solana_vote_api::{ + vote_instruction, + vote_state::{Vote, VoteInit, VoteState}, +}; use std::sync::Arc; fn fill_epoch_with_votes( @@ -88,12 +95,14 @@ fn test_stake_account_delegate() { .send_message(&[&mint_keypair], message) .expect("failed to create vote account"); + let authorized = stake_state::Authorized::auto(&staker_pubkey); // Create stake account and delegate to vote account let message = Message::new(stake_instruction::create_stake_account_and_delegate_stake( &mint_pubkey, &staker_pubkey, &vote_pubkey, 20000, + &authorized, )); bank_client .send_message(&[&mint_keypair, &staker_keypair], message) @@ -102,7 +111,7 @@ fn test_stake_account_delegate() { // Test that correct lamports are staked let account = bank.get_account(&staker_pubkey).expect("account not found"); let stake_state = account.state().expect("couldn't unpack account data"); - if let StakeState::Stake(stake) = stake_state { + if let StakeState::Stake(_authorized, _lockup, stake) = stake_state { assert_eq!(stake.stake, 20000); } else { assert!(false, "wrong account type found") @@ -124,7 +133,7 @@ fn test_stake_account_delegate() { // Test that lamports are still staked let account = bank.get_account(&staker_pubkey).expect("account not found"); let stake_state = account.state().expect("couldn't unpack account data"); - if let StakeState::Stake(stake) = stake_state { + if let StakeState::Stake(_authorized, _lockup, stake) = stake_state { assert_eq!(stake.stake, 20000); } else { assert!(false, "wrong account type found") @@ -168,7 +177,7 @@ fn test_stake_account_delegate() { let rewards; let account = bank.get_account(&staker_pubkey).expect("account not found"); let stake_state = account.state().expect("couldn't unpack account data"); - if let StakeState::Stake(stake) = stake_state { + if let StakeState::Stake(_authorized, _lockup, stake) = stake_state { assert!(account.lamports > 20000); assert_eq!(stake.stake, 20000); rewards = account.lamports - 20000; @@ -251,7 +260,7 @@ fn test_stake_account_delegate() { // Test that balance and stake is updated correctly (we have withdrawn all lamports except rewards) let account = bank.get_account(&staker_pubkey).expect("account not found"); let stake_state = account.state().expect("couldn't unpack account data"); - if let StakeState::Stake(_stake) = stake_state { + if let StakeState::Stake(_, _, _stake) = stake_state { assert_eq!(account.lamports, rewards); } else { assert!(false, "wrong account type found") diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index 09d12aed93..f4c3616548 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -41,6 +41,7 @@ pub fn create_genesis_block_with_leader( ); let stake_account = stake_state::create_account( + &staking_keypair.pubkey(), &voting_keypair.pubkey(), &vote_account, bootstrap_leader_stake_lamports, diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 411867fd7a..f01193ce1f 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -220,9 +220,11 @@ pub mod tests { // add stake to a vote_pubkey ( stake ) pub fn create_stake_account(stake: u64, vote_pubkey: &Pubkey) -> (Pubkey, Account) { + let stake_pubkey = Pubkey::new_rand(); ( - Pubkey::new_rand(), + stake_pubkey, stake_state::create_account( + &stake_pubkey, &vote_pubkey, &vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 1), stake,