Fix slow/stuck unstaking due to toggling in epoch (#13501)

* Fix slow/stuck unstaking due to toggling in epoch

* nits

* nits

* Add stake_program_v2 feature status check to cli

Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
Ryo Onodera 2020-11-12 06:11:57 +09:00 committed by GitHub
parent 38f15e41b5
commit 89b474e192
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 422 additions and 108 deletions

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult}, cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount}, spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
stake::is_stake_program_v2_enabled,
}; };
use chrono::{Local, TimeZone}; use chrono::{Local, TimeZone};
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}; use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
@ -1349,6 +1350,8 @@ pub fn process_show_stakes(
let stake_history = from_account(&stake_history_account).ok_or_else(|| { let stake_history = from_account(&stake_history_account).ok_or_else(|| {
CliError::RpcRequestError("Failed to deserialize stake history".to_string()) CliError::RpcRequestError("Failed to deserialize stake history".to_string())
})?; })?;
// At v1.6, this check can be removed and simply passed as `true`
let stake_program_v2_enabled = is_stake_program_v2_enabled(rpc_client);
let mut stake_accounts: Vec<CliKeyedStakeState> = vec![]; let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
for (stake_pubkey, stake_account) in all_stake_accounts { for (stake_pubkey, stake_account) in all_stake_accounts {
@ -1364,6 +1367,7 @@ pub fn process_show_stakes(
use_lamports_unit, use_lamports_unit,
&stake_history, &stake_history,
&clock, &clock,
stake_program_v2_enabled,
), ),
}); });
} }
@ -1382,6 +1386,7 @@ pub fn process_show_stakes(
use_lamports_unit, use_lamports_unit,
&stake_history, &stake_history,
&clock, &clock,
stake_program_v2_enabled,
), ),
}); });
} }

View File

