Remove support for stake redelegation (#7995)

* Remove support for stake redelegation

* fixup
This commit is contained in:
Rob Walker 2020-01-29 17:59:14 -08:00 committed by GitHub
parent effe6e3ff3
commit e6803daf10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 230 additions and 215 deletions

View File

@ -94,12 +94,13 @@ The Stakes and the RewardsPool are accounts that are owned by the same `Stake` p
### StakeInstruction::DelegateStake
The Stake account is moved from Ininitialized to StakeState::Stake form. This is how stakers choose their initial delegate validator node and activate their stake account lamports. The transaction must be signed by the stake's `authorized_staker`. If the stake account is already StakeState::Stake \(i.e. already activated\), the stake is re-delegated. Stakes may be re-delegated at any time, and updated stakes are reflected immediately, but only one re-delegation is permitted per epoch.
The Stake account is moved from Initialized to StakeState::Stake form, or from a deactivated (i.e. fully cooled-down) StakeState::Stake to activated StakeState::Stake. This is how stakers choose the vote account and validator node to which their stake account lamports are delegated. The transaction must be signed by the stake's `authorized_staker`.
* `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX
* `account[1]` - R - The VoteState instance.
* `account[2]` - R - sysvar::clock account, carries information about current Bank epoch
* `account[3]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
* `account[3]` - R - sysvar::stakehistory account, carries information about stake history
* `account[4]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
### StakeInstruction::Authorize\(Pubkey, StakeAuthorize\)

View File

@ -306,6 +306,7 @@ pub fn delegate_stake(
AccountMeta::new(*stake_pubkey, false),
AccountMeta::new_readonly(*vote_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(crate::config::id(), false),
]
.with_signer(authorized_pubkey);
@ -396,9 +397,10 @@ pub fn process_instruction(
StakeInstruction::DelegateStake => {
let vote = next_keyed_account(keyed_accounts)?;
me.delegate_stake(
me.delegate(
&vote,
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&StakeHistory::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&config::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&signers,
)
@ -418,7 +420,7 @@ pub fn process_instruction(
&signers,
)
}
StakeInstruction::Deactivate => me.deactivate_stake(
StakeInstruction::Deactivate => me.deactivate(
&Clock::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
&signers,
),
@ -658,6 +660,13 @@ mod tests {
false,
&RefCell::new(sysvar::clock::Clock::default().create_account(1))
),
KeyedAccount::new(
&sysvar::stake_history::id(),
false,
&RefCell::new(
sysvar::stake_history::StakeHistory::default().create_account(1)
)
),
KeyedAccount::new(
&config::id(),
false,

View File

@ -8,14 +8,11 @@ use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
account::{Account, KeyedAccount},
account_utils::{State, StateMut},
clock::{Clock, Epoch, Slot, UnixTimestamp},
clock::{Clock, Epoch, UnixTimestamp},
instruction::InstructionError,
pubkey::Pubkey,
rent::Rent,
sysvar::{
self,
stake_history::{StakeHistory, StakeHistoryEntry},
},
stake_history::{StakeHistory, StakeHistoryEntry},
};
use solana_vote_program::vote_state::VoteState;
use std::collections::HashSet;
@ -154,7 +151,7 @@ impl Meta {
pub struct Delegation {
/// to whom the stake is delegated
pub voter_pubkey: Pubkey,
/// activated stake amount, set at delegate_stake() time
/// activated stake amount, set at delegate() time
pub stake: u64,
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
pub activation_epoch: Epoch,
@ -311,32 +308,11 @@ impl Delegation {
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Stake {
pub delegation: Delegation,
/// the epoch when voter_pubkey was most recently set
pub voter_pubkey_epoch: Epoch,
/// credits observed is credits from vote account state when delegated or redeemed
pub credits_observed: u64,
/// history of prior delegates and the epoch ranges for which
/// they were set, circular buffer
pub prior_delegates: [(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES],
/// next pointer
pub prior_delegates_idx: usize,
}
const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exposed to a slashing condition
impl Default for Stake {
fn default() -> Self {
Self {
delegation: Delegation::default(),
voter_pubkey_epoch: 0,
credits_observed: 0,
prior_delegates: <[(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES]>::default(),
prior_delegates_idx: MAX_PRIOR_DELEGATES - 1,
}
}
}
impl Authorized {
@ -455,25 +431,19 @@ impl Stake {
voter_pubkey: &Pubkey,
vote_state: &VoteState,
clock: &Clock,
stake_history: &StakeHistory,
config: &Config,
) -> Result<(), StakeError> {
// only one re-delegation supported per epoch
if self.voter_pubkey_epoch == clock.epoch {
// can't redelegate if stake is active. either the stake
// is freshly activated or has fully de-activated. redelegation
// implies re-activation
if self.stake(clock.epoch, Some(stake_history)) != 0 {
return Err(StakeError::TooSoonToRedelegate);
}
// remember prior delegate and when we switched, to support later slashing
self.prior_delegates_idx += 1;
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
self.prior_delegates[self.prior_delegates_idx] = (
self.delegation.voter_pubkey,
self.voter_pubkey_epoch,
clock.epoch,
clock.slot,
);
self.delegation.activation_epoch = clock.epoch;
self.delegation.deactivation_epoch = std::u64::MAX;
self.delegation.voter_pubkey = *voter_pubkey;
self.voter_pubkey_epoch = clock.epoch;
self.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate;
self.credits_observed = vote_state.credits();
Ok(())
}
@ -507,9 +477,7 @@ impl Stake {
activation_epoch,
config.warmup_cooldown_rate,
),
voter_pubkey_epoch: activation_epoch,
credits_observed: vote_state.credits(),
..Stake::default()
}
}
@ -537,18 +505,15 @@ pub trait StakeAccount {
signers: &HashSet<Pubkey>,
clock: &Clock,
) -> Result<(), InstructionError>;
fn delegate_stake(
fn delegate(
&self,
vote_account: &KeyedAccount,
clock: &sysvar::clock::Clock,
clock: &Clock,
stake_history: &StakeHistory,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn deactivate_stake(
&self,
clock: &sysvar::clock::Clock,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError>;
fn set_lockup(
&self,
lockup: &Lockup,
@ -564,8 +529,8 @@ pub trait StakeAccount {
&self,
lamports: u64,
to: &KeyedAccount,
clock: &sysvar::clock::Clock,
stake_history: &sysvar::stake_history::StakeHistory,
clock: &Clock,
stake_history: &StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
}
@ -616,10 +581,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
_ => Err(InstructionError::InvalidAccountData),
}
}
fn delegate_stake(
fn delegate(
&self,
vote_account: &KeyedAccount,
clock: &sysvar::clock::Clock,
clock: &Clock,
stake_history: &StakeHistory,
config: &Config,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
@ -637,17 +603,19 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
}
StakeState::Stake(meta, mut stake) => {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?;
stake.redelegate(
vote_account.unsigned_key(),
&vote_account.state()?,
clock,
stake_history,
config,
)?;
self.set_state(&StakeState::Stake(meta, stake))
}
_ => Err(InstructionError::InvalidAccountData),
}
}
fn deactivate_stake(
&self,
clock: &sysvar::clock::Clock,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
fn deactivate(&self, clock: &Clock, signers: &HashSet<Pubkey>) -> Result<(), InstructionError> {
if let StakeState::Stake(meta, mut stake) = self.state()? {
meta.authorized.check(signers, StakeAuthorize::Staker)?;
stake.deactivate(clock.epoch)?;
@ -743,8 +711,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
&self,
lamports: u64,
to: &KeyedAccount,
clock: &sysvar::clock::Clock,
stake_history: &sysvar::stake_history::StakeHistory,
clock: &Clock,
stake_history: &StakeHistory,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> {
let (lockup, reserve, is_staked) = match self.state()? {
@ -1012,10 +980,10 @@ mod tests {
}
#[test]
fn test_stake_delegate_stake() {
let mut clock = sysvar::clock::Clock {
fn test_stake_delegate() {
let mut clock = Clock {
epoch: 1,
..sysvar::clock::Clock::default()
..Clock::default()
};
let vote_pubkey = Pubkey::new_rand();
@ -1052,25 +1020,12 @@ mod tests {
// unsigned keyed account
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
{
let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!(
stake_state,
StakeState::Initialized(Meta {
authorized: Authorized {
staker: stake_pubkey,
withdrawer: stake_pubkey,
},
..Meta::default()
})
);
}
let mut signers = HashSet::default();
assert_eq!(
stake_keyed_account.delegate_stake(
stake_keyed_account.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers,
),
@ -1081,10 +1036,16 @@ mod tests {
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
signers.insert(stake_pubkey);
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers,)
.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers,
)
.is_ok());
// verify that delegate_stake() looks right, compare against hand-rolled
// verify that delegate() looks right, compare against hand-rolled
let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap();
assert_eq!(
stake,
@ -1096,79 +1057,74 @@ mod tests {
deactivation_epoch: std::u64::MAX,
..Delegation::default()
},
voter_pubkey_epoch: clock.epoch,
credits_observed: vote_state.credits(),
..Stake::default()
}
);
// verify that delegate_stake can be called twice, 2nd is redelegate
clock.epoch += 1;
// verify that delegate fails if stake is still active
assert_eq!(
stake_keyed_account.delegate_stake(
stake_keyed_account.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers
),
Err(StakeError::TooSoonToRedelegate.into())
);
// deactivate, so we can re-delegate
stake_keyed_account.deactivate(&clock, &signers).unwrap();
// without stake history, cool down is instantaneous
clock.epoch += 1;
// verify that delegate_stake can be called twice, 2nd is redelegate
// verify that delegate can be called twice, 2nd is redelegate
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers
)
.is_ok());
// verify that non-stakes fail delegate_stake()
// verify that delegate() looks right, compare against hand-rolled
let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap();
assert_eq!(
stake,
Stake {
delegation: Delegation {
voter_pubkey: vote_pubkey,
stake: stake_lamports,
activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX,
..Delegation::default()
},
credits_observed: vote_state.credits(),
..Stake::default()
}
);
// verify that non-stakes fail delegate()
let stake_state = StakeState::RewardsPool;
stake_keyed_account.set_state(&stake_state).unwrap();
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers
)
.is_err());
}
#[test]
fn test_stake_redelegate() {
// what a freshly delegated stake looks like
let mut stake = Stake {
delegation: Delegation {
voter_pubkey: Pubkey::new_rand(),
..Delegation::default()
},
voter_pubkey_epoch: 0,
..Stake::default()
};
// verify that redelegation works when epoch is changing, that
// wraparound works, and that the stake is delegated
// to the most recent vote account
for epoch in 1..=MAX_PRIOR_DELEGATES + 2 {
let voter_pubkey = Pubkey::new_rand();
assert_eq!(
stake.redelegate(
&voter_pubkey,
&VoteState::default(),
&sysvar::clock::Clock {
epoch: epoch as u64,
..sysvar::clock::Clock::default()
},
),
Ok(())
);
assert_eq!(
stake.redelegate(
&voter_pubkey,
&VoteState::default(),
&sysvar::clock::Clock {
epoch: epoch as u64,
..sysvar::clock::Clock::default()
},
),
Err(StakeError::TooSoonToRedelegate)
);
assert_eq!(stake.delegation.voter_pubkey, voter_pubkey);
}
}
fn create_stake_history_from_delegations(
bootstrap: Option<u64>,
epochs: std::ops::Range<Epoch>,
@ -1546,7 +1502,7 @@ mod tests {
}
#[test]
fn test_deactivate_stake() {
fn test_deactivate() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let stake_account = Account::new_ref_data_with_space(
@ -1557,16 +1513,16 @@ mod tests {
)
.expect("stake_account");
let clock = sysvar::clock::Clock {
let clock = Clock {
epoch: 1,
..sysvar::clock::Clock::default()
..Clock::default()
};
// signed keyed account but not staked yet
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
stake_keyed_account.deactivate(&clock, &signers),
Err(InstructionError::InvalidAccountData)
);
@ -1581,9 +1537,10 @@ mod tests {
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!(
stake_keyed_account.delegate_stake(
stake_keyed_account.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers
),
@ -1593,20 +1550,17 @@ mod tests {
// no signers fails
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &HashSet::default()),
stake_keyed_account.deactivate(&clock, &HashSet::default()),
Err(InstructionError::MissingRequiredSignature)
);
// Deactivate after staking
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
Ok(())
);
assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(()));
// verify that deactivate() only works once
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
stake_keyed_account.deactivate(&clock, &signers),
Err(StakeError::AlreadyDeactivated.into())
);
}
@ -1673,9 +1627,10 @@ mod tests {
vote_keyed_account.set_state(&VoteState::default()).unwrap();
stake_keyed_account
.delegate_stake(
.delegate(
&vote_keyed_account,
&Clock::default(),
&StakeHistory::default(),
&Config::default(),
&vec![stake_pubkey].into_iter().collect(),
)
@ -1717,7 +1672,7 @@ mod tests {
)
.expect("stake_account");
let mut clock = sysvar::clock::Clock::default();
let mut clock = Clock::default();
let to = Pubkey::new_rand();
let to_account = Account::new_ref(1, 0, &system_program::id());
@ -1795,9 +1750,10 @@ mod tests {
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!(
stake_keyed_account.delegate_stake(
stake_keyed_account.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers,
),
@ -1837,10 +1793,7 @@ mod tests {
);
// deactivate the stake before withdrawal
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &signers),
Ok(())
);
assert_eq!(stake_keyed_account.deactivate(&clock, &signers), Ok(()));
// simulate time passing
clock.epoch += 100;
@ -1885,8 +1838,8 @@ mod tests {
)
.expect("stake_account");
let clock = sysvar::clock::Clock::default();
let mut future = sysvar::clock::Clock::default();
let clock = Clock::default();
let mut future = Clock::default();
future.epoch += 16;
let to = Pubkey::new_rand();
@ -1907,9 +1860,10 @@ mod tests {
vote_keyed_account.set_state(&VoteState::default()).unwrap();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
stake_keyed_account.delegate_stake(
stake_keyed_account.delegate(
&vote_keyed_account,
&future,
&StakeHistory::default(),
&Config::default(),
&signers,
),
@ -1960,7 +1914,7 @@ mod tests {
stake_keyed_account.withdraw(
total_lamports,
&to_keyed_account,
&sysvar::clock::Clock::default(),
&Clock::default(),
&StakeHistory::default(),
&signers,
),
@ -1994,7 +1948,7 @@ mod tests {
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
let mut clock = sysvar::clock::Clock::default();
let mut clock = Clock::default();
let signers = vec![stake_pubkey].into_iter().collect();
@ -2202,7 +2156,7 @@ mod tests {
let to_account = Account::new_ref(1, 0, &system_program::id());
let to_keyed_account = KeyedAccount::new(&to, false, &to_account);
let clock = sysvar::clock::Clock::default();
let clock = Clock::default();
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
let stake_pubkey0 = Pubkey::new_rand();
@ -2816,7 +2770,7 @@ mod tests {
)
.expect("stake_account");
let mut clock = Clock::default();
let clock = Clock::default();
let vote_pubkey = Pubkey::new_rand();
let vote_account = RefCell::new(vote_state::create_account(
@ -2830,9 +2784,18 @@ mod tests {
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
let signers = vec![stake_pubkey].into_iter().collect();
stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.delegate(
&vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&signers,
)
.unwrap();
// deactivate, so we can re-delegate
stake_keyed_account.deactivate(&clock, &signers).unwrap();
let new_staker_pubkey = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.authorize(
@ -2864,13 +2827,12 @@ mod tests {
let new_vote_keyed_account = KeyedAccount::new(&new_voter_pubkey, false, &new_vote_account);
new_vote_keyed_account.set_state(&vote_state).unwrap();
// time passes, so we can re-delegate
clock.epoch += 1;
// Random other account should fail
assert_eq!(
stake_keyed_account.delegate_stake(
stake_keyed_account.delegate(
&new_vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&other_signers,
),
@ -2880,9 +2842,10 @@ mod tests {
let new_signers = vec![new_staker_pubkey].into_iter().collect();
// Authorized staker should succeed
assert_eq!(
stake_keyed_account.delegate_stake(
stake_keyed_account.delegate(
&new_vote_keyed_account,
&clock,
&StakeHistory::default(),
&Config::default(),
&new_signers
),
@ -2893,9 +2856,6 @@ mod tests {
assert_eq!(stake.delegation.voter_pubkey, new_voter_pubkey);
// Test another staking action
assert_eq!(
stake_keyed_account.deactivate_stake(&clock, &new_signers),
Ok(())
);
assert_eq!(stake_keyed_account.deactivate(&clock, &new_signers), Ok(()));
}
}

View File

@ -26,6 +26,7 @@ pub mod rpc_port;
pub mod short_vec;
pub mod slot_hashes;
pub mod slot_history;
pub mod stake_history;
pub mod system_instruction;
pub mod system_program;
pub mod sysvar;

74
sdk/src/stake_history.rs Normal file
View File

@ -0,0 +1,74 @@
//! named accounts for synthesized data accounts for bank state, etc.
//!
//! this account carries history about stake activations and de-activations
//!
pub use crate::clock::Epoch;
use std::ops::Deref;
pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
pub struct StakeHistoryEntry {
pub effective: u64, // effective stake at this epoch
pub activating: u64, // sum of portion of stakes not fully warmed up
pub deactivating: u64, // requested to be cooled down, not fully deactivated yet
}
#[repr(C)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);
impl StakeHistory {
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn get(&self, epoch: &Epoch) -> Option<&StakeHistoryEntry> {
self.binary_search_by(|probe| epoch.cmp(&probe.0))
.ok()
.map(|index| &self[index].1)
}
pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) {
match self.binary_search_by(|probe| epoch.cmp(&probe.0)) {
Ok(index) => (self.0)[index] = (epoch, entry),
Err(index) => (self.0).insert(index, (epoch, entry)),
}
(self.0).truncate(MAX_ENTRIES);
}
}
impl Deref for StakeHistory {
type Target = Vec<(Epoch, StakeHistoryEntry)>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stake_history() {
let mut stake_history = StakeHistory::default();
for i in 0..MAX_ENTRIES as u64 + 1 {
stake_history.add(
i,
StakeHistoryEntry {
activating: i,
..StakeHistoryEntry::default()
},
);
}
assert_eq!(stake_history.len(), MAX_ENTRIES);
assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1);
assert_eq!(stake_history.get(&0), None);
assert_eq!(
stake_history.get(&1),
Some(&StakeHistoryEntry {
activating: 1,
..StakeHistoryEntry::default()
})
);
}
}

View File

@ -2,26 +2,12 @@
//!
//! this account carries history about stake activations and de-activations
//!
pub use crate::clock::Epoch;
pub use crate::stake_history::StakeHistory;
use crate::{account::Account, sysvar::Sysvar};
use std::ops::Deref;
crate::declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
pub struct StakeHistoryEntry {
pub effective: u64, // effective stake at this epoch
pub activating: u64, // sum of portion of stakes not fully warmed up
pub deactivating: u64, // requested to be cooled down, not fully deactivated yet
}
#[repr(C)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);
impl Sysvar for StakeHistory {
// override
fn size_of() -> usize {
@ -30,30 +16,6 @@ impl Sysvar for StakeHistory {
}
}
impl StakeHistory {
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn get(&self, epoch: &Epoch) -> Option<&StakeHistoryEntry> {
self.binary_search_by(|probe| epoch.cmp(&probe.0))
.ok()
.map(|index| &self[index].1)
}
pub fn add(&mut self, epoch: Epoch, entry: StakeHistoryEntry) {
match self.binary_search_by(|probe| epoch.cmp(&probe.0)) {
Ok(index) => (self.0)[index] = (epoch, entry),
Err(index) => (self.0).insert(index, (epoch, entry)),
}
(self.0).truncate(MAX_ENTRIES);
}
}
impl Deref for StakeHistory {
type Target = Vec<(Epoch, StakeHistoryEntry)>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account {
stake_history.create_account(lamports)
}
@ -61,15 +23,23 @@ pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account {
#[cfg(test)]
mod tests {
use super::*;
use crate::stake_history::*;
#[test]
fn test_size_of() {
let mut stake_history = StakeHistory::default();
for i in 0..MAX_ENTRIES as u64 {
stake_history.add(
i,
StakeHistoryEntry {
activating: i,
..StakeHistoryEntry::default()
},
);
}
assert_eq!(
bincode::serialized_size(&StakeHistory(vec![
(0, StakeHistoryEntry::default());
MAX_ENTRIES
]))
.unwrap() as usize,
bincode::serialized_size(&stake_history).unwrap() as usize,
StakeHistory::size_of()
);
}