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!(
"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() {
println!("delegated voter pubkey: {}", stake.voter_pubkey);
if stake.delegation.voter_pubkey != Pubkey::default() {
println!("delegated voter pubkey: {}", stake.delegation.voter_pubkey);
}
println!(
"stake activates starting from epoch: {}",
if stake.activation_epoch < std::u64::MAX {
stake.activation_epoch
if stake.delegation.activation_epoch < std::u64::MAX {
stake.delegation.activation_epoch
} else {
0
}
);
if stake.deactivation_epoch < std::u64::MAX {
if stake.delegation.deactivation_epoch < std::u64::MAX {
println!(
"stake deactivates starting from epoch: {}",
stake.deactivation_epoch
stake.delegation.deactivation_epoch
);
}
show_authorized(&authorized);

View File

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

View File

@ -185,8 +185,7 @@ impl LocalCluster {
stake_config::create_account(
1,
&stake_config::Config {
warmup_rate: 1_000_000_000.0f64,
cooldown_rate: 1_000_000_000.0f64,
warmup_cooldown_rate: 1_000_000_000.0f64,
slash_penalty: std::u8::MAX,
},
),
@ -551,8 +550,8 @@ impl LocalCluster {
VoteState::from(&vote_account),
) {
(Some(stake_state), Some(vote_state)) => {
if stake_state.voter_pubkey != vote_account_pubkey
|| stake_state.stake != amount
if stake_state.delegation.voter_pubkey != vote_account_pubkey
|| stake_state.delegation.stake != amount
{
Err(Error::new(ErrorKind::Other, "invalid stake account state"))
} 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
// epoch
pub const DEFAULT_WARMUP_RATE: f64 = 0.25;
pub const DEFAULT_COOLDOWN_RATE: f64 = 0.25;
pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25;
pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct Config {
/// how much stake we can activate per-epoch as a fraction of currently effective stake
pub warmup_rate: f64,
/// how much stake we can deactivate as a fraction of currently effective stake
pub cooldown_rate: f64,
/// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake
pub warmup_cooldown_rate: f64,
/// percentage of stake lost when slash, expressed as a portion of std::u8::MAX
pub slash_penalty: u8,
}
@ -39,8 +36,7 @@ impl Config {
impl Default for Config {
fn default() -> Self {
Self {
warmup_rate: DEFAULT_WARMUP_RATE,
cooldown_rate: DEFAULT_COOLDOWN_RATE,
warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE,
slash_penalty: DEFAULT_SLASH_PENALTY,
}
}

View File

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

View File

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

View File

@ -1558,11 +1558,6 @@ impl Bank {
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
/// attributed to each 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},
timing::years_as_slots,
};
use solana_stake_program::stake_state::Stake;
use solana_stake_program::stake_state::{Delegation, Stake};
use solana_vote_program::{
vote_instruction,
vote_state::{self, Vote, VoteInit, VoteState, MAX_LOCKOUT_HISTORY},
@ -3277,8 +3272,11 @@ mod tests {
assert!(leader_stake > 0);
let leader_stake = Stake {
stake: leader_lamports,
activation_epoch: std::u64::MAX, // bootstrap
delegation: Delegation {
stake: leader_lamports,
activation_epoch: std::u64::MAX, // bootstrap
..Delegation::default()
},
..Stake::default()
};

View File

@ -4,17 +4,29 @@ use solana_sdk::account::Account;
use solana_sdk::clock::Epoch;
use solana_sdk::pubkey::Pubkey;
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 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)]
pub struct Stakes {
/// vote accounts
vote_accounts: HashMap<Pubkey, (u64, Account)>,
/// stake_accounts
stake_accounts: HashMap<Pubkey, Account>,
/// stake_delegations
stake_delegations: HashMap<Pubkey, Delegation>,
/// unclaimed points.
// a point is a credit multiplied by the stake
@ -41,19 +53,15 @@ impl Stakes {
self.epoch,
new_stake_history_entry(
self.epoch,
self.stake_accounts
self.stake_delegations
.iter()
.filter_map(|(_pubkey, stake_account)| {
StakeState::stake_from(stake_account)
})
.collect::<Vec<_>>()
.iter(),
.map(|(_pubkey, stake_delegation)| stake_delegation),
Some(&self.stake_history),
),
);
Stakes {
stake_accounts: self.stake_accounts.clone(),
stake_delegations: self.stake_delegations.clone(),
points: self.points,
epoch,
vote_accounts: self
@ -81,16 +89,14 @@ impl Stakes {
epoch: Epoch,
stake_history: Option<&StakeHistory>,
) -> u64 {
self.stake_accounts
self.stake_delegations
.iter()
.map(|(_, stake_account)| {
StakeState::stake_from(stake_account).map_or(0, |stake| {
if &stake.voter_pubkey == voter_pubkey {
stake.stake(epoch, stake_history)
} else {
0
}
})
.map(|(_, stake_delegation)| {
if &stake_delegation.voter_pubkey == voter_pubkey {
stake_delegation.stake(epoch, stake_history)
} else {
0
}
})
.sum()
}
@ -126,20 +132,20 @@ impl Stakes {
}
} else if solana_stake_program::check_id(&account.owner) {
// 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| {
StakeState::stake_from(old_account).map(|stake| {
(
stake.voter_pubkey,
stake.stake(self.epoch, Some(&self.stake_history)),
)
})
let old_stake = self.stake_delegations.get(pubkey).map(|delegation| {
(
delegation.voter_pubkey,
delegation.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 {
stake.stake(self.epoch, Some(&self.stake_history))
delegation.stake(self.epoch, Some(&self.stake_history))
} else {
0
},
@ -161,9 +167,9 @@ impl Stakes {
}
if account.lamports == 0 {
self.stake_accounts.remove(pubkey);
} else {
self.stake_accounts.insert(*pubkey, account.clone());
self.stake_delegations.remove(pubkey);
} else if let Some(delegation) = delegation {
self.stake_delegations.insert(*pubkey, delegation);
}
}
}
@ -172,19 +178,6 @@ impl Stakes {
&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> {
self.vote_accounts
.iter()