Sfoglia il codice sorgente

add authorities to stake init (#6104)

* add authorities to stake init

* fixups

* code review
Rob Walker 6 anni fa
parent
commit
a964570b1a

+ 44 - 7
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<u64>,
     },
-    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

+ 6 - 4
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

+ 5 - 1
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),
             ),
         );
     }

+ 1 - 0
genesis/src/main.rs

@@ -315,6 +315,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
         1,
     );
     let stake_account = stake_state::create_account(
+        &bootstrap_stake_keypair.pubkey(),
         &bootstrap_vote_keypair.pubkey(),
         &vote_account,
         bootstrap_leader_stake_lamports,

+ 5 - 1
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,
             );

+ 57 - 36
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<Instruction> {
     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<Instruction> {
-    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<Instruction> {
-    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<AccountMeta> {
-    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),
         );

+ 214 - 137
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<Authorized> {
+        Self::from(account).and_then(|state: Self| state.authorized())
+    }
+
     pub fn stake(&self) -> Option<Stake> {
         match self {
-            StakeState::Stake(stake) => Some(stake.clone()),
+            StakeState::Stake(_authorized, _lockup, stake) => Some(*stake),
+            _ => None,
+        }
+    }
+    pub fn authorized(&self) -> Option<Authorized> {
+        match self {
+            StakeState::Stake(authorized, _lockup, _stake) => Some(*authorized),
             _ => None,
         }
     }
 }
 
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
+pub enum StakeAuthorize {
+    Staker,
+    Withdrawer,
+}
+
 #[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
 pub struct Lockup {
-    /// slot height at which this stake will allow withdrawal, unless to the custodian
+    /// 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
+    ///  withdrawal before lockup expires.  After lockup expires, custodian
+    ///  is irrelevant
     pub custodian: Pubkey,
-    /// alternate signer that is enabled to act on the Stake account
-    pub authority: Pubkey,
 }
 
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+#[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,18 +125,52 @@ impl Default for Stake {
     }
 }
 
-impl Stake {
-    fn is_bootstrap(&self) -> bool {
-        self.activation_epoch == std::u64::MAX
+impl Authorized {
+    pub fn auto(authorized: &Pubkey) -> Self {
+        Self {
+            staker: *authorized,
+            withdrawer: *authorized,
+        }
     }
-
-    fn check_authorized(
+    pub fn check(
         &self,
-        stake_pubkey_signer: Option<&Pubkey>,
+        stake_signer: Option<&Pubkey>,
         other_signers: &[KeyedAccount],
+        stake_authorize: StakeAuthorize,
     ) -> Result<(), InstructionError> {
-        self.lockup
-            .check_authorized(stake_pubkey_signer, other_signers)
+        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
     }
 
     pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
@@ -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::<StakeState>(), &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::<StakeState>(),
             &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::<StakeState>(),
             &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::<StakeState>(),
             &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::<StakeState>(),
             &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::<StakeState>(),
             &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::<StakeState>(),
             &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::<StakeState>(),
             &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());

+ 29 - 20
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")

+ 1 - 0
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,

+ 3 - 1
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,