1use {
4 crate::{
5 alpenglow_epoch_type::{AlpenglowEpochType, RewardEpochDelegatedStakes},
6 stake_delegation::delegation_effective_stake,
7 },
8 agave_votor_messages::migration::AG_MIGRATION_EPOCH_CREDIT,
9 log::error,
10 solana_clock::Epoch,
11 solana_instruction::error::InstructionError,
12 solana_pubkey::Pubkey,
13 solana_stake_history::StakeHistory,
14 solana_stake_interface::state::{Delegation, Stake, StakeStateV2},
15 solana_vote::vote_state_view::VoteStateView,
16 std::cmp::Ordering,
17};
18
19#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct PointValue {
25 pub rewards: u64, pub points: u128, }
28
29#[derive(Debug, PartialEq, Eq)]
30pub(crate) struct CalculatedStakePoints {
31 pub(crate) tower_points: u128,
32 pub(crate) ag_points: u128,
33 pub(crate) new_credits_observed: u64,
34 pub(crate) force_credits_update_with_skipped_reward: bool,
35}
36
37pub(crate) struct CalculationEnvironment<'a> {
39 pub(crate) rewarded_epoch: Epoch,
40 pub(crate) point_value: &'a PointValue,
41 pub(crate) stake_history: &'a StakeHistory,
42 pub(crate) new_rate_activation_epoch: Option<Epoch>,
43 pub(crate) commission_rate_in_basis_points: bool,
44 pub(crate) adjust_delegations_for_rent: bool,
45 pub(crate) use_fixed_point_stake_math: bool,
46}
47
48#[derive(Debug)]
49pub enum InflationPointCalculationEvent {
50 CalculatedPoints(u64, u128, u128, u128),
51 SplitRewards(u64, u64, u64, PointValue),
52 EffectiveStakeAtRewardedEpoch(u64),
53 PriorTotalLamports(u64),
54 Delegation(Delegation, Pubkey),
55 Commission(u8),
57 CommissionBps(u16),
60 CreditsObserved(u64, Option<u64>),
61 Skipped(SkippedReason),
62}
63
64pub(crate) fn null_tracer() -> Option<impl Fn(&InflationPointCalculationEvent)> {
65 None::<fn(&_)>
66}
67
68#[derive(Debug)]
69pub enum SkippedReason {
70 DisabledInflation,
71 JustActivated,
72 TooEarlyUnfairSplit,
73 ZeroPoints,
74 ZeroPointValue,
75 ZeroReward,
76 ZeroCreditsAndReturnZero,
77 ZeroCreditsAndReturnCurrent,
78 ZeroCreditsAndReturnRewound,
79}
80
81impl From<SkippedReason> for InflationPointCalculationEvent {
82 fn from(reason: SkippedReason) -> Self {
83 InflationPointCalculationEvent::Skipped(reason)
84 }
85}
86
87pub(crate) struct DelegatedVoteState<'a> {
90 pub(crate) credits: u64,
91 pub(crate) epoch_credits_iter: Box<dyn Iterator<Item = (Epoch, u64, u64)> + 'a>,
92}
93
94impl<'a> From<&'a VoteStateView> for DelegatedVoteState<'a> {
95 fn from(vote_state: &'a VoteStateView) -> Self {
96 DelegatedVoteState {
97 credits: vote_state.credits(),
98 epoch_credits_iter: Box::new(vote_state.epoch_credits_iter().map(Into::into)),
99 }
100 }
101}
102
103pub(crate) fn calculate_points_for_tower(
104 stake_state: &StakeStateV2,
105 vote_state: DelegatedVoteState,
106 stake_history: &StakeHistory,
107 new_rate_activation_epoch: Option<Epoch>,
108 use_fixed_point_stake_math: bool,
109) -> Result<u128, InstructionError> {
110 if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state {
111 Ok(calculate_stake_points_for_tower(
112 stake,
113 vote_state,
114 stake_history,
115 null_tracer(),
116 new_rate_activation_epoch,
117 use_fixed_point_stake_math,
118 ))
119 } else {
120 Err(InstructionError::InvalidAccountData)
121 }
122}
123
124fn calculate_stake_points_for_tower(
127 stake: &Stake,
128 vote_state: DelegatedVoteState,
129 stake_history: &StakeHistory,
130 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
131 new_rate_activation_epoch: Option<Epoch>,
132 use_fixed_point_stake_math: bool,
133) -> u128 {
134 calculate_stake_points_and_credits(
135 stake,
136 vote_state,
137 stake_history,
138 inflation_point_calc_tracer,
139 new_rate_activation_epoch,
140 &AlpenglowEpochType::Tower,
141 use_fixed_point_stake_math,
142 )
143 .tower_points
144}
145
146fn record_error(msg: String) {
147 error!("{msg}");
148 datapoint_error!(
149 "PER-total-stake-calculation-failure",
150 ("error", msg, String)
151 );
152}
153
154fn calc_earned_credits(
159 stake: &Stake,
160 final_epoch_credits: u64,
161 initial_epoch_credits: u64,
162 new_credits_observed: &mut u64,
163) -> u128 {
164 let credits_in_stake = stake.credits_observed;
165
166 let earned_credits = if credits_in_stake < initial_epoch_credits {
169 final_epoch_credits - initial_epoch_credits
171 } else if credits_in_stake < final_epoch_credits {
172 final_epoch_credits - *new_credits_observed
174 } else {
175 0
178 };
179 *new_credits_observed = (*new_credits_observed).max(final_epoch_credits);
180 u128::from(earned_credits)
181}
182
183fn tower_epoch_credits_iter(
188 stake: &Stake,
189 epoch_credits_iter: impl Iterator<Item = (Epoch, u64, u64)>,
190 stake_history: &StakeHistory,
191 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
192 new_rate_activation_epoch: Option<Epoch>,
193 use_fixed_point_stake_math: bool,
194) -> (u128, u64, bool) {
195 let mut points = 0;
196 let credits_in_stake = stake.credits_observed;
197 let mut new_credits_observed = credits_in_stake;
198 let mut saw_marker = false;
199
200 for entry in epoch_credits_iter {
201 if entry == AG_MIGRATION_EPOCH_CREDIT {
202 saw_marker = true;
203 break;
204 }
205 let (epoch, final_epoch_credits, initial_epoch_credits) = entry;
206 let earned_credits = calc_earned_credits(
207 stake,
208 final_epoch_credits,
209 initial_epoch_credits,
210 &mut new_credits_observed,
211 );
212 let stake_amount = u128::from(delegation_effective_stake(
213 &stake.delegation,
214 epoch,
215 stake_history,
216 new_rate_activation_epoch,
217 use_fixed_point_stake_math,
218 ));
219
220 let earned_points = stake_amount * earned_credits;
222 points += earned_points;
223
224 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
225 inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
226 epoch,
227 stake_amount,
228 earned_credits,
229 earned_points,
230 ));
231 }
232 }
233 (points, new_credits_observed, saw_marker)
234}
235
236fn calculate_alpenglow_points(
244 stake: &Stake,
245 reward_epoch_credits: Option<(Epoch, u64, u64)>,
246 stake_history: &StakeHistory,
247 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
248 new_rate_activation_epoch: Option<Epoch>,
249 use_fixed_point_stake_math: bool,
250 reward_epoch_delegated_stakes: &RewardEpochDelegatedStakes,
251) -> Result<(u128, u64), CalculatedStakePoints> {
252 let Some((epoch, final_epoch_credits, initial_epoch_credits)) = reward_epoch_credits else {
253 return Ok((0, stake.credits_observed));
254 };
255 if epoch != reward_epoch_delegated_stakes.epoch {
256 return Ok((0, stake.credits_observed));
259 }
260
261 let (earned_credits, new_credits_observed) = {
262 let mut new_credits_observed = stake.credits_observed;
263 let earned_credits = calc_earned_credits(
264 stake,
265 final_epoch_credits,
266 initial_epoch_credits,
267 &mut new_credits_observed,
268 );
269 (earned_credits, new_credits_observed)
270 };
271
272 let stake_amount = u128::from(delegation_effective_stake(
273 &stake.delegation,
274 epoch,
275 stake_history,
276 new_rate_activation_epoch,
277 use_fixed_point_stake_math,
278 ));
279
280 let earned_points = if earned_credits == 0 || stake_amount == 0 {
281 0
282 } else {
283 let Some(total_stake) = reward_epoch_delegated_stakes
284 .delegated_stakes
285 .get(&stake.delegation.voter_pubkey)
286 .copied()
287 .filter(|stake| *stake != 0)
288 else {
289 record_error(format!(
290 "AG delegated stake denominator for vote_pubkey={} in epoch={} failed",
291 stake.delegation.voter_pubkey, reward_epoch_delegated_stakes.epoch
292 ));
293 return Err(CalculatedStakePoints {
294 tower_points: 0,
295 ag_points: 0,
296 new_credits_observed,
297 force_credits_update_with_skipped_reward: true,
298 });
299 };
300 earned_credits * stake_amount / total_stake as u128
301 };
302
303 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
304 inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints(
305 epoch,
306 stake_amount,
307 earned_credits,
308 earned_points,
309 ));
310 }
311 Ok((earned_points, new_credits_observed))
312}
313
314fn calculate_migration_points(
320 stake: &Stake,
321 mut epoch_credits_iter: impl Iterator<Item = (Epoch, u64, u64)>,
322 stake_history: &StakeHistory,
323 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
324 new_rate_activation_epoch: Option<Epoch>,
325 use_fixed_point_stake_math: bool,
326 reward_epoch_delegated_stakes: &RewardEpochDelegatedStakes,
327) -> Result<(u128, u128, u64), CalculatedStakePoints> {
328 let (tower_points, tower_new_credits_observed, saw_marker) = tower_epoch_credits_iter(
329 stake,
330 epoch_credits_iter.by_ref(),
331 stake_history,
332 inflation_point_calc_tracer.as_ref(),
333 new_rate_activation_epoch,
334 use_fixed_point_stake_math,
335 );
336 let (ag_points, ag_new_credits_observed) = if saw_marker {
337 calculate_alpenglow_points(
338 stake,
339 epoch_credits_iter.next(),
340 stake_history,
341 inflation_point_calc_tracer,
342 new_rate_activation_epoch,
343 use_fixed_point_stake_math,
344 reward_epoch_delegated_stakes,
345 )?
346 } else {
347 (0, stake.credits_observed)
348 };
349
350 let new_credits_observed = tower_new_credits_observed.max(ag_new_credits_observed);
351 Ok((tower_points, ag_points, new_credits_observed))
352}
353
354pub(crate) fn calculate_stake_points_and_credits(
358 stake: &Stake,
359 vote_state: DelegatedVoteState,
360 stake_history: &StakeHistory,
361 inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
362 new_rate_activation_epoch: Option<Epoch>,
363 ag_epoch_type: &AlpenglowEpochType,
364 use_fixed_point_stake_math: bool,
365) -> CalculatedStakePoints {
366 let credits_in_stake = stake.credits_observed;
367 let credits_in_vote = vote_state.credits;
368 match credits_in_vote.cmp(&credits_in_stake) {
370 Ordering::Less => {
371 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
372 inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnRewound.into());
373 }
374 return CalculatedStakePoints {
393 tower_points: 0,
394 ag_points: 0,
395 new_credits_observed: credits_in_vote,
396 force_credits_update_with_skipped_reward: true,
397 };
398 }
399 Ordering::Equal => {
400 if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
401 inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into());
402 }
403 return CalculatedStakePoints {
405 tower_points: 0,
406 ag_points: 0,
407 new_credits_observed: credits_in_stake,
408 force_credits_update_with_skipped_reward: false,
409 };
410 }
411 Ordering::Greater => {}
412 }
413
414 let (tower_points, ag_points, new_credits_observed) = match ag_epoch_type {
415 AlpenglowEpochType::Tower => {
416 let (points, credits, _) = tower_epoch_credits_iter(
417 stake,
418 vote_state.epoch_credits_iter,
419 stake_history,
420 inflation_point_calc_tracer,
421 new_rate_activation_epoch,
422 use_fixed_point_stake_math,
423 );
424 (points, 0, credits)
425 }
426 AlpenglowEpochType::MigrationEpoch {
427 migration_epoch,
428 reward_epoch_delegated_stakes,
429 ..
430 } => {
431 debug_assert_eq!(reward_epoch_delegated_stakes.epoch, *migration_epoch);
432 match calculate_migration_points(
433 stake,
434 vote_state.epoch_credits_iter,
435 stake_history,
436 inflation_point_calc_tracer,
437 new_rate_activation_epoch,
438 use_fixed_point_stake_math,
439 reward_epoch_delegated_stakes,
440 ) {
441 Ok(r) => r,
442 Err(e) => return e,
443 }
444 }
445 AlpenglowEpochType::Alpenglow {
446 migration_epoch,
447 reward_epoch_delegated_stakes,
448 } => {
449 debug_assert!(reward_epoch_delegated_stakes.epoch > *migration_epoch);
450 let (ag_points, credits) = match calculate_alpenglow_points(
451 stake,
452 vote_state.epoch_credits_iter.last(),
453 stake_history,
454 inflation_point_calc_tracer,
455 new_rate_activation_epoch,
456 use_fixed_point_stake_math,
457 reward_epoch_delegated_stakes,
458 ) {
459 Ok(result) => result,
460 Err(e) => return e,
461 };
462 (0, ag_points, credits)
463 }
464 };
465 CalculatedStakePoints {
466 tower_points,
467 ag_points,
468 new_credits_observed,
469 force_credits_update_with_skipped_reward: false,
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use {
476 super::*,
477 solana_native_token::LAMPORTS_PER_SOL,
478 solana_vote_program::vote_state::{VoteStateV4, handler::VoteStateHandler},
479 std::cell::Cell,
480 };
481
482 impl<'a> From<&'a VoteStateV4> for DelegatedVoteState<'a> {
483 fn from(vote_state: &'a VoteStateV4) -> Self {
484 DelegatedVoteState {
485 credits: vote_state.credits(),
486 epoch_credits_iter: Box::new(vote_state.epoch_credits.iter().copied()),
487 }
488 }
489 }
490
491 fn new_stake(
492 stake: u64,
493 voter_pubkey: &Pubkey,
494 vote_state: &VoteStateV4,
495 activation_epoch: Epoch,
496 ) -> Stake {
497 Stake {
498 delegation: Delegation::new(voter_pubkey, stake, activation_epoch),
499 credits_observed: vote_state.credits(),
500 }
501 }
502
503 #[test]
504 fn test_stake_state_calculate_points_with_typical_values() {
505 let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::default());
506
507 let stake = new_stake(
510 10_000_000 * LAMPORTS_PER_SOL,
511 &Pubkey::default(),
512 vote_state.as_ref_v4(),
513 u64::MAX,
514 );
515
516 let epoch_slots: u128 = 14 * 24 * 3600 * 160;
517 for _ in 0..epoch_slots {
520 vote_state.increment_credits(0, 1);
521 }
522
523 assert_eq!(
525 u128::from(stake.delegation.stake) * epoch_slots,
526 calculate_stake_points_for_tower(
527 &stake,
528 DelegatedVoteState::from(vote_state.as_ref_v4()),
529 &StakeHistory::default(),
530 null_tracer(),
531 None,
532 true,
533 )
534 );
535 }
536
537 #[test]
538 fn test_tower_epoch_credits_iter() {
539 let stake_lamports = 10_000_000 * LAMPORTS_PER_SOL;
540 let credits = 1235;
541
542 let stake = new_stake(
543 stake_lamports,
544 &Pubkey::default(),
545 VoteStateHandler::new_v4(VoteStateV4::default()).as_ref_v4(),
546 u64::MAX,
547 );
548
549 let epoch_credits = vec![(0, credits, 0), (1, credits * 2, credits)];
550 let mut epoch_credits_iter = epoch_credits.into_iter();
551 let (points, new_credits, saw_marker) = tower_epoch_credits_iter(
552 &stake,
553 epoch_credits_iter.by_ref(),
554 &StakeHistory::default(),
555 null_tracer(),
556 None,
557 true,
558 );
559 assert_eq!(points, credits as u128 * stake_lamports as u128 * 2);
560 assert_eq!(new_credits, credits * 2);
561 assert_eq!(epoch_credits_iter.next(), None);
562 assert!(!saw_marker);
563
564 let epoch_credits = vec![
565 (0, credits, 0),
566 (1, credits * 2, credits),
567 AG_MIGRATION_EPOCH_CREDIT,
568 ];
569 let mut epoch_credits_iter = epoch_credits.into_iter();
570 let (points, new_credits, saw_marker) = tower_epoch_credits_iter(
571 &stake,
572 epoch_credits_iter.by_ref(),
573 &StakeHistory::default(),
574 null_tracer(),
575 None,
576 true,
577 );
578 assert_eq!(points, credits as u128 * stake_lamports as u128 * 2);
579 assert_eq!(new_credits, credits * 2);
580 assert_eq!(epoch_credits_iter.next(), None);
581 assert!(saw_marker);
582
583 let epoch_credits = vec![
584 (0, credits, 0),
585 (1, credits * 2, credits),
586 AG_MIGRATION_EPOCH_CREDIT,
587 (1, credits * 3, credits * 2),
588 ];
589 let mut epoch_credits_iter = epoch_credits.into_iter();
590 let (points, new_credits, saw_marker) = tower_epoch_credits_iter(
591 &stake,
592 epoch_credits_iter.by_ref(),
593 &StakeHistory::default(),
594 null_tracer(),
595 None,
596 true,
597 );
598 assert_eq!(points, credits as u128 * stake_lamports as u128 * 2);
599 assert_eq!(new_credits, credits * 2);
600 assert_eq!(
601 epoch_credits_iter.next().unwrap(),
602 (1, credits * 3, credits * 2)
603 );
604 assert!(saw_marker);
605 }
606
607 #[test]
608 fn test_calculate_alpenglow_points() {
609 let stake_lamports = 10_000_000 * LAMPORTS_PER_SOL;
610 let total_stake = stake_lamports * 2;
611 let credits = 1235;
612
613 let stake = new_stake(
614 stake_lamports,
615 &Pubkey::default(),
616 VoteStateHandler::new_v4(VoteStateV4::default()).as_ref_v4(),
617 u64::MAX,
618 );
619
620 let reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
621 epoch: 1,
622 delegated_stakes: [(Pubkey::default(), total_stake)].into_iter().collect(),
623 };
624
625 let epoch_credits = vec![(0, credits, 0), (1, credits * 2, credits)];
626 let (points, new_credits) = calculate_alpenglow_points(
627 &stake,
628 epoch_credits.into_iter().last(),
629 &StakeHistory::default(),
630 null_tracer(),
631 None,
632 true,
633 &reward_epoch_delegated_stakes,
634 )
635 .unwrap();
636 assert_eq!(
637 points,
638 credits as u128 * stake_lamports as u128 / total_stake as u128
639 );
640 assert_eq!(new_credits, credits * 2);
641
642 let epoch_credits = vec![
643 (0, credits, 0),
644 AG_MIGRATION_EPOCH_CREDIT,
645 (0, credits * 2, credits),
646 (1, credits * 3, credits * 2),
647 ];
648 let (points, new_credits) = calculate_alpenglow_points(
649 &stake,
650 epoch_credits.into_iter().last(),
651 &StakeHistory::default(),
652 null_tracer(),
653 None,
654 true,
655 &reward_epoch_delegated_stakes,
656 )
657 .unwrap();
658 assert_eq!(
659 points,
660 credits as u128 * stake_lamports as u128 / total_stake as u128
661 );
662 assert_eq!(new_credits, credits * 3);
663
664 let missing_reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
665 epoch: 2,
666 delegated_stakes: [(Pubkey::default(), total_stake)].into_iter().collect(),
667 };
668 let epoch_credits = vec![(0, credits, 0), (1, credits * 2, credits)];
669 let (points, new_credits) = calculate_alpenglow_points(
670 &stake,
671 epoch_credits.into_iter().last(),
672 &StakeHistory::default(),
673 null_tracer(),
674 None,
675 true,
676 &missing_reward_epoch_delegated_stakes,
677 )
678 .unwrap();
679 assert_eq!(points, 0);
680 assert_eq!(new_credits, 0);
681 }
682
683 #[test]
684 fn test_calculate_alpenglow_points_uses_current_delegated_stake_denominator() {
685 let stake_lamports = 200;
686 let current_delegated_total = 200;
687 let credits = 10;
688
689 let stake = Stake {
690 delegation: Delegation {
691 voter_pubkey: Pubkey::default(),
692 stake: stake_lamports,
693 activation_epoch: u64::MAX,
694 deactivation_epoch: u64::MAX,
695 ..Default::default()
696 },
697 credits_observed: 0,
698 };
699
700 let reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
701 epoch: 1,
702 delegated_stakes: [(Pubkey::default(), current_delegated_total)]
703 .into_iter()
704 .collect(),
705 };
706 let epoch_credits = vec![
707 (0, credits, 0),
708 AG_MIGRATION_EPOCH_CREDIT,
709 (0, credits * 2, credits),
710 (1, credits * 3, credits * 2),
711 ];
712
713 let (points, new_credits) = calculate_alpenglow_points(
714 &stake,
715 epoch_credits.into_iter().last(),
716 &StakeHistory::default(),
717 null_tracer(),
718 None,
719 true,
720 &reward_epoch_delegated_stakes,
721 )
722 .unwrap();
723
724 assert_eq!(points, credits as u128);
725 assert_eq!(new_credits, credits * 3);
726 }
727
728 #[test]
729 fn test_alpenglow_uses_rewarded_epoch_credits_only() {
730 let stake_lamports = 10_000_000 * LAMPORTS_PER_SOL;
731 let total_stake = stake_lamports * 2;
732 let credits = 1235;
733
734 let stake = Stake {
735 delegation: Delegation {
736 voter_pubkey: Pubkey::default(),
737 stake: stake_lamports,
738 activation_epoch: u64::MAX,
739 deactivation_epoch: u64::MAX,
740 ..Default::default()
741 },
742 credits_observed: 0,
743 };
744 let vote_state = VoteStateV4 {
745 epoch_credits: (1..=64)
746 .map(|epoch| {
747 let initial_credits = (epoch - 1) * credits;
748 (epoch, initial_credits + credits, initial_credits)
749 })
750 .collect(),
751 ..VoteStateV4::default()
752 };
753
754 let ag_epoch_type = AlpenglowEpochType::Alpenglow {
755 migration_epoch: 0,
756 reward_epoch_delegated_stakes: RewardEpochDelegatedStakes {
757 epoch: 64,
758 delegated_stakes: [(Pubkey::default(), total_stake)].into_iter().collect(),
759 },
760 };
761
762 let calculated_points_events = Cell::new(0);
763 let tracer = |event: &InflationPointCalculationEvent| {
764 if matches!(event, InflationPointCalculationEvent::CalculatedPoints(..)) {
765 calculated_points_events.set(calculated_points_events.get() + 1);
766 }
767 };
768
769 let calculated_stake_points = calculate_stake_points_and_credits(
770 &stake,
771 DelegatedVoteState::from(&vote_state),
772 &StakeHistory::default(),
773 Some(tracer),
774 None,
775 &ag_epoch_type,
776 true,
777 );
778
779 assert_eq!(calculated_points_events.get(), 1);
780 assert_eq!(
781 calculated_stake_points,
782 CalculatedStakePoints {
783 tower_points: 0,
784 ag_points: credits as u128 * stake_lamports as u128 / total_stake as u128,
785 new_credits_observed: credits * 64,
786 force_credits_update_with_skipped_reward: false,
787 }
788 );
789 }
790
791 #[test]
792 fn test_alpenglow_rewarded_epoch_uses_delegated_stake_denominator() {
793 let stake_lamports = 200;
794 let current_delegated_total = 200;
795 let credits = 10;
796
797 let stake = Stake {
798 delegation: Delegation {
799 voter_pubkey: Pubkey::default(),
800 stake: stake_lamports,
801 activation_epoch: u64::MAX,
802 deactivation_epoch: u64::MAX,
803 ..Default::default()
804 },
805 credits_observed: 0,
806 };
807 let vote_state = VoteStateV4 {
808 epoch_credits: vec![(1, credits, 0)],
809 ..VoteStateV4::default()
810 };
811
812 let ag_epoch_type = AlpenglowEpochType::Alpenglow {
813 migration_epoch: 0,
814 reward_epoch_delegated_stakes: RewardEpochDelegatedStakes {
815 epoch: 1,
816 delegated_stakes: [(Pubkey::default(), current_delegated_total)]
817 .into_iter()
818 .collect(),
819 },
820 };
821
822 let calculated_stake_points = calculate_stake_points_and_credits(
823 &stake,
824 DelegatedVoteState::from(&vote_state),
825 &StakeHistory::default(),
826 null_tracer(),
827 None,
828 &ag_epoch_type,
829 true,
830 );
831
832 assert_eq!(
833 calculated_stake_points,
834 CalculatedStakePoints {
835 tower_points: 0,
836 ag_points: credits as u128,
837 new_credits_observed: credits,
838 force_credits_update_with_skipped_reward: false,
839 }
840 );
841 }
842
843 #[test]
844 fn test_calculate_migration_points() {
845 let stake_lamports = 10_000_000 * LAMPORTS_PER_SOL;
846 let total_stake = stake_lamports * 2;
847 let credits = 1235;
848
849 let stake = new_stake(
850 stake_lamports,
851 &Pubkey::default(),
852 VoteStateHandler::new_v4(VoteStateV4::default()).as_ref_v4(),
853 u64::MAX,
854 );
855
856 let epoch_credits = vec![(0, credits, 0), (1, credits * 2, credits)];
857 let reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
858 epoch: 2,
859 delegated_stakes: [(Pubkey::default(), total_stake)].into_iter().collect(),
860 };
861 let (tower_points, ag_points, new_credits) = calculate_migration_points(
862 &stake,
863 epoch_credits.into_iter(),
864 &StakeHistory::default(),
865 null_tracer(),
866 None,
867 true,
868 &reward_epoch_delegated_stakes,
869 )
870 .unwrap();
871 assert_eq!(tower_points, credits as u128 * stake_lamports as u128 * 2);
872 assert_eq!(ag_points, 0);
873 assert_eq!(new_credits, credits * 2);
874
875 let reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
876 epoch: 0,
877 delegated_stakes: [(Pubkey::default(), total_stake)].into_iter().collect(),
878 };
879 let epoch_credits = vec![
880 (0, credits, 0),
881 AG_MIGRATION_EPOCH_CREDIT,
882 (0, credits * 2, credits),
883 ];
884 let (tower_points, ag_points, new_credits) = calculate_migration_points(
885 &stake,
886 epoch_credits.into_iter(),
887 &StakeHistory::default(),
888 null_tracer(),
889 None,
890 true,
891 &reward_epoch_delegated_stakes,
892 )
893 .unwrap();
894 assert_eq!(tower_points, credits as u128 * stake_lamports as u128);
895 assert_eq!(
896 ag_points,
897 credits as u128 * stake_lamports as u128 / total_stake as u128
898 );
899 assert_eq!(new_credits, credits * 2);
900
901 let epoch_credits = vec![AG_MIGRATION_EPOCH_CREDIT, (0, credits * 2, credits)];
902 let (tower_points, ag_points, new_credits) = calculate_migration_points(
903 &stake,
904 epoch_credits.into_iter(),
905 &StakeHistory::default(),
906 null_tracer(),
907 None,
908 true,
909 &reward_epoch_delegated_stakes,
910 )
911 .unwrap();
912 assert_eq!(tower_points, 0);
913 assert_eq!(
914 ag_points,
915 credits as u128 * stake_lamports as u128 / total_stake as u128
916 );
917 assert_eq!(new_credits, credits * 2);
918 }
919
920 #[test]
921 fn test_changing_total_stake() {
922 let pubkey = Pubkey::new_unique();
923 let staker_delegation = LAMPORTS_PER_SOL;
924 let reward_epoch_validator_stake = staker_delegation * 5;
925 let stake = Stake {
926 delegation: Delegation {
927 voter_pubkey: pubkey,
928 stake: staker_delegation,
929 activation_epoch: u64::MAX,
930 deactivation_epoch: u64::MAX,
931 ..Default::default()
932 },
933 credits_observed: 0,
934 };
935
936 let credits = 1235;
937 let epoch_credits = vec![
938 (0, credits, 0),
939 AG_MIGRATION_EPOCH_CREDIT,
940 (0, credits * 2, credits),
941 (1, credits * 3, credits * 2),
942 (2, credits * 4, credits * 3),
943 ];
944 let reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
945 epoch: 2,
946 delegated_stakes: [(pubkey, reward_epoch_validator_stake)]
947 .into_iter()
948 .collect(),
949 };
950 let (points, new_credits) = calculate_alpenglow_points(
951 &stake,
952 epoch_credits.into_iter().last(),
953 &StakeHistory::default(),
954 null_tracer(),
955 None,
956 true,
957 &reward_epoch_delegated_stakes,
958 )
959 .unwrap();
960 assert_eq!(new_credits, credits * 4);
961 let expected_points = credits * staker_delegation / reward_epoch_validator_stake;
962 assert_eq!(points, expected_points as u128);
963 }
964
965 #[test]
966 fn test_stake_activating_deactivating() {
967 let stake_lamports = 10_000_000 * LAMPORTS_PER_SOL;
968 let credits = 1235;
969
970 for (activation_epoch, deactivation_epoch) in [(0, u64::MAX), (u64::MAX, 0)] {
971 let stake = Stake {
972 delegation: Delegation {
973 voter_pubkey: Pubkey::default(),
974 stake: stake_lamports,
975 activation_epoch,
976 deactivation_epoch,
977 ..Default::default()
978 },
979 credits_observed: 0,
980 };
981 let epoch_credits = vec![(0, credits, 0), (1, credits * 2, credits)];
982 let mut epoch_credits_iter = epoch_credits.into_iter();
983 let (points, new_credits, saw_marker) = tower_epoch_credits_iter(
984 &stake,
985 epoch_credits_iter.by_ref(),
986 &StakeHistory::default(),
987 null_tracer(),
988 None,
989 true,
990 );
991 assert_eq!(points, credits as u128 * stake_lamports as u128);
992 assert_eq!(new_credits, credits * 2);
993 assert_eq!(epoch_credits_iter.next(), None);
994 assert!(!saw_marker);
995 }
996
997 for (activation_epoch, deactivation_epoch) in [(0, u64::MAX), (u64::MAX, 0)] {
998 let stake = Stake {
999 delegation: Delegation {
1000 voter_pubkey: Pubkey::default(),
1001 stake: stake_lamports,
1002 activation_epoch,
1003 deactivation_epoch,
1004 ..Default::default()
1005 },
1006 credits_observed: 0,
1007 };
1008
1009 let total_stake = stake_lamports * 2;
1010 let epoch_credits = vec![
1011 (0, credits, 0),
1012 AG_MIGRATION_EPOCH_CREDIT,
1013 (0, credits * 2, credits),
1014 (1, credits * 3, credits * 2),
1015 ];
1016 let reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
1017 epoch: 1,
1018 delegated_stakes: [(Pubkey::default(), total_stake)].into_iter().collect(),
1019 };
1020 let (points, new_credits) = calculate_alpenglow_points(
1021 &stake,
1022 epoch_credits.into_iter().last(),
1023 &StakeHistory::default(),
1024 null_tracer(),
1025 None,
1026 true,
1027 &reward_epoch_delegated_stakes,
1028 )
1029 .unwrap();
1030 let expected_points = if activation_epoch == 0 {
1031 credits as u128 * stake_lamports as u128 / total_stake as u128
1032 } else {
1033 0
1034 };
1035 assert_eq!(points, expected_points);
1036 assert_eq!(new_credits, credits * 3);
1037 }
1038
1039 for (activation_epoch, deactivation_epoch) in [(0, u64::MAX), (u64::MAX, 0)] {
1040 let stake = Stake {
1041 delegation: Delegation {
1042 voter_pubkey: Pubkey::default(),
1043 stake: stake_lamports,
1044 activation_epoch,
1045 deactivation_epoch,
1046 ..Default::default()
1047 },
1048 credits_observed: 0,
1049 };
1050
1051 let total_stake = stake_lamports * 2;
1052 let epoch_credits = vec![
1053 (0, credits, 0),
1054 AG_MIGRATION_EPOCH_CREDIT,
1055 (0, credits * 2, credits),
1056 (1, credits * 3, credits * 2),
1057 ];
1058 let reward_epoch_delegated_stakes = RewardEpochDelegatedStakes {
1059 epoch: 0,
1060 delegated_stakes: [(Pubkey::default(), total_stake)].into_iter().collect(),
1061 };
1062 let (tower_points, ag_points, new_credits) = calculate_migration_points(
1063 &stake,
1064 epoch_credits.into_iter(),
1065 &StakeHistory::default(),
1066 null_tracer(),
1067 None,
1068 true,
1069 &reward_epoch_delegated_stakes,
1070 )
1071 .unwrap();
1072 if activation_epoch == 0 {
1073 assert_eq!(tower_points, 0);
1074 } else {
1075 assert_eq!(tower_points, credits as u128 * stake_lamports as u128);
1076 }
1077 let expected_ag_points = if activation_epoch == 0 {
1078 0
1079 } else {
1080 credits as u128 * stake_lamports as u128 / total_stake as u128
1081 };
1082 assert_eq!(ag_points, expected_ag_points);
1083 assert_eq!(new_credits, credits * 2);
1084 }
1085 }
1086}