shrink stakes (#7122)

This commit is contained in:
Rob Walker 2019-11-25 13:14:32 -08:00 committed by GitHub
parent 0f66e5e49b
commit acbe89a159
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 298 additions and 245 deletions

View File

@ -564,23 +564,23 @@ pub fn process_show_stake_account(
println!("credits observed: {}", stake.credits_observed); println!("credits observed: {}", stake.credits_observed);
println!( println!(
"delegated stake: {}", "delegated stake: {}",
build_balance_message(stake.stake, use_lamports_unit, true) build_balance_message(stake.delegation.stake, use_lamports_unit, true)
); );
if stake.voter_pubkey != Pubkey::default() { if stake.delegation.voter_pubkey != Pubkey::default() {
println!("delegated voter pubkey: {}", stake.voter_pubkey); println!("delegated voter pubkey: {}", stake.delegation.voter_pubkey);
} }
println!( println!(
"stake activates starting from epoch: {}", "stake activates starting from epoch: {}",
if stake.activation_epoch < std::u64::MAX { if stake.delegation.activation_epoch < std::u64::MAX {
stake.activation_epoch stake.delegation.activation_epoch
} else { } else {
0 0
} }
); );
if stake.deactivation_epoch < std::u64::MAX { if stake.delegation.deactivation_epoch < std::u64::MAX {
println!( println!(
"stake deactivates starting from epoch: {}", "stake deactivates starting from epoch: {}",
stake.deactivation_epoch stake.delegation.deactivation_epoch
); );
} }
show_authorized(&authorized); show_authorized(&authorized);

View File

@ -115,7 +115,7 @@ pub(crate) mod tests {
}; };
use solana_stake_program::{ use solana_stake_program::{
stake_instruction, stake_instruction,
stake_state::{Authorized, Stake}, stake_state::{Authorized, Delegation, Stake},
}; };
use solana_vote_program::{vote_instruction, vote_state::VoteInit}; use solana_vote_program::{vote_instruction, vote_state::VoteInit};
use std::sync::Arc; use std::sync::Arc;
@ -183,8 +183,11 @@ pub(crate) mod tests {
solana_logger::setup(); solana_logger::setup();
let stake = BOOTSTRAP_LEADER_LAMPORTS * 100; let stake = BOOTSTRAP_LEADER_LAMPORTS * 100;
let leader_stake = Stake { let leader_stake = Stake {
stake: BOOTSTRAP_LEADER_LAMPORTS, delegation: Delegation {
activation_epoch: std::u64::MAX, // mark as bootstrap stake: BOOTSTRAP_LEADER_LAMPORTS,
activation_epoch: std::u64::MAX, // mark as bootstrap
..Delegation::default()
},
..Stake::default() ..Stake::default()
}; };
@ -217,8 +220,11 @@ pub(crate) mod tests {
// simulated stake // simulated stake
let other_stake = Stake { let other_stake = Stake {
stake, delegation: Delegation {
activation_epoch: bank.epoch(), stake,
activation_epoch: bank.epoch(),
..Delegation::default()
},
..Stake::default() ..Stake::default()
}; };

View File

@ -185,8 +185,7 @@ impl LocalCluster {
stake_config::create_account( stake_config::create_account(
1, 1,
&stake_config::Config { &stake_config::Config {
warmup_rate: 1_000_000_000.0f64, warmup_cooldown_rate: 1_000_000_000.0f64,
cooldown_rate: 1_000_000_000.0f64,
slash_penalty: std::u8::MAX, slash_penalty: std::u8::MAX,
}, },
), ),
@ -551,8 +550,8 @@ impl LocalCluster {
VoteState::from(&vote_account), VoteState::from(&vote_account),
) { ) {
(Some(stake_state), Some(vote_state)) => { (Some(stake_state), Some(vote_state)) => {
if stake_state.voter_pubkey != vote_account_pubkey if stake_state.delegation.voter_pubkey != vote_account_pubkey
|| stake_state.stake != amount || stake_state.delegation.stake != amount
{ {
Err(Error::new(ErrorKind::Other, "invalid stake account state")) Err(Error::new(ErrorKind::Other, "invalid stake account state"))
} else if vote_state.node_pubkey != node_pubkey { } else if vote_state.node_pubkey != node_pubkey {

View File

@ -14,16 +14,13 @@ solana_sdk::declare_id!("StakeConfig11111111111111111111111111111111");
// means that no more than RATE of current effective stake may be added or subtracted per // means that no more than RATE of current effective stake may be added or subtracted per
// epoch // epoch
pub const DEFAULT_WARMUP_RATE: f64 = 0.25; pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
pub const DEFAULT_COOLDOWN_RATE: f64 = 0.25;
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8; pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct Config { pub struct Config {
/// how much stake we can activate per-epoch as a fraction of currently effective stake /// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake
pub warmup_rate: f64, pub warmup_cooldown_rate: f64,
/// how much stake we can deactivate as a fraction of currently effective stake
pub cooldown_rate: f64,
/// percentage of stake lost when slash, expressed as a portion of std::u8::MAX /// percentage of stake lost when slash, expressed as a portion of std::u8::MAX
pub slash_penalty: u8, pub slash_penalty: u8,
} }
@ -39,8 +36,7 @@ impl Config {
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
warmup_rate: DEFAULT_WARMUP_RATE, warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE,
cooldown_rate: DEFAULT_COOLDOWN_RATE,
slash_penalty: DEFAULT_SLASH_PENALTY, slash_penalty: DEFAULT_SLASH_PENALTY,
} }
} }

View File

@ -44,17 +44,27 @@ impl StakeState {
pub fn stake_from(account: &Account) -> Option<Stake> { pub fn stake_from(account: &Account) -> Option<Stake> {
Self::from(account).and_then(|state: Self| state.stake()) 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> { pub fn stake(&self) -> Option<Stake> {
match self { match self {
StakeState::Stake(_meta, stake) => Some(*stake), StakeState::Stake(_meta, stake) => Some(*stake),
_ => None, _ => None,
} }
} }
pub fn delegation_from(account: &Account) -> Option<Delegation> {
Self::from(account).and_then(|state: Self| state.delegation())
}
pub fn delegation(&self) -> Option<Delegation> {
match self {
StakeState::Stake(_meta, stake) => Some(stake.delegation),
_ => None,
}
}
pub fn authorized_from(account: &Account) -> Option<Authorized> {
Self::from(account).and_then(|state: Self| state.authorized())
}
pub fn authorized(&self) -> Option<Authorized> { pub fn authorized(&self) -> Option<Authorized> {
match self { match self {
StakeState::Stake(meta, _stake) => Some(meta.authorized), StakeState::Stake(meta, _stake) => Some(meta.authorized),
@ -105,82 +115,47 @@ impl Meta {
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Stake { pub struct Delegation {
/// most recently delegated vote account pubkey /// to whom the stake is delegated
pub voter_pubkey: Pubkey, pub voter_pubkey: Pubkey,
/// 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,
/// activated stake amount, set at delegate_stake() time /// activated stake amount, set at delegate_stake() time
pub stake: u64, pub stake: u64,
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake /// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
pub activation_epoch: Epoch, pub activation_epoch: Epoch,
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated /// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
pub deactivation_epoch: Epoch, pub deactivation_epoch: Epoch,
/// stake config (warmup, etc.) /// how much stake we can activate per-epoch as a fraction of currently effective stake
pub config: Config, pub warmup_cooldown_rate: f64,
/// 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 Delegation {
impl Default for Stake {
fn default() -> Self { fn default() -> Self {
Self { Self {
voter_pubkey: Pubkey::default(), voter_pubkey: Pubkey::default(),
voter_pubkey_epoch: 0,
credits_observed: 0,
stake: 0, stake: 0,
activation_epoch: 0, activation_epoch: 0,
deactivation_epoch: std::u64::MAX, deactivation_epoch: std::u64::MAX,
config: Config::default(), warmup_cooldown_rate: Config::default().warmup_cooldown_rate,
prior_delegates: <[(Pubkey, Epoch, Epoch, Slot); MAX_PRIOR_DELEGATES]>::default(),
prior_delegates_idx: MAX_PRIOR_DELEGATES - 1,
} }
} }
} }
impl Authorized { impl Delegation {
pub fn auto(authorized: &Pubkey) -> Self { pub fn new(
voter_pubkey: &Pubkey,
stake: u64,
activation_epoch: Epoch,
warmup_cooldown_rate: f64,
) -> Self {
Self { Self {
staker: *authorized, voter_pubkey: *voter_pubkey,
withdrawer: *authorized, stake,
activation_epoch,
warmup_cooldown_rate,
..Delegation::default()
} }
} }
pub fn check( pub fn is_bootstrap(&self) -> bool {
&self,
signers: &HashSet<Pubkey>,
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
match stake_authorize {
StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
_ => Err(InstructionError::MissingRequiredSignature),
}
}
pub fn authorize(
&mut self,
signers: &HashSet<Pubkey>,
new_authorized: &Pubkey,
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
self.check(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 self.activation_epoch == std::u64::MAX
} }
@ -222,7 +197,7 @@ impl Stake {
// portion of activating stake in this epoch I'm entitled to // portion of activating stake in this epoch I'm entitled to
effective_stake = effective_stake.saturating_sub( effective_stake = effective_stake.saturating_sub(
((weight * entry.effective as f64 * self.config.cooldown_rate) as u64).max(1), ((weight * entry.effective as f64 * self.warmup_cooldown_rate) as u64).max(1),
); );
if effective_stake == 0 { if effective_stake == 0 {
@ -274,7 +249,7 @@ impl Stake {
// portion of activating stake in this epoch I'm entitled to // portion of activating stake in this epoch I'm entitled to
effective_stake += effective_stake +=
((weight * entry.effective as f64 * self.config.warmup_rate) as u64).max(1); ((weight * entry.effective as f64 * self.warmup_cooldown_rate) as u64).max(1);
if effective_stake >= self.stake { if effective_stake >= self.stake {
effective_stake = self.stake; effective_stake = self.stake;
@ -297,7 +272,74 @@ impl Stake {
(self.stake, 0) (self.stake, 0)
} }
} }
}
#[derive(Debug, 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 {
pub fn auto(authorized: &Pubkey) -> Self {
Self {
staker: *authorized,
withdrawer: *authorized,
}
}
pub fn check(
&self,
signers: &HashSet<Pubkey>,
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
match stake_authorize {
StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
_ => Err(InstructionError::MissingRequiredSignature),
}
}
pub fn authorize(
&mut self,
signers: &HashSet<Pubkey>,
new_authorized: &Pubkey,
stake_authorize: StakeAuthorize,
) -> Result<(), InstructionError> {
self.check(signers, stake_authorize)?;
match stake_authorize {
StakeAuthorize::Staker => self.staker = *new_authorized,
StakeAuthorize::Withdrawer => self.withdrawer = *new_authorized,
}
Ok(())
}
}
impl Stake {
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
self.delegation.stake(epoch, history)
}
/// for a given stake and vote_state, calculate what distributions and what updates should be made /// for a given stake and vote_state, calculate what distributions and what updates should be made
/// returns a tuple in the case of a payout of: /// returns a tuple in the case of a payout of:
/// * voter_rewards to be distributed /// * voter_rewards to be distributed
@ -332,7 +374,7 @@ impl Stake {
}; };
total_rewards += total_rewards +=
(self.stake(*epoch, stake_history) * epoch_credits) as f64 * point_value; (self.delegation.stake(*epoch, stake_history) * epoch_credits) as f64 * point_value;
// don't want to assume anything about order of the iterator... // don't want to assume anything about order of the iterator...
credits_observed = credits_observed.max(*credits); credits_observed = credits_observed.max(*credits);
@ -372,25 +414,28 @@ impl Stake {
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES; self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
self.prior_delegates[self.prior_delegates_idx] = ( self.prior_delegates[self.prior_delegates_idx] = (
self.voter_pubkey, self.delegation.voter_pubkey,
self.voter_pubkey_epoch, self.voter_pubkey_epoch,
clock.epoch, clock.epoch,
clock.slot, clock.slot,
); );
self.voter_pubkey = *voter_pubkey; self.delegation.voter_pubkey = *voter_pubkey;
self.voter_pubkey_epoch = clock.epoch; self.voter_pubkey_epoch = clock.epoch;
self.credits_observed = vote_state.credits(); self.credits_observed = vote_state.credits();
Ok(()) Ok(())
} }
fn split(&mut self, lamports: u64) -> Result<Self, StakeError> { fn split(&mut self, lamports: u64) -> Result<Self, StakeError> {
if lamports > self.stake { if lamports > self.delegation.stake {
return Err(StakeError::InsufficientStake); return Err(StakeError::InsufficientStake);
} }
self.stake -= lamports; self.delegation.stake -= lamports;
let new = Self { let new = Self {
stake: lamports, delegation: Delegation {
stake: lamports,
..self.delegation
},
..*self ..*self
}; };
Ok(new) Ok(new)
@ -404,21 +449,23 @@ impl Stake {
config: &Config, config: &Config,
) -> Self { ) -> Self {
Self { Self {
stake, delegation: Delegation::new(
activation_epoch, voter_pubkey,
voter_pubkey: *voter_pubkey, stake,
activation_epoch,
config.warmup_cooldown_rate,
),
voter_pubkey_epoch: activation_epoch, voter_pubkey_epoch: activation_epoch,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
config: *config,
..Stake::default() ..Stake::default()
} }
} }
fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> { fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
if self.deactivation_epoch != std::u64::MAX { if self.delegation.deactivation_epoch != std::u64::MAX {
Err(StakeError::AlreadyDeactivated) Err(StakeError::AlreadyDeactivated)
} else { } else {
self.deactivation_epoch = epoch; self.delegation.deactivation_epoch = epoch;
Ok(()) Ok(())
} }
} }
@ -576,7 +623,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
// the only valid use of current voter_pubkey, redelegation breaks // the only valid use of current voter_pubkey, redelegation breaks
// rewards redemption for previous voter_pubkey // rewards redemption for previous voter_pubkey
if stake.voter_pubkey != *vote_account.unsigned_key() { if stake.delegation.voter_pubkey != *vote_account.unsigned_key() {
return Err(InstructionError::InvalidArgument); return Err(InstructionError::InvalidArgument);
} }
@ -596,7 +643,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
vote_account.account.lamports += voters_reward; vote_account.account.lamports += voters_reward;
stake.credits_observed = credits_observed; stake.credits_observed = credits_observed;
stake.stake += stakers_reward; stake.delegation.stake += stakers_reward;
self.set_state(&StakeState::Stake(meta, stake)) self.set_state(&StakeState::Stake(meta, stake))
} else { } else {
@ -688,13 +735,13 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
StakeState::Stake(meta, stake) => { StakeState::Stake(meta, stake) => {
meta.authorized.check(signers, StakeAuthorize::Withdrawer)?; meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
// if we have a deactivation epoch and we're in cooldown // if we have a deactivation epoch and we're in cooldown
let staked = if clock.epoch >= stake.deactivation_epoch { let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
stake.stake(clock.epoch, Some(stake_history)) stake.delegation.stake(clock.epoch, Some(stake_history))
} else { } else {
// Assume full stake if the stake account hasn't been // Assume full stake if the stake account hasn't been
// de-activated, because in the future the exposed stake // de-activated, because in the future the exposed stake
// might be higher than stake.stake() due to warmup // might be higher than stake.stake() due to warmup
stake.stake stake.delegation.stake
}; };
(meta.lockup, staked + meta.rent_exempt_reserve, staked != 0) (meta.lockup, staked + meta.rent_exempt_reserve, staked != 0)
@ -746,7 +793,7 @@ pub fn new_stake_history_entry<'a, I>(
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
) -> StakeHistoryEntry ) -> StakeHistoryEntry
where where
I: Iterator<Item = &'a Stake>, I: Iterator<Item = &'a Delegation>,
{ {
// whatever the stake says they had for the epoch // whatever the stake says they had for the epoch
// and whatever the were still waiting for // and whatever the were still waiting for
@ -820,17 +867,17 @@ mod tests {
#[test] #[test]
fn test_stake_is_bootstrap() { fn test_stake_is_bootstrap() {
assert_eq!( assert_eq!(
Stake { Delegation {
activation_epoch: std::u64::MAX, activation_epoch: std::u64::MAX,
..Stake::default() ..Delegation::default()
} }
.is_bootstrap(), .is_bootstrap(),
true true
); );
assert_eq!( assert_eq!(
Stake { Delegation {
activation_epoch: 0, activation_epoch: 0,
..Stake::default() ..Delegation::default()
} }
.is_bootstrap(), .is_bootstrap(),
false false
@ -911,12 +958,15 @@ mod tests {
assert_eq!( assert_eq!(
stake, stake,
Stake { Stake {
voter_pubkey: vote_pubkey, delegation: Delegation {
voter_pubkey: vote_pubkey,
stake: stake_lamports,
activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX,
..Delegation::default()
},
voter_pubkey_epoch: clock.epoch, voter_pubkey_epoch: clock.epoch,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
stake: stake_lamports,
activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX,
..Stake::default() ..Stake::default()
} }
); );
@ -950,7 +1000,10 @@ mod tests {
fn test_stake_redelegate() { fn test_stake_redelegate() {
// what a freshly delegated stake looks like // what a freshly delegated stake looks like
let mut stake = Stake { let mut stake = Stake {
voter_pubkey: Pubkey::new_rand(), delegation: Delegation {
voter_pubkey: Pubkey::new_rand(),
..Delegation::default()
},
voter_pubkey_epoch: 0, voter_pubkey_epoch: 0,
..Stake::default() ..Stake::default()
}; };
@ -981,22 +1034,22 @@ mod tests {
), ),
Err(StakeError::TooSoonToRedelegate) Err(StakeError::TooSoonToRedelegate)
); );
assert_eq!(stake.voter_pubkey, voter_pubkey); assert_eq!(stake.delegation.voter_pubkey, voter_pubkey);
} }
} }
fn create_stake_history_from_stakes( fn create_stake_history_from_delegations(
bootstrap: Option<u64>, bootstrap: Option<u64>,
epochs: std::ops::Range<Epoch>, epochs: std::ops::Range<Epoch>,
stakes: &[Stake], delegations: &[Delegation],
) -> StakeHistory { ) -> StakeHistory {
let mut stake_history = StakeHistory::default(); let mut stake_history = StakeHistory::default();
let bootstrap_stake = if let Some(bootstrap) = bootstrap { let bootstrap_delegation = if let Some(bootstrap) = bootstrap {
vec![Stake { vec![Delegation {
activation_epoch: std::u64::MAX, activation_epoch: std::u64::MAX,
stake: bootstrap, stake: bootstrap,
..Stake::default() ..Delegation::default()
}] }]
} else { } else {
vec![] vec![]
@ -1005,7 +1058,7 @@ mod tests {
for epoch in epochs { for epoch in epochs {
let entry = new_stake_history_entry( let entry = new_stake_history_entry(
epoch, epoch,
stakes.iter().chain(bootstrap_stake.iter()), delegations.iter().chain(bootstrap_delegation.iter()),
Some(&stake_history), Some(&stake_history),
); );
stake_history.add(epoch, entry); stake_history.add(epoch, entry);
@ -1016,15 +1069,15 @@ mod tests {
#[test] #[test]
fn test_stake_activating_and_deactivating() { fn test_stake_activating_and_deactivating() {
let stake = Stake { let stake = Delegation {
stake: 1_000, stake: 1_000,
activation_epoch: 0, // activating at zero activation_epoch: 0, // activating at zero
deactivation_epoch: 5, deactivation_epoch: 5,
..Stake::default() ..Delegation::default()
}; };
// save this off so stake.config.warmup_rate changes don't break this test // save this off so stake.config.warmup_rate changes don't break this test
let increment = (1_000 as f64 * stake.config.warmup_rate) as u64; let increment = (1_000 as f64 * stake.warmup_cooldown_rate) as u64;
let mut stake_history = StakeHistory::default(); let mut stake_history = StakeHistory::default();
// assert that this stake follows step function if there's no history // assert that this stake follows step function if there's no history
@ -1123,11 +1176,11 @@ mod tests {
#[test] #[test]
fn test_stop_activating_after_deactivation() { fn test_stop_activating_after_deactivation() {
solana_logger::setup(); solana_logger::setup();
let stake = Stake { let stake = Delegation {
stake: 1_000, stake: 1_000,
activation_epoch: 0, activation_epoch: 0,
deactivation_epoch: 3, deactivation_epoch: 3,
..Stake::default() ..Delegation::default()
}; };
let base_stake = 1_000; let base_stake = 1_000;
@ -1158,11 +1211,11 @@ mod tests {
); );
if epoch < stake.deactivation_epoch { if epoch < stake.deactivation_epoch {
let increase = (effective as f64 * stake.config.warmup_rate) as u64; let increase = (effective as f64 * stake.warmup_cooldown_rate) as u64;
effective += increase.min(activating); effective += increase.min(activating);
other_activations.push(0); other_activations.push(0);
} else { } else {
let decrease = (effective as f64 * stake.config.cooldown_rate) as u64; let decrease = (effective as f64 * stake.warmup_cooldown_rate) as u64;
effective -= decrease.min(deactivating); effective -= decrease.min(deactivating);
effective += other_activation; effective += other_activation;
other_activations.push(other_activation); other_activations.push(other_activation);
@ -1187,25 +1240,26 @@ mod tests {
#[test] #[test]
fn test_stake_warmup_cooldown_sub_integer_moves() { fn test_stake_warmup_cooldown_sub_integer_moves() {
let stakes = [Stake { let delegations = [Delegation {
stake: 2, stake: 2,
activation_epoch: 0, // activating at zero activation_epoch: 0, // activating at zero
deactivation_epoch: 5, deactivation_epoch: 5,
..Stake::default() ..Delegation::default()
}]; }];
// give 2 epochs of cooldown // give 2 epochs of cooldown
let epochs = 7; let epochs = 7;
// make boostrap stake smaller than warmup so warmup/cooldownn // make boostrap stake smaller than warmup so warmup/cooldownn
// increment is always smaller than 1 // increment is always smaller than 1
let bootstrap = (stakes[0].config.warmup_rate * 100.0 / 2.0) as u64; let bootstrap = (delegations[0].warmup_cooldown_rate * 100.0 / 2.0) as u64;
let stake_history = create_stake_history_from_stakes(Some(bootstrap), 0..epochs, &stakes); let stake_history =
create_stake_history_from_delegations(Some(bootstrap), 0..epochs, &delegations);
let mut max_stake = 0; let mut max_stake = 0;
let mut min_stake = 2; let mut min_stake = 2;
for epoch in 0..epochs { for epoch in 0..epochs {
let stake = stakes let stake = delegations
.iter() .iter()
.map(|stake| stake.stake(epoch, Some(&stake_history))) .map(|delegation| delegation.stake(epoch, Some(&stake_history)))
.sum::<u64>(); .sum::<u64>();
max_stake = max_stake.max(stake); max_stake = max_stake.max(stake);
min_stake = min_stake.min(stake); min_stake = min_stake.min(stake);
@ -1216,42 +1270,42 @@ mod tests {
#[test] #[test]
fn test_stake_warmup_cooldown() { fn test_stake_warmup_cooldown() {
let stakes = [ let delegations = [
Stake { Delegation {
// never deactivates // never deactivates
stake: 1_000, stake: 1_000,
activation_epoch: std::u64::MAX, activation_epoch: std::u64::MAX,
..Stake::default() ..Delegation::default()
}, },
Stake { Delegation {
stake: 1_000, stake: 1_000,
activation_epoch: 0, activation_epoch: 0,
deactivation_epoch: 9, deactivation_epoch: 9,
..Stake::default() ..Delegation::default()
}, },
Stake { Delegation {
stake: 1_000, stake: 1_000,
activation_epoch: 1, activation_epoch: 1,
deactivation_epoch: 6, deactivation_epoch: 6,
..Stake::default() ..Delegation::default()
}, },
Stake { Delegation {
stake: 1_000, stake: 1_000,
activation_epoch: 2, activation_epoch: 2,
deactivation_epoch: 5, deactivation_epoch: 5,
..Stake::default() ..Delegation::default()
}, },
Stake { Delegation {
stake: 1_000, stake: 1_000,
activation_epoch: 2, activation_epoch: 2,
deactivation_epoch: 4, deactivation_epoch: 4,
..Stake::default() ..Delegation::default()
}, },
Stake { Delegation {
stake: 1_000, stake: 1_000,
activation_epoch: 4, activation_epoch: 4,
deactivation_epoch: 4, deactivation_epoch: 4,
..Stake::default() ..Delegation::default()
}, },
]; ];
// chosen to ensure that the last activated stake (at 4) finishes // chosen to ensure that the last activated stake (at 4) finishes
@ -1260,19 +1314,19 @@ mod tests {
// when all alone, but the above overlap a lot // when all alone, but the above overlap a lot
let epochs = 20; let epochs = 20;
let stake_history = create_stake_history_from_stakes(None, 0..epochs, &stakes); let stake_history = create_stake_history_from_delegations(None, 0..epochs, &delegations);
let mut prev_total_effective_stake = stakes let mut prev_total_effective_stake = delegations
.iter() .iter()
.map(|stake| stake.stake(0, Some(&stake_history))) .map(|delegation| delegation.stake(0, Some(&stake_history)))
.sum::<u64>(); .sum::<u64>();
// uncomment and add ! for fun with graphing // uncomment and add ! for fun with graphing
// eprintln("\n{:8} {:8} {:8}", " epoch", " total", " delta"); // eprintln("\n{:8} {:8} {:8}", " epoch", " total", " delta");
for epoch in 1..epochs { for epoch in 1..epochs {
let total_effective_stake = stakes let total_effective_stake = delegations
.iter() .iter()
.map(|stake| stake.stake(epoch, Some(&stake_history))) .map(|delegation| delegation.stake(epoch, Some(&stake_history)))
.sum::<u64>(); .sum::<u64>();
let delta = if total_effective_stake > prev_total_effective_stake { let delta = if total_effective_stake > prev_total_effective_stake {
@ -1288,7 +1342,7 @@ mod tests {
assert!( assert!(
delta delta
<= ((prev_total_effective_stake as f64 * Config::default().warmup_rate) as u64) <= ((prev_total_effective_stake as f64 * Config::default().warmup_cooldown_rate) as u64)
.max(1) .max(1)
); );
@ -1610,10 +1664,12 @@ mod tests {
Ok(()) Ok(())
); );
let stake_history = create_stake_history_from_stakes( let stake_history = create_stake_history_from_delegations(
None, None,
0..future.epoch, 0..future.epoch,
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()], &[StakeState::stake_from(&stake_keyed_account.account)
.unwrap()
.delegation],
); );
// Try to withdraw stake // Try to withdraw stake
@ -1763,14 +1819,14 @@ mod tests {
// this one should be able to collect exactly 2 // this one should be able to collect exactly 2
assert_eq!( assert_eq!(
Some((0, stake.stake * 2, 2)), Some((0, stake.delegation.stake * 2, 2)),
stake.calculate_rewards(1.0, &vote_state, None) stake.calculate_rewards(1.0, &vote_state, None)
); );
stake.credits_observed = 1; stake.credits_observed = 1;
// this one should be able to collect exactly 1 (only observed one) // this one should be able to collect exactly 1 (only observed one)
assert_eq!( assert_eq!(
Some((0, stake.stake * 1, 2)), Some((0, stake.delegation.stake * 1, 2)),
stake.calculate_rewards(1.0, &vote_state, None) stake.calculate_rewards(1.0, &vote_state, None)
); );
@ -1783,7 +1839,7 @@ mod tests {
vote_state.increment_credits(2); vote_state.increment_credits(2);
// this one should be able to collect 1 now, one credit by a stake of 1 // this one should be able to collect 1 now, one credit by a stake of 1
assert_eq!( assert_eq!(
Some((0, stake.stake * 1, 3)), Some((0, stake.delegation.stake * 1, 3)),
stake.calculate_rewards(1.0, &vote_state, None) stake.calculate_rewards(1.0, &vote_state, None)
); );
@ -1791,7 +1847,11 @@ mod tests {
// this one should be able to collect everything from t=0 a warmed up stake of 2 // this one should be able to collect everything from t=0 a warmed up stake of 2
// (2 credits at stake of 1) + (1 credit at a stake of 2) // (2 credits at stake of 1) + (1 credit at a stake of 2)
assert_eq!( assert_eq!(
Some((0, stake.stake * 1 + stake.stake * 2, 3)), Some((
0,
stake.delegation.stake * 1 + stake.delegation.stake * 2,
3
)),
stake.calculate_rewards(1.0, &vote_state, None) stake.calculate_rewards(1.0, &vote_state, None)
); );
@ -1858,10 +1918,12 @@ mod tests {
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers) .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers)
.is_ok()); .is_ok());
let stake_history = create_stake_history_from_stakes( let stake_history = create_stake_history_from_delegations(
Some(100), Some(100),
0..10, 0..10,
&[StakeState::stake_from(&stake_keyed_account.account).unwrap()], &[StakeState::stake_from(&stake_keyed_account.account)
.unwrap()
.delegation],
); );
// no credits to claim // no credits to claim
@ -1936,7 +1998,7 @@ mod tests {
); );
// verify rewards are added to stake // verify rewards are added to stake
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap(); let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!(stake.stake, stake_keyed_account.account.lamports); assert_eq!(stake.delegation.stake, stake_keyed_account.account.lamports);
let wrong_vote_pubkey = Pubkey::new_rand(); let wrong_vote_pubkey = Pubkey::new_rand();
let mut wrong_vote_keyed_account = let mut wrong_vote_keyed_account =
@ -2124,13 +2186,7 @@ mod tests {
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Stake( &StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)),
Meta::auto(&stake_pubkey),
Stake {
stake: stake_lamports,
..Stake::default()
},
),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -2154,6 +2210,17 @@ mod tests {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
); );
} }
impl Stake {
fn just_stake(stake: u64) -> Self {
Self {
delegation: Delegation {
stake,
..Delegation::default()
},
..Stake::default()
}
}
}
#[test] #[test]
fn test_split_more_than_staked() { fn test_split_more_than_staked() {
@ -2163,10 +2230,7 @@ mod tests {
stake_lamports, stake_lamports,
&StakeState::Stake( &StakeState::Stake(
Meta::auto(&stake_pubkey), Meta::auto(&stake_pubkey),
Stake { Stake::just_stake(stake_lamports / 2 - 1),
stake: stake_lamports / 2 - 1,
..Stake::default()
},
), ),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
@ -2209,13 +2273,7 @@ mod tests {
// test splitting both an Initialized stake and a Staked stake // test splitting both an Initialized stake and a Staked stake
for state in &[ for state in &[
StakeState::Initialized(meta), StakeState::Initialized(meta),
StakeState::Stake( StakeState::Stake(meta, Stake::just_stake(stake_lamports)),
meta,
Stake {
stake: stake_lamports,
..Stake::default()
},
),
] { ] {
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
@ -2277,7 +2335,10 @@ mod tests {
Ok(StakeState::Stake( Ok(StakeState::Stake(
*meta, *meta,
Stake { Stake {
stake: stake_lamports - rent_exempt_reserve, delegation: Delegation {
stake: stake_lamports - rent_exempt_reserve,
..stake.delegation
},
..*stake ..*stake
} }
)) ))
@ -2306,13 +2367,7 @@ mod tests {
// test splitting both an Initialized stake and a Staked stake // test splitting both an Initialized stake and a Staked stake
for state in &[ for state in &[
StakeState::Initialized(Meta::auto(&stake_pubkey)), StakeState::Initialized(Meta::auto(&stake_pubkey)),
StakeState::Stake( StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)),
Meta::auto(&stake_pubkey),
Stake {
stake: stake_lamports,
..Stake::default()
},
),
] { ] {
let mut split_stake_account = Account::new_data_with_space( let mut split_stake_account = Account::new_data_with_space(
0, 0,
@ -2370,7 +2425,10 @@ mod tests {
Ok(StakeState::Stake( Ok(StakeState::Stake(
*meta, *meta,
Stake { Stake {
stake: stake_lamports / 2, delegation: Delegation {
stake: stake_lamports / 2,
..stake.delegation
},
..*stake ..*stake
} }
)), )),
@ -2380,7 +2438,10 @@ mod tests {
Ok(StakeState::Stake( Ok(StakeState::Stake(
*meta, *meta,
Stake { Stake {
stake: stake_lamports / 2, delegation: Delegation {
stake: stake_lamports / 2,
..stake.delegation
},
..*stake ..*stake
} }
)), )),
@ -2467,7 +2528,7 @@ mod tests {
Ok(()) Ok(())
); );
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap(); let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!(stake.voter_pubkey, new_voter_pubkey); assert_eq!(stake.delegation.voter_pubkey, new_voter_pubkey);
// Test another staking action // Test another staking action
assert_eq!( assert_eq!(

View File

@ -70,7 +70,7 @@ fn fill_epoch_with_votes(
fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool { fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool {
let stake = StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap(); let stake = StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap();
stake.stake stake.delegation.stake
== stake.stake( == stake.stake(
bank.epoch(), bank.epoch(),
Some( Some(
@ -150,7 +150,7 @@ fn test_stake_account_lifetime() {
let account = bank.get_account(&stake_pubkey).expect("account not found"); let account = bank.get_account(&stake_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(_meta, stake) = stake_state { if let StakeState::Stake(_meta, stake) = stake_state {
assert_eq!(stake.stake, 1_000_000); assert_eq!(stake.delegation.stake, 1_000_000);
} else { } else {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")
} }
@ -173,7 +173,7 @@ fn test_stake_account_lifetime() {
let account = bank.get_account(&stake_pubkey).expect("account not found"); let account = bank.get_account(&stake_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(_meta, stake) = stake_state { if let StakeState::Stake(_meta, stake) = stake_state {
assert_eq!(stake.stake, 1_000_000); assert_eq!(stake.delegation.stake, 1_000_000);
} else { } else {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")
} }

View File

@ -1558,11 +1558,6 @@ impl Bank {
self.stakes.read().unwrap().vote_accounts().clone() self.stakes.read().unwrap().vote_accounts().clone()
} }
/// current stake accounts for this bank
pub fn stake_accounts(&self) -> HashMap<Pubkey, Account> {
self.stakes.read().unwrap().stake_accounts().clone()
}
/// vote accounts for the specific epoch along with the stake /// vote accounts for the specific epoch along with the stake
/// attributed to each account /// attributed to each account
pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&HashMap<Pubkey, (u64, Account)>> { pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&HashMap<Pubkey, (u64, Account)>> {
@ -1692,7 +1687,7 @@ mod tests {
sysvar::{fees::Fees, rewards::Rewards}, sysvar::{fees::Fees, rewards::Rewards},
timing::years_as_slots, timing::years_as_slots,
}; };
use solana_stake_program::stake_state::Stake; use solana_stake_program::stake_state::{Delegation, Stake};
use solana_vote_program::{ use solana_vote_program::{
vote_instruction, vote_instruction,
vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY}, vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
@ -3277,8 +3272,11 @@ mod tests {
assert!(leader_stake > 0); assert!(leader_stake > 0);
let leader_stake = Stake { let leader_stake = Stake {
stake: leader_lamports, delegation: Delegation {
activation_epoch: std::u64::MAX, // bootstrap stake: leader_lamports,
activation_epoch: std::u64::MAX, // bootstrap
..Delegation::default()
},
..Stake::default() ..Stake::default()
}; };

View File

@ -4,17 +4,29 @@ use solana_sdk::account::Account;
use solana_sdk::clock::Epoch; use solana_sdk::clock::Epoch;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::sysvar::stake_history::StakeHistory; use solana_sdk::sysvar::stake_history::StakeHistory;
use solana_stake_program::stake_state::{new_stake_history_entry, StakeState}; use solana_stake_program::stake_state::{new_stake_history_entry, Delegation, StakeState};
use solana_vote_program::vote_state::VoteState; use solana_vote_program::vote_state::VoteState;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct StakeDelegation {
/// to whom the stake is delegated
pub voter_pubkey: Pubkey,
/// activated stake amount, set at delegate_stake() time
pub stake: u64,
/// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
pub activation_epoch: Epoch,
/// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
pub deactivation_epoch: Epoch,
}
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)] #[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Stakes { pub struct Stakes {
/// vote accounts /// vote accounts
vote_accounts: HashMap<Pubkey, (u64, Account)>, vote_accounts: HashMap<Pubkey, (u64, Account)>,
/// stake_accounts /// stake_delegations
stake_accounts: HashMap<Pubkey, Account>, stake_delegations: HashMap<Pubkey, Delegation>,
/// unclaimed points. /// unclaimed points.
// a point is a credit multiplied by the stake // a point is a credit multiplied by the stake
@ -41,19 +53,15 @@ impl Stakes {
self.epoch, self.epoch,
new_stake_history_entry( new_stake_history_entry(
self.epoch, self.epoch,
self.stake_accounts self.stake_delegations
.iter() .iter()
.filter_map(|(_pubkey, stake_account)| { .map(|(_pubkey, stake_delegation)| stake_delegation),
StakeState::stake_from(stake_account)
})
.collect::<Vec<_>>()
.iter(),
Some(&self.stake_history), Some(&self.stake_history),
), ),
); );
Stakes { Stakes {
stake_accounts: self.stake_accounts.clone(), stake_delegations: self.stake_delegations.clone(),
points: self.points, points: self.points,
epoch, epoch,
vote_accounts: self vote_accounts: self
@ -81,16 +89,14 @@ impl Stakes {
epoch: Epoch, epoch: Epoch,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
) -> u64 { ) -> u64 {
self.stake_accounts self.stake_delegations
.iter() .iter()
.map(|(_, stake_account)| { .map(|(_, stake_delegation)| {
StakeState::stake_from(stake_account).map_or(0, |stake| { if &stake_delegation.voter_pubkey == voter_pubkey {
if &stake.voter_pubkey == voter_pubkey { stake_delegation.stake(epoch, stake_history)
stake.stake(epoch, stake_history) } else {
} else { 0
0 }
}
})
}) })
.sum() .sum()
} }
@ -126,20 +132,20 @@ impl Stakes {
} }
} else if solana_stake_program::check_id(&account.owner) { } else if solana_stake_program::check_id(&account.owner) {
// old_stake is stake lamports and voter_pubkey from the pre-store() version // old_stake is stake lamports and voter_pubkey from the pre-store() version
let old_stake = self.stake_accounts.get(pubkey).and_then(|old_account| { let old_stake = self.stake_delegations.get(pubkey).map(|delegation| {
StakeState::stake_from(old_account).map(|stake| { (
( delegation.voter_pubkey,
stake.voter_pubkey, delegation.stake(self.epoch, Some(&self.stake_history)),
stake.stake(self.epoch, Some(&self.stake_history)), )
)
})
}); });
let stake = StakeState::stake_from(account).map(|stake| { let delegation = StakeState::delegation_from(account);
let stake = delegation.map(|delegation| {
( (
stake.voter_pubkey, delegation.voter_pubkey,
if account.lamports != 0 { if account.lamports != 0 {
stake.stake(self.epoch, Some(&self.stake_history)) delegation.stake(self.epoch, Some(&self.stake_history))
} else { } else {
0 0
}, },
@ -161,9 +167,9 @@ impl Stakes {
} }
if account.lamports == 0 { if account.lamports == 0 {
self.stake_accounts.remove(pubkey); self.stake_delegations.remove(pubkey);
} else { } else if let Some(delegation) = delegation {
self.stake_accounts.insert(*pubkey, account.clone()); self.stake_delegations.insert(*pubkey, delegation);
} }
} }
} }
@ -172,19 +178,6 @@ impl Stakes {
&self.vote_accounts &self.vote_accounts
} }
pub fn stake_accounts(&self) -> &HashMap<Pubkey, Account> {
&self.stake_accounts
}
pub fn rewards_pools(&self) -> impl Iterator<Item = (&Pubkey, &Account)> {
self.stake_accounts
.iter()
.filter(|(_key, account)| match StakeState::from(account) {
Some(StakeState::RewardsPool) => true,
_ => false,
})
}
pub fn highest_staked_node(&self) -> Option<Pubkey> { pub fn highest_staked_node(&self) -> Option<Pubkey> {
self.vote_accounts self.vote_accounts
.iter() .iter()