LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 806 0.0 %
Date: 2025-10-13 04:42:14 Functions: 0 30 0.0 %

          Line data    Source code
       1             : #include "fd_rewards.h"
       2             : #include <math.h>
       3             : 
       4             : #include "../runtime/fd_acc_mgr.h"
       5             : #include "../runtime/fd_executor_err.h"
       6             : #include "../runtime/program/fd_vote_program.h"
       7             : #include "../runtime/sysvar/fd_sysvar_epoch_rewards.h"
       8             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       9             : #include "../stakes/fd_stakes.h"
      10             : #include "../runtime/program/fd_stake_program.h"
      11             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
      12             : #include "../runtime/context/fd_capture_ctx.h"
      13             : #include "../runtime/fd_runtime.h"
      14             : 
      15             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
      16             : static double
      17           0 : total( fd_inflation_t const * inflation, double year ) {
      18           0 :   if ( FD_UNLIKELY( year == 0.0 ) ) {
      19           0 :     FD_LOG_ERR(( "inflation year 0" ));
      20           0 :   }
      21           0 :   double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
      22           0 :   return (tapered > inflation->terminal) ? tapered : inflation->terminal;
      23           0 : }
      24             : 
      25             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
      26             : static double
      27           0 : foundation( fd_inflation_t const * inflation, double year ) {
      28           0 :   return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
      29           0 : }
      30             : 
      31             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
      32             : static double
      33           0 : validator( fd_inflation_t const * inflation, double year) {
      34             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
      35           0 :   FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
      36           0 :   return total( inflation, year ) - foundation( inflation, year );
      37           0 : }
      38             : 
      39             : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
      40             :     activation slot of the following features:
      41             :     - devnet_and_testnet
      42             :     - full_inflation_enable, if full_inflation_vote has been activated
      43             : 
      44             :     https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
      45             : static FD_FN_CONST ulong
      46           0 : get_inflation_start_slot( fd_bank_t const * bank ) {
      47           0 :   ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
      48           0 :       ? fd_bank_features_query( bank )->devnet_and_testnet
      49           0 :       : ULONG_MAX;
      50             : 
      51           0 :   ulong enable = ULONG_MAX;
      52           0 :   if( FD_FEATURE_ACTIVE_BANK( bank, full_inflation_vote ) &&
      53           0 :       FD_FEATURE_ACTIVE_BANK( bank, full_inflation_enable ) ) {
      54           0 :     enable = fd_bank_features_query( bank )->full_inflation_enable;
      55           0 :   }
      56             : 
      57           0 :   ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
      58           0 :   if( min_slot == ULONG_MAX ) {
      59           0 :     if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
      60           0 :       min_slot = fd_bank_features_query( bank )->pico_inflation;
      61           0 :     } else {
      62           0 :       min_slot = 0;
      63           0 :     }
      64           0 :   }
      65           0 :   return min_slot;
      66           0 : }
      67             : 
      68             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2110 */
      69             : static ulong
      70             : get_inflation_num_slots( fd_bank_t const *           bank,
      71             :                          fd_epoch_schedule_t const * epoch_schedule,
      72           0 :                          ulong                       slot ) {
      73           0 :   ulong inflation_activation_slot = get_inflation_start_slot( bank );
      74           0 :   ulong inflation_start_slot      = fd_epoch_slot0( epoch_schedule,
      75           0 :                                                     fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule,
      76           0 :                                                                                         inflation_activation_slot, NULL ),
      77           0 :                                                                       1UL ) );
      78             : 
      79           0 :   ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, NULL );
      80             : 
      81           0 :   return fd_epoch_slot0( epoch_schedule, epoch ) - inflation_start_slot;
      82           0 : }
      83             : 
      84             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2121 */
      85             : static double
      86           0 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
      87           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
      88           0 :   ulong num_slots = get_inflation_num_slots( bank, epoch_schedule, fd_bank_slot_get( bank ) );
      89           0 :   return (double)num_slots / (double)fd_bank_slots_per_year_get( bank );
      90           0 : }
      91             : 
      92             : /* For a given stake and vote_state, calculate how many points were earned (credits * stake) and new value
      93             :    for credits_observed were the points paid
      94             : 
      95             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
      96             : static void
      97             : calculate_stake_points_and_credits( fd_stake_history_t const *     stake_history,
      98             :                                     fd_stake_delegation_t const *  stake,
      99             :                                     fd_vote_state_ele_t const *    vote_state,
     100             :                                     ulong *                        new_rate_activation_epoch,
     101           0 :                                     fd_calculated_stake_points_t * result ) {
     102             : 
     103           0 :   ulong credits_in_stake = stake->credits_observed;
     104           0 :   ulong credits_in_vote  = 0UL;
     105           0 :   if( FD_LIKELY( vote_state->credits_cnt>0UL ) ) {
     106           0 :     credits_in_vote = vote_state->credits[vote_state->credits_cnt-1UL];
     107           0 :   }
     108             : 
     109             :   /* If the Vote account has less credits observed than the Stake account,
     110             :       something is wrong and we need to force an update.
     111             : 
     112             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
     113           0 :   if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
     114           0 :     result->points = 0;
     115           0 :     result->new_credits_observed = credits_in_vote;
     116           0 :     result->force_credits_update_with_skipped_reward = 1;
     117           0 :     return;
     118           0 :   }
     119             : 
     120             :   /* If the Vote account has the same amount of credits observed as the Stake account,
     121             :       then the Vote account hasn't earnt any credits and so there is nothing to update.
     122             : 
     123             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
     124           0 :   if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
     125           0 :     result->points = 0;
     126           0 :     result->new_credits_observed = credits_in_vote;
     127           0 :     result->force_credits_update_with_skipped_reward = 0;
     128           0 :     return;
     129           0 :   }
     130             : 
     131             :   /* Calculate the points for each epoch credit */
     132           0 :   uint128 points               = 0;
     133           0 :   ulong   new_credits_observed = credits_in_stake;
     134           0 :   for( ulong i=0UL; i<vote_state->credits_cnt; i++ ) {
     135             : 
     136           0 :     ulong final_epoch_credits   = vote_state->credits[i];
     137           0 :     ulong initial_epoch_credits = vote_state->prev_credits[i];
     138           0 :     uint128 earned_credits = 0;
     139           0 :     if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
     140           0 :       earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
     141           0 :     } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
     142           0 :       earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
     143           0 :     }
     144             : 
     145           0 :     new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
     146             : 
     147           0 :     fd_delegation_t delegation = {
     148           0 :       .voter_pubkey         = stake->vote_account,
     149           0 :       .stake                = stake->stake,
     150           0 :       .activation_epoch     = stake->activation_epoch,
     151           0 :       .deactivation_epoch   = stake->deactivation_epoch,
     152           0 :       .warmup_cooldown_rate = stake->warmup_cooldown_rate,
     153           0 :     };
     154             : 
     155           0 :     ulong stake_amount = fd_stake_activating_and_deactivating(
     156           0 :         &delegation,
     157           0 :         vote_state->epoch[i],
     158           0 :         stake_history,
     159           0 :         new_rate_activation_epoch ).effective;
     160             : 
     161           0 :     points += (uint128)stake_amount * earned_credits;
     162             : 
     163           0 :   }
     164             : 
     165             : 
     166           0 :   result->points = points;
     167           0 :   result->new_credits_observed = new_credits_observed;
     168           0 :   result->force_credits_update_with_skipped_reward = 0;
     169           0 : }
     170             : 
     171             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L127 */
     172             : static int
     173             : calculate_stake_rewards( fd_stake_history_t const *      stake_history,
     174             :                          fd_stake_delegation_t const *   stake,
     175             :                          fd_vote_state_ele_t const *     vote_state,
     176             :                          ulong                           rewarded_epoch,
     177             :                          fd_point_value_t *              point_value,
     178             :                          ulong *                         new_rate_activation_epoch,
     179           0 :                          fd_calculated_stake_rewards_t * result ) {
     180           0 :   fd_calculated_stake_points_t stake_points_result = {0};
     181             : 
     182           0 :   calculate_stake_points_and_credits(
     183           0 :       stake_history,
     184           0 :       stake,
     185           0 :       vote_state,
     186           0 :       new_rate_activation_epoch,
     187           0 :       &stake_points_result);
     188             : 
     189             :   // Drive credits_observed forward unconditionally when rewards are disabled
     190             :   // or when this is the stake's activation epoch
     191           0 :   if( ( point_value->rewards==0UL ) ||
     192           0 :       ( stake->activation_epoch==rewarded_epoch ) ) {
     193           0 :       stake_points_result.force_credits_update_with_skipped_reward |= 1;
     194           0 :   }
     195             : 
     196           0 :   if( stake_points_result.force_credits_update_with_skipped_reward ) {
     197           0 :     result->staker_rewards = 0;
     198           0 :     result->voter_rewards = 0;
     199           0 :     result->new_credits_observed = stake_points_result.new_credits_observed;
     200           0 :     return 0;
     201           0 :   }
     202           0 :   if( stake_points_result.points == 0 || point_value->points == 0 ) {
     203           0 :     return 1;
     204           0 :   }
     205             : 
     206             :   /* FIXME: need to error out if the conversion from uint128 to u64 fails, also use 128 checked mul and div */
     207           0 :   ulong rewards = (ulong)(stake_points_result.points * (uint128)(point_value->rewards) / (uint128) point_value->points);
     208           0 :   if( rewards == 0 ) {
     209           0 :     return 1;
     210           0 :   }
     211             : 
     212           0 :   fd_commission_split_t split_result;
     213           0 :   fd_vote_commission_split( vote_state->commission, rewards, &split_result );
     214           0 :   if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
     215           0 :     return 1;
     216           0 :   }
     217             : 
     218           0 :   result->staker_rewards = split_result.staker_portion;
     219           0 :   result->voter_rewards = split_result.voter_portion;
     220           0 :   result->new_credits_observed = stake_points_result.new_credits_observed;
     221           0 :   return 0;
     222           0 : }
     223             : 
     224             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
     225             : static int
     226             : redeem_rewards( fd_stake_history_t const *      stake_history,
     227             :                 fd_stake_delegation_t const *   stake,
     228             :                 fd_vote_state_ele_t const *     vote_state,
     229             :                 ulong                           rewarded_epoch,
     230             :                 fd_point_value_t *              point_value,
     231             :                 ulong *                         new_rate_activation_epoch,
     232           0 :                 fd_calculated_stake_rewards_t * calculated_stake_rewards ) {
     233             : 
     234           0 :   int rc = calculate_stake_rewards(
     235           0 :       stake_history,
     236           0 :       stake,
     237           0 :       vote_state,
     238           0 :       rewarded_epoch,
     239           0 :       point_value,
     240           0 :       new_rate_activation_epoch,
     241           0 :       calculated_stake_rewards );
     242           0 :   if( FD_UNLIKELY( rc!=0 ) ) {
     243           0 :     return rc;
     244           0 :   }
     245             : 
     246           0 :   return FD_EXECUTOR_INSTR_SUCCESS;
     247           0 : }
     248             : 
     249             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L70 */
     250             : static int
     251             : calculate_points( fd_stake_delegation_t const * stake,
     252             :                   fd_vote_state_ele_t const *   vote_state,
     253             :                   fd_stake_history_t const  *   stake_history,
     254             :                   ulong *                       new_rate_activation_epoch,
     255           0 :                   uint128 *                     result ) {
     256           0 :   fd_calculated_stake_points_t stake_point_result;
     257           0 :   calculate_stake_points_and_credits( stake_history,
     258           0 :                                       stake,
     259           0 :                                       vote_state,
     260           0 :                                       new_rate_activation_epoch,
     261           0 :                                       &stake_point_result );
     262           0 :   *result = stake_point_result.points;
     263             : 
     264           0 :   return FD_EXECUTOR_INSTR_SUCCESS;
     265           0 : }
     266             : 
     267             : /* Returns the length of the given epoch in slots
     268             : 
     269             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
     270             : static ulong
     271             : get_slots_in_epoch( ulong                       epoch,
     272           0 :                     fd_epoch_schedule_t const * epoch_schedule ) {
     273           0 :   return (epoch < epoch_schedule->first_normal_epoch) ?
     274           0 :           1UL << fd_ulong_sat_add(epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO) :
     275           0 :           epoch_schedule->slots_per_epoch;
     276           0 : }
     277             : 
     278             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
     279             : static double
     280             : epoch_duration_in_years( fd_bank_t const * bank,
     281           0 :                          ulong             prev_epoch ) {
     282           0 :   ulong slots_in_epoch = get_slots_in_epoch( prev_epoch, fd_bank_epoch_schedule_query( bank ) );
     283           0 :   return (double)slots_in_epoch / (double)fd_bank_slots_per_year_get( bank );
     284           0 : }
     285             : 
     286             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
     287             : static void
     288             : calculate_previous_epoch_inflation_rewards( fd_bank_t const *                   bank,
     289             :                                             ulong                               prev_epoch_capitalization,
     290             :                                             ulong                               prev_epoch,
     291           0 :                                             fd_prev_epoch_inflation_rewards_t * rewards ) {
     292           0 :   double slot_in_year = slot_in_year_for_inflation( bank );
     293             : 
     294           0 :   rewards->validator_rate               = validator( fd_bank_inflation_query( bank ), slot_in_year );
     295           0 :   rewards->foundation_rate              = foundation( fd_bank_inflation_query( bank ), slot_in_year );
     296           0 :   rewards->prev_epoch_duration_in_years = epoch_duration_in_years( bank, prev_epoch );
     297           0 :   rewards->validator_rewards            = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
     298           0 :   FD_LOG_DEBUG(( "Rewards %lu, Rate %.16f, Duration %.18f Capitalization %lu Slot in year %.16f", rewards->validator_rewards, rewards->validator_rate, rewards->prev_epoch_duration_in_years, prev_epoch_capitalization, slot_in_year ));
     299           0 : }
     300             : 
     301             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/lib.rs#L29 */
     302             : static ulong
     303           0 : get_minimum_stake_delegation( fd_bank_t * bank ) {
     304           0 :   if( !FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
     305           0 :     return 0UL;
     306           0 :   }
     307             : 
     308           0 :   if( FD_FEATURE_ACTIVE_BANK( bank, stake_raise_minimum_delegation_to_1_sol ) ) {
     309           0 :     return LAMPORTS_PER_SOL;
     310           0 :   }
     311             : 
     312           0 :   return 1;
     313           0 : }
     314             : 
     315             : static uint128
     316             : calculate_points_all( fd_bank_t *                    bank,
     317             :                       fd_stake_delegations_t const * stake_delegations,
     318             :                       fd_stake_history_t const *     stake_history,
     319             :                       ulong *                        new_warmup_cooldown_rate_epoch,
     320           0 :                       ulong                          minimum_stake_delegation ) {
     321             : 
     322           0 :   uint128 total_points = 0;
     323             : 
     324           0 :   fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( bank );
     325             : 
     326           0 :   fd_stake_delegations_iter_t iter_[1];
     327           0 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     328           0 :        !fd_stake_delegations_iter_done( iter );
     329           0 :        fd_stake_delegations_iter_next( iter ) ) {
     330           0 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     331             : 
     332           0 :     if( FD_UNLIKELY( stake_delegation->stake<minimum_stake_delegation ) ) {
     333           0 :       continue;
     334           0 :     }
     335             : 
     336           0 :     fd_vote_state_ele_t * vote_state_ele = fd_vote_states_query( vote_states, &stake_delegation->vote_account );
     337           0 :     if( FD_UNLIKELY( !vote_state_ele ) ) {
     338           0 :       continue;
     339           0 :     }
     340             : 
     341           0 :     uint128 account_points;
     342           0 :     int err = calculate_points(
     343           0 :         stake_delegation,
     344           0 :         vote_state_ele,
     345           0 :         stake_history,
     346           0 :         new_warmup_cooldown_rate_epoch, &account_points );
     347           0 :     if( FD_UNLIKELY( err ) ) {
     348           0 :       FD_LOG_DEBUG(( "failed to calculate points" ));
     349           0 :       continue;
     350           0 :     }
     351             : 
     352           0 :     total_points += account_points;
     353           0 :   }
     354             : 
     355           0 :   fd_bank_vote_states_end_locking_query( bank );
     356             : 
     357           0 :   return total_points;
     358           0 : }
     359             : 
     360             : /* Calculates epoch reward points from stake/vote accounts.
     361             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L472 */
     362             : static void
     363             : calculate_reward_points_partitioned( fd_bank_t *                    bank,
     364             :                                      fd_stake_delegations_t const * stake_delegations,
     365             :                                      fd_stake_history_t const *     stake_history,
     366             :                                      ulong                          rewards,
     367           0 :                                      fd_point_value_t *             result ) {
     368           0 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
     369             : 
     370             :   /* Calculate the points for each stake delegation */
     371           0 :   int _err[1];
     372           0 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     373           0 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     374           0 :   int is_some = fd_new_warmup_cooldown_rate_epoch(
     375           0 :       fd_bank_epoch_schedule_query( bank ),
     376           0 :       fd_bank_features_query( bank ),
     377           0 :       fd_bank_slot_get( bank ),
     378           0 :       new_warmup_cooldown_rate_epoch,
     379           0 :       _err );
     380           0 :   if( FD_UNLIKELY( !is_some ) ) {
     381           0 :     new_warmup_cooldown_rate_epoch = NULL;
     382           0 :   }
     383             : 
     384           0 :   uint128 points = calculate_points_all(
     385           0 :       bank,
     386           0 :       stake_delegations,
     387           0 :       stake_history,
     388           0 :       new_warmup_cooldown_rate_epoch,
     389           0 :       minimum_stake_delegation );
     390             : 
     391           0 :   if( points > 0 ) {
     392           0 :     result->points  = points;
     393           0 :     result->rewards = rewards;
     394           0 :   }
     395           0 : }
     396             : 
     397             : static void
     398             : calculate_stake_vote_rewards_account( fd_bank_t *                                bank,
     399             :                                       fd_stake_delegations_t const *             stake_delegations,
     400             :                                       fd_capture_ctx_t const *                   capture_ctx,
     401             :                                       fd_stake_history_t const *                 stake_history,
     402             :                                       ulong const                                rewarded_epoch,
     403             :                                       ulong *                                    new_warmup_cooldown_rate_epoch,
     404             :                                       fd_point_value_t *                         point_value,
     405             :                                       fd_calculate_stake_vote_rewards_result_t * result,
     406             :                                       fd_spad_t *                                spad,
     407           0 :                                       int                                        is_recalculation ) {
     408             : 
     409           0 :   FD_SPAD_FRAME_BEGIN( spad ) {
     410             : 
     411           0 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
     412           0 :   ulong total_stake_rewards      = 0UL;
     413           0 :   ulong dlist_additional_cnt     = 0UL;
     414           0 :   ulong stake_delegation_cnt     = fd_stake_delegations_cnt( stake_delegations );
     415             : 
     416             :   /* Build a local vote reward map */
     417           0 :   fd_vote_reward_t_mapnode_t * vote_reward_map_pool = fd_vote_reward_t_map_join( fd_vote_reward_t_map_new( fd_spad_alloc(
     418           0 :       spad, fd_vote_reward_t_map_align(), fd_vote_reward_t_map_footprint( stake_delegation_cnt ) ), stake_delegation_cnt ) );
     419           0 :   fd_vote_reward_t_mapnode_t * vote_reward_map_root = NULL;
     420             : 
     421           0 :   fd_vote_states_t const * vote_states = NULL;
     422           0 :   if( !is_recalculation ) {
     423           0 :     vote_states = fd_bank_vote_states_locking_query( bank );
     424           0 :   } else {
     425           0 :     vote_states = fd_bank_vote_states_prev_locking_query( bank );
     426           0 :   }
     427           0 :   if( FD_UNLIKELY( !vote_states ) ) {
     428           0 :     FD_LOG_CRIT(( "vote_states is NULL" ));
     429           0 :   }
     430             : 
     431           0 :   fd_stake_delegations_iter_t iter_[1];
     432           0 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     433           0 :        !fd_stake_delegations_iter_done( iter );
     434           0 :        fd_stake_delegations_iter_next( iter ) ) {
     435           0 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     436             : 
     437           0 :     if( FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
     438           0 :       if( stake_delegation->stake<minimum_stake_delegation ) {
     439           0 :         continue;
     440           0 :       }
     441           0 :     }
     442             : 
     443           0 :     fd_pubkey_t const *   voter_acc      = &stake_delegation->vote_account;
     444           0 :     fd_vote_state_ele_t * vote_state_ele = fd_vote_states_query( vote_states, voter_acc );
     445           0 :     if( FD_UNLIKELY( !vote_state_ele ) ) {
     446           0 :       FD_LOG_DEBUG(( "failed to query vote state" ));
     447           0 :       continue;
     448           0 :     }
     449             : 
     450             :     /* Note, this doesn't actually redeem any rewards.. this is a misnomer. */
     451           0 :     fd_calculated_stake_rewards_t calculated_stake_rewards[1] = {0};
     452             : 
     453           0 :     int err = redeem_rewards(
     454           0 :         stake_history,
     455           0 :         stake_delegation,
     456           0 :         vote_state_ele,
     457           0 :         rewarded_epoch,
     458           0 :         point_value,
     459           0 :         new_warmup_cooldown_rate_epoch,
     460           0 :         calculated_stake_rewards );
     461             : 
     462           0 :     if( FD_UNLIKELY( err!=0 ) ) {
     463           0 :       FD_LOG_DEBUG(( "redeem_rewards failed for %s with error %d", FD_BASE58_ENC_32_ALLOCA( &stake_delegation->stake_account ), err ));
     464           0 :       continue;
     465           0 :     }
     466             : 
     467           0 :     if( capture_ctx ) {
     468           0 :       fd_solcap_write_stake_reward_event( capture_ctx->capture,
     469           0 :           &stake_delegation->stake_account,
     470           0 :           voter_acc,
     471           0 :           vote_state_ele->commission,
     472           0 :           (long)calculated_stake_rewards->voter_rewards,
     473           0 :           (long)calculated_stake_rewards->staker_rewards,
     474           0 :           (long)calculated_stake_rewards->new_credits_observed );
     475           0 :     }
     476             : 
     477             :     // Find and update the vote reward node in the local map
     478           0 :     fd_vote_reward_t_mapnode_t vote_map_key[1];
     479           0 :     vote_map_key->elem.pubkey = *voter_acc;
     480           0 :     fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_find( result->vote_reward_map_pool, result->vote_reward_map_root, vote_map_key );
     481           0 :     if( FD_UNLIKELY( vote_reward_node==NULL ) ) {
     482           0 :       FD_LOG_WARNING(( "vote account is missing from the vote rewards pool" ));
     483           0 :       continue;
     484           0 :     }
     485             : 
     486           0 :     vote_reward_node = fd_vote_reward_t_map_find( vote_reward_map_pool, vote_reward_map_root, vote_map_key );
     487             : 
     488           0 :     if( vote_reward_node==NULL ) {
     489           0 :       vote_reward_node                    = fd_vote_reward_t_map_acquire( vote_reward_map_pool );
     490           0 :       vote_reward_node->elem.pubkey       = *voter_acc;
     491           0 :       vote_reward_node->elem.commission   = vote_state_ele->commission;
     492           0 :       vote_reward_node->elem.vote_rewards = calculated_stake_rewards->voter_rewards;
     493           0 :       vote_reward_node->elem.needs_store  = 1;
     494           0 :       fd_vote_reward_t_map_insert( vote_reward_map_pool, &vote_reward_map_root, vote_reward_node );
     495           0 :     } else {
     496           0 :       vote_reward_node->elem.vote_rewards += calculated_stake_rewards->voter_rewards;
     497           0 :     }
     498             : 
     499             :     /* Add the stake reward to list of all stake rewards. The update is
     500             :       thread-safe because each index in the dlist is only ever accessed
     501             :       / written to once among all threads. */
     502             : 
     503             : 
     504           0 :     fd_stake_reward_t * stake_reward = fd_stake_reward_calculation_pool_ele_acquire( result->stake_reward_calculation.pool );
     505           0 :     if( FD_UNLIKELY( !stake_reward ) ) {
     506           0 :       FD_LOG_CRIT(( "insufficient space allocated for stake reward calculation pool" ));
     507           0 :     }
     508             : 
     509           0 :     fd_memcpy( stake_reward->stake_pubkey.uc, &stake_delegation->stake_account, sizeof(fd_pubkey_t) );
     510           0 :     stake_reward->lamports         = calculated_stake_rewards->staker_rewards;
     511           0 :     stake_reward->credits_observed = calculated_stake_rewards->new_credits_observed;
     512           0 :     stake_reward->valid            = 1;
     513             : 
     514             :     /* Update the total stake rewards */
     515           0 :     total_stake_rewards += calculated_stake_rewards->staker_rewards;
     516           0 :     dlist_additional_cnt++;
     517             : 
     518           0 :     fd_stake_reward_calculation_dlist_ele_push_tail( result->stake_reward_calculation.stake_rewards, stake_reward, result->stake_reward_calculation.pool );
     519           0 :   }
     520             : 
     521           0 :   if( !is_recalculation ) {
     522           0 :     fd_bank_vote_states_end_locking_query( bank );
     523           0 :   } else {
     524           0 :     fd_bank_vote_states_prev_end_locking_query( bank );
     525           0 :   }
     526             : 
     527             :   /* Merge vote rewards with result after */
     528           0 :   for( fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_minimum( vote_reward_map_pool, vote_reward_map_root );
     529           0 :         vote_reward_node;
     530           0 :         vote_reward_node = fd_vote_reward_t_map_successor( vote_reward_map_pool, vote_reward_node ) ) {
     531             : 
     532           0 :     fd_vote_reward_t_mapnode_t * result_reward_node = fd_vote_reward_t_map_find( result->vote_reward_map_pool, result->vote_reward_map_root, vote_reward_node );
     533           0 :     result_reward_node->elem.commission    = vote_reward_node->elem.commission;
     534           0 :     result_reward_node->elem.vote_rewards += vote_reward_node->elem.vote_rewards;
     535           0 :     result_reward_node->elem.needs_store   = 1;
     536           0 :   }
     537             : 
     538           0 :   result->stake_reward_calculation.total_stake_rewards_lamports += total_stake_rewards;
     539           0 :   result->stake_reward_calculation.stake_rewards_len            += dlist_additional_cnt;
     540             : 
     541           0 :   } FD_SPAD_FRAME_END;
     542             : 
     543             : 
     544           0 : }
     545             : 
     546             : /* Calculates epoch rewards for stake/vote accounts.
     547             :    Returns vote rewards, stake rewards, and the sum of all stake rewards
     548             :    in lamports.
     549             : 
     550             :    In future, the calculation will be cached in the snapshot, but
     551             :    for now we just re-calculate it (as Agave does).
     552             :    calculate_stake_vote_rewards is responsible for calculating
     553             :    stake account rewards based off of a combination of the
     554             :    stake delegation state as well as the vote account. If this
     555             :    calculation is done at the end of an epoch, we can just use the
     556             :    vote states at the end of the current epoch. However, because we
     557             :    are presumably booting up a node in the middle of rewards
     558             :    distribution, we need to make sure that we are using the vote
     559             :    states from the end of the previous epoch.
     560             : 
     561             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
     562             : static void
     563             : calculate_stake_vote_rewards( fd_bank_t *                                bank,
     564             :                               fd_stake_delegations_t const *             stake_delegations,
     565             :                               fd_capture_ctx_t *                         capture_ctx,
     566             :                               fd_stake_history_t const *                 stake_history,
     567             :                               ulong                                      rewarded_epoch,
     568             :                               fd_point_value_t *                         point_value,
     569             :                               fd_calculate_stake_vote_rewards_result_t * result,
     570             :                               fd_spad_t *                                runtime_spad,
     571           0 :                               int                                        is_recalculation ) {
     572             : 
     573           0 :   int _err[1];
     574           0 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     575           0 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     576           0 :   int is_some = fd_new_warmup_cooldown_rate_epoch(
     577           0 :       fd_bank_epoch_schedule_query( bank ),
     578           0 :       fd_bank_features_query( bank ),
     579           0 :       fd_bank_slot_get( bank ),
     580           0 :       new_warmup_cooldown_rate_epoch,
     581           0 :       _err );
     582           0 :   if( FD_UNLIKELY( !is_some ) ) {
     583           0 :     new_warmup_cooldown_rate_epoch = NULL;
     584           0 :   }
     585             : 
     586           0 :   ulong rewards_max_count = fd_stake_delegations_cnt( stake_delegations );
     587             : 
     588             :   /* Create the stake rewards pool and dlist. The pool will be destoyed after the stake rewards have been distributed. */
     589           0 :   result->stake_reward_calculation.pool = fd_stake_reward_calculation_pool_join( fd_stake_reward_calculation_pool_new( fd_spad_alloc( runtime_spad,
     590           0 :                                                                                                                                       fd_stake_reward_calculation_pool_align(),
     591           0 :                                                                                                                                       fd_stake_reward_calculation_pool_footprint( rewards_max_count ) ),
     592           0 :                                                                                                                                       rewards_max_count ) );
     593           0 :   result->stake_reward_calculation.stake_rewards = fd_spad_alloc( runtime_spad,
     594           0 :                                                                   fd_stake_reward_calculation_dlist_align(),
     595           0 :                                                                   fd_stake_reward_calculation_dlist_footprint() );
     596             : 
     597           0 :   fd_stake_reward_calculation_dlist_new( result->stake_reward_calculation.stake_rewards );
     598           0 :   result->stake_reward_calculation.stake_rewards_len = 0UL;
     599             : 
     600           0 :   fd_vote_states_t const * vote_states = NULL;
     601           0 :   if( !is_recalculation ) {
     602           0 :     vote_states = fd_bank_vote_states_locking_query( bank );
     603           0 :   } else {
     604           0 :     vote_states = fd_bank_vote_states_prev_locking_query( bank );
     605           0 :   }
     606           0 :   if( FD_UNLIKELY( !vote_states ) ) {
     607           0 :     FD_LOG_CRIT(( "vote_states is NULL" ));
     608           0 :   }
     609             : 
     610             :   /* Create the vote rewards map. This will be destroyed after the vote rewards have been distributed. */
     611           0 :   ulong vote_account_cnt       = fd_vote_states_cnt( vote_states );
     612           0 :   result->vote_reward_map_pool = fd_vote_reward_t_map_join( fd_vote_reward_t_map_new( fd_spad_alloc( runtime_spad,
     613           0 :                                                                                                      fd_vote_reward_t_map_align(),
     614           0 :                                                                                                      fd_vote_reward_t_map_footprint( vote_account_cnt )),
     615           0 :                                                                                       vote_account_cnt ) );
     616           0 :   result->vote_reward_map_root = NULL;
     617             : 
     618             :   /* Pre-fill the vote pubkeys in the vote rewards map pool */
     619           0 :   fd_vote_states_iter_t iter_[1];
     620           0 :   for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( iter_, vote_states ); !fd_vote_states_iter_done( iter ); fd_vote_states_iter_next( iter ) ) {
     621           0 :     fd_vote_state_ele_t const * vote_state = fd_vote_states_iter_ele( iter );
     622             : 
     623           0 :     fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_acquire( result->vote_reward_map_pool );
     624             : 
     625           0 :     vote_reward_node->elem.pubkey       = vote_state->vote_account;
     626           0 :     vote_reward_node->elem.vote_rewards = 0UL;
     627           0 :     vote_reward_node->elem.needs_store  = 0;
     628             : 
     629           0 :     fd_vote_reward_t_map_insert( result->vote_reward_map_pool, &result->vote_reward_map_root, vote_reward_node );
     630           0 :   }
     631             : 
     632           0 :   if( !is_recalculation ) {
     633           0 :     fd_bank_vote_states_end_locking_query( bank );
     634           0 :   } else {
     635           0 :     fd_bank_vote_states_prev_end_locking_query( bank );
     636           0 :   }
     637             : 
     638             :   /* Loop over all the delegations
     639             :      https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L367  */
     640           0 :   calculate_stake_vote_rewards_account(
     641           0 :       bank,
     642           0 :       stake_delegations,
     643           0 :       capture_ctx,
     644           0 :       stake_history,
     645           0 :       rewarded_epoch,
     646           0 :       new_warmup_cooldown_rate_epoch,
     647           0 :       point_value,
     648           0 :       result,
     649           0 :       runtime_spad,
     650           0 :       is_recalculation );
     651           0 : }
     652             : 
     653             : /* Calculate epoch reward and return vote and stake rewards.
     654             : 
     655             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
     656             : static void
     657             : calculate_validator_rewards( fd_bank_t *                               bank,
     658             :                              fd_funk_t *                               funk,
     659             :                              fd_funk_txn_xid_t const *                 xid,
     660             :                              fd_stake_delegations_t const *            stake_delegations,
     661             :                              fd_capture_ctx_t *                        capture_ctx,
     662             :                              ulong                                     rewarded_epoch,
     663             :                              ulong                                     rewards,
     664             :                              fd_calculate_validator_rewards_result_t * result,
     665           0 :                              fd_spad_t *                               runtime_spad ) {
     666             :     /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L2759-L2786 */
     667           0 :   fd_stake_history_t const * stake_history = fd_sysvar_stake_history_read( funk, xid, runtime_spad );
     668           0 :     if( FD_UNLIKELY( !stake_history ) ) {
     669           0 :     FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
     670           0 :   }
     671             : 
     672             :   /* Calculate the epoch reward points from stake/vote accounts */
     673           0 :   calculate_reward_points_partitioned(
     674           0 :       bank,
     675           0 :       stake_delegations,
     676           0 :       stake_history,
     677           0 :       rewards,
     678           0 :       &result->point_value );
     679             : 
     680           0 :   if( capture_ctx ) {
     681           0 :     ulong const epoch = fd_bank_epoch_get( bank );
     682           0 :     fd_solcap_writer_stake_rewards_begin( capture_ctx->capture,
     683           0 :         epoch,
     684           0 :         epoch-1, /* FIXME this is not strictly correct */
     685           0 :         result->point_value.rewards,
     686           0 :         result->point_value.points );
     687           0 :   }
     688             : 
     689             :   /* Calculate the stake and vote rewards for each account. We want to
     690             :      use the vote states from the end of the current_epoch. */
     691           0 :   calculate_stake_vote_rewards(
     692           0 :       bank,
     693           0 :       stake_delegations,
     694           0 :       capture_ctx,
     695           0 :       stake_history,
     696           0 :       rewarded_epoch,
     697           0 :       &result->point_value,
     698           0 :       &result->calculate_stake_vote_rewards_result,
     699           0 :       runtime_spad,
     700           0 :       0 );
     701           0 : }
     702             : 
     703             : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
     704             : 
     705             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
     706             :  */
     707             : static ulong
     708             : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     709             :                                     ulong                       slot,
     710           0 :                                     ulong                       total_stake_accounts ) {
     711             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
     712           0 :   if( epoch_schedule->warmup &&
     713           0 :       fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
     714           0 :     return 1UL;
     715           0 :   }
     716             : 
     717           0 :   ulong num_chunks = total_stake_accounts / (ulong)STAKE_ACCOUNT_STORES_PER_BLOCK + (total_stake_accounts % STAKE_ACCOUNT_STORES_PER_BLOCK != 0);
     718           0 :   num_chunks       = fd_ulong_max( num_chunks, 1UL );
     719           0 :   num_chunks       = fd_ulong_min( num_chunks,
     720           0 :                                    fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
     721           0 :   return num_chunks;
     722           0 : }
     723             : 
     724             : static void
     725             : hash_rewards_into_partitions( fd_bank_t *                     bank,
     726             :                               fd_stake_reward_calculation_t * stake_reward_calculation,
     727             :                               fd_hash_t const *               parent_blockhash,
     728           0 :                               ulong                           num_partitions ) {
     729             : 
     730           0 :   fd_epoch_rewards_t * epoch_rewards = fd_epoch_rewards_join( fd_epoch_rewards_new( fd_bank_epoch_rewards_locking_modify( bank ), FD_RUNTIME_MAX_STAKE_ACCOUNTS ) );
     731           0 :   if( FD_UNLIKELY( !epoch_rewards ) ) {
     732           0 :     FD_LOG_CRIT(( "failed to join epoch rewards" ));
     733           0 :   }
     734           0 :   fd_epoch_rewards_set_num_partitions( epoch_rewards, num_partitions );
     735             : 
     736             :   /* Iterate over all the stake rewards, moving references to them into the appropiate partitions.
     737             :       IMPORTANT: after this, we cannot use the original stake rewards dlist anymore. */
     738           0 :   fd_stake_reward_calculation_dlist_iter_t next_iter;
     739           0 :   for( fd_stake_reward_calculation_dlist_iter_t iter = fd_stake_reward_calculation_dlist_iter_fwd_init( stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     740           0 :         !fd_stake_reward_calculation_dlist_iter_done( iter, stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     741           0 :         iter = next_iter ) {
     742           0 :     fd_stake_reward_t * stake_reward = fd_stake_reward_calculation_dlist_iter_ele( iter, stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     743             :     /* Cache the next iter here, as we will overwrite the DLIST_NEXT value further down in the loop iteration. */
     744           0 :     next_iter = fd_stake_reward_calculation_dlist_iter_fwd_next( iter, stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     745             : 
     746           0 :     if( FD_UNLIKELY( !stake_reward->valid ) ) {
     747           0 :       continue;
     748           0 :     }
     749             : 
     750             :     /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/epoch_rewards_hasher.rs#L43C31-L61 */
     751           0 :     int err = fd_epoch_rewards_hash_and_insert(
     752           0 :         epoch_rewards,
     753           0 :         parent_blockhash,
     754           0 :         &stake_reward->stake_pubkey,
     755           0 :         stake_reward->credits_observed,
     756           0 :         stake_reward->lamports );
     757           0 :     if( FD_UNLIKELY( err ) ) {
     758           0 :       FD_LOG_CRIT(( "failed to hash and insert stake reward" ));
     759           0 :     }
     760           0 :   }
     761             : 
     762           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
     763           0 : }
     764             : 
     765             : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
     766             : 
     767             :    https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L214 */
     768             : static void
     769             : calculate_rewards_for_partitioning( fd_bank_t *                            bank,
     770             :                                     fd_funk_t *                            funk,
     771             :                                     fd_funk_txn_xid_t const *              xid,
     772             :                                     fd_stake_delegations_t const *         stake_delegations,
     773             :                                     fd_capture_ctx_t *                     capture_ctx,
     774             :                                     ulong                                  prev_epoch,
     775             :                                     const fd_hash_t *                      parent_blockhash,
     776             :                                     fd_partitioned_rewards_calculation_t * result,
     777           0 :                                     fd_spad_t *                            runtime_spad ) {
     778             :   /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L227 */
     779           0 :   fd_prev_epoch_inflation_rewards_t rewards;
     780             : 
     781           0 :   calculate_previous_epoch_inflation_rewards( bank,
     782           0 :                                               fd_bank_capitalization_get( bank ),
     783           0 :                                               prev_epoch,
     784           0 :                                               &rewards );
     785             : 
     786           0 :   fd_calculate_validator_rewards_result_t validator_result[1] = {0};
     787           0 :   calculate_validator_rewards( bank,
     788           0 :                                funk,
     789           0 :                                xid,
     790           0 :                                stake_delegations,
     791           0 :                                capture_ctx,
     792           0 :                                prev_epoch,
     793           0 :                                rewards.validator_rewards,
     794           0 :                                validator_result,
     795           0 :                                runtime_spad );
     796             : 
     797           0 :   fd_stake_reward_calculation_t * stake_reward_calculation = &validator_result->calculate_stake_vote_rewards_result.stake_reward_calculation;
     798           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
     799             : 
     800           0 :   ulong num_partitions = get_reward_distribution_num_blocks(
     801           0 :       epoch_schedule,
     802           0 :       fd_bank_slot_get( bank ),
     803           0 :       stake_reward_calculation->stake_rewards_len );
     804           0 :   hash_rewards_into_partitions(
     805           0 :       bank,
     806           0 :       stake_reward_calculation,
     807           0 :       parent_blockhash,
     808           0 :       num_partitions );
     809             : 
     810           0 :   result->stake_rewards_by_partition.total_stake_rewards_lamports =
     811           0 :     validator_result->calculate_stake_vote_rewards_result.stake_reward_calculation.total_stake_rewards_lamports;
     812             : 
     813           0 :   result->vote_reward_map_pool         = validator_result->calculate_stake_vote_rewards_result.vote_reward_map_pool;
     814           0 :   result->vote_reward_map_root         = validator_result->calculate_stake_vote_rewards_result.vote_reward_map_root;
     815           0 :   result->validator_rewards            = rewards.validator_rewards;
     816           0 :   result->validator_rate               = rewards.validator_rate;
     817           0 :   result->foundation_rate              = rewards.foundation_rate;
     818           0 :   result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
     819           0 :   result->capitalization               = fd_bank_capitalization_get( bank );
     820           0 :   result->point_value                  = validator_result->point_value;
     821           0 : }
     822             : 
     823             : /* Calculate rewards from previous epoch and distribute vote rewards
     824             : 
     825             :    https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L97 */
     826             : static void
     827             : calculate_rewards_and_distribute_vote_rewards( fd_bank_t *                    bank,
     828             :                                                fd_funk_t *                    funk,
     829             :                                                fd_funk_txn_xid_t const *      xid,
     830             :                                                fd_stake_delegations_t const * stake_delegations,
     831             :                                                fd_capture_ctx_t *             capture_ctx,
     832             :                                                ulong                          prev_epoch,
     833             :                                                fd_hash_t const *              parent_blockhash,
     834           0 :                                                fd_spad_t *                    runtime_spad ) {
     835             : 
     836             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L2406-L2492 */
     837           0 :   fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
     838           0 :   calculate_rewards_for_partitioning( bank,
     839           0 :                                       funk,
     840           0 :                                       xid,
     841           0 :                                       stake_delegations,
     842           0 :                                       capture_ctx,
     843           0 :                                       prev_epoch,
     844           0 :                                       parent_blockhash,
     845           0 :                                       rewards_calc_result,
     846           0 :                                       runtime_spad );
     847             : 
     848             :   /* Iterate over all the vote reward nodes */
     849           0 :   ulong distributed_rewards = 0UL;
     850           0 :   for( fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_minimum( rewards_calc_result->vote_reward_map_pool, rewards_calc_result->vote_reward_map_root);
     851           0 :        vote_reward_node;
     852           0 :        vote_reward_node = fd_vote_reward_t_map_successor( rewards_calc_result->vote_reward_map_pool, vote_reward_node ) ) {
     853             : 
     854           0 :     if( FD_UNLIKELY( !vote_reward_node->elem.needs_store ) ) {
     855           0 :       continue;
     856           0 :     }
     857             : 
     858           0 :     fd_pubkey_t const * vote_pubkey = &vote_reward_node->elem.pubkey;
     859           0 :     FD_TXN_ACCOUNT_DECL( vote_rec );
     860           0 :     fd_funk_rec_prepare_t prepare = {0};
     861             : 
     862           0 :     if( FD_UNLIKELY( fd_txn_account_init_from_funk_mutable( vote_rec,
     863           0 :                                                             vote_pubkey,
     864           0 :                                                             funk,
     865           0 :                                                             xid,
     866           0 :                                                             1,
     867           0 :                                                             0UL,
     868           0 :                                                             &prepare )!=FD_ACC_MGR_SUCCESS ) ) {
     869           0 :       FD_LOG_ERR(( "Unable to modify vote account" ));
     870           0 :     }
     871             : 
     872           0 :     fd_lthash_value_t prev_hash[1];
     873           0 :     fd_hashes_account_lthash(
     874           0 :       vote_pubkey,
     875           0 :       fd_txn_account_get_meta( vote_rec ),
     876           0 :       fd_txn_account_get_data( vote_rec ),
     877           0 :       prev_hash );
     878             : 
     879           0 :     fd_txn_account_set_slot( vote_rec, fd_bank_slot_get( bank ) );
     880             : 
     881           0 :     if( FD_UNLIKELY( fd_txn_account_checked_add_lamports( vote_rec, vote_reward_node->elem.vote_rewards ) ) ) {
     882           0 :       FD_LOG_ERR(( "Adding lamports to vote account would cause overflow" ));
     883           0 :     }
     884             : 
     885           0 :     fd_hashes_update_lthash( vote_rec, prev_hash,bank, capture_ctx );
     886           0 :     fd_txn_account_mutable_fini( vote_rec, funk, &prepare );
     887             : 
     888           0 :     distributed_rewards = fd_ulong_sat_add( distributed_rewards, vote_reward_node->elem.vote_rewards );
     889             : 
     890           0 :     if( capture_ctx ) {
     891           0 :       fd_solcap_write_vote_account_payout( capture_ctx->capture,
     892           0 :           vote_pubkey,
     893           0 :           fd_bank_slot_get( bank ),
     894           0 :           fd_txn_account_get_lamports( vote_rec ),
     895           0 :           (long)vote_reward_node->elem.vote_rewards );
     896           0 :     }
     897           0 :   }
     898             : 
     899             :   /* There is no need to free the vote reward map since it was spad*/
     900             : 
     901             :   /* Verify that we didn't pay any more than we expected to */
     902           0 :   ulong total_rewards = fd_ulong_sat_add( distributed_rewards, rewards_calc_result->stake_rewards_by_partition.total_stake_rewards_lamports );
     903           0 :   if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
     904           0 :     FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
     905           0 :   }
     906             : 
     907           0 :   fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + distributed_rewards );
     908             : 
     909           0 :   fd_epoch_rewards_t * epoch_rewards = fd_bank_epoch_rewards_locking_modify( bank );
     910           0 :   fd_epoch_rewards_set_distributed_rewards( epoch_rewards, distributed_rewards );
     911           0 :   fd_epoch_rewards_set_total_rewards( epoch_rewards, rewards_calc_result->point_value.rewards );
     912           0 :   fd_epoch_rewards_set_total_points( epoch_rewards, rewards_calc_result->point_value.points );
     913           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
     914           0 : }
     915             : 
     916             : /* Distributes a single partitioned reward to a single stake account */
     917             : static int
     918             : distribute_epoch_reward_to_stake_acc( fd_bank_t *               bank,
     919             :                                       fd_funk_t *               funk,
     920             :                                       fd_funk_txn_xid_t const * xid,
     921             :                                       fd_capture_ctx_t *        capture_ctx,
     922             :                                       fd_pubkey_t *             stake_pubkey,
     923             :                                       ulong                     reward_lamports,
     924           0 :                                       ulong                     new_credits_observed ) {
     925           0 :   FD_TXN_ACCOUNT_DECL( stake_acc_rec );
     926           0 :   fd_funk_rec_prepare_t prepare = {0};
     927           0 :   if( FD_UNLIKELY( fd_txn_account_init_from_funk_mutable( stake_acc_rec,
     928           0 :                                                           stake_pubkey,
     929           0 :                                                           funk,
     930           0 :                                                           xid,
     931           0 :                                                           0,
     932           0 :                                                           0UL,
     933           0 :                                                           &prepare )!=FD_ACC_MGR_SUCCESS ) ) {
     934           0 :     FD_LOG_ERR(( "Unable to modify stake account" ));
     935           0 :   }
     936             : 
     937           0 :   fd_lthash_value_t prev_hash[1];
     938           0 :   fd_hashes_account_lthash(
     939           0 :     stake_pubkey,
     940           0 :     fd_txn_account_get_meta( stake_acc_rec ),
     941           0 :     fd_txn_account_get_data( stake_acc_rec ),
     942           0 :     prev_hash );
     943             : 
     944           0 :   fd_txn_account_set_slot( stake_acc_rec, fd_bank_slot_get( bank ) );
     945             : 
     946           0 :   fd_stake_state_v2_t stake_state[1] = {0};
     947           0 :   if( fd_stake_get_state( stake_acc_rec, stake_state ) != 0 ) {
     948           0 :     FD_LOG_DEBUG(( "failed to read stake state for %s", FD_BASE58_ENC_32_ALLOCA( stake_pubkey ) ));
     949           0 :     return 1;
     950           0 :   }
     951             : 
     952           0 :   if ( !fd_stake_state_v2_is_stake( stake_state ) ) {
     953           0 :     FD_LOG_DEBUG(( "non-stake stake account, this should never happen" ));
     954           0 :     return 1;
     955           0 :   }
     956             : 
     957           0 :   if( fd_txn_account_checked_add_lamports( stake_acc_rec, reward_lamports ) ) {
     958           0 :     FD_LOG_DEBUG(( "failed to add lamports to stake account" ));
     959           0 :     return 1;
     960           0 :   }
     961             : 
     962           0 :   ulong old_credits_observed = stake_state->inner.stake.stake.credits_observed;
     963           0 :   stake_state->inner.stake.stake.credits_observed = new_credits_observed;
     964           0 :   stake_state->inner.stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->inner.stake.stake.delegation.stake,
     965           0 :                                                                       reward_lamports );
     966             : 
     967             :   /* The stake account has just been updated, so we need to update the
     968             :      stake delegations stored in the bank. */
     969           0 :   fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_delta_locking_modify( bank );
     970           0 :   fd_stake_delegations_update(
     971           0 :       stake_delegations,
     972           0 :       stake_pubkey,
     973           0 :       &stake_state->inner.stake.stake.delegation.voter_pubkey,
     974           0 :       stake_state->inner.stake.stake.delegation.stake,
     975           0 :       stake_state->inner.stake.stake.delegation.activation_epoch,
     976           0 :       stake_state->inner.stake.stake.delegation.deactivation_epoch,
     977           0 :       stake_state->inner.stake.stake.credits_observed,
     978           0 :       stake_state->inner.stake.stake.delegation.warmup_cooldown_rate );
     979           0 :   fd_bank_stake_delegations_delta_end_locking_modify( bank );
     980             : 
     981           0 :   if( capture_ctx ) {
     982           0 :     fd_solcap_write_stake_account_payout( capture_ctx->capture,
     983           0 :         stake_pubkey,
     984           0 :         fd_bank_slot_get( bank ),
     985           0 :         fd_txn_account_get_lamports( stake_acc_rec ),
     986           0 :         (long)reward_lamports,
     987           0 :         new_credits_observed,
     988           0 :         (long)( new_credits_observed-old_credits_observed ),
     989           0 :         stake_state->inner.stake.stake.delegation.stake,
     990           0 :         (long)reward_lamports );
     991           0 :   }
     992             : 
     993           0 :   if( FD_UNLIKELY( write_stake_state( stake_acc_rec, stake_state ) != 0 ) ) {
     994           0 :     FD_LOG_ERR(( "write_stake_state failed" ));
     995           0 :   }
     996             : 
     997           0 :   fd_hashes_update_lthash( stake_acc_rec, prev_hash, bank, capture_ctx );
     998           0 :   fd_txn_account_mutable_fini( stake_acc_rec, funk, &prepare );
     999             : 
    1000           0 :   return 0;
    1001           0 : }
    1002             : 
    1003             : /* Sets the epoch reward status to inactive, and destroys any allocated state associated with the active state. */
    1004             : static void
    1005           0 : set_epoch_reward_status_inactive( fd_bank_t * bank ) {
    1006           0 :   fd_epoch_rewards_t * epoch_rewards = fd_bank_epoch_rewards_locking_modify( bank );
    1007           0 :   if( fd_epoch_rewards_is_active( epoch_rewards ) ) {
    1008           0 :     FD_LOG_NOTICE(( "Done partitioning rewards for current epoch" ));
    1009           0 :   }
    1010           0 :   fd_epoch_rewards_set_active( epoch_rewards, 0 );
    1011           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
    1012           0 : }
    1013             : 
    1014             : /* Sets the epoch reward status to active.
    1015             : 
    1016             :     Takes ownership of the given stake_rewards_by_partition data structure,
    1017             :     which will be destroyed when set_epoch_reward_status_inactive is called. */
    1018             : static void
    1019             : set_epoch_reward_status_active( fd_bank_t * bank,
    1020           0 :                                 ulong       distribution_starting_block_height ) {
    1021           0 :   FD_LOG_NOTICE(( "Setting epoch reward status as active" ));
    1022             : 
    1023           0 :   fd_epoch_rewards_t * epoch_rewards = fd_bank_epoch_rewards_locking_modify( bank );
    1024             : 
    1025           0 :   fd_epoch_rewards_set_active( epoch_rewards, 1 );
    1026           0 :   fd_epoch_rewards_set_starting_block_height( epoch_rewards, distribution_starting_block_height );
    1027           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
    1028           0 : }
    1029             : 
    1030             : /*  Process reward credits for a partition of rewards.
    1031             :     Store the rewards to AccountsDB, update reward history record and total capitalization
    1032             : 
    1033             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
    1034             : static void
    1035             : distribute_epoch_rewards_in_partition( fd_epoch_stake_reward_dlist_t * stake_reward_dlist,
    1036             :                                        fd_epoch_stake_reward_t *       stake_reward_pool,
    1037             :                                        fd_bank_t *                     bank,
    1038             :                                         fd_funk_t *                    funk,
    1039             :                                         fd_funk_txn_xid_t const *      xid,
    1040           0 :                                        fd_capture_ctx_t *              capture_ctx ) {
    1041             : 
    1042           0 :   ulong lamports_distributed = 0UL;
    1043           0 :   ulong lamports_burned      = 0UL;
    1044             : 
    1045           0 :   for( fd_epoch_stake_reward_dlist_iter_t iter = fd_epoch_stake_reward_dlist_iter_fwd_init( stake_reward_dlist, stake_reward_pool );
    1046           0 :         !fd_epoch_stake_reward_dlist_iter_done( iter, stake_reward_dlist, stake_reward_pool );
    1047           0 :         iter = fd_epoch_stake_reward_dlist_iter_fwd_next( iter, stake_reward_dlist, stake_reward_pool ) ) {
    1048           0 :     fd_epoch_stake_reward_t * stake_reward = fd_epoch_stake_reward_dlist_iter_ele( iter, stake_reward_dlist, stake_reward_pool );
    1049           0 :     if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc(
    1050           0 :         bank,
    1051           0 :         funk,
    1052           0 :         xid,
    1053           0 :         capture_ctx,
    1054           0 :         &stake_reward->stake_pubkey,
    1055           0 :         stake_reward->lamports,
    1056           0 :         stake_reward->credits_observed ) )  ) {
    1057           0 :       lamports_distributed += stake_reward->lamports;
    1058           0 :     } else {
    1059           0 :       lamports_burned += stake_reward->lamports;
    1060           0 :     }
    1061           0 :   }
    1062             : 
    1063             :   /* Update the epoch rewards sysvar with the amount distributed and burnt */
    1064           0 :   fd_sysvar_epoch_rewards_distribute( bank, funk, xid, capture_ctx, lamports_distributed + lamports_burned );
    1065             : 
    1066           0 :   FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
    1067             : 
    1068           0 :   fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + lamports_distributed );
    1069           0 : }
    1070             : 
    1071             : /* Process reward distribution for the block if it is inside reward interval.
    1072             : 
    1073             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L42 */
    1074             : void
    1075             : fd_distribute_partitioned_epoch_rewards( fd_bank_t *               bank,
    1076             :                                          fd_funk_t *               funk,
    1077             :                                          fd_funk_txn_xid_t const * xid,
    1078           0 :                                          fd_capture_ctx_t *        capture_ctx ) {
    1079             : 
    1080           0 :   fd_epoch_rewards_t const * epoch_rewards = fd_bank_epoch_rewards_locking_query( bank );
    1081             : 
    1082           0 :   if( !fd_epoch_rewards_is_active( epoch_rewards ) ) {
    1083           0 :     fd_bank_epoch_rewards_end_locking_query( bank );
    1084           0 :     return;
    1085           0 :   }
    1086             : 
    1087           0 :   ulong height                             = fd_bank_block_height_get( bank );
    1088           0 :   ulong distribution_starting_block_height = fd_epoch_rewards_get_starting_block_height( epoch_rewards );
    1089           0 :   ulong distribution_end_exclusive         = fd_epoch_rewards_get_exclusive_ending_block_height( epoch_rewards );
    1090             : 
    1091           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
    1092           0 :   ulong                       epoch          = fd_bank_epoch_get( bank );
    1093             : 
    1094           0 :   if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_schedule ) <= fd_epoch_rewards_get_num_partitions( epoch_rewards ) ) ) {
    1095           0 :     FD_LOG_CRIT(( "Should not be distributing rewards" ));
    1096           0 :   }
    1097             : 
    1098           0 :   if( (height>=distribution_starting_block_height) && (height < distribution_end_exclusive) ) {
    1099             : 
    1100           0 :     fd_epoch_stake_reward_t * stake_reward_pool = fd_epoch_rewards_get_stake_reward_pool( epoch_rewards );
    1101           0 :     if( FD_UNLIKELY( !stake_reward_pool ) ) {
    1102           0 :       FD_LOG_CRIT(( "failed to get stake reward pool" ));
    1103           0 :     }
    1104             : 
    1105           0 :     ulong                           partition_index    = height - distribution_starting_block_height;
    1106           0 :     fd_epoch_stake_reward_dlist_t * stake_reward_dlist = fd_epoch_rewards_get_partition_index( epoch_rewards, partition_index );
    1107           0 :     if( FD_UNLIKELY( !stake_reward_dlist ) ) {
    1108           0 :       FD_LOG_CRIT(( "failed to get partition dlist" ));
    1109           0 :     }
    1110             : 
    1111           0 :     distribute_epoch_rewards_in_partition( stake_reward_dlist,
    1112           0 :                                            stake_reward_pool,
    1113           0 :                                            bank,
    1114           0 :                                            funk,
    1115           0 :                                            xid,
    1116           0 :                                            capture_ctx );
    1117           0 :   }
    1118             : 
    1119           0 :   fd_bank_epoch_rewards_end_locking_query( bank );
    1120             : 
    1121             :   /* If we have finished distributing rewards, set the status to inactive */
    1122           0 :   if( fd_ulong_sat_add( height, 1UL ) >= distribution_end_exclusive ) {
    1123           0 :     set_epoch_reward_status_inactive( bank );
    1124           0 :     fd_sysvar_epoch_rewards_set_inactive( bank, funk, xid, capture_ctx );
    1125           0 :   }
    1126           0 : }
    1127             : 
    1128             : /* Partitioned epoch rewards entry-point.
    1129             : 
    1130             :    https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L41
    1131             : */
    1132             : void
    1133             : fd_begin_partitioned_rewards( fd_bank_t *                    bank,
    1134             :                               fd_funk_t *                    funk,
    1135             :                               fd_funk_txn_xid_t const *      xid,
    1136             :                               fd_capture_ctx_t *             capture_ctx,
    1137             :                               fd_stake_delegations_t const * stake_delegations,
    1138             :                               fd_hash_t const *              parent_blockhash,
    1139             :                               ulong                          parent_epoch,
    1140           0 :                               fd_spad_t *                    runtime_spad ) {
    1141             : 
    1142             :   /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L55 */
    1143           0 :   calculate_rewards_and_distribute_vote_rewards(
    1144           0 :       bank,
    1145           0 :       funk,
    1146           0 :       xid,
    1147           0 :       stake_delegations,
    1148           0 :       capture_ctx,
    1149           0 :       parent_epoch,
    1150           0 :       parent_blockhash,
    1151           0 :       runtime_spad );
    1152             : 
    1153             :   /* https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L62 */
    1154           0 :   ulong distribution_starting_block_height = fd_bank_block_height_get( bank ) + REWARD_CALCULATION_NUM_BLOCKS;
    1155             : 
    1156             :   /* Set the epoch reward status to be active */
    1157           0 :   set_epoch_reward_status_active( bank, distribution_starting_block_height );
    1158             : 
    1159             :   /* Initialize the epoch rewards sysvar
    1160             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L78 */
    1161           0 :   fd_epoch_rewards_t const * epoch_rewards = fd_bank_epoch_rewards_locking_query( bank );
    1162           0 :   fd_sysvar_epoch_rewards_init(
    1163           0 :       bank,
    1164           0 :       funk,
    1165           0 :       xid,
    1166           0 :       capture_ctx,
    1167           0 :       fd_epoch_rewards_get_distributed_rewards( epoch_rewards ),
    1168           0 :       distribution_starting_block_height,
    1169           0 :       fd_epoch_rewards_get_num_partitions( epoch_rewards ),
    1170           0 :       fd_epoch_rewards_get_total_rewards( epoch_rewards ),
    1171           0 :       fd_epoch_rewards_get_total_points( epoch_rewards ),
    1172           0 :       parent_blockhash );
    1173           0 :   fd_bank_epoch_rewards_end_locking_query( bank );
    1174           0 : }
    1175             : 
    1176             : /*
    1177             :     Re-calculates partitioned stake rewards.
    1178             :     This updates the slot context's epoch reward status with the recalculated partitioned rewards.
    1179             : 
    1180             :     https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521 */
    1181             : void
    1182             : fd_rewards_recalculate_partitioned_rewards( fd_banks_t *              banks,
    1183             :                                             fd_bank_t *               bank,
    1184             :                                             fd_funk_t *               funk,
    1185             :                                             fd_funk_txn_xid_t const * xid,
    1186             :                                             fd_capture_ctx_t *        capture_ctx,
    1187           0 :                                             fd_spad_t *               runtime_spad ) {
    1188           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    1189             : 
    1190           0 :   fd_sysvar_epoch_rewards_t epoch_rewards[1];
    1191           0 :   if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read( funk, xid, epoch_rewards ) ) ) {
    1192           0 :     FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
    1193           0 :     set_epoch_reward_status_inactive( bank );
    1194           0 :     return;
    1195           0 :   }
    1196             : 
    1197           0 :   FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
    1198             : 
    1199           0 :   if( FD_UNLIKELY( epoch_rewards->active ) ) {
    1200             : 
    1201             :     /* If partitioned rewards are active, the rewarded epoch is always the immediately
    1202             :         preceeding epoch.
    1203             : 
    1204             :         https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
    1205           0 :     FD_LOG_DEBUG(( "epoch rewards is active" ));
    1206             : 
    1207           0 :     ulong const slot           = fd_bank_slot_get( bank );
    1208           0 :     ulong const epoch          = fd_bank_epoch_get( bank );
    1209           0 :     ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
    1210             : 
    1211           0 :     int _err[1] = {0};
    1212           0 :     ulong new_warmup_cooldown_rate_epoch_;
    1213           0 :     ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_;
    1214           0 :     int is_some = fd_new_warmup_cooldown_rate_epoch(
    1215           0 :         fd_bank_epoch_schedule_query( bank ),
    1216           0 :         fd_bank_features_query( bank ),
    1217           0 :         slot,
    1218           0 :         new_warmup_cooldown_rate_epoch,
    1219           0 :         _err );
    1220           0 :     if( FD_UNLIKELY( !is_some ) ) {
    1221           0 :       new_warmup_cooldown_rate_epoch = NULL;
    1222           0 :     }
    1223             : 
    1224           0 :     fd_stake_history_t const * stake_history = fd_sysvar_stake_history_read( funk, xid, runtime_spad );
    1225           0 :     if( FD_UNLIKELY( !stake_history ) ) {
    1226           0 :       FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
    1227           0 :     }
    1228             : 
    1229           0 :     fd_point_value_t point_value = { .points  = epoch_rewards->total_points,
    1230           0 :                                      .rewards = epoch_rewards->total_rewards };
    1231             : 
    1232           0 :     fd_stake_history_entry_t _accumulator = {
    1233           0 :         .effective   = 0UL,
    1234           0 :         .activating  = 0UL,
    1235           0 :         .deactivating = 0UL
    1236           0 :     };
    1237             : 
    1238           0 :     fd_stake_delegations_t const * stake_delegations = fd_bank_stake_delegations_frontier_query( banks, bank );
    1239           0 :     if( FD_UNLIKELY( !stake_delegations ) ) {
    1240           0 :       FD_LOG_CRIT(( "stake_delegations is NULL" ));
    1241           0 :     }
    1242             : 
    1243           0 :     fd_accumulate_stake_infos(
    1244           0 :         epoch,
    1245           0 :         stake_delegations,
    1246           0 :         stake_history,
    1247           0 :         new_warmup_cooldown_rate_epoch,
    1248           0 :         &_accumulator );
    1249             : 
    1250             :     /* Make sure is_recalculation is ==1 since we are booting up in the
    1251             :        middle of rewards distribution (so we should use the epoch
    1252             :        stakes for the end of epoch E-1 since we are still distributing
    1253             :        rewards for the previous epoch). */
    1254           0 :     fd_calculate_stake_vote_rewards_result_t calculate_stake_vote_rewards_result[1];
    1255           0 :     calculate_stake_vote_rewards(
    1256           0 :         bank,
    1257           0 :         stake_delegations,
    1258           0 :         capture_ctx,
    1259           0 :         stake_history,
    1260           0 :         rewarded_epoch,
    1261           0 :         &point_value,
    1262           0 :         calculate_stake_vote_rewards_result,
    1263           0 :         runtime_spad,
    1264           0 :         1 /* is_recalculation */ );
    1265             : 
    1266             :     /* The vote reward map isn't actually used in this code path and
    1267             :        will only be freed after rewards have been distributed. */
    1268             : 
    1269             : 
    1270             :     /* Use the epoch rewards sysvar parent_blockhash and num_partitions.
    1271             :        https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L579 */
    1272           0 :     hash_rewards_into_partitions(
    1273           0 :         bank,
    1274           0 :         &calculate_stake_vote_rewards_result->stake_reward_calculation,
    1275           0 :         &epoch_rewards->parent_blockhash,
    1276           0 :         epoch_rewards->num_partitions );
    1277             : 
    1278             :     /* Update the epoch reward status with the newly re-calculated partitions. */
    1279           0 :     set_epoch_reward_status_active( bank, epoch_rewards->distribution_starting_block_height );
    1280           0 :   } else {
    1281           0 :     set_epoch_reward_status_inactive( bank );
    1282           0 :   }
    1283             : 
    1284           0 :   } FD_SPAD_FRAME_END;
    1285           0 : }

Generated by: LCOV version 1.14