Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions ledger-tool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2030,7 +2030,7 @@ fn main() {
epochs: usize,
voter: Pubkey,
point: u128,
stake: u128,
stake: u64,
total_stake: u64,
rent_exempt_reserve: u64,
credits: u128,
Expand All @@ -2055,17 +2055,18 @@ fn main() {
match event {
InflationPointCalculationEvent::CalculatedPoints(
point,
stake,
credits,
) => {
// Don't sum for epochs where no credits are earned
if *credits > 0 {
detail.epochs += 1;
detail.point += *point;
detail.stake += *stake;
detail.credits += *credits;
}
}
InflationPointCalculationEvent::Stake(stake) => {
detail.stake = *stake;
}
InflationPointCalculationEvent::SplitRewards(
all,
voter,
Expand Down
90 changes: 56 additions & 34 deletions programs/stake/src/stake_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pub enum StakeState {

#[derive(Debug)]
pub enum InflationPointCalculationEvent {
CalculatedPoints(u128, u128, u128),
CalculatedPoints(u128, u128),
Stake(u64),
SplitRewards(u64, u64, u64, PointValue),
RentExemptReserve(u64),
Delegation(Delegation),
Expand Down Expand Up @@ -467,14 +468,14 @@ impl Stake {
&mut self,
point_value: &PointValue,
vote_state: &VoteState,
stake_history: Option<&StakeHistory>,
stake_context: Option<(Epoch, &StakeHistory)>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> Option<(u64, u64)> {
self.calculate_rewards(
point_value,
vote_state,
stake_history,
stake_context,
inflation_point_calc_tracer,
fix_stake_deactivate,
)
Expand All @@ -488,13 +489,13 @@ impl Stake {
pub fn calculate_points(
&self,
vote_state: &VoteState,
stake_history: Option<&StakeHistory>,
stake_context: Option<(Epoch, &StakeHistory)>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> u128 {
self.calculate_points_and_credits(
vote_state,
stake_history,
stake_context,
inflation_point_calc_tracer,
fix_stake_deactivate,
)
Expand All @@ -507,26 +508,42 @@ impl Stake {
pub fn calculate_points_and_credits(
&self,
new_vote_state: &VoteState,
stake_history: Option<&StakeHistory>,
stake_context: Option<(Epoch, &StakeHistory)>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> (u128, u64) {
// if there is no newer credits since observed, return no point
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move comment along...

let mut stake_at_target_epoch = None;
if let Some((target_epoch, stake_history)) = stake_context {
let stake =
self.delegation
.stake(target_epoch, Some(stake_history), fix_stake_deactivate);
stake_at_target_epoch = Some(stake);

if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
inflation_point_calc_tracer(&InflationPointCalculationEvent::Stake(stake));
}
}

if new_vote_state.credits() <= self.credits_observed {
return (0, 0);
}

let mut points = 0;
let mut new_credits_observed = self.credits_observed;

for (epoch, final_epoch_credits, initial_epoch_credits) in
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into this further... I believe this change can deny small stakes delegated to validators with small, but non-zero commissions ever getting paid out, as per

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
return None;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah! correct! I intentionally done this way now. My two cent is posted here: #13680 (comment)

new_vote_state.epoch_credits().iter().copied()
if let Some((epoch, final_epoch_credits, initial_epoch_credits)) =
new_vote_state.epoch_credits().last().copied()
Comment thread
ryoqun marked this conversation as resolved.
{
let stake = u128::from(self.delegation.stake(
epoch,
stake_history,
fix_stake_deactivate,
));
let stake = if let Some((target_epoch, _stake_history)) = stake_context {
if epoch != target_epoch {
return (0, 0);
}
Comment on lines +539 to +541
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write a test for this; this bug is later found after manual testing.

Copy link
Copy Markdown
Contributor Author

@ryoqun ryoqun Nov 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, if we don't exclude historical vote_state's epoch credits by doing like this early return, this way, we might accidentally reward stale delegations using its (last and stale) epoch_credits, which is well past to target_epoch.

stake_at_target_epoch.unwrap()
} else {
self.delegation.stake(epoch, None, fix_stake_deactivate)
};
let stake = u128::from(stake);

// figure out how much this stake has seen that
// for which the vote account has a record
Expand All @@ -535,24 +552,23 @@ impl Stake {
final_epoch_credits - initial_epoch_credits
} else if self.credits_observed < final_epoch_credits {
// the staker registered sometime during the epoch, partial credit
final_epoch_credits - new_credits_observed
final_epoch_credits - self.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);
// try to update credits_observed
new_credits_observed = final_epoch_credits;

// finally calculate points for this epoch
points += stake * earned_credits;

if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
points,
stake,
earned_credits,
));
}
Expand All @@ -571,13 +587,13 @@ impl Stake {
&self,
point_value: &PointValue,
vote_state: &VoteState,
stake_history: Option<&StakeHistory>,
stake_context: Option<(Epoch, &StakeHistory)>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> Option<(u64, u64, u64)> {
let (points, credits_observed) = self.calculate_points_and_credits(
vote_state,
stake_history,
stake_context,
inflation_point_calc_tracer,
fix_stake_deactivate,
);
Expand Down Expand Up @@ -1100,7 +1116,7 @@ pub fn redeem_rewards(
stake_account: &mut Account,
vote_account: &mut Account,
point_value: &PointValue,
stake_history: Option<&StakeHistory>,
stake_context: Option<(Epoch, &StakeHistory)>,
inflation_point_calc_tracer: &mut Option<impl FnMut(&InflationPointCalculationEvent)>,
fix_stake_deactivate: bool,
) -> Result<(u64, u64), InstructionError> {
Expand All @@ -1119,7 +1135,7 @@ pub fn redeem_rewards(
if let Some((stakers_reward, voters_reward)) = stake.redeem_rewards(
point_value,
&vote_state,
stake_history,
stake_context,
inflation_point_calc_tracer,
fix_stake_deactivate,
) {
Expand All @@ -1141,7 +1157,7 @@ pub fn redeem_rewards(
pub fn calculate_points(
stake_account: &Account,
vote_account: &Account,
stake_history: Option<&StakeHistory>,
stake_context: Option<(Epoch, &StakeHistory)>,
fix_stake_deactivate: bool,
) -> Result<u128, InstructionError> {
if let StakeState::Stake(_meta, stake) = stake_account.state()? {
Expand All @@ -1150,7 +1166,7 @@ pub fn calculate_points(

Ok(stake.calculate_points(
&vote_state,
stake_history,
stake_context,
&mut null_tracer(),
fix_stake_deactivate,
))
Expand Down Expand Up @@ -2853,13 +2869,17 @@ mod tests {
)
);

let mut current_credits = 0;

// put 2 credits in at epoch 0
vote_state.increment_credits(0);
current_credits += 1;
vote_state.increment_credits(0);
current_credits += 1;

// this one should be able to collect exactly 2
assert_eq!(
Some((stake.delegation.stake * 2, 0, 2)),
Some((stake.delegation.stake * 2, 0, current_credits)),
stake.calculate_rewards(
&PointValue {
rewards: 2,
Expand All @@ -2875,7 +2895,7 @@ mod tests {
stake.credits_observed = 1;
// this one should be able to collect exactly 1 (already observed one)
assert_eq!(
Some((stake.delegation.stake, 0, 2)),
Some((stake.delegation.stake, 0, current_credits)),
stake.calculate_rewards(
&PointValue {
rewards: 1,
Expand All @@ -2890,11 +2910,12 @@ mod tests {

// put 1 credit in epoch 1
vote_state.increment_credits(1);
current_credits += 1;

stake.credits_observed = 2;
// this one should be able to collect the one just added
assert_eq!(
Some((stake.delegation.stake, 0, 3)),
Some((stake.delegation.stake, 0, current_credits)),
stake.calculate_rewards(
&PointValue {
rewards: 2,
Expand All @@ -2909,9 +2930,12 @@ mod tests {

// put 1 credit in epoch 2
vote_state.increment_credits(2);
current_credits += 1;
vote_state.increment_credits(2);
current_credits += 1;
// this one should be able to collect 2 now
assert_eq!(
Some((stake.delegation.stake * 2, 0, 4)),
Some((stake.delegation.stake * 2, 0, current_credits)),
stake.calculate_rewards(
&PointValue {
rewards: 2,
Expand All @@ -2925,15 +2949,13 @@ mod tests {
);

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)
// even if creidts_observed is so small,
// this one should collect only the last epoch
assert_eq!(
Some((
stake.delegation.stake * 2 // epoch 0
+ stake.delegation.stake // epoch 1
+ stake.delegation.stake, // epoch 2
stake.delegation.stake * 2, // only epoch 2
0,
4
current_credits
)),
stake.calculate_rewards(
&PointValue {
Expand All @@ -2951,7 +2973,7 @@ mod tests {
// 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)),
None, // would be Some((0, 2 * 1 + 1 * 2, current_credits)),
stake.calculate_rewards(
&PointValue {
rewards: 4,
Expand Down
6 changes: 4 additions & 2 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,7 @@ impl Bank {
let old_vote_balance_and_staked = self.stakes.read().unwrap().vote_balance_and_staked();

let validator_point_value = self.pay_validator_rewards(
prev_epoch,
validator_rewards,
reward_calc_tracer,
self.stake_program_v2_enabled(),
Expand Down Expand Up @@ -1438,6 +1439,7 @@ impl Bank {
/// successfully load and parse, return the lamport value of one point
fn pay_validator_rewards(
&mut self,
target_epoch: Epoch,
rewards: u64,
reward_calc_tracer: &mut Option<impl FnMut(&RewardCalculationEvent)>,
fix_stake_deactivate: bool,
Expand All @@ -1457,7 +1459,7 @@ impl Bank {
stake_state::calculate_points(
&stake_account,
&vote_account,
Some(&stake_history),
Some((target_epoch, &stake_history)),
fix_stake_deactivate,
)
.unwrap_or(0)
Expand Down Expand Up @@ -1489,7 +1491,7 @@ impl Bank {
stake_account,
vote_account,
&point_value,
Some(&stake_history),
Some((target_epoch, &stake_history)),
&mut reward_calc_tracer.as_mut(),
fix_stake_deactivate,
);
Expand Down