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