Split runtime utilities out of stake_state.rs (#35386)

* Add points module

* Add rewards module

* Hide rewards doc

* Fixup ledger-tool imports
This commit is contained in:
Tyera 2024-03-01 09:08:55 -07:00 committed by GitHub
parent 245530b28e
commit a7f9fe103f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 912 additions and 846 deletions

View File

@ -72,7 +72,7 @@ use {
system_program,
transaction::{MessageHash, SanitizedTransaction, SimpleAddressLoader},
},
solana_stake_program::stake_state::{self, PointValue},
solana_stake_program::{points::PointValue, stake_state},
solana_unified_scheduler_pool::DefaultSchedulerPool,
solana_vote_program::{
self,
@ -2589,7 +2589,7 @@ fn main() {
new_credits_observed: Option<u64>,
skipped_reasons: String,
}
use solana_stake_program::stake_state::InflationPointCalculationEvent;
use solana_stake_program::points::InflationPointCalculationEvent;
let stake_calculation_details: DashMap<Pubkey, CalculationDetail> =
DashMap::new();
let last_point_value = Arc::new(RwLock::new(None));

View File

@ -12,6 +12,9 @@ use solana_sdk::{
};
pub mod config;
pub mod points;
#[doc(hidden)]
pub mod rewards;
pub mod stake_instruction;
pub mod stake_state;

View File

@ -0,0 +1,249 @@
//! Information about points calculation based on stake state.
//! Used by `solana-runtime`.
use {
solana_sdk::{
clock::Epoch,
instruction::InstructionError,
pubkey::Pubkey,
stake::state::{Delegation, Stake, StakeStateV2},
stake_history::StakeHistory,
},
solana_vote_program::vote_state::VoteState,
std::cmp::Ordering,
};
/// captures a rewards round as lamports to be awarded
/// and the total points over which those lamports
/// are to be distributed
// basically read as rewards/points, but in integers instead of as an f64
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PointValue {
pub rewards: u64, // lamports to split
pub points: u128, // over these points
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct CalculatedStakePoints {
pub(crate) points: u128,
pub(crate) new_credits_observed: u64,
pub(crate) force_credits_update_with_skipped_reward: bool,
}
#[derive(Debug)]
pub enum InflationPointCalculationEvent {
CalculatedPoints(u64, u128, u128, u128),
SplitRewards(u64, u64, u64, PointValue),
EffectiveStakeAtRewardedEpoch(u64),
RentExemptReserve(u64),
Delegation(Delegation, Pubkey),
Commission(u8),
CreditsObserved(u64, Option<u64>),
Skipped(SkippedReason),
}
pub(crate) fn null_tracer() -> Option<impl Fn(&InflationPointCalculationEvent)> {
None::<fn(&_)>
}
#[derive(Debug)]
pub enum SkippedReason {
DisabledInflation,
JustActivated,
TooEarlyUnfairSplit,
ZeroPoints,
ZeroPointValue,
ZeroReward,
ZeroCreditsAndReturnZero,
ZeroCreditsAndReturnCurrent,
ZeroCreditsAndReturnRewinded,
}
impl From<SkippedReason> for InflationPointCalculationEvent {
fn from(reason: SkippedReason) -> Self {
InflationPointCalculationEvent::Skipped(reason)
}
}
// utility function, used by runtime
#[doc(hidden)]
pub fn calculate_points(
stake_state: &StakeStateV2,
vote_state: &VoteState,
stake_history: &StakeHistory,
new_rate_activation_epoch: Option<Epoch>,
) -> Result<u128, InstructionError> {
if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state {
Ok(calculate_stake_points(
stake,
vote_state,
stake_history,
null_tracer(),
new_rate_activation_epoch,
))
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn calculate_stake_points(
stake: &Stake,
vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> u128 {
calculate_stake_points_and_credits(
stake,
vote_state,
stake_history,
inflation_point_calc_tracer,
new_rate_activation_epoch,
)
.points
}
/// for a given stake and vote_state, calculate how many
/// points were earned (credits * stake) and new value
/// for credits_observed were the points paid
pub(crate) fn calculate_stake_points_and_credits(
stake: &Stake,
new_vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> CalculatedStakePoints {
let credits_in_stake = stake.credits_observed;
let credits_in_vote = new_vote_state.credits();
// if there is no newer credits since observed, return no point
match credits_in_vote.cmp(&credits_in_stake) {
Ordering::Less => {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewinded.into());
}
// Don't adjust stake.activation_epoch for simplicity:
// - generally fast-forwarding stake.activation_epoch forcibly (for
// artificial re-activation with re-warm-up) skews the stake
// history sysvar. And properly handling all the cases
// regarding deactivation epoch/warm-up/cool-down without
// introducing incentive skew is hard.
// - Conceptually, it should be acceptable for the staked SOLs at
// the recreated vote to receive rewards again immediately after
// rewind even if it looks like instant activation. That's
// because it must have passed the required warmed-up at least
// once in the past already
// - Also such a stake account remains to be a part of overall
// effective stake calculation even while the vote account is
// missing for (indefinite) time or remains to be pre-remove
// credits score. It should be treated equally to staking with
// delinquent validator with no differentiation.
// hint with true to indicate some exceptional credits handling is needed
return CalculatedStakePoints {
points: 0,
new_credits_observed: credits_in_vote,
force_credits_update_with_skipped_reward: true,
};
}
Ordering::Equal => {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
}
// don't hint caller and return current value if credits remain unchanged (= delinquent)
return CalculatedStakePoints {
points: 0,
new_credits_observed: credits_in_stake,
force_credits_update_with_skipped_reward: false,
};
}
Ordering::Greater => {}
}
let mut points = 0;
let mut new_credits_observed = credits_in_stake;
for (epoch, final_epoch_credits, initial_epoch_credits) in
new_vote_state.epoch_credits().iter().copied()
{
let stake_amount = u128::from(stake.delegation.stake(
epoch,
stake_history,
new_rate_activation_epoch,
));
// figure out how much this stake has seen that
// for which the vote account has a record
let earned_credits = if credits_in_stake < initial_epoch_credits {
// the staker observed the entire epoch
final_epoch_credits - initial_epoch_credits
} else if credits_in_stake < final_epoch_credits {
// the staker registered sometime during the epoch, partial credit
final_epoch_credits - new_credits_observed
} else {
// the staker has already observed or been redeemed this epoch
// or was activated after this epoch
0
};
let earned_credits = u128::from(earned_credits);
// don't want to assume anything about order of the iterator...
new_credits_observed = new_credits_observed.max(final_epoch_credits);
// finally calculate points for this epoch
let earned_points = stake_amount * earned_credits;
points += earned_points;
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
epoch,
stake_amount,
earned_credits,
earned_points,
));
}
}
CalculatedStakePoints {
points,
new_credits_observed,
force_credits_update_with_skipped_reward: false,
}
}
#[cfg(test)]
mod tests {
use {super::*, crate::stake_state::new_stake, solana_sdk::native_token};
#[test]
fn test_stake_state_calculate_points_with_typical_values() {
let mut vote_state = VoteState::default();
// bootstrap means fully-vested stake at epoch 0 with
// 10_000_000 SOL is a big but not unreasaonable stake
let stake = new_stake(
native_token::sol_to_lamports(10_000_000f64),
&Pubkey::default(),
&vote_state,
std::u64::MAX,
);
let epoch_slots: u128 = 14 * 24 * 3600 * 160;
// put 193,536,000 credits in at epoch 0, typical for a 14-day epoch
// this loop takes a few seconds...
for _ in 0..epoch_slots {
vote_state.increment_credits(0, 1);
}
// no overflow on points
assert_eq!(
u128::from(stake.delegation.stake) * epoch_slots,
calculate_stake_points(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
}
}

View File

@ -0,0 +1,647 @@
//! Information about stake and voter rewards based on stake state.
//! Used by `solana-runtime`.
use {
crate::points::{
calculate_stake_points_and_credits, CalculatedStakePoints, InflationPointCalculationEvent,
PointValue, SkippedReason,
},
solana_sdk::{
account::{AccountSharedData, WritableAccount},
account_utils::StateMut,
clock::Epoch,
instruction::InstructionError,
stake::{
instruction::StakeError,
state::{Stake, StakeStateV2},
},
stake_history::StakeHistory,
},
solana_vote_program::vote_state::VoteState,
};
#[derive(Debug, PartialEq, Eq)]
struct CalculatedStakeRewards {
staker_rewards: u64,
voter_rewards: u64,
new_credits_observed: u64,
}
// utility function, used by runtime
// returns a tuple of (stakers_reward,voters_reward)
#[doc(hidden)]
pub fn redeem_rewards(
rewarded_epoch: Epoch,
stake_state: StakeStateV2,
stake_account: &mut AccountSharedData,
vote_state: &VoteState,
point_value: &PointValue,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Result<(u64, u64), InstructionError> {
if let StakeStateV2::Stake(meta, mut stake, stake_flags) = stake_state {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(
&InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake(
rewarded_epoch,
stake_history,
new_rate_activation_epoch,
)),
);
inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve(
meta.rent_exempt_reserve,
));
inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission(
vote_state.commission,
));
}
if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards(
rewarded_epoch,
&mut stake,
point_value,
vote_state,
stake_history,
inflation_point_calc_tracer,
new_rate_activation_epoch,
) {
stake_account.checked_add_lamports(stakers_reward)?;
stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))?;
Ok((stakers_reward, voters_reward))
} else {
Err(StakeError::NoCreditsToRedeem.into())
}
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn redeem_stake_rewards(
rewarded_epoch: Epoch,
stake: &mut Stake,
point_value: &PointValue,
vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Option<(u64, u64)> {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
stake.credits_observed,
None,
));
}
calculate_stake_rewards(
rewarded_epoch,
stake,
point_value,
vote_state,
stake_history,
inflation_point_calc_tracer.as_ref(),
new_rate_activation_epoch,
)
.map(|calculated_stake_rewards| {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
stake.credits_observed,
Some(calculated_stake_rewards.new_credits_observed),
));
}
stake.credits_observed = calculated_stake_rewards.new_credits_observed;
stake.delegation.stake += calculated_stake_rewards.staker_rewards;
(
calculated_stake_rewards.staker_rewards,
calculated_stake_rewards.voter_rewards,
)
})
}
/// 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:
/// * staker_rewards to be distributed
/// * voter_rewards to be distributed
/// * new value for credits_observed in the stake
/// returns None if there's no payout or if any deserved payout is < 1 lamport
fn calculate_stake_rewards(
rewarded_epoch: Epoch,
stake: &Stake,
point_value: &PointValue,
vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Option<CalculatedStakeRewards> {
// ensure to run to trigger (optional) inflation_point_calc_tracer
let CalculatedStakePoints {
points,
new_credits_observed,
mut force_credits_update_with_skipped_reward,
} = calculate_stake_points_and_credits(
stake,
vote_state,
stake_history,
inflation_point_calc_tracer.as_ref(),
new_rate_activation_epoch,
);
// Drive credits_observed forward unconditionally when rewards are disabled
// or when this is the stake's activation epoch
if point_value.rewards == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into());
}
force_credits_update_with_skipped_reward = true;
} else if stake.delegation.activation_epoch == rewarded_epoch {
// not assert!()-ed; but points should be zero
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::JustActivated.into());
}
force_credits_update_with_skipped_reward = true;
}
if force_credits_update_with_skipped_reward {
return Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed,
});
}
if points == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into());
}
return None;
}
if point_value.points == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into());
}
return None;
}
let rewards = points
.checked_mul(u128::from(point_value.rewards))
.unwrap()
.checked_div(point_value.points)
.unwrap();
let rewards = u64::try_from(rewards).unwrap();
// don't bother trying to split if fractional lamports got truncated
if rewards == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroReward.into());
}
return None;
}
let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards);
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards(
rewards,
voter_rewards,
staker_rewards,
(*point_value).clone(),
));
}
if (voter_rewards == 0 || staker_rewards == 0) && is_split {
// don't collect if we lose a whole lamport somewhere
// is_split means there should be tokens on both sides,
// uncool to move credits_observed if one side didn't get paid
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into());
}
return None;
}
Some(CalculatedStakeRewards {
staker_rewards,
voter_rewards,
new_credits_observed,
})
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{points::null_tracer, stake_state::new_stake},
solana_sdk::{native_token, pubkey::Pubkey},
};
#[test]
fn test_stake_state_redeem_rewards() {
let mut vote_state = VoteState::default();
// assume stake.stake() is right
// bootstrap means fully-vested stake at epoch 0
let stake_lamports = 1;
let mut stake = new_stake(
stake_lamports,
&Pubkey::default(),
&vote_state,
std::u64::MAX,
);
// this one can't collect now, credits_observed == vote_state.credits()
assert_eq!(
None,
redeem_stake_rewards(
0,
&mut stake,
&PointValue {
rewards: 1_000_000_000,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 2 credits in at epoch 0
vote_state.increment_credits(0, 1);
vote_state.increment_credits(0, 1);
// this one should be able to collect exactly 2
assert_eq!(
Some((stake_lamports * 2, 0)),
redeem_stake_rewards(
0,
&mut stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
assert_eq!(
stake.delegation.stake,
stake_lamports + (stake_lamports * 2)
);
assert_eq!(stake.credits_observed, 2);
}
#[test]
fn test_stake_state_calculate_rewards() {
let mut vote_state = VoteState::default();
// assume stake.stake() is right
// bootstrap means fully-vested stake at epoch 0
let mut stake = new_stake(1, &Pubkey::default(), &vote_state, std::u64::MAX);
// this one can't collect now, credits_observed == vote_state.credits()
assert_eq!(
None,
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 1_000_000_000,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 2 credits in at epoch 0
vote_state.increment_credits(0, 1);
vote_state.increment_credits(0, 1);
// this one should be able to collect exactly 2
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2,
voter_rewards: 0,
new_credits_observed: 2,
}),
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 2,
points: 2 // all his
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
stake.credits_observed = 1;
// this one should be able to collect exactly 1 (already observed one)
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake,
voter_rewards: 0,
new_credits_observed: 2,
}),
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 1 credit in epoch 1
vote_state.increment_credits(1, 1);
stake.credits_observed = 2;
// this one should be able to collect the one just added
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake,
voter_rewards: 0,
new_credits_observed: 3,
}),
calculate_stake_rewards(
1,
&stake,
&PointValue {
rewards: 2,
points: 2
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 1 credit in epoch 2
vote_state.increment_credits(2, 1);
// this one should be able to collect 2 now
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 2,
points: 2
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
stake.credits_observed = 0;
// 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(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2 // epoch 0
+ stake.delegation.stake // epoch 1
+ stake.delegation.stake, // epoch 2
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 4,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// same as above, but is a really small commission out of 32 bits,
// verify that None comes back on small redemptions where no one gets paid
vote_state.commission = 1;
assert_eq!(
None, // would be Some((0, 2 * 1 + 1 * 2, 4)),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 4,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
vote_state.commission = 99;
assert_eq!(
None, // would be Some((0, 2 * 1 + 1 * 2, 4)),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 4,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// now one with inflation disabled. no one gets paid, but we still need
// to advance the stake state's credits_observed field to prevent back-
// paying rewards when inflation is turned on.
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 0,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// credits_observed remains at previous level when vote_state credits are
// not advancing and inflation is disabled
stake.credits_observed = 4;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 0,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: false,
},
calculate_stake_points_and_credits(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
// credits_observed is auto-rewinded when vote_state credits are assumed to have been
// recreated
stake.credits_observed = 1000;
// this is new behavior 1; return the post-recreation rewinded credits from the vote account
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: true,
},
calculate_stake_points_and_credits(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
// this is new behavior 2; don't hint when credits both from stake and vote are identical
stake.credits_observed = 4;
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: false,
},
calculate_stake_points_and_credits(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
// get rewards and credits observed when not the activation epoch
vote_state.commission = 0;
stake.credits_observed = 3;
stake.delegation.activation_epoch = 1;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake, // epoch 2
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// credits_observed is moved forward for the stake's activation epoch,
// and no rewards are perceived
stake.delegation.activation_epoch = 2;
stake.credits_observed = 3;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
}
#[test]
fn test_stake_state_calculate_points_with_typical_values() {
let vote_state = VoteState::default();
// bootstrap means fully-vested stake at epoch 0 with
// 10_000_000 SOL is a big but not unreasaonable stake
let stake = new_stake(
native_token::sol_to_lamports(10_000_000f64),
&Pubkey::default(),
&vote_state,
std::u64::MAX,
);
// this one can't collect now, credits_observed == vote_state.credits()
assert_eq!(
None,
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 1_000_000_000,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
}
}

View File

@ -11,7 +11,7 @@ pub use solana_sdk::stake::state::*;
use {
solana_program_runtime::{ic_msg, invoke_context::InvokeContext},
solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
account::{AccountSharedData, ReadableAccount},
account_utils::StateMut,
clock::{Clock, Epoch},
feature_set::{self, FeatureSet},
@ -30,44 +30,9 @@ use {
},
},
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
std::{cmp::Ordering, collections::HashSet, convert::TryFrom},
std::{collections::HashSet, convert::TryFrom},
};
#[derive(Debug)]
pub enum SkippedReason {
DisabledInflation,
JustActivated,
TooEarlyUnfairSplit,
ZeroPoints,
ZeroPointValue,
ZeroReward,
ZeroCreditsAndReturnZero,
ZeroCreditsAndReturnCurrent,
ZeroCreditsAndReturnRewinded,
}
impl From<SkippedReason> for InflationPointCalculationEvent {
fn from(reason: SkippedReason) -> Self {
InflationPointCalculationEvent::Skipped(reason)
}
}
#[derive(Debug)]
pub enum InflationPointCalculationEvent {
CalculatedPoints(u64, u128, u128, u128),
SplitRewards(u64, u64, u64, PointValue),
EffectiveStakeAtRewardedEpoch(u64),
RentExemptReserve(u64),
Delegation(Delegation, Pubkey),
Commission(u8),
CreditsObserved(u64, Option<u64>),
Skipped(SkippedReason),
}
pub(crate) fn null_tracer() -> Option<impl Fn(&InflationPointCalculationEvent)> {
None::<fn(&_)>
}
// utility function, used by Stakes, tests
pub fn from<T: ReadableAccount + StateMut<StakeStateV2>>(account: &T) -> Option<StakeStateV2> {
account.state().ok()
@ -179,300 +144,6 @@ pub(crate) fn new_stake(
}
}
/// captures a rewards round as lamports to be awarded
/// and the total points over which those lamports
/// are to be distributed
// basically read as rewards/points, but in integers instead of as an f64
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PointValue {
pub rewards: u64, // lamports to split
pub points: u128, // over these points
}
fn redeem_stake_rewards(
rewarded_epoch: Epoch,
stake: &mut Stake,
point_value: &PointValue,
vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Option<(u64, u64)> {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
stake.credits_observed,
None,
));
}
calculate_stake_rewards(
rewarded_epoch,
stake,
point_value,
vote_state,
stake_history,
inflation_point_calc_tracer.as_ref(),
new_rate_activation_epoch,
)
.map(|calculated_stake_rewards| {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
stake.credits_observed,
Some(calculated_stake_rewards.new_credits_observed),
));
}
stake.credits_observed = calculated_stake_rewards.new_credits_observed;
stake.delegation.stake += calculated_stake_rewards.staker_rewards;
(
calculated_stake_rewards.staker_rewards,
calculated_stake_rewards.voter_rewards,
)
})
}
fn calculate_stake_points(
stake: &Stake,
vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> u128 {
calculate_stake_points_and_credits(
stake,
vote_state,
stake_history,
inflation_point_calc_tracer,
new_rate_activation_epoch,
)
.points
}
#[derive(Debug, PartialEq, Eq)]
struct CalculatedStakePoints {
points: u128,
new_credits_observed: u64,
force_credits_update_with_skipped_reward: bool,
}
/// for a given stake and vote_state, calculate how many
/// points were earned (credits * stake) and new value
/// for credits_observed were the points paid
fn calculate_stake_points_and_credits(
stake: &Stake,
new_vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> CalculatedStakePoints {
let credits_in_stake = stake.credits_observed;
let credits_in_vote = new_vote_state.credits();
// if there is no newer credits since observed, return no point
match credits_in_vote.cmp(&credits_in_stake) {
Ordering::Less => {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewinded.into());
}
// Don't adjust stake.activation_epoch for simplicity:
// - generally fast-forwarding stake.activation_epoch forcibly (for
// artificial re-activation with re-warm-up) skews the stake
// history sysvar. And properly handling all the cases
// regarding deactivation epoch/warm-up/cool-down without
// introducing incentive skew is hard.
// - Conceptually, it should be acceptable for the staked SOLs at
// the recreated vote to receive rewards again immediately after
// rewind even if it looks like instant activation. That's
// because it must have passed the required warmed-up at least
// once in the past already
// - Also such a stake account remains to be a part of overall
// effective stake calculation even while the vote account is
// missing for (indefinite) time or remains to be pre-remove
// credits score. It should be treated equally to staking with
// delinquent validator with no differentiation.
// hint with true to indicate some exceptional credits handling is needed
return CalculatedStakePoints {
points: 0,
new_credits_observed: credits_in_vote,
force_credits_update_with_skipped_reward: true,
};
}
Ordering::Equal => {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
}
// don't hint caller and return current value if credits remain unchanged (= delinquent)
return CalculatedStakePoints {
points: 0,
new_credits_observed: credits_in_stake,
force_credits_update_with_skipped_reward: false,
};
}
Ordering::Greater => {}
}
let mut points = 0;
let mut new_credits_observed = credits_in_stake;
for (epoch, final_epoch_credits, initial_epoch_credits) in
new_vote_state.epoch_credits().iter().copied()
{
let stake_amount = u128::from(stake.delegation.stake(
epoch,
stake_history,
new_rate_activation_epoch,
));
// figure out how much this stake has seen that
// for which the vote account has a record
let earned_credits = if credits_in_stake < initial_epoch_credits {
// the staker observed the entire epoch
final_epoch_credits - initial_epoch_credits
} else if credits_in_stake < final_epoch_credits {
// the staker registered sometime during the epoch, partial credit
final_epoch_credits - new_credits_observed
} else {
// the staker has already observed or been redeemed this epoch
// or was activated after this epoch
0
};
let earned_credits = u128::from(earned_credits);
// don't want to assume anything about order of the iterator...
new_credits_observed = new_credits_observed.max(final_epoch_credits);
// finally calculate points for this epoch
let earned_points = stake_amount * earned_credits;
points += earned_points;
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
epoch,
stake_amount,
earned_credits,
earned_points,
));
}
}
CalculatedStakePoints {
points,
new_credits_observed,
force_credits_update_with_skipped_reward: false,
}
}
#[derive(Debug, PartialEq, Eq)]
struct CalculatedStakeRewards {
staker_rewards: u64,
voter_rewards: u64,
new_credits_observed: u64,
}
/// 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:
/// * staker_rewards to be distributed
/// * voter_rewards to be distributed
/// * new value for credits_observed in the stake
/// returns None if there's no payout or if any deserved payout is < 1 lamport
fn calculate_stake_rewards(
rewarded_epoch: Epoch,
stake: &Stake,
point_value: &PointValue,
vote_state: &VoteState,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Option<CalculatedStakeRewards> {
// ensure to run to trigger (optional) inflation_point_calc_tracer
let CalculatedStakePoints {
points,
new_credits_observed,
mut force_credits_update_with_skipped_reward,
} = calculate_stake_points_and_credits(
stake,
vote_state,
stake_history,
inflation_point_calc_tracer.as_ref(),
new_rate_activation_epoch,
);
// Drive credits_observed forward unconditionally when rewards are disabled
// or when this is the stake's activation epoch
if point_value.rewards == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into());
}
force_credits_update_with_skipped_reward = true;
} else if stake.delegation.activation_epoch == rewarded_epoch {
// not assert!()-ed; but points should be zero
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::JustActivated.into());
}
force_credits_update_with_skipped_reward = true;
}
if force_credits_update_with_skipped_reward {
return Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed,
});
}
if points == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into());
}
return None;
}
if point_value.points == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into());
}
return None;
}
let rewards = points
.checked_mul(u128::from(point_value.rewards))
.unwrap()
.checked_div(point_value.points)
.unwrap();
let rewards = u64::try_from(rewards).unwrap();
// don't bother trying to split if fractional lamports got truncated
if rewards == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroReward.into());
}
return None;
}
let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards);
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards(
rewards,
voter_rewards,
staker_rewards,
(*point_value).clone(),
));
}
if (voter_rewards == 0 || staker_rewards == 0) && is_split {
// don't collect if we lose a whole lamport somewhere
// is_split means there should be tokens on both sides,
// uncool to move credits_observed if one side didn't get paid
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into());
}
return None;
}
Some(CalculatedStakeRewards {
staker_rewards,
voter_rewards,
new_credits_observed,
})
}
pub fn initialize(
stake_account: &mut BorrowedAccount,
authorized: &Authorized,
@ -1577,78 +1248,6 @@ fn stake_weighted_credits_observed(
}
}
// utility function, used by runtime
// returns a tuple of (stakers_reward,voters_reward)
#[doc(hidden)]
pub fn redeem_rewards(
rewarded_epoch: Epoch,
stake_state: StakeStateV2,
stake_account: &mut AccountSharedData,
vote_state: &VoteState,
point_value: &PointValue,
stake_history: &StakeHistory,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
new_rate_activation_epoch: Option<Epoch>,
) -> Result<(u64, u64), InstructionError> {
if let StakeStateV2::Stake(meta, mut stake, stake_flags) = stake_state {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(
&InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake(
rewarded_epoch,
stake_history,
new_rate_activation_epoch,
)),
);
inflation_point_calc_tracer(&InflationPointCalculationEvent::RentExemptReserve(
meta.rent_exempt_reserve,
));
inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission(
vote_state.commission,
));
}
if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards(
rewarded_epoch,
&mut stake,
point_value,
vote_state,
stake_history,
inflation_point_calc_tracer,
new_rate_activation_epoch,
) {
stake_account.checked_add_lamports(stakers_reward)?;
stake_account.set_state(&StakeStateV2::Stake(meta, stake, stake_flags))?;
Ok((stakers_reward, voters_reward))
} else {
Err(StakeError::NoCreditsToRedeem.into())
}
} else {
Err(InstructionError::InvalidAccountData)
}
}
// utility function, used by runtime
#[doc(hidden)]
pub fn calculate_points(
stake_state: &StakeStateV2,
vote_state: &VoteState,
stake_history: &StakeHistory,
new_rate_activation_epoch: Option<Epoch>,
) -> Result<u128, InstructionError> {
if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state {
Ok(calculate_stake_points(
stake,
vote_state,
stake_history,
null_tracer(),
new_rate_activation_epoch,
))
} else {
Err(InstructionError::InvalidAccountData)
}
}
pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64));
// utility function, used by runtime::Stakes, tests
@ -1804,7 +1403,6 @@ mod tests {
solana_sdk::{
account::{create_account_shared_data_for_test, AccountSharedData},
epoch_schedule::EpochSchedule,
native_token,
pubkey::Pubkey,
stake::state::warmup_cooldown_rate,
sysvar::{epoch_schedule, SysvarId},
@ -2526,438 +2124,6 @@ mod tests {
}
}
#[test]
fn test_stake_state_redeem_rewards() {
let mut vote_state = VoteState::default();
// assume stake.stake() is right
// bootstrap means fully-vested stake at epoch 0
let stake_lamports = 1;
let mut stake = new_stake(
stake_lamports,
&Pubkey::default(),
&vote_state,
std::u64::MAX,
);
// this one can't collect now, credits_observed == vote_state.credits()
assert_eq!(
None,
redeem_stake_rewards(
0,
&mut stake,
&PointValue {
rewards: 1_000_000_000,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 2 credits in at epoch 0
vote_state.increment_credits(0, 1);
vote_state.increment_credits(0, 1);
// this one should be able to collect exactly 2
assert_eq!(
Some((stake_lamports * 2, 0)),
redeem_stake_rewards(
0,
&mut stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
assert_eq!(
stake.delegation.stake,
stake_lamports + (stake_lamports * 2)
);
assert_eq!(stake.credits_observed, 2);
}
#[test]
fn test_stake_state_calculate_points_with_typical_values() {
let mut vote_state = VoteState::default();
// bootstrap means fully-vested stake at epoch 0 with
// 10_000_000 SOL is a big but not unreasaonable stake
let stake = new_stake(
native_token::sol_to_lamports(10_000_000f64),
&Pubkey::default(),
&vote_state,
std::u64::MAX,
);
// this one can't collect now, credits_observed == vote_state.credits()
assert_eq!(
None,
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 1_000_000_000,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
let epoch_slots: u128 = 14 * 24 * 3600 * 160;
// put 193,536,000 credits in at epoch 0, typical for a 14-day epoch
// this loop takes a few seconds...
for _ in 0..epoch_slots {
vote_state.increment_credits(0, 1);
}
// no overflow on points
assert_eq!(
u128::from(stake.delegation.stake) * epoch_slots,
calculate_stake_points(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
}
#[test]
fn test_stake_state_calculate_rewards() {
let mut vote_state = VoteState::default();
// assume stake.stake() is right
// bootstrap means fully-vested stake at epoch 0
let mut stake = new_stake(1, &Pubkey::default(), &vote_state, std::u64::MAX);
// this one can't collect now, credits_observed == vote_state.credits()
assert_eq!(
None,
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 1_000_000_000,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 2 credits in at epoch 0
vote_state.increment_credits(0, 1);
vote_state.increment_credits(0, 1);
// this one should be able to collect exactly 2
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2,
voter_rewards: 0,
new_credits_observed: 2,
}),
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 2,
points: 2 // all his
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
stake.credits_observed = 1;
// this one should be able to collect exactly 1 (already observed one)
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake,
voter_rewards: 0,
new_credits_observed: 2,
}),
calculate_stake_rewards(
0,
&stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 1 credit in epoch 1
vote_state.increment_credits(1, 1);
stake.credits_observed = 2;
// this one should be able to collect the one just added
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake,
voter_rewards: 0,
new_credits_observed: 3,
}),
calculate_stake_rewards(
1,
&stake,
&PointValue {
rewards: 2,
points: 2
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// put 1 credit in epoch 2
vote_state.increment_credits(2, 1);
// this one should be able to collect 2 now
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 2,
points: 2
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
stake.credits_observed = 0;
// 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(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2 // epoch 0
+ stake.delegation.stake // epoch 1
+ stake.delegation.stake, // epoch 2
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 4,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// same as above, but is a really small commission out of 32 bits,
// verify that None comes back on small redemptions where no one gets paid
vote_state.commission = 1;
assert_eq!(
None, // would be Some((0, 2 * 1 + 1 * 2, 4)),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 4,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
vote_state.commission = 99;
assert_eq!(
None, // would be Some((0, 2 * 1 + 1 * 2, 4)),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 4,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// now one with inflation disabled. no one gets paid, but we still need
// to advance the stake state's credits_observed field to prevent back-
// paying rewards when inflation is turned on.
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 0,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// credits_observed remains at previous level when vote_state credits are
// not advancing and inflation is disabled
stake.credits_observed = 4;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 0,
points: 4
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: false,
},
calculate_stake_points_and_credits(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
// credits_observed is auto-rewinded when vote_state credits are assumed to have been
// recreated
stake.credits_observed = 1000;
// this is new behavior 1; return the post-recreation rewinded credits from the vote account
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: true,
},
calculate_stake_points_and_credits(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
// this is new behavior 2; don't hint when credits both from stake and vote are identical
stake.credits_observed = 4;
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: false,
},
calculate_stake_points_and_credits(
&stake,
&vote_state,
&StakeHistory::default(),
null_tracer(),
None
)
);
// get rewards and credits observed when not the activation epoch
vote_state.commission = 0;
stake.credits_observed = 3;
stake.delegation.activation_epoch = 1;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake, // epoch 2
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
// credits_observed is moved forward for the stake's activation epoch,
// and no rewards are perceived
stake.delegation.activation_epoch = 2;
stake.credits_observed = 3;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
2,
&stake,
&PointValue {
rewards: 1,
points: 1
},
&vote_state,
&StakeHistory::default(),
null_tracer(),
None,
)
);
}
#[test]
fn test_lockup_is_expired() {
let custodian = solana_sdk::pubkey::new_rand();

View File

@ -156,8 +156,9 @@ use {
},
transaction_context::{TransactionAccount, TransactionReturnData},
},
solana_stake_program::stake_state::{
self, InflationPointCalculationEvent, PointValue, StakeStateV2,
solana_stake_program::{
points::{InflationPointCalculationEvent, PointValue},
stake_state::StakeStateV2,
},
solana_svm::{
account_loader::{TransactionCheckResult, TransactionLoadResult},
@ -2989,7 +2990,7 @@ impl Bank {
return 0;
};
stake_state::calculate_points(
solana_stake_program::points::calculate_points(
stake_account.stake_state(),
vote_state,
stake_history,
@ -3026,7 +3027,7 @@ impl Bank {
delegations
.par_iter()
.map(|(_stake_pubkey, stake_account)| {
stake_state::calculate_points(
solana_stake_program::points::calculate_points(
stake_account.stake_state(),
vote_state,
stake_history,
@ -3106,7 +3107,7 @@ impl Bank {
let pre_lamport = stake_account.lamports();
let redeemed = stake_state::redeem_rewards(
let redeemed = solana_stake_program::rewards::redeem_rewards(
rewarded_epoch,
stake_state,
&mut stake_account,
@ -3154,7 +3155,7 @@ impl Bank {
});
} else {
debug!(
"stake_state::redeem_rewards() failed for {}: {:?}",
"solana_stake_program::rewards::redeem_rewards() failed for {}: {:?}",
stake_pubkey, redeemed
);
}
@ -3225,7 +3226,7 @@ impl Bank {
});
let (mut stake_account, stake_state) =
<(AccountSharedData, StakeStateV2)>::from(stake_account);
let redeemed = stake_state::redeem_rewards(
let redeemed = solana_stake_program::rewards::redeem_rewards(
rewarded_epoch,
stake_state,
&mut stake_account,
@ -3261,7 +3262,7 @@ impl Bank {
});
} else {
debug!(
"stake_state::redeem_rewards() failed for {}: {:?}",
"solana_stake_program::rewards::redeem_rewards() failed for {}: {:?}",
stake_pubkey, redeemed
);
}