add stake lockup (#5782)

* add stake lockup

* fixup
This commit is contained in:
Rob Walker 2019-09-04 13:34:09 -07:00 committed by GitHub
parent 94eb78d399
commit 933e835838
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 203 additions and 50 deletions

View File

@ -19,7 +19,7 @@ source scripts/ulimit-n.sh
# Clear cached json keypair files # Clear cached json keypair files
rm -rf "$HOME/.config/solana" rm -rf "$HOME/.config/solana"
# Clear the C dependency files, if dependeny moves these files are not regenerated # Clear the C dependency files, if dependency moves these files are not regenerated
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete test -d target/release/bpf && find target/release/bpf -name '*.d' -delete

View File

@ -895,7 +895,9 @@ fn process_show_stake_account(
Ok("".to_string()) Ok("".to_string())
} }
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()), Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()), Ok(StakeState::Uninitialized) | Ok(StakeState::Lockup(_)) => {
Ok("Stake account is uninitialized".to_string())
}
Err(err) => Err(WalletError::RpcRequestError(format!( Err(err) => Err(WalletError::RpcRequestError(format!(
"Account data could not be deserialized to stake state: {:?}", "Account data could not be deserialized to stake state: {:?}",
err err

View File

@ -10,14 +10,25 @@ use solana_sdk::{
instruction::{AccountMeta, Instruction, InstructionError}, instruction::{AccountMeta, Instruction, InstructionError},
pubkey::Pubkey, pubkey::Pubkey,
system_instruction, sysvar, system_instruction, sysvar,
timing::Slot,
}; };
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum StakeInstruction { pub enum StakeInstruction {
/// `Lockup` a stake until the specified slot
///
/// Expects 1 Account:
/// 0 - Uninitialized StakeAccount to be lockup'd
///
/// The u64 is the portion of the Stake account balance to be activated,
/// must be less than StakeAccount.lamports
///
Lockup(Slot),
/// `Delegate` a stake to a particular node /// `Delegate` a stake to a particular node
/// ///
/// Expects 3 Accounts: /// Expects 3 Accounts:
/// 0 - Uninitialized StakeAccount to be delegated <= must have this signature /// 0 - Lockup'd StakeAccount to be delegated <= must have this signature
/// 1 - VoteAccount to which this Stake will be delegated /// 1 - VoteAccount to which this Stake will be delegated
/// 2 - Clock sysvar Account that carries clock bank epoch /// 2 - Clock sysvar Account that carries clock bank epoch
/// 3 - Config Account that carries stake config /// 3 - Config Account that carries stake config
@ -58,28 +69,44 @@ pub enum StakeInstruction {
Deactivate, Deactivate,
} }
pub fn create_stake_account_with_lockup(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
lamports: u64,
lockup: Slot,
) -> Vec<Instruction> {
vec![
system_instruction::create_account(
from_pubkey,
stake_pubkey,
lamports,
std::mem::size_of::<StakeState>() as u64,
&id(),
),
Instruction::new(
id(),
&StakeInstruction::Lockup(lockup),
vec![AccountMeta::new(*stake_pubkey, false)],
),
]
}
pub fn create_stake_account( pub fn create_stake_account(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
staker_pubkey: &Pubkey, stake_pubkey: &Pubkey,
lamports: u64, lamports: u64,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
vec![system_instruction::create_account( create_stake_account_with_lockup(from_pubkey, stake_pubkey, lamports, 0)
from_pubkey,
staker_pubkey,
lamports,
std::mem::size_of::<StakeState>() as u64,
&id(),
)]
} }
pub fn create_stake_account_and_delegate_stake( pub fn create_stake_account_and_delegate_stake(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
staker_pubkey: &Pubkey, stake_pubkey: &Pubkey,
vote_pubkey: &Pubkey, vote_pubkey: &Pubkey,
lamports: u64, lamports: u64,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
let mut instructions = create_stake_account(from_pubkey, staker_pubkey, lamports); let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports);
instructions.push(delegate_stake(staker_pubkey, vote_pubkey, lamports)); instructions.push(delegate_stake(stake_pubkey, vote_pubkey, lamports));
instructions instructions
} }
@ -142,6 +169,7 @@ pub fn process_instruction(
// TODO: data-driven unpack and dispatch of KeyedAccounts // TODO: data-driven unpack and dispatch of KeyedAccounts
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
StakeInstruction::Lockup(slot) => me.lockup(slot),
StakeInstruction::DelegateStake(stake) => { StakeInstruction::DelegateStake(stake) => {
if rest.len() != 3 { if rest.len() != 3 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;

View File

@ -14,13 +14,14 @@ use solana_sdk::{
self, self,
stake_history::{StakeHistory, StakeHistoryEntry}, stake_history::{StakeHistory, StakeHistoryEntry},
}, },
timing::Epoch, timing::{Epoch, Slot},
}; };
use solana_vote_api::vote_state::VoteState; use solana_vote_api::vote_state::VoteState;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum StakeState { pub enum StakeState {
Uninitialized, Uninitialized,
Lockup(Slot),
Stake(Stake), Stake(Stake),
RewardsPool, RewardsPool,
} }
@ -57,6 +58,7 @@ pub struct Stake {
pub activation_epoch: Epoch, // epoch the stake was activated, std::Epoch::MAX if is a bootstrap stake pub activation_epoch: Epoch, // epoch the stake was activated, std::Epoch::MAX if is a bootstrap stake
pub deactivation_epoch: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated pub deactivation_epoch: Epoch, // epoch the stake was deactivated, std::Epoch::MAX if not deactivated
pub config: Config, pub config: Config,
pub lockup: Slot,
} }
impl Default for Stake { impl Default for Stake {
@ -68,6 +70,7 @@ impl Default for Stake {
activation_epoch: 0, activation_epoch: 0,
deactivation_epoch: std::u64::MAX, deactivation_epoch: std::u64::MAX,
config: Config::default(), config: Config::default(),
lockup: 0,
} }
} }
} }
@ -256,6 +259,7 @@ impl Stake {
vote_state, vote_state,
std::u64::MAX, std::u64::MAX,
&Config::default(), &Config::default(),
0,
) )
} }
@ -265,6 +269,7 @@ impl Stake {
vote_state: &VoteState, vote_state: &VoteState,
activation_epoch: Epoch, activation_epoch: Epoch,
config: &Config, config: &Config,
lockup: Slot,
) -> Self { ) -> Self {
Self { Self {
stake, stake,
@ -272,6 +277,7 @@ impl Stake {
voter_pubkey: *voter_pubkey, voter_pubkey: *voter_pubkey,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
config: *config, config: *config,
lockup,
..Stake::default() ..Stake::default()
} }
} }
@ -282,6 +288,7 @@ impl Stake {
} }
pub trait StakeAccount { pub trait StakeAccount {
fn lockup(&mut self, slot: Slot) -> Result<(), InstructionError>;
fn delegate_stake( fn delegate_stake(
&mut self, &mut self,
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
@ -311,6 +318,13 @@ pub trait StakeAccount {
} }
impl<'a> StakeAccount for KeyedAccount<'a> { impl<'a> StakeAccount for KeyedAccount<'a> {
fn lockup(&mut self, lockup: Slot) -> Result<(), InstructionError> {
if let StakeState::Uninitialized = self.state()? {
self.set_state(&StakeState::Lockup(lockup))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn delegate_stake( fn delegate_stake(
&mut self, &mut self,
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
@ -326,13 +340,14 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
if let StakeState::Uninitialized = self.state()? { if let StakeState::Lockup(lockup) = self.state()? {
let stake = Stake::new( let stake = Stake::new(
new_stake, new_stake,
vote_account.unsigned_key(), vote_account.unsigned_key(),
&vote_account.state()?, &vote_account.state()?,
clock.epoch, clock.epoch,
config, config,
lockup,
); );
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(stake))
@ -410,6 +425,19 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
fn transfer(
from: &mut Account,
to: &mut Account,
lamports: u64,
) -> Result<(), InstructionError> {
if lamports > from.lamports {
return Err(InstructionError::InsufficientFunds);
}
from.lamports -= lamports;
to.lamports += lamports;
Ok(())
}
match self.state()? { match self.state()? {
StakeState::Stake(stake) => { StakeState::Stake(stake) => {
// if we have a deactivation epoch and we're in cooldown // if we have a deactivation epoch and we're in cooldown
@ -425,20 +453,16 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
if lamports > self.account.lamports.saturating_sub(staked) { if lamports > self.account.lamports.saturating_sub(staked) {
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
self.account.lamports -= lamports;
to.account.lamports += lamports;
Ok(())
} }
StakeState::Uninitialized => { StakeState::Lockup(lockup) => {
if lamports > self.account.lamports { if lockup > clock.slot {
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
self.account.lamports -= lamports;
to.account.lamports += lamports;
Ok(())
} }
_ => Err(InstructionError::InvalidAccountData), StakeState::Uninitialized => {}
_ => return Err(InstructionError::InvalidAccountData),
} }
transfer(&mut self.account, &mut to.account, lamports)
} }
} }
@ -546,15 +570,20 @@ mod tests {
let stake_pubkey = Pubkey::default(); let stake_pubkey = Pubkey::default();
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = let mut stake_account = Account::new_data_with_space(
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id()); stake_lamports,
&StakeState::Lockup(0),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
// unsigned keyed account // unsigned keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
{ {
let stake_state: StakeState = stake_keyed_account.state().unwrap(); let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!(stake_state, StakeState::default()); assert_eq!(stake_state, StakeState::Lockup(0));
} }
assert_eq!( assert_eq!(
@ -583,7 +612,8 @@ mod tests {
stake: stake_lamports, stake: stake_lamports,
activation_epoch: clock.epoch, activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX, deactivation_epoch: std::u64::MAX,
config: Config::default() config: Config::default(),
lockup: 0
}) })
); );
// verify that delegate_stake can't be called twice StakeState::default() // verify that delegate_stake can't be called twice StakeState::default()
@ -865,12 +895,41 @@ mod tests {
} }
#[test] #[test]
fn test_deactivate_stake() { fn test_stake_lockup() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = let mut stake_account =
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id()); Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
// unsigned keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!(stake_keyed_account.lockup(1), Ok(()));
// first time works, as is uninit
assert_eq!(
StakeState::from(&stake_keyed_account.account).unwrap(),
StakeState::Lockup(1)
);
// 2nd time fails, can't move it from anything other than uninit->lockup
assert_eq!(
stake_keyed_account.lockup(1),
Err(InstructionError::InvalidAccountData)
);
}
#[test]
fn test_deactivate_stake() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Lockup(0),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let clock = sysvar::clock::Clock { let clock = sysvar::clock::Clock {
epoch: 1, epoch: 1,
..sysvar::clock::Clock::default() ..sysvar::clock::Clock::default()
@ -923,8 +982,13 @@ mod tests {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let total_lamports = 100; let total_lamports = 100;
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = let mut stake_account = Account::new_data_with_space(
Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id()); total_lamports,
&StakeState::Lockup(0),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let mut clock = sysvar::clock::Clock::default(); let mut clock = sysvar::clock::Clock::default();
@ -1051,8 +1115,13 @@ mod tests {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let total_lamports = 100; let total_lamports = 100;
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = let mut stake_account = Account::new_data_with_space(
Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id()); total_lamports,
&StakeState::Lockup(0),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let clock = sysvar::clock::Clock::default(); let clock = sysvar::clock::Clock::default();
let mut future = sysvar::clock::Clock::default(); let mut future = sysvar::clock::Clock::default();
@ -1102,21 +1171,49 @@ mod tests {
fn test_withdraw_stake_invalid_state() { fn test_withdraw_stake_invalid_state() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let total_lamports = 100; let total_lamports = 100;
let mut stake_account = let mut stake_account = Account::new_data_with_space(
Account::new(total_lamports, std::mem::size_of::<StakeState>(), &id()); total_lamports,
&StakeState::RewardsPool,
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");;
let clock = sysvar::clock::Clock::default(); let to = Pubkey::new_rand();
let mut future = sysvar::clock::Clock::default(); let mut to_account = Account::new(1, 0, &system_program::id());
future.epoch += 16; let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.withdraw(
total_lamports,
&mut to_keyed_account,
&sysvar::clock::Clock::default(),
&StakeHistory::default()
),
Err(InstructionError::InvalidAccountData)
);
}
#[test]
fn test_withdraw_lockout() {
let stake_pubkey = Pubkey::new_rand();
let total_lamports = 100;
let mut stake_account = Account::new_data_with_space(
total_lamports,
&StakeState::Lockup(1),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let to = Pubkey::new_rand(); let to = Pubkey::new_rand();
let mut to_account = Account::new(1, 0, &system_program::id()); let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account); let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let stake_state = StakeState::RewardsPool;
stake_keyed_account.set_state(&stake_state).unwrap();
let mut clock = sysvar::clock::Clock::default();
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports, total_lamports,
@ -1124,7 +1221,18 @@ mod tests {
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
), ),
Err(InstructionError::InvalidAccountData) Err(InstructionError::InsufficientFunds)
);
clock.slot += 1;
assert_eq!(
stake_keyed_account.withdraw(
total_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default()
),
Ok(())
); );
} }
@ -1221,8 +1329,14 @@ mod tests {
let pubkey = Pubkey::default(); let pubkey = Pubkey::default();
let stake_lamports = 100; let stake_lamports = 100;
let mut stake_account = let mut stake_account = Account::new_data_with_space(
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id()); stake_lamports,
&StakeState::Lockup(0),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
let vote_pubkey = Pubkey::new_rand(); let vote_pubkey = Pubkey::new_rand();

View File

@ -1,7 +1,7 @@
use bincode::{deserialize_from, serialize_into, serialized_size}; use bincode::{deserialize_from, serialize_into, serialized_size};
use memmap::MmapMut; use memmap::MmapMut;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::{account::Account, pubkey::Pubkey, Epoch}; use solana_sdk::{account::Account, pubkey::Pubkey, timing::Epoch};
use std::fmt; use std::fmt;
use std::fs::{create_dir_all, remove_file, OpenOptions}; use std::fs::{create_dir_all, remove_file, OpenOptions};
use std::io; use std::io;

View File

@ -1,5 +1,4 @@
use crate::pubkey::Pubkey; use crate::{pubkey::Pubkey, timing::Epoch};
use crate::Epoch;
use std::{cmp, fmt}; use std::{cmp, fmt};
/// An Account with data that is stored on chain /// An Account with data that is stored on chain
@ -64,6 +63,19 @@ impl Account {
}) })
} }
pub fn new_data_with_space<T: serde::Serialize>(
lamports: u64,
state: &T,
space: usize,
owner: &Pubkey,
) -> Result<Account, bincode::Error> {
let mut account = Self::new(lamports, space, owner);
account.serialize_data(state)?;
Ok(account)
}
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> { pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
bincode::deserialize(&self.data) bincode::deserialize(&self.data)
} }

View File

@ -28,6 +28,3 @@ pub mod transport;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
pub type Epoch = u64;
pub type Slot = u64;