@ -35,6 +35,7 @@ use solana_sdk::{
account::from_account, account::from_account,
account_utils::StateMut, account_utils::StateMut,
clock::{Clock, Epoch, Slot, UnixTimestamp, SECONDS_PER_DAY}, clock::{Clock, Epoch, Slot, UnixTimestamp, SECONDS_PER_DAY},
feature, feature_set,
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
system_instruction::SystemError, system_instruction::SystemError,
@ -1501,6 +1502,7 @@ pub fn build_stake_state(
use_lamports_unit: bool, use_lamports_unit: bool,
stake_history: &StakeHistory, stake_history: &StakeHistory,
clock: &Clock, clock: &Clock,
stake_program_v2_enabled: bool,
) -> CliStakeState { ) -> CliStakeState {
match stake_state { match stake_state {
StakeState::Stake( StakeState::Stake(
@ -1512,9 +1514,12 @@ pub fn build_stake_state(
stake, stake,
) => { ) => {
let current_epoch = clock.epoch; let current_epoch = clock.epoch;
let (active_stake, activating_stake, deactivating_stake) = stake let (active_stake, activating_stake, deactivating_stake) =
.delegation stake.delegation.stake_activating_and_deactivating(
.stake_activating_and_deactivating(current_epoch, Some(stake_history)); current_epoch,
Some(stake_history),
stake_program_v2_enabled,
);
let lockup = if lockup.is_in_force(clock, None) { let lockup = if lockup.is_in_force(clock, None) {
Some(lockup.into()) Some(lockup.into())
} else { } else {
@ -1710,6 +1715,7 @@ pub fn process_show_stake_account(
use_lamports_unit, use_lamports_unit,
&stake_history, &stake_history,
&clock, &clock,
is_stake_program_v2_enabled(rpc_client), // At v1.6, this check can be removed and simply passed as `true`
); );
if state.stake_type == CliStakeType::Stake { if state.stake_type == CliStakeType::Stake {
@ -1881,6 +1887,15 @@ pub fn process_delegate_stake(
} }
} }
pub fn is_stake_program_v2_enabled(rpc_client: &RpcClient) -> bool {
rpc_client
.get_account(&feature_set::stake_program_v2::id())
.ok()
.and_then(|account| feature::from_account(&account))
.and_then(|feature| feature.activated_at)
.is_some()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1039,8 +1039,11 @@ impl JsonRpcRequestProcessor {
solana_sdk::account::from_account::<StakeHistory>(&stake_history_account) solana_sdk::account::from_account::<StakeHistory>(&stake_history_account)
.ok_or_else(Error::internal_error)?; .ok_or_else(Error::internal_error)?;
let (active, activating, deactivating) = let (active, activating, deactivating) = delegation.stake_activating_and_deactivating(
delegation.stake_activating_and_deactivating(epoch, Some(&stake_history)); epoch,
Some(&stake_history),
bank.stake_program_v2_enabled(),
);
let stake_activation_state = if deactivating > 0 { let stake_activation_state = if deactivating > 0 {
StakeActivationState::Deactivating StakeActivationState::Deactivating
} else if activating > 0 { } else if activating > 0 {

View File

@ -234,7 +234,10 @@ pub(crate) mod tests {
let result: Vec<_> = epoch_stakes_and_lockouts(&bank, first_leader_schedule_epoch); let result: Vec<_> = epoch_stakes_and_lockouts(&bank, first_leader_schedule_epoch);
assert_eq!( assert_eq!(
result, result,
vec![(leader_stake.stake(first_leader_schedule_epoch, None), None)] vec![(
leader_stake.stake(first_leader_schedule_epoch, None, true),
None
)]
); );
// epoch stakes and lockouts are saved off for the future epoch, should // epoch stakes and lockouts are saved off for the future epoch, should
@ -244,8 +247,14 @@ pub(crate) mod tests {
let stake_history = let stake_history =
from_account::<StakeHistory>(&bank.get_account(&stake_history::id()).unwrap()).unwrap(); from_account::<StakeHistory>(&bank.get_account(&stake_history::id()).unwrap()).unwrap();
let mut expected = vec![ let mut expected = vec![
(leader_stake.stake(bank.epoch(), Some(&stake_history)), None), (
(other_stake.stake(bank.epoch(), Some(&stake_history)), None), leader_stake.stake(bank.epoch(), Some(&stake_history), true),
None,
),
(
other_stake.stake(bank.epoch(), Some(&stake_history), true),
None,
),
]; ];
expected.sort(); expected.sort();

View File

@ -100,7 +100,7 @@ pub struct Stake {
impl Stake { impl Stake {
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
self.delegation.stake(epoch, history) self.delegation.stake(epoch, history, false)
} }
pub fn redeem_rewards( pub fn redeem_rewards(
@ -145,7 +145,7 @@ impl Stake {
for (epoch, final_epoch_credits, initial_epoch_credits) in for (epoch, final_epoch_credits, initial_epoch_credits) in
new_vote_state.epoch_credits().iter().copied() new_vote_state.epoch_credits().iter().copied()
{ {
let stake = u128::from(self.delegation.stake(epoch, stake_history)); let stake = u128::from(self.delegation.stake(epoch, stake_history, false));
// figure out how much this stake has seen that // figure out how much this stake has seen that
// for which the vote account has a record // for which the vote account has a record
@ -596,7 +596,9 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
.check(&signers, StakeAuthorize::Withdrawer)?; .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.delegation.deactivation_epoch { let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
stake.delegation.stake(clock.epoch, Some(stake_history)) stake
.delegation
.stake(clock.epoch, Some(stake_history), false)
} 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
@ -693,11 +695,12 @@ pub fn calculate_points(
} }
} }
// utility function, used by runtime::Stakes, tests // utility function, used by runtime::Stakes and tests
pub fn new_stake_history_entry<'a, I>( pub fn new_stake_history_entry<'a, I>(
epoch: Epoch, epoch: Epoch,
stakes: I, stakes: I,
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> StakeHistoryEntry ) -> StakeHistoryEntry
where where
I: Iterator<Item = &'a Delegation>, I: Iterator<Item = &'a Delegation>,
@ -708,7 +711,10 @@ where
(a.0 + b.0, a.1 + b.1, a.2 + b.2) (a.0 + b.0, a.1 + b.1, a.2 + b.2)
} }
let (effective, activating, deactivating) = stakes.fold((0, 0, 0), |sum, stake| { let (effective, activating, deactivating) = stakes.fold((0, 0, 0), |sum, stake| {
add(sum, stake.stake_activating_and_deactivating(epoch, history)) add(
sum,
stake.stake_activating_and_deactivating(epoch, history, fix_stake_deactivate),
)
}); });
StakeHistoryEntry { StakeHistoryEntry {
@ -1041,6 +1047,7 @@ mod tests {
epoch, epoch,
delegations.iter().chain(bootstrap_delegation.iter()), delegations.iter().chain(bootstrap_delegation.iter()),
Some(&stake_history), Some(&stake_history),
false,
); );
stake_history.add(epoch, entry); stake_history.add(epoch, entry);
} }
@ -1063,25 +1070,34 @@ mod tests {
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
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(stake.activation_epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(
stake.activation_epoch,
Some(&stake_history),
false
),
(0, stake.stake, 0) (0, stake.stake, 0)
); );
for epoch in stake.activation_epoch + 1..stake.deactivation_epoch { for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(epoch, Some(&stake_history), false),
(stake.stake, 0, 0) (stake.stake, 0, 0)
); );
} }
// assert that this stake is full deactivating // assert that this stake is full deactivating
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(
stake.deactivation_epoch,
Some(&stake_history),
false
),
(stake.stake, 0, stake.stake) (stake.stake, 0, stake.stake)
); );
// assert that this stake is fully deactivated if there's no history // assert that this stake is fully deactivated if there's no history
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1, stake.deactivation_epoch + 1,
Some(&stake_history) Some(&stake_history),
false,
), ),
(0, 0, 0) (0, 0, 0)
); );
@ -1096,7 +1112,7 @@ mod tests {
); );
// assert that this stake is broken, because above setup is broken // assert that this stake is broken, because above setup is broken
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(1, Some(&stake_history)), stake.stake_activating_and_deactivating(1, Some(&stake_history), false),
(0, stake.stake, 0) (0, stake.stake, 0)
); );
@ -1111,7 +1127,7 @@ mod tests {
); );
// assert that this stake is broken, because above setup is broken // assert that this stake is broken, because above setup is broken
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(2, Some(&stake_history)), stake.stake_activating_and_deactivating(2, Some(&stake_history), false),
(increment, stake.stake - increment, 0) (increment, stake.stake - increment, 0)
); );
@ -1130,7 +1146,8 @@ mod tests {
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1, stake.deactivation_epoch + 1,
Some(&stake_history) Some(&stake_history),
false,
), ),
(stake.stake, 0, stake.stake) // says "I'm still waiting for deactivation" (stake.stake, 0, stake.stake) // says "I'm still waiting for deactivation"
); );
@ -1148,7 +1165,8 @@ mod tests {
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 2, stake.deactivation_epoch + 2,
Some(&stake_history) Some(&stake_history),
false,
), ),
(stake.stake - increment, 0, stake.stake - increment) // hung, should be lower (stake.stake - increment, 0, stake.stake - increment) // hung, should be lower
); );
@ -1213,7 +1231,7 @@ mod tests {
(0, history.deactivating) (0, history.deactivating)
}; };
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(epoch, Some(&stake_history), false),
(expected_stake, expected_activating, expected_deactivating) (expected_stake, expected_activating, expected_deactivating)
); );
} }
@ -1240,7 +1258,7 @@ mod tests {
for epoch in 0..epochs { for epoch in 0..epochs {
let stake = delegations let stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(epoch, Some(&stake_history))) .map(|delegation| delegation.stake(epoch, Some(&stake_history), false))
.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);
@ -1299,7 +1317,7 @@ mod tests {
let mut prev_total_effective_stake = delegations let mut prev_total_effective_stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(0, Some(&stake_history))) .map(|delegation| delegation.stake(0, Some(&stake_history), false))
.sum::<u64>(); .sum::<u64>();
// uncomment and add ! for fun with graphing // uncomment and add ! for fun with graphing
@ -1307,7 +1325,7 @@ mod tests {
for epoch in 1..epochs { for epoch in 1..epochs {
let total_effective_stake = delegations let total_effective_stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(epoch, Some(&stake_history))) .map(|delegation| delegation.stake(epoch, Some(&stake_history), false))
.sum::<u64>(); .sum::<u64>();
let delta = if total_effective_stake > prev_total_effective_stake { let delta = if total_effective_stake > prev_total_effective_stake {

View File

@ -213,8 +213,14 @@ impl Delegation {
self.activation_epoch == std::u64::MAX self.activation_epoch == std::u64::MAX
} }
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { pub fn stake(
self.stake_activating_and_deactivating(epoch, history).0 &self,
epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> u64 {
self.stake_activating_and_deactivating(epoch, history, fix_stake_deactivate)
.0
} }
#[allow(clippy::comparison_chain)] #[allow(clippy::comparison_chain)]
@ -222,11 +228,13 @@ impl Delegation {
&self, &self,
target_epoch: Epoch, target_epoch: Epoch,
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> (u64, u64, u64) { ) -> (u64, u64, u64) {
let delegated_stake = self.stake; let delegated_stake = self.stake;
// first, calculate an effective and activating stake // first, calculate an effective and activating stake
let (effective_stake, activating_stake) = self.stake_and_activating(target_epoch, history); let (effective_stake, activating_stake) =
self.stake_and_activating(target_epoch, history, fix_stake_deactivate);
// then de-activate some portion if necessary // then de-activate some portion if necessary
if target_epoch < self.deactivation_epoch { if target_epoch < self.deactivation_epoch {
@ -302,12 +310,17 @@ impl Delegation {
&self, &self,
target_epoch: Epoch, target_epoch: Epoch,
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> (u64, u64) { ) -> (u64, u64) {
let delegated_stake = self.stake; let delegated_stake = self.stake;
if self.is_bootstrap() { if self.is_bootstrap() {
// fully effective immediately // fully effective immediately
(delegated_stake, 0) (delegated_stake, 0)
} else if fix_stake_deactivate && self.activation_epoch == self.deactivation_epoch {
// activated but instantly deactivated; no stake at all regardless of target_epoch
// this must be after the bootstrap check and before all-is-activating check
(0, 0)
} else if target_epoch == self.activation_epoch { } else if target_epoch == self.activation_epoch {
// all is activating // all is activating
(0, delegated_stake) (0, delegated_stake)
@ -441,8 +454,13 @@ pub struct PointValue {
} }
impl Stake { impl Stake {
pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 { pub fn stake(
self.delegation.stake(epoch, history) &self,
epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> u64 {
self.delegation.stake(epoch, history, fix_stake_deactivate)
} }
pub fn redeem_rewards( pub fn redeem_rewards(
@ -451,12 +469,14 @@ impl Stake {
vote_state: &VoteState, vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> Option<(u64, u64)> { ) -> Option<(u64, u64)> {
self.calculate_rewards( self.calculate_rewards(
point_value, point_value,
vote_state, vote_state,
stake_history, stake_history,
inflation_point_calc_tracer, inflation_point_calc_tracer,
fix_stake_deactivate,
) )
.map(|(stakers_reward, voters_reward, credits_observed)| { .map(|(stakers_reward, voters_reward, credits_observed)| {
self.credits_observed = credits_observed; self.credits_observed = credits_observed;
@ -470,9 +490,15 @@ impl Stake {
vote_state: &VoteState, vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> u128 { ) -> u128 {
self.calculate_points_and_credits(vote_state, stake_history, inflation_point_calc_tracer) self.calculate_points_and_credits(
.0 vote_state,
stake_history,
inflation_point_calc_tracer,
fix_stake_deactivate,
)
.0
} }
/// for a given stake and vote_state, calculate how many /// for a given stake and vote_state, calculate how many
@ -483,6 +509,7 @@ impl Stake {
new_vote_state: &VoteState, new_vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> (u128, u64) { ) -> (u128, u64) {
// if there is no newer credits since observed, return no point // if there is no newer credits since observed, return no point
if new_vote_state.credits() <= self.credits_observed { if new_vote_state.credits() <= self.credits_observed {
@ -495,7 +522,11 @@ impl Stake {
for (epoch, final_epoch_credits, initial_epoch_credits) in for (epoch, final_epoch_credits, initial_epoch_credits) in
new_vote_state.epoch_credits().iter().copied() new_vote_state.epoch_credits().iter().copied()
{ {
let stake = u128::from(self.delegation.stake(epoch, stake_history)); let stake = u128::from(self.delegation.stake(
epoch,
stake_history,
fix_stake_deactivate,
));
// figure out how much this stake has seen that // figure out how much this stake has seen that
// for which the vote account has a record // for which the vote account has a record
@ -542,11 +573,13 @@ impl Stake {
vote_state: &VoteState, vote_state: &VoteState,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> Option<(u64, u64, u64)> { ) -> Option<(u64, u64, u64)> {
let (points, credits_observed) = self.calculate_points_and_credits( let (points, credits_observed) = self.calculate_points_and_credits(
vote_state, vote_state,
stake_history, stake_history,
inflation_point_calc_tracer, inflation_point_calc_tracer,
fix_stake_deactivate,
); );
if points == 0 || point_value.points == 0 { if points == 0 || point_value.points == 0 {
@ -597,7 +630,7 @@ impl Stake {
// can't redelegate if stake is active. either the stake // can't redelegate if stake is active. either the stake
// is freshly activated or has fully de-activated. redelegation // is freshly activated or has fully de-activated. redelegation
// implies re-activation // implies re-activation
if self.stake(clock.epoch, Some(stake_history)) != 0 { if self.stake(clock.epoch, Some(stake_history), true) != 0 {
return Err(StakeError::TooSoonToRedelegate); return Err(StakeError::TooSoonToRedelegate);
} }
self.delegation.stake = stake_lamports; self.delegation.stake = stake_lamports;
@ -951,7 +984,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
let meta = match self.state()? { let meta = match self.state()? {
StakeState::Stake(meta, stake) => { StakeState::Stake(meta, stake) => {
// stake must be fully de-activated // stake must be fully de-activated
if stake.stake(clock.epoch, Some(stake_history)) != 0 { if stake.stake(clock.epoch, Some(stake_history), true) != 0 {
return Err(StakeError::MergeActivatedStake.into()); return Err(StakeError::MergeActivatedStake.into());
} }
meta meta
@ -965,7 +998,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
let source_meta = match source_stake.state()? { let source_meta = match source_stake.state()? {
StakeState::Stake(meta, stake) => { StakeState::Stake(meta, stake) => {
// stake must be fully de-activated // stake must be fully de-activated
if stake.stake(clock.epoch, Some(stake_history)) != 0 { if stake.stake(clock.epoch, Some(stake_history), true) != 0 {
return Err(StakeError::MergeActivatedStake.into()); return Err(StakeError::MergeActivatedStake.into());
} }
meta meta
@ -1007,7 +1040,9 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
.check(&signers, StakeAuthorize::Withdrawer)?; .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.delegation.deactivation_epoch { let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
stake.delegation.stake(clock.epoch, Some(stake_history)) stake
.delegation
.stake(clock.epoch, Some(stake_history), true)
} 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
@ -1067,6 +1102,7 @@ pub fn redeem_rewards(
point_value: &PointValue, point_value: &PointValue,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>, inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> Result<(u64, u64), InstructionError> { ) -> Result<(u64, u64), InstructionError> {
if let StakeState::Stake(meta, mut stake) = stake_account.state()? { if let StakeState::Stake(meta, mut stake) = stake_account.state()? {
let vote_state: VoteState = let vote_state: VoteState =
@ -1085,6 +1121,7 @@ pub fn redeem_rewards(
&vote_state, &vote_state,
stake_history, stake_history,
inflation_point_calc_tracer, inflation_point_calc_tracer,
fix_stake_deactivate,
) { ) {
stake_account.lamports += stakers_reward; stake_account.lamports += stakers_reward;
vote_account.lamports += voters_reward; vote_account.lamports += voters_reward;
@ -1105,12 +1142,18 @@ pub fn calculate_points(
stake_account: &Account, stake_account: &Account,
vote_account: &Account, vote_account: &Account,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> Result<u128, InstructionError> { ) -> Result<u128, InstructionError> {
if let StakeState::Stake(_meta, stake) = stake_account.state()? { if let StakeState::Stake(_meta, stake) = stake_account.state()? {
let vote_state: VoteState = let vote_state: VoteState =
StateMut::<VoteStateVersions>::state(vote_account)?.convert_to_current(); StateMut::<VoteStateVersions>::state(vote_account)?.convert_to_current();
Ok(stake.calculate_points(&vote_state, stake_history, &mut null_tracer())) Ok(stake.calculate_points(
&vote_state,
stake_history,
&mut null_tracer(),
fix_stake_deactivate,
))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
@ -1134,6 +1177,7 @@ pub fn new_stake_history_entry<'a, I>(
epoch: Epoch, epoch: Epoch,
stakes: I, stakes: I,
history: Option<&StakeHistory>, history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> StakeHistoryEntry ) -> StakeHistoryEntry
where where
I: Iterator<Item = &'a Delegation>, I: Iterator<Item = &'a Delegation>,
@ -1144,7 +1188,10 @@ where
(a.0 + b.0, a.1 + b.1, a.2 + b.2) (a.0 + b.0, a.1 + b.1, a.2 + b.2)
} }
let (effective, activating, deactivating) = stakes.fold((0, 0, 0), |sum, stake| { let (effective, activating, deactivating) = stakes.fold((0, 0, 0), |sum, stake| {
add(sum, stake.stake_activating_and_deactivating(epoch, history)) add(
sum,
stake.stake_activating_and_deactivating(epoch, history, fix_stake_deactivate),
)
}); });
StakeHistoryEntry { StakeHistoryEntry {
@ -1486,6 +1533,7 @@ mod tests {
epoch, epoch,
delegations.iter().chain(bootstrap_delegation.iter()), delegations.iter().chain(bootstrap_delegation.iter()),
Some(&stake_history), Some(&stake_history),
true,
); );
stake_history.add(epoch, entry); stake_history.add(epoch, entry);
} }
@ -1508,25 +1556,34 @@ mod tests {
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
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(stake.activation_epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(
stake.activation_epoch,
Some(&stake_history),
true
),
(0, stake.stake, 0) (0, stake.stake, 0)
); );
for epoch in stake.activation_epoch + 1..stake.deactivation_epoch { for epoch in stake.activation_epoch + 1..stake.deactivation_epoch {
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(epoch, Some(&stake_history), true),
(stake.stake, 0, 0) (stake.stake, 0, 0)
); );
} }
// assert that this stake is full deactivating // assert that this stake is full deactivating
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(stake.deactivation_epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(
stake.deactivation_epoch,
Some(&stake_history),
true
),
(stake.stake, 0, stake.stake) (stake.stake, 0, stake.stake)
); );
// assert that this stake is fully deactivated if there's no history // assert that this stake is fully deactivated if there's no history
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1, stake.deactivation_epoch + 1,
Some(&stake_history) Some(&stake_history),
true,
), ),
(0, 0, 0) (0, 0, 0)
); );
@ -1541,7 +1598,7 @@ mod tests {
); );
// assert that this stake is broken, because above setup is broken // assert that this stake is broken, because above setup is broken
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(1, Some(&stake_history)), stake.stake_activating_and_deactivating(1, Some(&stake_history), true),
(0, stake.stake, 0) (0, stake.stake, 0)
); );
@ -1556,7 +1613,7 @@ mod tests {
); );
// assert that this stake is broken, because above setup is broken // assert that this stake is broken, because above setup is broken
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(2, Some(&stake_history)), stake.stake_activating_and_deactivating(2, Some(&stake_history), true),
(increment, stake.stake - increment, 0) (increment, stake.stake - increment, 0)
); );
@ -1575,7 +1632,8 @@ mod tests {
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 1, stake.deactivation_epoch + 1,
Some(&stake_history) Some(&stake_history),
true,
), ),
(stake.stake, 0, stake.stake) // says "I'm still waiting for deactivation" (stake.stake, 0, stake.stake) // says "I'm still waiting for deactivation"
); );
@ -1593,12 +1651,158 @@ mod tests {
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating( stake.stake_activating_and_deactivating(
stake.deactivation_epoch + 2, stake.deactivation_epoch + 2,
Some(&stake_history) Some(&stake_history),
true,
), ),
(stake.stake - increment, 0, stake.stake - increment) // hung, should be lower (stake.stake - increment, 0, stake.stake - increment) // hung, should be lower
); );
} }
mod same_epoch_activation_then_deactivation {
use super::*;
enum OldDeactivationBehavior {
Stuck,
Slow,
}
fn do_test(
old_behavior: OldDeactivationBehavior,
fix_stake_deactivate: bool,
expected_stakes: &[(u64, u64, u64)],
) {
let cluster_stake = 1_000;
let activating_stake = 10_000;
let some_stake = 700;
let some_epoch = 0;
let stake = Delegation {
stake: some_stake,
activation_epoch: some_epoch,
deactivation_epoch: some_epoch,
..Delegation::default()
};
let mut stake_history = StakeHistory::default();
let cluster_deactivation_at_stake_modified_epoch = match old_behavior {
OldDeactivationBehavior::Stuck => 0,
OldDeactivationBehavior::Slow => 1000,
};
let stake_history_entries = vec![
(
cluster_stake,
activating_stake,
cluster_deactivation_at_stake_modified_epoch,
),
(cluster_stake, activating_stake, 1000),
(cluster_stake, activating_stake, 1000),
(cluster_stake, activating_stake, 100),
(cluster_stake, activating_stake, 100),
(cluster_stake, activating_stake, 100),
(cluster_stake, activating_stake, 100),
];
for (epoch, (effective, activating, deactivating)) in
stake_history_entries.into_iter().enumerate()
{
stake_history.add(
epoch as Epoch,
StakeHistoryEntry {
effective,
activating,
deactivating,
},
);
}
assert_eq!(
expected_stakes,
(0..expected_stakes.len())
.map(|epoch| stake.stake_activating_and_deactivating(
epoch as u64,
Some(&stake_history),
fix_stake_deactivate
))
.collect::<Vec<_>>()
);
}
#[test]
fn test_old_behavior_slow() {
do_test(
OldDeactivationBehavior::Slow,
false,
&[
(0, 0, 0),
(13, 0, 13),
(10, 0, 10),
(8, 0, 8),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
],
);
}
#[test]
fn test_old_behavior_stuck() {
do_test(
OldDeactivationBehavior::Stuck,
false,
&[
(0, 0, 0),
(17, 0, 17),
(17, 0, 17),
(17, 0, 17),
(17, 0, 17),
(17, 0, 17),
(17, 0, 17),
],
);
}
#[test]
fn test_new_behavior_previously_slow() {
// any stake accounts activated and deactivated at the same epoch
// shouldn't been activated (then deactivated) at all!
do_test(
OldDeactivationBehavior::Slow,
true,
&[
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
],
);
}
#[test]
fn test_new_behavior_previously_stuck() {
// any stake accounts activated and deactivated at the same epoch
// shouldn't been activated (then deactivated) at all!
do_test(
OldDeactivationBehavior::Stuck,
true,
&[
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
],
);
}
}
#[test] #[test]
fn test_stop_activating_after_deactivation() { fn test_stop_activating_after_deactivation() {
solana_logger::setup(); solana_logger::setup();
@ -1658,7 +1862,7 @@ mod tests {
(0, history.deactivating) (0, history.deactivating)
}; };
assert_eq!( assert_eq!(
stake.stake_activating_and_deactivating(epoch, Some(&stake_history)), stake.stake_activating_and_deactivating(epoch, Some(&stake_history), true),
(expected_stake, expected_activating, expected_deactivating) (expected_stake, expected_activating, expected_deactivating)
); );
} }
@ -1685,7 +1889,7 @@ mod tests {
for epoch in 0..epochs { for epoch in 0..epochs {
let stake = delegations let stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(epoch, Some(&stake_history))) .map(|delegation| delegation.stake(epoch, Some(&stake_history), true))
.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);
@ -1744,7 +1948,7 @@ mod tests {
let mut prev_total_effective_stake = delegations let mut prev_total_effective_stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(0, Some(&stake_history))) .map(|delegation| delegation.stake(0, Some(&stake_history), true))
.sum::<u64>(); .sum::<u64>();
// uncomment and add ! for fun with graphing // uncomment and add ! for fun with graphing
@ -1752,7 +1956,7 @@ mod tests {
for epoch in 1..epochs { for epoch in 1..epochs {
let total_effective_stake = delegations let total_effective_stake = delegations
.iter() .iter()
.map(|delegation| delegation.stake(epoch, Some(&stake_history))) .map(|delegation| delegation.stake(epoch, Some(&stake_history), true))
.sum::<u64>(); .sum::<u64>();
let delta = if total_effective_stake > prev_total_effective_stake { let delta = if total_effective_stake > prev_total_effective_stake {
@ -2548,6 +2752,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2566,6 +2771,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2601,6 +2807,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2614,7 +2821,7 @@ mod tests {
// no overflow on points // no overflow on points
assert_eq!( assert_eq!(
u128::from(stake.delegation.stake) * epoch_slots, u128::from(stake.delegation.stake) * epoch_slots,
stake.calculate_points(&vote_state, None, &mut null_tracer()) stake.calculate_points(&vote_state, None, &mut null_tracer(), true)
); );
} }
@ -2642,6 +2849,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2660,6 +2868,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2675,6 +2884,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2693,6 +2903,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2709,6 +2920,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2731,6 +2943,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
@ -2747,6 +2960,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
vote_state.commission = 99; vote_state.commission = 99;
@ -2760,6 +2974,7 @@ mod tests {
&vote_state, &vote_state,
None, None,
&mut null_tracer(), &mut null_tracer(),
true,
) )
); );
} }

View File

@ -828,7 +828,7 @@ impl Bank {
capitalization: AtomicU64::new(parent.capitalization()), capitalization: AtomicU64::new(parent.capitalization()),
inflation: parent.inflation.clone(), inflation: parent.inflation.clone(),
transaction_count: AtomicU64::new(parent.transaction_count()), transaction_count: AtomicU64::new(parent.transaction_count()),
stakes: RwLock::new(parent.stakes.read().unwrap().clone_with_epoch(epoch)), stakes: RwLock::new(parent.stakes.read().unwrap().clone()),
epoch_stakes: parent.epoch_stakes.clone(), epoch_stakes: parent.epoch_stakes.clone(),
parent_hash: parent.hash(), parent_hash: parent.hash(),
parent_slot: parent.slot(), parent_slot: parent.slot(),
@ -860,8 +860,6 @@ impl Bank {
("block_height", new.block_height, i64) ("block_height", new.block_height, i64)
); );
let leader_schedule_epoch = epoch_schedule.get_leader_schedule_epoch(slot);
new.update_epoch_stakes(leader_schedule_epoch);
new.ancestors.insert(new.slot(), 0); new.ancestors.insert(new.slot(), 0);
new.parents().iter().enumerate().for_each(|(i, p)| { new.parents().iter().enumerate().for_each(|(i, p)| {
new.ancestors.insert(p.slot(), i + 1); new.ancestors.insert(p.slot(), i + 1);
@ -871,7 +869,15 @@ impl Bank {
if parent.epoch() < new.epoch() { if parent.epoch() < new.epoch() {
new.apply_feature_activations(false); new.apply_feature_activations(false);
} }
let cloned = new
.stakes
.read()
.unwrap()
.clone_with_epoch(epoch, new.stake_program_v2_enabled());
*new.stakes.write().unwrap() = cloned;
let leader_schedule_epoch = epoch_schedule.get_leader_schedule_epoch(slot);
new.update_epoch_stakes(leader_schedule_epoch);
new.update_slot_hashes(); new.update_slot_hashes();
new.update_rewards(parent.epoch(), reward_calc_tracer); new.update_rewards(parent.epoch(), reward_calc_tracer);
new.update_stake_history(Some(parent.epoch())); new.update_stake_history(Some(parent.epoch()));
@ -1315,8 +1321,11 @@ impl Bank {
let old_vote_balance_and_staked = self.stakes.read().unwrap().vote_balance_and_staked(); let old_vote_balance_and_staked = self.stakes.read().unwrap().vote_balance_and_staked();
let validator_point_value = let validator_point_value = self.pay_validator_rewards(
self.pay_validator_rewards(validator_rewards, reward_calc_tracer); validator_rewards,
reward_calc_tracer,
self.stake_program_v2_enabled(),
);
if !self if !self
.feature_set .feature_set
@ -1429,6 +1438,7 @@ impl Bank {
&mut self, &mut self,
rewards: u64, rewards: u64,
reward_calc_tracer: &mut Option<impl FnMut(&RewardCalculationEvent)>, reward_calc_tracer: &mut Option<impl FnMut(&RewardCalculationEvent)>,
fix_stake_deactivate: bool,
) -> f64 { ) -> f64 {
let stake_history = self.stakes.read().unwrap().history().clone(); let stake_history = self.stakes.read().unwrap().history().clone();
@ -1442,8 +1452,13 @@ impl Bank {
.map(move |(_stake_pubkey, stake_account)| (stake_account, vote_account)) .map(move |(_stake_pubkey, stake_account)| (stake_account, vote_account))
}) })
.map(|(stake_account, vote_account)| { .map(|(stake_account, vote_account)| {
stake_state::calculate_points(&stake_account, &vote_account, Some(&stake_history)) stake_state::calculate_points(
.unwrap_or(0) &stake_account,
&vote_account,
Some(&stake_history),
fix_stake_deactivate,
)
.unwrap_or(0)
}) })
.sum(); .sum();
@ -1474,6 +1489,7 @@ impl Bank {
&point_value, &point_value,
Some(&stake_history), Some(&stake_history),
&mut reward_calc_tracer.as_mut(), &mut reward_calc_tracer.as_mut(),
fix_stake_deactivate,
); );
if let Ok((stakers_reward, _voters_reward)) = redeemed { if let Ok((stakers_reward, _voters_reward)) = redeemed {
self.store_account(&stake_pubkey, &stake_account); self.store_account(&stake_pubkey, &stake_account);
@ -3413,7 +3429,10 @@ impl Bank {
self.rc.accounts.store_slow(self.slot(), pubkey, account); self.rc.accounts.store_slow(self.slot(), pubkey, account);
if Stakes::is_stake(account) { if Stakes::is_stake(account) {
self.stakes.write().unwrap().store(pubkey, account); self.stakes
.write()
.unwrap()
.store(pubkey, account, self.stake_program_v2_enabled());
} }
} }
@ -3860,9 +3879,11 @@ impl Bank {
.filter(|(_key, account)| (Stakes::is_stake(account))) .filter(|(_key, account)| (Stakes::is_stake(account)))
{ {
if Stakes::is_stake(account) { if Stakes::is_stake(account) {
if let Some(old_vote_account) = if let Some(old_vote_account) = self.stakes.write().unwrap().store(
self.stakes.write().unwrap().store(pubkey, account) pubkey,
{ account,
self.stake_program_v2_enabled(),
) {
overwritten_vote_accounts.push(OverwrittenVoteAccount { overwritten_vote_accounts.push(OverwrittenVoteAccount {
account: old_vote_account, account: old_vote_account,
transaction_index, transaction_index,
@ -4049,6 +4070,11 @@ impl Bank {
self.feature_set.cumulative_rent_related_fixes_enabled() self.feature_set.cumulative_rent_related_fixes_enabled()
} }
pub fn stake_program_v2_enabled(&self) -> bool {
self.feature_set
.is_active(&feature_set::stake_program_v2::id())
}
// This is called from snapshot restore AND for each epoch boundary // This is called from snapshot restore AND for each epoch boundary
// The entire code path herein must be idempotent // The entire code path herein must be idempotent
fn apply_feature_activations(&mut self, init_finish_or_warp: bool) { fn apply_feature_activations(&mut self, init_finish_or_warp: bool) {
@ -6033,7 +6059,8 @@ pub(crate) mod tests {
.map(move |(_stake_pubkey, stake_account)| (stake_account, vote_account)) .map(move |(_stake_pubkey, stake_account)| (stake_account, vote_account))
}) })
.map(|(stake_account, vote_account)| { .map(|(stake_account, vote_account)| {
stake_state::calculate_points(&stake_account, &vote_account, None).unwrap_or(0) stake_state::calculate_points(&stake_account, &vote_account, None, true)
.unwrap_or(0)
}) })
.sum(); .sum();
@ -7453,7 +7480,7 @@ pub(crate) mod tests {
// epoch_stakes are a snapshot at the leader_schedule_slot_offset boundary // epoch_stakes are a snapshot at the leader_schedule_slot_offset boundary
// in the prior epoch (0 in this case) // in the prior epoch (0 in this case)
assert_eq!( assert_eq!(
leader_stake.stake(0, None), leader_stake.stake(0, None, true),
vote_accounts.unwrap().get(&leader_vote_account).unwrap().0 vote_accounts.unwrap().get(&leader_vote_account).unwrap().0
); );
@ -7469,7 +7496,7 @@ pub(crate) mod tests {
assert!(child.epoch_vote_accounts(epoch).is_some()); assert!(child.epoch_vote_accounts(epoch).is_some());
assert_eq!( assert_eq!(
leader_stake.stake(child.epoch(), None), leader_stake.stake(child.epoch(), None, true),
child child
.epoch_vote_accounts(epoch) .epoch_vote_accounts(epoch)
.unwrap() .unwrap()
@ -7487,7 +7514,7 @@ pub(crate) mod tests {
); );
assert!(child.epoch_vote_accounts(epoch).is_some()); assert!(child.epoch_vote_accounts(epoch).is_some());
assert_eq!( assert_eq!(
leader_stake.stake(child.epoch(), None), leader_stake.stake(child.epoch(), None, true),
child child
.epoch_vote_accounts(epoch) .epoch_vote_accounts(epoch)
.unwrap() .unwrap()

View File

@ -29,7 +29,7 @@ impl Stakes {
pub fn history(&self) -> &StakeHistory { pub fn history(&self) -> &StakeHistory {
&self.stake_history &self.stake_history
} }
pub fn clone_with_epoch(&self, next_epoch: Epoch) -> Self { pub fn clone_with_epoch(&self, next_epoch: Epoch, fix_stake_deactivate: bool) -> Self {
let prev_epoch = self.epoch; let prev_epoch = self.epoch;
if prev_epoch == next_epoch { if prev_epoch == next_epoch {
self.clone() self.clone()
@ -44,6 +44,7 @@ impl Stakes {
.iter() .iter()
.map(|(_pubkey, stake_delegation)| stake_delegation), .map(|(_pubkey, stake_delegation)| stake_delegation),
Some(&self.stake_history), Some(&self.stake_history),
fix_stake_deactivate,
), ),
); );
@ -59,6 +60,7 @@ impl Stakes {
pubkey, pubkey,
next_epoch, next_epoch,
Some(&stake_history_upto_prev_epoch), Some(&stake_history_upto_prev_epoch),
fix_stake_deactivate,
), ),
account.clone(), account.clone(),
), ),
@ -82,12 +84,13 @@ impl Stakes {
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
epoch: Epoch, epoch: Epoch,
stake_history: Option<&StakeHistory>, stake_history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> u64 { ) -> u64 {
self.stake_delegations self.stake_delegations
.iter() .iter()
.map(|(_, stake_delegation)| { .map(|(_, stake_delegation)| {
if &stake_delegation.voter_pubkey == voter_pubkey { if &stake_delegation.voter_pubkey == voter_pubkey {
stake_delegation.stake(epoch, stake_history) stake_delegation.stake(epoch, stake_history, fix_stake_deactivate)
} else { } else {
0 0
} }
@ -113,12 +116,24 @@ impl Stakes {
&& account.data.len() >= std::mem::size_of::<StakeState>() && account.data.len() >= std::mem::size_of::<StakeState>()
} }
pub fn store(&mut self, pubkey: &Pubkey, account: &Account) -> Option<Account> { pub fn store(
&mut self,
pubkey: &Pubkey,
account: &Account,
fix_stake_deactivate: bool,
) -> Option<Account> {
if solana_vote_program::check_id(&account.owner) { if solana_vote_program::check_id(&account.owner) {
let old = self.vote_accounts.remove(pubkey); let old = self.vote_accounts.remove(pubkey);
if account.lamports != 0 { if account.lamports != 0 {
let stake = old.as_ref().map_or_else( let stake = old.as_ref().map_or_else(
|| self.calculate_stake(pubkey, self.epoch, Some(&self.stake_history)), || {
self.calculate_stake(
pubkey,
self.epoch,
Some(&self.stake_history),
fix_stake_deactivate,
)
},
|v| v.0, |v| v.0,
); );
@ -130,7 +145,7 @@ impl Stakes {
let old_stake = self.stake_delegations.get(pubkey).map(|delegation| { let old_stake = self.stake_delegations.get(pubkey).map(|delegation| {
( (
delegation.voter_pubkey, delegation.voter_pubkey,
delegation.stake(self.epoch, Some(&self.stake_history)), delegation.stake(self.epoch, Some(&self.stake_history), fix_stake_deactivate),
) )
}); });
@ -140,7 +155,11 @@ impl Stakes {
( (
delegation.voter_pubkey, delegation.voter_pubkey,
if account.lamports != 0 { if account.lamports != 0 {
delegation.stake(self.epoch, Some(&self.stake_history)) delegation.stake(
self.epoch,
Some(&self.stake_history),
fix_stake_deactivate,
)
} else { } else {
0 0
}, },
@ -264,44 +283,44 @@ pub mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, mut stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
let stake = StakeState::stake_from(&stake_account).unwrap(); let stake = StakeState::stake_from(&stake_account).unwrap();
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0, vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(i, None) stake.stake(i, None, true)
); );
} }
stake_account.lamports = 42; stake_account.lamports = 42;
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0, vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(i, None) stake.stake(i, None, true)
); // stays old stake, because only 10 is activated ); // stays old stake, because only 10 is activated
} }
// activate more // activate more
let (_stake_pubkey, mut stake_account) = create_stake_account(42, &vote_pubkey); let (_stake_pubkey, mut stake_account) = create_stake_account(42, &vote_pubkey);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
let stake = StakeState::stake_from(&stake_account).unwrap(); let stake = StakeState::stake_from(&stake_account).unwrap();
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0, vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(i, None) stake.stake(i, None, true)
); // now stake of 42 is activated ); // now stake of 42 is activated
} }
stake_account.lamports = 0; stake_account.lamports = 0;
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
@ -319,14 +338,14 @@ pub mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) = let ((vote11_pubkey, vote11_account), (stake11_pubkey, stake11_account)) =
create_staked_node_accounts(20); create_staked_node_accounts(20);
stakes.store(&vote11_pubkey, &vote11_account); stakes.store(&vote11_pubkey, &vote11_account, true);
stakes.store(&stake11_pubkey, &stake11_account); stakes.store(&stake11_pubkey, &stake11_account, true);
let vote11_node_pubkey = VoteState::from(&vote11_account).unwrap().node_pubkey; let vote11_node_pubkey = VoteState::from(&vote11_account).unwrap().node_pubkey;
@ -341,8 +360,8 @@ pub mod tests {
let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, mut vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
@ -351,14 +370,14 @@ pub mod tests {
} }
vote_account.lamports = 0; vote_account.lamports = 0;
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert!(vote_accounts.get(&vote_pubkey).is_none()); assert!(vote_accounts.get(&vote_pubkey).is_none());
} }
vote_account.lamports = 1; vote_account.lamports = 1;
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
@ -378,11 +397,11 @@ pub mod tests {
let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) = let ((vote_pubkey2, vote_account2), (_stake_pubkey2, stake_account2)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
stakes.store(&vote_pubkey2, &vote_account2); stakes.store(&vote_pubkey2, &vote_account2, true);
// delegates to vote_pubkey // delegates to vote_pubkey
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
let stake = StakeState::stake_from(&stake_account).unwrap(); let stake = StakeState::stake_from(&stake_account).unwrap();
@ -391,14 +410,14 @@ pub mod tests {
assert!(vote_accounts.get(&vote_pubkey).is_some()); assert!(vote_accounts.get(&vote_pubkey).is_some());
assert_eq!( assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0, vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), true)
); );
assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert!(vote_accounts.get(&vote_pubkey2).is_some());
assert_eq!(vote_accounts.get(&vote_pubkey2).unwrap().0, 0); assert_eq!(vote_accounts.get(&vote_pubkey2).unwrap().0, 0);
} }
// delegates to vote_pubkey2 // delegates to vote_pubkey2
stakes.store(&stake_pubkey, &stake_account2); stakes.store(&stake_pubkey, &stake_account2, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
@ -407,7 +426,7 @@ pub mod tests {
assert!(vote_accounts.get(&vote_pubkey2).is_some()); assert!(vote_accounts.get(&vote_pubkey2).is_some());
assert_eq!( assert_eq!(
vote_accounts.get(&vote_pubkey2).unwrap().0, vote_accounts.get(&vote_pubkey2).unwrap().0,
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), true)
); );
} }
} }
@ -421,11 +440,11 @@ pub mod tests {
let (stake_pubkey2, stake_account2) = create_stake_account(10, &vote_pubkey); let (stake_pubkey2, stake_account2) = create_stake_account(10, &vote_pubkey);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
// delegates to vote_pubkey // delegates to vote_pubkey
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
stakes.store(&stake_pubkey2, &stake_account2); stakes.store(&stake_pubkey2, &stake_account2, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
@ -440,23 +459,23 @@ pub mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
let stake = StakeState::stake_from(&stake_account).unwrap(); let stake = StakeState::stake_from(&stake_account).unwrap();
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert_eq!( assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0, vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), true)
); );
} }
let stakes = stakes.clone_with_epoch(3); let stakes = stakes.clone_with_epoch(3, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
assert_eq!( assert_eq!(
vote_accounts.get(&vote_pubkey).unwrap().0, vote_accounts.get(&vote_pubkey).unwrap().0,
stake.stake(stakes.epoch, Some(&stakes.stake_history)) stake.stake(stakes.epoch, Some(&stakes.stake_history), true)
); );
} }
} }
@ -469,8 +488,8 @@ pub mod tests {
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_staked_node_accounts(10); create_staked_node_accounts(10);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
@ -482,6 +501,7 @@ pub mod tests {
stakes.store( stakes.store(
&stake_pubkey, &stake_pubkey,
&Account::new(1, 0, &solana_stake_program::id()), &Account::new(1, 0, &solana_stake_program::id()),
true,
); );
{ {
let vote_accounts = stakes.vote_accounts(); let vote_accounts = stakes.vote_accounts();
@ -511,14 +531,14 @@ pub mod tests {
let genesis_epoch = 0; let genesis_epoch = 0;
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) = let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
create_warming_staked_node_accounts(10, genesis_epoch); create_warming_staked_node_accounts(10, genesis_epoch);
stakes.store(&vote_pubkey, &vote_account); stakes.store(&vote_pubkey, &vote_account, true);
stakes.store(&stake_pubkey, &stake_account); stakes.store(&stake_pubkey, &stake_account, true);
assert_eq!(stakes.vote_balance_and_staked(), 11); assert_eq!(stakes.vote_balance_and_staked(), 11);
assert_eq!(stakes.vote_balance_and_warmed_staked(), 1); assert_eq!(stakes.vote_balance_and_warmed_staked(), 1);
for (epoch, expected_warmed_stake) in ((genesis_epoch + 1)..=3).zip(&[2, 3, 4]) { for (epoch, expected_warmed_stake) in ((genesis_epoch + 1)..=3).zip(&[2, 3, 4]) {
stakes = stakes.clone_with_epoch(epoch); stakes = stakes.clone_with_epoch(epoch, true);
// vote_balance_and_staked() always remain to return same lamports // vote_balance_and_staked() always remain to return same lamports
// while vote_balance_and_warmed_staked() gradually increases // while vote_balance_and_warmed_staked() gradually increases
assert_eq!(stakes.vote_balance_and_staked(), 11); assert_eq!(stakes.vote_balance_and_staked(), 11);

View File

@ -81,6 +81,7 @@ fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool {
) )
.unwrap(), .unwrap(),
), ),
true,
) )
} }
@ -95,6 +96,7 @@ fn get_staked(bank: &Bank, stake_pubkey: &Pubkey) -> u64 {
) )
.unwrap(), .unwrap(),
), ),
true,
) )
} }