LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 750 0.0 %
Date: 2025-03-20 12:08:36 Functions: 0 31 0.0 %

          Line data    Source code
       1             : #include "fd_rewards.h"
       2             : #include <math.h>
       3             : 
       4             : #include "../runtime/fd_executor_err.h"
       5             : #include "../runtime/fd_system_ids.h"
       6             : #include "../runtime/fd_runtime.h"
       7             : #include "../runtime/context/fd_exec_slot_ctx.h"
       8             : #include "../../ballet/siphash13/fd_siphash13.h"
       9             : #include "../runtime/program/fd_program_util.h"
      10             : 
      11             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/native_token.rs#L6 */
      12           0 : #define LAMPORTS_PER_SOL                     (1000000000UL)
      13             : 
      14             : /* Number of blocks for reward calculation and storing vote accounts.
      15             :    Distributing rewards to stake accounts begins AFTER this many blocks.
      16             : 
      17             :    https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L27 */
      18           0 : #define REWARD_CALCULATION_NUM_BLOCKS        (1UL)
      19             : 
      20             : /* stake accounts to store in one block during partitioned reward interval. Target to store 64 rewards per entry/tick in a block. A block has a minimum of 64 entries/tick. This gives 4096 total rewards to store in one block. */
      21           0 : #define STAKE_ACCOUNT_STORES_PER_BLOCK       (4096UL)
      22             : 
      23             : /* https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L219 */
      24           0 : #define MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH (10UL)
      25             : 
      26             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
      27             : static double
      28           0 : total( fd_inflation_t const * inflation, double year ) {
      29           0 :   if ( FD_UNLIKELY( year == 0.0 ) ) {
      30           0 :     FD_LOG_ERR(( "inflation year 0" ));
      31           0 :   }
      32           0 :   double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
      33           0 :   return (tapered > inflation->terminal) ? tapered : inflation->terminal;
      34           0 : }
      35             : 
      36             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
      37             : static double
      38           0 : foundation( fd_inflation_t const * inflation, double year ) {
      39           0 :   return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
      40           0 : }
      41             : 
      42             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
      43             : static double
      44           0 : validator( fd_inflation_t const * inflation, double year) {
      45             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
      46           0 :   FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
      47           0 :   return total( inflation, year ) - foundation( inflation, year );
      48           0 : }
      49             : 
      50             : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
      51             :     activation slot of the following features:
      52             :     - devnet_and_testnet
      53             :     - full_inflation_enable, if full_inflation_vote has been activated
      54             : 
      55             :     https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
      56             : static FD_FN_CONST ulong
      57           0 : get_inflation_start_slot( fd_exec_slot_ctx_t * slot_ctx ) {
      58           0 :     ulong devnet_and_testnet = FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, devnet_and_testnet ) ? slot_ctx->epoch_ctx->features.devnet_and_testnet : ULONG_MAX;
      59             : 
      60           0 :     ulong enable = ULONG_MAX;
      61           0 :     if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, full_inflation_vote ) &&
      62           0 :         FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, full_inflation_enable ) ) {
      63           0 :       enable = slot_ctx->epoch_ctx->features.full_inflation_enable;
      64           0 :     }
      65             : 
      66           0 :     ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
      67           0 :     if( min_slot == ULONG_MAX ) {
      68           0 :       if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, pico_inflation ) ) {
      69           0 :         min_slot = slot_ctx->epoch_ctx->features.pico_inflation;
      70           0 :       } else {
      71           0 :         min_slot = 0;
      72           0 :       }
      73           0 :     }
      74           0 :     return min_slot;
      75           0 : }
      76             : 
      77             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2110 */
      78             : static ulong
      79             : get_inflation_num_slots( fd_exec_slot_ctx_t * slot_ctx,
      80             :                          fd_epoch_schedule_t const * epoch_schedule,
      81           0 :                          ulong slot ) {
      82           0 :   ulong inflation_activation_slot = get_inflation_start_slot( slot_ctx );
      83           0 :   ulong inflation_start_slot      = fd_epoch_slot0( epoch_schedule,
      84           0 :                                                     fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule,
      85           0 :                                                                                         inflation_activation_slot, NULL ),
      86           0 :                                                                       1UL ) );
      87             : 
      88           0 :   ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, NULL );
      89             : 
      90           0 :   return fd_epoch_slot0( epoch_schedule, epoch ) - inflation_start_slot;
      91           0 : }
      92             : 
      93             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2121 */
      94             : static double
      95           0 : slot_in_year_for_inflation( fd_exec_slot_ctx_t * slot_ctx ) {
      96           0 :   fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
      97           0 :   ulong num_slots = get_inflation_num_slots( slot_ctx, &epoch_bank->epoch_schedule, slot_ctx->slot_bank.slot );
      98           0 :   return (double)num_slots / (double)epoch_bank->slots_per_year;
      99           0 : }
     100             : 
     101             : /* For a given stake and vote_state, calculate how many points were earned (credits * stake) and new value
     102             :    for credits_observed were the points paid
     103             : 
     104             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
     105             : static void
     106             : calculate_stake_points_and_credits( fd_stake_history_t const *     stake_history,
     107             :                                     fd_stake_t const *             stake,
     108             :                                     fd_vote_state_versioned_t *    vote_state_versioned,
     109             :                                     ulong *                        new_rate_activation_epoch,
     110           0 :                                     fd_calculated_stake_points_t * result ) {
     111             : 
     112           0 :   ulong credits_in_stake = stake->credits_observed;
     113             : 
     114           0 :   fd_vote_epoch_credits_t * epoch_credits;
     115           0 :   switch( vote_state_versioned->discriminant ) {
     116           0 :     case fd_vote_state_versioned_enum_current:
     117           0 :       epoch_credits = vote_state_versioned->inner.current.epoch_credits;
     118           0 :       break;
     119           0 :     case fd_vote_state_versioned_enum_v0_23_5:
     120           0 :       epoch_credits = vote_state_versioned->inner.v0_23_5.epoch_credits;
     121           0 :       break;
     122           0 :     case fd_vote_state_versioned_enum_v1_14_11:
     123           0 :       epoch_credits = vote_state_versioned->inner.v1_14_11.epoch_credits;
     124           0 :       break;
     125           0 :     default:
     126           0 :       FD_LOG_ERR(( "invalid vote account, should never happen" ));
     127           0 :   }
     128             : 
     129           0 :   ulong credits_in_vote = 0UL;
     130           0 :   if( FD_LIKELY( !deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) {
     131           0 :     credits_in_vote = deq_fd_vote_epoch_credits_t_peek_tail_const( epoch_credits )->credits;
     132           0 :   }
     133             : 
     134             :   /* If the Vote account has less credits observed than the Stake account,
     135             :       something is wrong and we need to force an update.
     136             : 
     137             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
     138           0 :   if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
     139           0 :     result->points = 0;
     140           0 :     result->new_credits_observed = credits_in_vote;
     141           0 :     result->force_credits_update_with_skipped_reward = 1;
     142           0 :     return;
     143           0 :   }
     144             : 
     145             :   /* If the Vote account has the same amount of credits observed as the Stake account,
     146             :       then the Vote account hasn't earnt any credits and so there is nothing to update.
     147             : 
     148             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
     149           0 :   if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
     150           0 :     result->points = 0;
     151           0 :     result->new_credits_observed = credits_in_vote;
     152           0 :     result->force_credits_update_with_skipped_reward = 0;
     153           0 :     return;
     154           0 :   }
     155             : 
     156             :   /* Calculate the points for each epoch credit */
     157           0 :   uint128 points = 0;
     158           0 :   ulong new_credits_observed = credits_in_stake;
     159           0 :   for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits );
     160           0 :         !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter );
     161           0 :         iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) {
     162             : 
     163           0 :     fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter );
     164           0 :     ulong final_epoch_credits = ele->credits;
     165           0 :     ulong initial_epoch_credits = ele->prev_credits;
     166           0 :     uint128 earned_credits = 0;
     167           0 :     if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
     168           0 :       earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
     169           0 :     } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
     170           0 :       earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
     171           0 :     }
     172             : 
     173           0 :     new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
     174             : 
     175           0 :     ulong stake_amount = fd_stake_activating_and_deactivating( &stake->delegation, ele->epoch, stake_history, new_rate_activation_epoch ).effective;
     176             : 
     177           0 :     points += (uint128)stake_amount * earned_credits;
     178           0 :   }
     179             : 
     180           0 :   result->points = points;
     181           0 :   result->new_credits_observed = new_credits_observed;
     182           0 :   result->force_credits_update_with_skipped_reward = 0;
     183           0 : }
     184             : 
     185             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L127 */
     186             : static int
     187             : calculate_stake_rewards( fd_stake_history_t const *      stake_history,
     188             :                          fd_stake_t const *              stake,
     189             :                          fd_vote_state_versioned_t *     vote_state_versioned,
     190             :                          ulong                           rewarded_epoch,
     191             :                          fd_point_value_t *              point_value,
     192             :                          ulong *                         new_rate_activation_epoch,
     193           0 :                          fd_calculated_stake_rewards_t * result ) {
     194           0 :   fd_calculated_stake_points_t stake_points_result = {0};
     195           0 :   calculate_stake_points_and_credits( stake_history, stake, vote_state_versioned, new_rate_activation_epoch, &stake_points_result);
     196             : 
     197             :   // Drive credits_observed forward unconditionally when rewards are disabled
     198             :   // or when this is the stake's activation epoch
     199           0 :   if( ( point_value->rewards==0UL ) ||
     200           0 :       ( stake->delegation.activation_epoch==rewarded_epoch ) ) {
     201           0 :       stake_points_result.force_credits_update_with_skipped_reward |= 1;
     202           0 :   }
     203             : 
     204           0 :   if( stake_points_result.force_credits_update_with_skipped_reward ) {
     205           0 :     result->staker_rewards = 0;
     206           0 :     result->voter_rewards = 0;
     207           0 :     result->new_credits_observed = stake_points_result.new_credits_observed;
     208           0 :     return 0;
     209           0 :   }
     210           0 :   if( stake_points_result.points == 0 || point_value->points == 0 ) {
     211           0 :     return 1;
     212           0 :   }
     213             : 
     214             :   /* FIXME: need to error out if the conversion from uint128 to u64 fails, also use 128 checked mul and div */
     215           0 :   ulong rewards = (ulong)(stake_points_result.points * (uint128)(point_value->rewards) / (uint128) point_value->points);
     216           0 :   if( rewards == 0 ) {
     217           0 :     return 1;
     218           0 :   }
     219             : 
     220           0 :   fd_commission_split_t split_result;
     221           0 :   fd_vote_commission_split( vote_state_versioned, rewards, &split_result );
     222           0 :   if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
     223           0 :     return 1;
     224           0 :   }
     225             : 
     226           0 :   result->staker_rewards = split_result.staker_portion;
     227           0 :   result->voter_rewards = split_result.voter_portion;
     228           0 :   result->new_credits_observed = stake_points_result.new_credits_observed;
     229           0 :   return 0;
     230           0 : }
     231             : 
     232             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
     233             : static int
     234             : redeem_rewards( fd_stake_history_t const *      stake_history,
     235             :                 fd_stake_t const *              stake,
     236             :                 fd_vote_state_versioned_t *     vote_state_versioned,
     237             :                 ulong                           rewarded_epoch,
     238             :                 fd_point_value_t *              point_value,
     239             :                 ulong *                         new_rate_activation_epoch,
     240           0 :                 fd_calculated_stake_rewards_t * calculated_stake_rewards) {
     241             : 
     242           0 :   int rc = calculate_stake_rewards( stake_history, stake, vote_state_versioned, rewarded_epoch, point_value, new_rate_activation_epoch, calculated_stake_rewards );
     243           0 :   if( FD_UNLIKELY( rc!=0 ) ) {
     244           0 :     return rc;
     245           0 :   }
     246             : 
     247           0 :   return FD_EXECUTOR_INSTR_SUCCESS;
     248           0 : }
     249             : 
     250             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L70 */
     251             : static int
     252             : calculate_points( fd_stake_t const *          stake,
     253             :                   fd_vote_state_versioned_t * vote_state_versioned,
     254             :                   fd_stake_history_t const  * stake_history,
     255             :                   ulong *                     new_rate_activation_epoch,
     256           0 :                   uint128 *                   result ) {
     257           0 :   fd_calculated_stake_points_t stake_point_result;
     258           0 :   calculate_stake_points_and_credits( stake_history, stake, vote_state_versioned, new_rate_activation_epoch, &stake_point_result );
     259           0 :   *result = stake_point_result.points;
     260             : 
     261           0 :   return FD_EXECUTOR_INSTR_SUCCESS;
     262           0 : }
     263             : 
     264             : /* Returns the length of the given epoch in slots
     265             : 
     266             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
     267             : static ulong
     268             : get_slots_in_epoch( ulong                   epoch,
     269           0 :                     fd_epoch_bank_t const * epoch_bank ) {
     270           0 :   return (epoch < epoch_bank->epoch_schedule.first_normal_epoch) ?
     271           0 :           1UL << fd_ulong_sat_add(epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO) :
     272           0 :           epoch_bank->epoch_schedule.slots_per_epoch;
     273           0 : }
     274             : 
     275             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
     276             : static double
     277             : epoch_duration_in_years( fd_epoch_bank_t const * epoch_bank,
     278           0 :                          ulong                   prev_epoch ) {
     279           0 :   ulong slots_in_epoch = get_slots_in_epoch( prev_epoch, epoch_bank );
     280           0 :   return (double)slots_in_epoch / (double) epoch_bank->slots_per_year;
     281           0 : }
     282             : 
     283             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
     284             : static void
     285             : calculate_previous_epoch_inflation_rewards( fd_exec_slot_ctx_t *                slot_ctx,
     286             :                                             ulong                               prev_epoch_capitalization,
     287             :                                             ulong                               prev_epoch,
     288           0 :                                             fd_prev_epoch_inflation_rewards_t * rewards ) {
     289           0 :     double slot_in_year = slot_in_year_for_inflation( slot_ctx );
     290             : 
     291           0 :     fd_epoch_bank_t const * epoch_bank    = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
     292           0 :     rewards->validator_rate               = validator( &epoch_bank->inflation, slot_in_year );
     293           0 :     rewards->foundation_rate              = foundation( &epoch_bank->inflation, slot_in_year );
     294           0 :     rewards->prev_epoch_duration_in_years = epoch_duration_in_years( epoch_bank, prev_epoch );
     295           0 :     rewards->validator_rewards            = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
     296           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 ));
     297           0 : }
     298             : 
     299             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/lib.rs#L29 */
     300             : static ulong
     301           0 : get_minimum_stake_delegation( fd_exec_slot_ctx_t * slot_ctx ) {
     302           0 :   if( !FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, stake_minimum_delegation_for_rewards ) ) {
     303           0 :     return 0UL;
     304           0 :   }
     305             : 
     306           0 :   if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, stake_raise_minimum_delegation_to_1_sol ) ) {
     307           0 :     return LAMPORTS_PER_SOL;
     308           0 :   }
     309             : 
     310           0 :   return 1;
     311           0 : }
     312             : 
     313             : static void
     314             : calculate_points_tpool( void  *tpool,
     315             :                         ulong t0 FD_PARAM_UNUSED,      ulong t1 FD_PARAM_UNUSED,
     316             :                         void  *args,
     317             :                         void  *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
     318             :                         ulong l0 FD_PARAM_UNUSED,      ulong l1 FD_PARAM_UNUSED,
     319             :                         ulong m0,                      ulong m1,
     320           0 :                         ulong n0 FD_PARAM_UNUSED,      ulong n1 FD_PARAM_UNUSED ) {
     321           0 :   fd_epoch_info_pair_t const *      stake_infos                    = ((fd_epoch_info_pair_t const *)tpool);
     322           0 :   fd_calculate_points_task_args_t * task_args                      = (fd_calculate_points_task_args_t *)args;
     323           0 :   fd_stake_history_t const *        stake_history                  = task_args->stake_history;
     324           0 :   ulong *                           new_warmup_cooldown_rate_epoch = task_args->new_warmup_cooldown_rate_epoch;
     325           0 :   ulong                             minimum_stake_delegation       = task_args->minimum_stake_delegation;
     326             : 
     327           0 :   uint128 total_points = 0;
     328           0 :   for( ulong i=m0; i<m1; i++ ) {
     329           0 :     fd_epoch_info_pair_t const * stake_info = stake_infos + i;
     330           0 :     fd_stake_t const *           stake      = &stake_info->stake;
     331             : 
     332           0 :     if( FD_UNLIKELY( stake->delegation.stake<minimum_stake_delegation ) ) {
     333           0 :       continue;
     334           0 :     }
     335             : 
     336             :     /* Check that the vote account is present in our cache */
     337           0 :     fd_vote_info_pair_t_mapnode_t query_key;
     338           0 :     fd_pubkey_t const * voter_acc = &stake->delegation.voter_pubkey;
     339           0 :     fd_memcpy( &query_key.elem.account, voter_acc, sizeof(fd_pubkey_t) );
     340           0 :     fd_vote_info_pair_t_mapnode_t * vote_state_info = fd_vote_info_pair_t_map_find( task_args->vote_states_pool, task_args->vote_states_root, &query_key );
     341           0 :     if( FD_UNLIKELY( vote_state_info==NULL ) ) {
     342           0 :       FD_LOG_DEBUG(( "vote account missing from cache" ));
     343           0 :       continue;
     344           0 :     }
     345             : 
     346           0 :     uint128 account_points;
     347           0 :     int err = calculate_points( stake, &vote_state_info->elem.state, stake_history, new_warmup_cooldown_rate_epoch, &account_points );
     348           0 :     if( FD_UNLIKELY( err ) ) {
     349           0 :       FD_LOG_DEBUG(( "failed to calculate points" ));
     350           0 :       continue;
     351           0 :     }
     352             : 
     353           0 :     total_points += account_points;
     354           0 :   }
     355             : 
     356           0 :   FD_ATOMIC_FETCH_AND_ADD( task_args->total_points, total_points );
     357             : 
     358           0 : }
     359             : 
     360             : /* Calculates epoch reward points from stake/vote accounts.
     361             : 
     362             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L472 */
     363             : static void
     364             : calculate_reward_points_partitioned( fd_exec_slot_ctx_t *       slot_ctx,
     365             :                                      fd_stake_history_t const * stake_history,
     366             :                                      ulong                      rewards,
     367             :                                      fd_point_value_t *         result,
     368             :                                      fd_tpool_t *               tpool,
     369           0 :                                      fd_epoch_info_t *          temp_info ) {
     370             : 
     371           0 :   uint128 points = 0;
     372           0 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( slot_ctx );
     373             : 
     374             :   /* Calculate the points for each stake delegation */
     375           0 :   int _err[1];
     376           0 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     377           0 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     378           0 :   int is_some = fd_new_warmup_cooldown_rate_epoch( slot_ctx->slot_bank.slot,
     379           0 :                                                    slot_ctx->sysvar_cache,
     380           0 :                                                    &slot_ctx->epoch_ctx->features,
     381           0 :                                                    new_warmup_cooldown_rate_epoch,
     382           0 :                                                    _err );
     383           0 :   if( FD_UNLIKELY( !is_some ) ) {
     384           0 :     new_warmup_cooldown_rate_epoch = NULL;
     385           0 :   }
     386             : 
     387           0 :   fd_calculate_points_task_args_t task_args = {
     388           0 :     .stake_history                  = stake_history,
     389           0 :     .new_warmup_cooldown_rate_epoch = new_warmup_cooldown_rate_epoch,
     390           0 :     .minimum_stake_delegation       = minimum_stake_delegation,
     391           0 :     .vote_states_pool               = temp_info->vote_states_pool,
     392           0 :     .vote_states_root               = temp_info->vote_states_root,
     393           0 :     .total_points                   = &points,
     394           0 :   };
     395             : 
     396           0 :   fd_tpool_exec_all_batch( tpool, 0UL, fd_tpool_worker_cnt( tpool ), calculate_points_tpool,
     397           0 :                            temp_info->stake_infos, &task_args, NULL, 1UL, 0UL, temp_info->stake_infos_len );
     398             : 
     399           0 :   if( points > 0 ) {
     400           0 :     result->points  = points;
     401           0 :     result->rewards = rewards;
     402           0 :   }
     403           0 : }
     404             : 
     405             : /* Calculate the partitioned stake rewards for a single stake/vote account pair, updates result with these. */
     406             : static void
     407             : calculate_stake_vote_rewards_account_tpool( void  *tpool,
     408             :                                             ulong t0 FD_PARAM_UNUSED,      ulong t1 FD_PARAM_UNUSED,
     409             :                                             void  *args,
     410             :                                             void  *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
     411             :                                             ulong l0 FD_PARAM_UNUSED,      ulong l1 FD_PARAM_UNUSED,
     412             :                                             ulong m0,                      ulong m1,
     413           0 :                                             ulong n0 FD_PARAM_UNUSED,      ulong n1 FD_PARAM_UNUSED ) {
     414             : 
     415           0 :   fd_epoch_info_t const *                             temp_info                      = ((fd_epoch_info_t const *)tpool);
     416           0 :   fd_epoch_info_pair_t const *                        stake_infos                    = temp_info->stake_infos;
     417             : 
     418           0 :   fd_calculate_stake_vote_rewards_task_args_t const * task_args                      = (fd_calculate_stake_vote_rewards_task_args_t const *)args;
     419           0 :   fd_exec_slot_ctx_t *                                slot_ctx                       = task_args->slot_ctx;
     420           0 :   fd_stake_history_t const *                          stake_history                  = task_args->stake_history;
     421           0 :   ulong                                               rewarded_epoch                 = task_args->rewarded_epoch;
     422           0 :   ulong *                                             new_warmup_cooldown_rate_epoch = task_args->new_warmup_cooldown_rate_epoch;
     423           0 :   fd_point_value_t *                                  point_value                    = task_args->point_value;
     424           0 :   fd_calculate_stake_vote_rewards_result_t *          result                         = task_args->result; // written to
     425           0 :   fd_spad_t *                                         spad                           = task_args->exec_spads[ fd_tile_idx() ];
     426           0 :   fd_spad_push(spad);
     427             : 
     428           0 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( slot_ctx );
     429           0 :   ulong total_stake_rewards      = 0UL;
     430           0 :   ulong dlist_additional_cnt     = 0UL;
     431             : 
     432             :   /* Build a local vote reward map */
     433           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( spad,
     434           0 :                                                                                                                           fd_vote_reward_t_map_align(),
     435           0 :                                                                                                                           fd_vote_reward_t_map_footprint( m1-m0 )),
     436           0 :                                                                                  m1-m0 ) );
     437           0 :   fd_vote_reward_t_mapnode_t * vote_reward_map_root = NULL;
     438             : 
     439           0 :   for( ulong i=m0; i<m1; i++ ) {
     440           0 :     fd_epoch_info_pair_t const * stake_info = stake_infos + i;
     441           0 :     fd_pubkey_t const *          stake_acc  = &stake_info->account;
     442           0 :     fd_stake_t const *           stake      = &stake_info->stake;
     443             : 
     444           0 :     if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, stake_minimum_delegation_for_rewards ) ) {
     445           0 :       if( stake->delegation.stake<minimum_stake_delegation ) {
     446           0 :         continue;
     447           0 :       }
     448           0 :     }
     449             : 
     450           0 :     fd_pubkey_t const * voter_acc = &stake->delegation.voter_pubkey;
     451           0 :     fd_vote_info_pair_t_mapnode_t key;
     452           0 :     fd_memcpy( &key.elem.account, voter_acc, sizeof(fd_pubkey_t) );
     453           0 :     fd_vote_info_pair_t_mapnode_t * vote_state_entry = fd_vote_info_pair_t_map_find( temp_info->vote_states_pool,
     454           0 :                                                                                      temp_info->vote_states_root,
     455           0 :                                                                                      &key );
     456           0 :     if( FD_UNLIKELY( vote_state_entry==NULL ) ) {
     457           0 :       continue;
     458           0 :     }
     459             : 
     460           0 :     fd_vote_state_versioned_t * vote_state = &vote_state_entry->elem.state;
     461             : 
     462             :     /* Note, this doesn't actually redeem any rewards.. this is a misnomer. */
     463           0 :     fd_calculated_stake_rewards_t calculated_stake_rewards[1] = {0};
     464           0 :     int err = redeem_rewards( stake_history,
     465           0 :                               stake,
     466           0 :                               vote_state,
     467           0 :                               rewarded_epoch,
     468           0 :                               point_value,
     469           0 :                               new_warmup_cooldown_rate_epoch,
     470           0 :                               calculated_stake_rewards );
     471           0 :     if( FD_UNLIKELY( err!=0 ) ) {
     472           0 :       FD_LOG_DEBUG(( "redeem_rewards failed for %s with error %d", FD_BASE58_ENC_32_ALLOCA( stake_acc->key ), err ));
     473           0 :       continue;
     474           0 :     }
     475             : 
     476             :     /* Fetch the comission for the vote account */
     477           0 :     uchar commission = 0;
     478           0 :     switch( vote_state->discriminant ) {
     479           0 :       case fd_vote_state_versioned_enum_current:
     480           0 :         commission = vote_state->inner.current.commission;
     481           0 :         break;
     482           0 :       case fd_vote_state_versioned_enum_v0_23_5:
     483           0 :         commission = vote_state->inner.v0_23_5.commission;
     484           0 :         break;
     485           0 :       case fd_vote_state_versioned_enum_v1_14_11:
     486           0 :         commission = vote_state->inner.v1_14_11.commission;
     487           0 :         break;
     488           0 :       default:
     489           0 :         FD_LOG_DEBUG(( "unsupported vote account" ));
     490           0 :         continue;
     491           0 :     }
     492             : 
     493             :     // Find and update the vote reward node in the local map
     494           0 :     fd_vote_reward_t_mapnode_t vote_map_key[1];
     495           0 :     fd_memcpy( &vote_map_key->elem.pubkey, voter_acc, sizeof(fd_pubkey_t) );
     496           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 );
     497           0 :     if( FD_UNLIKELY( vote_reward_node==NULL ) ) {
     498           0 :       FD_LOG_WARNING(( "vote account is missing from the vote rewards pool" ));
     499           0 :       continue;
     500           0 :     }
     501             : 
     502           0 :     vote_reward_node = fd_vote_reward_t_map_find( vote_reward_map_pool, vote_reward_map_root, vote_map_key );
     503             : 
     504           0 :     if( vote_reward_node==NULL ) {
     505           0 :       vote_reward_node = fd_vote_reward_t_map_acquire( vote_reward_map_pool );
     506           0 :       fd_memcpy( &vote_reward_node->elem.pubkey, voter_acc, sizeof(fd_pubkey_t) );
     507           0 :       vote_reward_node->elem.commission   = commission;
     508           0 :       vote_reward_node->elem.vote_rewards = calculated_stake_rewards->voter_rewards;
     509           0 :       vote_reward_node->elem.needs_store  = 1;
     510           0 :       fd_vote_reward_t_map_insert( vote_reward_map_pool, &vote_reward_map_root, vote_reward_node );
     511           0 :     } else {
     512           0 :       vote_reward_node->elem.vote_rewards += calculated_stake_rewards->voter_rewards;
     513           0 :     }
     514             : 
     515             :     /* Add the stake reward to list of all stake rewards. The update is thread-safe because each index in the dlist
     516             :       is only ever accessed / written to once among all threads. */
     517           0 :     fd_stake_reward_t * stake_reward = fd_stake_reward_pool_ele( result->stake_reward_calculation.pool, i );
     518           0 :     if( FD_UNLIKELY( stake_reward==NULL ) ) {
     519           0 :       FD_LOG_WARNING(( "could not find stake reward node in pool" ));
     520           0 :       continue;
     521           0 :     }
     522             : 
     523           0 :     fd_memcpy( stake_reward->stake_pubkey.uc, stake_acc, sizeof(fd_pubkey_t) );
     524           0 :     stake_reward->lamports         = calculated_stake_rewards->staker_rewards;
     525           0 :     stake_reward->credits_observed = calculated_stake_rewards->new_credits_observed;
     526           0 :     stake_reward->valid            = 1;
     527             : 
     528             :     /* Update the total stake rewards */
     529           0 :     total_stake_rewards += calculated_stake_rewards->staker_rewards;
     530           0 :     dlist_additional_cnt++;
     531           0 :   }
     532             : 
     533             :   /* Merge vote rewards with result after */
     534           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 );
     535           0 :        vote_reward_node;
     536           0 :        vote_reward_node = fd_vote_reward_t_map_successor( vote_reward_map_pool, vote_reward_node ) ) {
     537             : 
     538           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 );
     539           0 :     FD_ATOMIC_CAS( &result_reward_node->elem.commission, 0, vote_reward_node->elem.commission );
     540           0 :     FD_ATOMIC_FETCH_AND_ADD( &result_reward_node->elem.vote_rewards, vote_reward_node->elem.vote_rewards );
     541           0 :     FD_ATOMIC_CAS( &result_reward_node->elem.needs_store, 0, 1 );
     542           0 :   }
     543             : 
     544           0 :   FD_ATOMIC_FETCH_AND_ADD( &result->stake_reward_calculation.total_stake_rewards_lamports, total_stake_rewards );
     545           0 :   FD_ATOMIC_FETCH_AND_ADD( &result->stake_reward_calculation.stake_rewards_len, dlist_additional_cnt );
     546           0 :   fd_spad_pop(spad);
     547             : 
     548           0 : }
     549             : 
     550             : /* Calculates epoch rewards for stake/vote accounts.
     551             :    Returns vote rewards, stake rewards, and the sum of all stake rewards in lamports.
     552             : 
     553             :    This uses a pool to allocate the stake rewards, which means that we can use dlists to
     554             :    distribute these into partitions of variable size without copying them or over-allocating
     555             :    the partitions.
     556             :    - We use a single dlist to put all the stake rewards during the calculation phase.
     557             :    - We then distribute these into partitions (whose size cannot be known in advance), where each
     558             :      partition is a seperate dlist.
     559             :    - The dlist elements are all backed by the same pool, and allocated once.
     560             :    This approach optimizes memory usage and reduces copying.
     561             : 
     562             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L334 */
     563             : static void
     564             : calculate_stake_vote_rewards( fd_exec_slot_ctx_t *                       slot_ctx,
     565             :                               fd_stake_history_t const *                 stake_history,
     566             :                               ulong                                      rewarded_epoch,
     567             :                               fd_point_value_t *                         point_value,
     568             :                               fd_calculate_stake_vote_rewards_result_t * result,
     569             :                               fd_epoch_info_t *                          temp_info,
     570             :                               fd_tpool_t *                               tpool,
     571             :                               fd_spad_t * *                              exec_spads,
     572             :                               ulong                                      exec_spad_cnt,
     573           0 :                               fd_spad_t *                                runtime_spad ) {
     574             : 
     575           0 :   int _err[1];
     576           0 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     577           0 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     578           0 :   int is_some = fd_new_warmup_cooldown_rate_epoch( slot_ctx->slot_bank.slot,
     579           0 :                                                    slot_ctx->sysvar_cache,
     580           0 :                                                     &slot_ctx->epoch_ctx->features,
     581           0 :                                                    new_warmup_cooldown_rate_epoch,
     582           0 :                                                    _err );
     583           0 :   if( FD_UNLIKELY( !is_some ) ) {
     584           0 :     new_warmup_cooldown_rate_epoch = NULL;
     585           0 :   }
     586             : 
     587           0 :   ulong rewards_max_count = temp_info->stake_infos_len;
     588             : 
     589             :   /* Create the stake rewards pool and dlist. The pool will be destoyed after the stake rewards have been distributed. */
     590           0 :   result->stake_reward_calculation.pool = fd_stake_reward_pool_join( fd_stake_reward_pool_new( fd_spad_alloc( runtime_spad,
     591           0 :                                                                                                               fd_stake_reward_pool_align(),
     592           0 :                                                                                                               fd_stake_reward_pool_footprint( rewards_max_count ) ),
     593           0 :                                                                      rewards_max_count ) );
     594           0 :   fd_stake_reward_dlist_new( &result->stake_reward_calculation.stake_rewards );
     595           0 :   result->stake_reward_calculation.stake_rewards_len = 0UL;
     596             : 
     597             :   /* Create the vote rewards map. This will be destroyed after the vote rewards have been distributed. */
     598           0 :   ulong vote_account_cnt       = fd_vote_info_pair_t_map_size( temp_info->vote_states_pool, temp_info->vote_states_root );
     599           0 :   result->vote_reward_map_pool = fd_vote_reward_t_map_join( fd_vote_reward_t_map_new( fd_spad_alloc( runtime_spad,
     600           0 :                                                                                                      fd_vote_reward_t_map_align(),
     601           0 :                                                                                                      fd_vote_reward_t_map_footprint( vote_account_cnt )),
     602           0 :                                                                                       vote_account_cnt ) );
     603           0 :   result->vote_reward_map_root = NULL;
     604             : 
     605             :   /* Pre-fill the vote pubkeys in the vote rewards map pool */
     606           0 :   for( fd_vote_info_pair_t_mapnode_t * vote_info = fd_vote_info_pair_t_map_minimum( temp_info->vote_states_pool, temp_info->vote_states_root );
     607           0 :        vote_info;
     608           0 :        vote_info = fd_vote_info_pair_t_map_successor( temp_info->vote_states_pool, vote_info ) ) {
     609             : 
     610           0 :     fd_pubkey_t const *          voter_pubkey     = &vote_info->elem.account;
     611           0 :     fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_acquire( result->vote_reward_map_pool );
     612             : 
     613           0 :     fd_memcpy( &vote_reward_node->elem.pubkey, voter_pubkey, sizeof(fd_pubkey_t) );
     614           0 :     vote_reward_node->elem.vote_rewards = 0UL;
     615           0 :     vote_reward_node->elem.needs_store  = 0;
     616             : 
     617           0 :     fd_vote_reward_t_map_insert( result->vote_reward_map_pool, &result->vote_reward_map_root, vote_reward_node );
     618           0 :   }
     619             : 
     620             :   /* Pre-allocate the dlist stake reward elements */
     621           0 :   for( ulong i=0UL; i<temp_info->stake_infos_len; i++ ) {
     622           0 :     fd_stake_reward_t * stake_reward = fd_stake_reward_pool_ele_acquire( result->stake_reward_calculation.pool );
     623           0 :     if( FD_UNLIKELY( stake_reward==NULL ) ) {
     624           0 :       FD_LOG_ERR(( "insufficient space allocated for stake reward calculation pool" ));
     625           0 :       return;
     626           0 :     }
     627           0 :     stake_reward->valid = 0;
     628           0 :     fd_stake_reward_dlist_ele_push_tail( &result->stake_reward_calculation.stake_rewards, stake_reward, result->stake_reward_calculation.pool );
     629           0 :   }
     630             : 
     631           0 :   fd_calculate_stake_vote_rewards_task_args_t task_args = {
     632           0 :     .slot_ctx                       = slot_ctx,
     633           0 :     .stake_history                  = stake_history,
     634           0 :     .rewarded_epoch                 = rewarded_epoch,
     635           0 :     .new_warmup_cooldown_rate_epoch = new_warmup_cooldown_rate_epoch,
     636           0 :     .point_value                    = point_value,
     637           0 :     .result                         = result,
     638           0 :     .exec_spads                     = exec_spads,
     639           0 :     .exec_spad_cnt                  = exec_spad_cnt,
     640           0 :   };
     641             : 
     642             :   /* Loop over all the delegations
     643             :      https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L367  */
     644           0 :   fd_tpool_exec_all_batch( tpool, 0UL, fd_tpool_worker_cnt( tpool ), calculate_stake_vote_rewards_account_tpool,
     645           0 :                            temp_info, &task_args, NULL, 1UL, 0UL, temp_info->stake_infos_len );
     646           0 : }
     647             : 
     648             : /* Calculate epoch reward and return vote and stake rewards.
     649             : 
     650             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
     651             : static void
     652             : calculate_validator_rewards( fd_exec_slot_ctx_t *                      slot_ctx,
     653             :                              ulong                                     rewarded_epoch,
     654             :                              ulong                                     rewards,
     655             :                              fd_calculate_validator_rewards_result_t * result,
     656             :                              fd_epoch_info_t *                         temp_info,
     657             :                              fd_tpool_t *                              tpool,
     658             :                              fd_spad_t * *                             exec_spads,
     659             :                              ulong                                     exec_spad_cnt,
     660           0 :                              fd_spad_t *                               runtime_spad ) {
     661             :     /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L2759-L2786 */
     662           0 :   fd_stake_history_t const * stake_history = (fd_stake_history_t const *)fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache );
     663           0 :   if( FD_UNLIKELY( !stake_history ) ) {
     664           0 :     FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
     665           0 :   }
     666             : 
     667             :   /* Calculate the epoch reward points from stake/vote accounts */
     668           0 :   calculate_reward_points_partitioned( slot_ctx,
     669           0 :                                        stake_history,
     670           0 :                                        rewards,
     671           0 :                                        &result->point_value,
     672           0 :                                        tpool,
     673           0 :                                        temp_info );
     674             : 
     675             :   /* Calculate the stake and vote rewards for each account */
     676           0 :   calculate_stake_vote_rewards( slot_ctx,
     677           0 :                                 stake_history,
     678           0 :                                 rewarded_epoch,
     679           0 :                                 &result->point_value,
     680           0 :                                 &result->calculate_stake_vote_rewards_result,
     681           0 :                                 temp_info,
     682           0 :                                 tpool,
     683           0 :                                 exec_spads,
     684           0 :                                 exec_spad_cnt,
     685           0 :                                 runtime_spad );
     686           0 : }
     687             : 
     688             : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
     689             : 
     690             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
     691             :  */
     692             : static ulong
     693             : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     694             :                                     ulong                       slot,
     695           0 :                                     ulong                       total_stake_accounts ) {
     696             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
     697           0 :   if( epoch_schedule->warmup &&
     698           0 :       fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
     699           0 :     return 1UL;
     700           0 :   }
     701             : 
     702           0 :   ulong num_chunks = total_stake_accounts / (ulong)STAKE_ACCOUNT_STORES_PER_BLOCK + (total_stake_accounts % STAKE_ACCOUNT_STORES_PER_BLOCK != 0);
     703           0 :   num_chunks       = fd_ulong_max( num_chunks, 1UL );
     704           0 :   num_chunks       = fd_ulong_min( num_chunks,
     705           0 :                                    fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
     706           0 :   return num_chunks;
     707           0 : }
     708             : 
     709             : static void
     710             : hash_rewards_into_partitions( fd_exec_slot_ctx_t *                        slot_ctx,
     711             :                               fd_stake_reward_calculation_t *             stake_reward_calculation,
     712             :                               fd_hash_t const *                           parent_blockhash,
     713             :                               fd_stake_reward_calculation_partitioned_t * result,
     714           0 :                               fd_spad_t *                                 runtime_spad ) {
     715             : 
     716             :   /* Initialize a dlist for every partition.
     717             :       These will all use the same pool - we do not re-allocate the stake rewards, only move them into partitions. */
     718           0 :   result->partitioned_stake_rewards.pool = stake_reward_calculation->pool;
     719           0 :   ulong num_partitions = get_reward_distribution_num_blocks( &fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx )->epoch_schedule,
     720           0 :                                                               slot_ctx->slot_bank.slot,
     721           0 :                                                               stake_reward_calculation->stake_rewards_len );
     722           0 :   result->partitioned_stake_rewards.partitions_len = num_partitions;
     723           0 :   result->partitioned_stake_rewards.partitions     = fd_spad_alloc( runtime_spad,
     724           0 :                                                                     fd_stake_reward_dlist_align(),
     725           0 :                                                                     fd_stake_reward_dlist_footprint() * num_partitions );
     726             : 
     727             :   /* Ownership of these dlist's and the pool gets transferred to stake_rewards_by_partition, which then gets transferred to epoch_reward_status.
     728             :       These are eventually cleaned up when epoch_reward_status_inactive is called. */
     729           0 :   for( ulong i = 0; i < num_partitions; ++i ) {
     730           0 :     fd_stake_reward_dlist_new( &result->partitioned_stake_rewards.partitions[ i ] );
     731           0 :   }
     732             : 
     733             :   /* Iterate over all the stake rewards, moving references to them into the appropiate partitions.
     734             :       IMPORTANT: after this, we cannot use the original stake rewards dlist anymore. */
     735           0 :   fd_stake_reward_dlist_iter_t next_iter;
     736           0 :   for( fd_stake_reward_dlist_iter_t iter = fd_stake_reward_dlist_iter_fwd_init( &stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     737           0 :         !fd_stake_reward_dlist_iter_done( iter, &stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     738           0 :         iter = next_iter ) {
     739           0 :     fd_stake_reward_t * stake_reward = fd_stake_reward_dlist_iter_ele( iter, &stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     740             :     /* Cache the next iter here, as we will overwrite the DLIST_NEXT value further down in the loop iteration. */
     741           0 :     next_iter = fd_stake_reward_dlist_iter_fwd_next( iter, &stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
     742             : 
     743           0 :     if( FD_UNLIKELY( !stake_reward->valid ) ) {
     744           0 :       continue;
     745           0 :     }
     746             : 
     747             :     /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/epoch_rewards_hasher.rs#L43C31-L61 */
     748           0 :     fd_siphash13_t  _sip[1] = {0};
     749           0 :     fd_siphash13_t * hasher = fd_siphash13_init( _sip, 0UL, 0UL );
     750             : 
     751           0 :     hasher = fd_siphash13_append( hasher, parent_blockhash->hash, sizeof(fd_hash_t) );
     752           0 :     fd_siphash13_append( hasher, (const uchar *) stake_reward->stake_pubkey.key, sizeof(fd_pubkey_t) );
     753             : 
     754           0 :     ulong hash64 = fd_siphash13_fini( hasher );
     755             :     /* hash_to_partition */
     756             :     /* FIXME: should be saturating add */
     757           0 :     ulong partition_index = (ulong)((uint128) num_partitions *
     758           0 :                                     (uint128) hash64 /
     759           0 :                                     ((uint128)ULONG_MAX + 1));
     760             : 
     761             :     /* Move the stake reward to the partition's dlist */
     762           0 :     fd_stake_reward_dlist_t * partition = &result->partitioned_stake_rewards.partitions[ partition_index ];
     763           0 :     fd_stake_reward_dlist_ele_push_tail( partition, stake_reward, stake_reward_calculation->pool );
     764           0 :   }
     765           0 : }
     766             : 
     767             : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
     768             : 
     769             :    https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L214 */
     770             : static void
     771             : calculate_rewards_for_partitioning( fd_exec_slot_ctx_t *                   slot_ctx,
     772             :                                     ulong                                  prev_epoch,
     773             :                                     const fd_hash_t *                      parent_blockhash,
     774             :                                     fd_partitioned_rewards_calculation_t * result,
     775             :                                     fd_epoch_info_t *                      temp_info,
     776             :                                     fd_tpool_t *                           tpool,
     777             :                                     fd_spad_t * *                          exec_spads,
     778             :                                     ulong                                  exec_spad_cnt,
     779           0 :                                     fd_spad_t *                            runtime_spad ) {
     780             :   /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L227 */
     781           0 :   fd_prev_epoch_inflation_rewards_t rewards;
     782           0 :   calculate_previous_epoch_inflation_rewards( slot_ctx, slot_ctx->slot_bank.capitalization, prev_epoch, &rewards );
     783             : 
     784           0 :   fd_slot_bank_t const * slot_bank = &slot_ctx->slot_bank;
     785             : 
     786           0 :   fd_calculate_validator_rewards_result_t validator_result[1] = {0};
     787           0 :   calculate_validator_rewards( slot_ctx,
     788           0 :                                prev_epoch,
     789           0 :                                rewards.validator_rewards,
     790           0 :                                validator_result,
     791           0 :                                temp_info,
     792           0 :                                tpool,
     793           0 :                                exec_spads,
     794           0 :                                exec_spad_cnt,
     795           0 :                                runtime_spad );
     796             : 
     797           0 :   hash_rewards_into_partitions( slot_ctx,
     798           0 :                                 &validator_result->calculate_stake_vote_rewards_result.stake_reward_calculation,
     799           0 :                                 parent_blockhash,
     800           0 :                                 &result->stake_rewards_by_partition,
     801           0 :                                 runtime_spad );
     802             : 
     803           0 :   result->stake_rewards_by_partition.total_stake_rewards_lamports =
     804           0 :     validator_result->calculate_stake_vote_rewards_result.stake_reward_calculation.total_stake_rewards_lamports;
     805             : 
     806           0 :   result->vote_reward_map_pool         = validator_result->calculate_stake_vote_rewards_result.vote_reward_map_pool;
     807           0 :   result->vote_reward_map_root         = validator_result->calculate_stake_vote_rewards_result.vote_reward_map_root;
     808           0 :   result->validator_rewards            = rewards.validator_rewards;
     809           0 :   result->validator_rate               = rewards.validator_rate;
     810           0 :   result->foundation_rate              = rewards.foundation_rate;
     811           0 :   result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
     812           0 :   result->capitalization               = slot_bank->capitalization;
     813           0 :   fd_memcpy( &result->point_value, &validator_result->point_value, FD_POINT_VALUE_FOOTPRINT );
     814           0 : }
     815             : 
     816             : /* Calculate rewards from previous epoch and distribute vote rewards
     817             : 
     818             :    https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L97 */
     819             : static void
     820             : calculate_rewards_and_distribute_vote_rewards( fd_exec_slot_ctx_t *                                        slot_ctx,
     821             :                                                ulong                                                       prev_epoch,
     822             :                                                fd_hash_t const *                                           parent_blockhash,
     823             :                                                fd_calculate_rewards_and_distribute_vote_rewards_result_t * result,
     824             :                                                fd_epoch_info_t *                                           temp_info,
     825             :                                                fd_tpool_t *                                                tpool,
     826             :                                                fd_spad_t * *                                               exec_spads,
     827             :                                                ulong                                                       exec_spad_cnt,
     828           0 :                                                fd_spad_t *                                                 runtime_spad ) {
     829             : 
     830             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L2406-L2492 */
     831           0 :   fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
     832           0 :   calculate_rewards_for_partitioning( slot_ctx,
     833           0 :                                       prev_epoch,
     834           0 :                                       parent_blockhash,
     835           0 :                                       rewards_calc_result,
     836           0 :                                       temp_info,
     837           0 :                                       tpool,
     838           0 :                                       exec_spads,
     839           0 :                                       exec_spad_cnt,
     840           0 :                                       runtime_spad );
     841             : 
     842             :   /* Iterate over all the vote reward nodes */
     843           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);
     844           0 :        vote_reward_node;
     845           0 :        vote_reward_node = fd_vote_reward_t_map_successor( rewards_calc_result->vote_reward_map_pool, vote_reward_node ) ) {
     846             : 
     847           0 :     if( FD_UNLIKELY( !vote_reward_node->elem.needs_store ) ) {
     848           0 :       continue;
     849           0 :     }
     850             : 
     851           0 :     fd_pubkey_t const * vote_pubkey = &vote_reward_node->elem.pubkey;
     852           0 :     FD_TXN_ACCOUNT_DECL( vote_rec );
     853             : 
     854           0 :     if( FD_UNLIKELY( fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, vote_pubkey, 1, 0UL, vote_rec ) != FD_ACC_MGR_SUCCESS ) ) {
     855           0 :       FD_LOG_ERR(( "Unable to modify vote account" ));
     856           0 :     }
     857             : 
     858           0 :     vote_rec->meta->slot = slot_ctx->slot_bank.slot;
     859             : 
     860           0 :     if( FD_UNLIKELY( fd_txn_account_checked_add_lamports( vote_rec, vote_reward_node->elem.vote_rewards ) ) ) {
     861           0 :       FD_LOG_ERR(( "Adding lamports to vote account would cause overflow" ));
     862           0 :     }
     863             : 
     864           0 :     result->distributed_rewards = fd_ulong_sat_add( result->distributed_rewards, vote_reward_node->elem.vote_rewards );
     865           0 :   }
     866             : 
     867             :   /* There is no need to free the vote reward map since it was spad*/
     868             : 
     869             :   /* Verify that we didn't pay any more than we expected to */
     870           0 :   result->total_rewards = fd_ulong_sat_add( result->distributed_rewards, rewards_calc_result->stake_rewards_by_partition.total_stake_rewards_lamports );
     871           0 :   if( FD_UNLIKELY( rewards_calc_result->validator_rewards < result->total_rewards ) ) {
     872           0 :     FD_LOG_ERR(( "Unexpected rewards calculation result" ));
     873           0 :   }
     874             : 
     875           0 :   slot_ctx->slot_bank.capitalization += result->distributed_rewards;
     876             : 
     877             :   /* Cheap because this doesn't copy all the rewards, just pointers to the dlist */
     878           0 :   fd_memcpy( &result->stake_rewards_by_partition, &rewards_calc_result->stake_rewards_by_partition, FD_STAKE_REWARD_CALCULATION_PARTITIONED_FOOTPRINT );
     879           0 :   fd_memcpy( &result->point_value, &rewards_calc_result->point_value, FD_POINT_VALUE_FOOTPRINT );
     880           0 : }
     881             : 
     882             : /* Distributes a single partitioned reward to a single stake account */
     883             : static int
     884             : distribute_epoch_reward_to_stake_acc( fd_exec_slot_ctx_t * slot_ctx,
     885             :                                       fd_pubkey_t *        stake_pubkey,
     886             :                                       ulong                reward_lamports,
     887           0 :                                       ulong                new_credits_observed ) {
     888             : 
     889           0 :   FD_TXN_ACCOUNT_DECL( stake_acc_rec );
     890             : 
     891           0 :   if( FD_UNLIKELY( fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, stake_pubkey, 0, 0UL, stake_acc_rec ) != FD_ACC_MGR_SUCCESS ) ) {
     892           0 :     FD_LOG_ERR(( "Unable to modify stake account" ));
     893           0 :   }
     894             : 
     895           0 :   stake_acc_rec->meta->slot = slot_ctx->slot_bank.slot;
     896             : 
     897           0 :   fd_stake_state_v2_t stake_state[1] = {0};
     898           0 :   if( fd_stake_get_state( stake_acc_rec, stake_state ) != 0 ) {
     899           0 :     FD_LOG_DEBUG(( "failed to read stake state for %s", FD_BASE58_ENC_32_ALLOCA( stake_pubkey ) ));
     900           0 :     return 1;
     901           0 :   }
     902             : 
     903           0 :   if ( !fd_stake_state_v2_is_stake( stake_state ) ) {
     904           0 :     FD_LOG_DEBUG(( "non-stake stake account, this should never happen" ));
     905           0 :     return 1;
     906           0 :   }
     907             : 
     908           0 :   if( fd_txn_account_checked_add_lamports( stake_acc_rec, reward_lamports ) ) {
     909           0 :     FD_LOG_DEBUG(( "failed to add lamports to stake account" ));
     910           0 :     return 1;
     911           0 :   }
     912             : 
     913           0 :   stake_state->inner.stake.stake.credits_observed = new_credits_observed;
     914           0 :   stake_state->inner.stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->inner.stake.stake.delegation.stake,
     915           0 :                                                                       reward_lamports );
     916             : 
     917           0 :   if( FD_UNLIKELY( write_stake_state( stake_acc_rec, stake_state ) != 0 ) ) {
     918           0 :     FD_LOG_ERR(( "write_stake_state failed" ));
     919           0 :   }
     920             : 
     921           0 :   return 0;
     922           0 : }
     923             : 
     924             : /* Sets the epoch reward status to inactive, and destroys any allocated state associated with the active state. */
     925             : static void
     926             : set_epoch_reward_status_inactive( fd_exec_slot_ctx_t * slot_ctx,
     927           0 :                                   fd_spad_t *          runtime_spad ) {
     928           0 :   if( slot_ctx->epoch_reward_status.discriminant == fd_epoch_reward_status_enum_Active ) {
     929           0 :     FD_LOG_NOTICE(( "Done partitioning rewards for current epoch" ));
     930           0 :     fd_spad_pop( runtime_spad );
     931           0 :   }
     932           0 :   slot_ctx->epoch_reward_status.discriminant = fd_epoch_reward_status_enum_Inactive;
     933           0 : }
     934             : 
     935             : /* Sets the epoch reward status to active.
     936             : 
     937             :     Takes ownership of the given stake_rewards_by_partition data structure,
     938             :     which will be destroyed when set_epoch_reward_status_inactive is called. */
     939             : static void
     940             : set_epoch_reward_status_active( fd_exec_slot_ctx_t *             slot_ctx,
     941             :                                 ulong                            distribution_starting_block_height,
     942           0 :                                 fd_partitioned_stake_rewards_t * partitioned_rewards ) {
     943             : 
     944           0 :   FD_LOG_NOTICE(( "Setting epoch reward status as active" ));
     945           0 :   slot_ctx->epoch_reward_status.discriminant                                    = fd_epoch_reward_status_enum_Active;
     946           0 :   slot_ctx->epoch_reward_status.inner.Active.distribution_starting_block_height = distribution_starting_block_height;
     947             : 
     948           0 :   fd_memcpy( &slot_ctx->epoch_reward_status.inner.Active.partitioned_stake_rewards, partitioned_rewards, FD_PARTITIONED_STAKE_REWARDS_FOOTPRINT );
     949           0 : }
     950             : 
     951             : /*  Process reward credits for a partition of rewards.
     952             :     Store the rewards to AccountsDB, update reward history record and total capitalization
     953             : 
     954             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
     955             : static void
     956             : distribute_epoch_rewards_in_partition( fd_stake_reward_dlist_t * partition,
     957             :                                        fd_stake_reward_t *       pool,
     958             :                                        fd_exec_slot_ctx_t *      slot_ctx,
     959           0 :                                        fd_spad_t *               runtime_spad ) {
     960             : 
     961           0 :   ulong lamports_distributed = 0UL;
     962           0 :   ulong lamports_burned      = 0UL;
     963             : 
     964           0 :   for( fd_stake_reward_dlist_iter_t iter = fd_stake_reward_dlist_iter_fwd_init( partition, pool );
     965           0 :         !fd_stake_reward_dlist_iter_done( iter, partition, pool );
     966           0 :         iter = fd_stake_reward_dlist_iter_fwd_next( iter, partition, pool ) ) {
     967           0 :     fd_stake_reward_t * stake_reward = fd_stake_reward_dlist_iter_ele( iter, partition, pool );
     968             : 
     969           0 :     if( distribute_epoch_reward_to_stake_acc( slot_ctx,
     970           0 :                                               &stake_reward->stake_pubkey,
     971           0 :                                               stake_reward->lamports,
     972           0 :                                               stake_reward->credits_observed ) == 0 ) {
     973           0 :       lamports_distributed += stake_reward->lamports;
     974           0 :     } else {
     975           0 :       lamports_burned += stake_reward->lamports;
     976           0 :     }
     977           0 :   }
     978             : 
     979             :   /* Update the epoch rewards sysvar with the amount distributed and burnt */
     980           0 :   if( FD_LIKELY( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, enable_partitioned_epoch_reward ) ||
     981           0 :                  FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, partitioned_epoch_rewards_superfeature ) ) ) {
     982           0 :     fd_sysvar_epoch_rewards_distribute( slot_ctx,
     983           0 :                                         lamports_distributed + lamports_burned,
     984           0 :                                         runtime_spad );
     985           0 :   }
     986             : 
     987           0 :   FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
     988             : 
     989           0 :   slot_ctx->slot_bank.capitalization += lamports_distributed;
     990           0 : }
     991             : 
     992             : /* Process reward distribution for the block if it is inside reward interval.
     993             : 
     994             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L42 */
     995             : void
     996             : fd_distribute_partitioned_epoch_rewards( fd_exec_slot_ctx_t * slot_ctx,
     997             :                                          fd_tpool_t *         tpool,
     998             :                                          fd_spad_t * *        exec_spads,
     999             :                                          ulong                exec_spad_cnt,
    1000           0 :                                          fd_spad_t *          runtime_spad ) {
    1001             : 
    1002           0 :   (void)tpool;
    1003           0 :   (void)exec_spads;
    1004           0 :   (void)exec_spad_cnt;
    1005             : 
    1006           0 :   if( slot_ctx->epoch_reward_status.discriminant == fd_epoch_reward_status_enum_Inactive ) {
    1007           0 :     return;
    1008           0 :   }
    1009             : 
    1010           0 :   fd_start_block_height_and_rewards_t * status = &slot_ctx->epoch_reward_status.inner.Active;
    1011             : 
    1012           0 :   fd_slot_bank_t *        slot_bank  = &slot_ctx->slot_bank;
    1013           0 :   ulong                   height     = slot_bank->block_height;
    1014           0 :   fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank_const( slot_ctx->epoch_ctx );
    1015             : 
    1016           0 :   ulong distribution_starting_block_height = status->distribution_starting_block_height;
    1017           0 :   ulong distribution_end_exclusive         = distribution_starting_block_height + status->partitioned_stake_rewards.partitions_len;
    1018             : 
    1019             :   /* TODO: track current epoch in epoch ctx? */
    1020           0 :   ulong epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_bank->slot, NULL );
    1021           0 :   if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_bank ) <= status->partitioned_stake_rewards.partitions_len ) ) {
    1022           0 :     FD_LOG_ERR(( "Should not be distributing rewards" ));
    1023           0 :   }
    1024             : 
    1025           0 :   if( (height>=distribution_starting_block_height) && (height < distribution_end_exclusive) ) {
    1026           0 :     ulong partition_index = height - distribution_starting_block_height;
    1027           0 :     distribute_epoch_rewards_in_partition( &status->partitioned_stake_rewards.partitions[ partition_index ],
    1028           0 :                                            status->partitioned_stake_rewards.pool,
    1029           0 :                                            slot_ctx,
    1030           0 :                                            runtime_spad );
    1031           0 :   }
    1032             : 
    1033             :   /* If we have finished distributing rewards, set the status to inactive */
    1034           0 :   if( fd_ulong_sat_add( height, 1UL ) >= distribution_end_exclusive ) {
    1035           0 :     set_epoch_reward_status_inactive( slot_ctx, runtime_spad );
    1036           0 :     fd_sysvar_epoch_rewards_set_inactive( slot_ctx,runtime_spad );
    1037           0 :   }
    1038           0 : }
    1039             : 
    1040             : /* Non-partitioned epoch rewards entry-point. This uses the same logic as the partitioned epoch rewards code,
    1041             :    but distributes the rewards in one go.  */
    1042             : void
    1043             : fd_update_rewards( fd_exec_slot_ctx_t * slot_ctx,
    1044             :                    fd_hash_t const *    parent_blockhash,
    1045             :                    ulong                parent_epoch,
    1046             :                    fd_epoch_info_t *    temp_info,
    1047             :                    fd_tpool_t *         tpool,
    1048             :                    fd_spad_t * *        exec_spads,
    1049             :                    ulong                exec_spad_cnt,
    1050           0 :                    fd_spad_t *          runtime_spad ) {
    1051             : 
    1052             :   /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L55 */
    1053           0 :   fd_calculate_rewards_and_distribute_vote_rewards_result_t rewards_result[1] = {0};
    1054           0 :   calculate_rewards_and_distribute_vote_rewards( slot_ctx,
    1055           0 :                                                  parent_epoch,
    1056           0 :                                                  parent_blockhash,
    1057           0 :                                                  rewards_result,
    1058           0 :                                                  temp_info,
    1059           0 :                                                  tpool,
    1060           0 :                                                  exec_spads,
    1061           0 :                                                  exec_spad_cnt,
    1062           0 :                                                  runtime_spad );
    1063             : 
    1064             :   /* Distribute all of the partitioned epoch rewards in one go */
    1065           0 :   for( ulong i = 0UL; i < rewards_result->stake_rewards_by_partition.partitioned_stake_rewards.partitions_len; i++ ) {
    1066           0 :     distribute_epoch_rewards_in_partition( &rewards_result->stake_rewards_by_partition.partitioned_stake_rewards.partitions[ i ],
    1067           0 :                                            rewards_result->stake_rewards_by_partition.partitioned_stake_rewards.pool,
    1068           0 :                                            slot_ctx,
    1069           0 :                                            runtime_spad );
    1070           0 :   }
    1071           0 : }
    1072             : 
    1073             : /* Partitioned epoch rewards entry-point.
    1074             : 
    1075             :    https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L41
    1076             : */
    1077             : void
    1078             : fd_begin_partitioned_rewards( fd_exec_slot_ctx_t * slot_ctx,
    1079             :                               fd_hash_t const *    parent_blockhash,
    1080             :                               ulong                parent_epoch,
    1081             :                               fd_epoch_info_t *    temp_info,
    1082             :                               fd_tpool_t *         tpool,
    1083             :                               fd_spad_t * *        exec_spads,
    1084             :                               ulong                exec_spad_cnt,
    1085           0 :                               fd_spad_t *          runtime_spad ) {
    1086             : 
    1087             :   /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L55 */
    1088           0 :   fd_calculate_rewards_and_distribute_vote_rewards_result_t rewards_result[1] = {0};
    1089           0 :   calculate_rewards_and_distribute_vote_rewards( slot_ctx,
    1090           0 :                                                  parent_epoch,
    1091           0 :                                                  parent_blockhash,
    1092           0 :                                                  rewards_result,
    1093           0 :                                                  temp_info,
    1094           0 :                                                  tpool,
    1095           0 :                                                  exec_spads,
    1096           0 :                                                  exec_spad_cnt,
    1097           0 :                                                  runtime_spad );
    1098             : 
    1099             :   /* https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L62 */
    1100           0 :   ulong distribution_starting_block_height = slot_ctx->slot_bank.block_height + REWARD_CALCULATION_NUM_BLOCKS;
    1101             : 
    1102             :   /* Set the epoch reward status to be active */
    1103           0 :   set_epoch_reward_status_active( slot_ctx,
    1104           0 :                                   distribution_starting_block_height,
    1105           0 :                                   &rewards_result->stake_rewards_by_partition.partitioned_stake_rewards );
    1106             : 
    1107             :   /* Initialize the epoch rewards sysvar
    1108             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L78 */
    1109           0 :   fd_sysvar_epoch_rewards_init( slot_ctx,
    1110           0 :                                 rewards_result->total_rewards,
    1111           0 :                                 rewards_result->distributed_rewards,
    1112           0 :                                 distribution_starting_block_height,
    1113           0 :                                 rewards_result->stake_rewards_by_partition.partitioned_stake_rewards.partitions_len,
    1114           0 :                                 rewards_result->point_value,
    1115           0 :                                 parent_blockhash );
    1116           0 : }
    1117             : 
    1118             : /*
    1119             :     Re-calculates partitioned stake rewards.
    1120             :     This updates the slot context's epoch reward status with the recalculated partitioned rewards.
    1121             : 
    1122             :     https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L536 */
    1123             : void
    1124             : fd_rewards_recalculate_partitioned_rewards( fd_exec_slot_ctx_t * slot_ctx,
    1125             :                                             fd_tpool_t *         tpool,
    1126             :                                             fd_spad_t * *        exec_spads,
    1127             :                                             ulong                exec_spad_cnt,
    1128           0 :                                             fd_spad_t *          runtime_spad ) {
    1129           0 :   fd_sysvar_epoch_rewards_t epoch_rewards[1];
    1130           0 :   if( FD_UNLIKELY( fd_sysvar_epoch_rewards_read( epoch_rewards,
    1131           0 :                                                  slot_ctx->sysvar_cache,
    1132           0 :                                                  slot_ctx->acc_mgr,
    1133           0 :                                                  slot_ctx->funk_txn )==NULL ) ) {
    1134           0 :     FD_LOG_NOTICE(( "failed to read sysvar epoch rewards - the sysvar may not have been created yet" ));
    1135           0 :     set_epoch_reward_status_inactive( slot_ctx, runtime_spad );
    1136           0 :     return;
    1137           0 :   }
    1138             : 
    1139           0 :   FD_LOG_NOTICE(( "recalculating partitioned rewards" ));
    1140             : 
    1141           0 :   if( FD_UNLIKELY( epoch_rewards->active ) ) {
    1142             : 
    1143             :     /* If epoch rewards are active, we must calculate the rewards partitions
    1144             :        so we can start distributing. For the same reason as described in
    1145             :        fd_runtime_process_new_epoch, we must push on a spad frame at this
    1146             :        point. */
    1147           0 :     fd_spad_push( runtime_spad );
    1148             : 
    1149             :     /* If partitioned rewards are active, the rewarded epoch is always the immediately
    1150             :         preceeding epoch.
    1151             : 
    1152             :         https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
    1153           0 :     FD_LOG_NOTICE(( "epoch rewards is active" ));
    1154             : 
    1155           0 :     fd_epoch_schedule_t * epoch_schedule = &fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx )->epoch_schedule;
    1156           0 :     ulong epoch          = fd_slot_to_epoch( epoch_schedule, slot_ctx->slot_bank.slot, NULL );
    1157           0 :     ulong rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
    1158             : 
    1159           0 :     int _err[1] = {0};
    1160           0 :     ulong * new_warmup_cooldown_rate_epoch = fd_spad_alloc( runtime_spad, alignof(ulong), sizeof(ulong) );
    1161           0 :     int is_some = fd_new_warmup_cooldown_rate_epoch( slot_ctx->slot_bank.slot,
    1162           0 :                                                      slot_ctx->sysvar_cache,
    1163           0 :                                                      &slot_ctx->epoch_ctx->features,
    1164           0 :                                                      new_warmup_cooldown_rate_epoch, _err );
    1165           0 :     if( FD_UNLIKELY( !is_some ) ) {
    1166           0 :       new_warmup_cooldown_rate_epoch = NULL;
    1167           0 :     }
    1168             : 
    1169           0 :     fd_stake_history_t const * stake_history = (fd_stake_history_t const *)fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache );
    1170           0 :     if( FD_UNLIKELY( !stake_history ) ) {
    1171           0 :       FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
    1172           0 :     }
    1173             : 
    1174           0 :     fd_point_value_t point_value = { .points  = epoch_rewards->total_points,
    1175           0 :                                      .rewards = epoch_rewards->total_rewards };
    1176             : 
    1177             :     /* Populate vote and stake state info from vote and stakes cache for the stake vote rewards calculation */
    1178           0 :     fd_stakes_t * stakes = &slot_ctx->epoch_ctx->epoch_bank.stakes;
    1179           0 :     fd_epoch_info_t epoch_info = {0};
    1180           0 :     fd_epoch_info_new( &epoch_info );
    1181             : 
    1182           0 :     ulong stake_delegation_sz  = fd_delegation_pair_t_map_size( stakes->stake_delegations_pool, stakes->stake_delegations_root );
    1183           0 :     epoch_info.stake_infos_len = 0UL;
    1184           0 :     epoch_info.stake_infos     = fd_spad_alloc( runtime_spad, FD_EPOCH_INFO_PAIR_ALIGN, FD_EPOCH_INFO_PAIR_FOOTPRINT*stake_delegation_sz );
    1185             : 
    1186           0 :     fd_stake_history_entry_t _accumulator = {
    1187           0 :         .effective = 0UL,
    1188           0 :         .activating = 0UL,
    1189           0 :         .deactivating = 0UL
    1190           0 :     };
    1191             : 
    1192           0 :     fd_accumulate_stake_infos( slot_ctx,
    1193           0 :                                stakes,
    1194           0 :                                stake_history,
    1195           0 :                                new_warmup_cooldown_rate_epoch,
    1196           0 :                                &_accumulator,
    1197           0 :                                &epoch_info,
    1198           0 :                                tpool,
    1199           0 :                                exec_spads,
    1200           0 :                                exec_spad_cnt,
    1201           0 :                                runtime_spad );
    1202           0 :     fd_refresh_vote_accounts( slot_ctx,
    1203           0 :                               stake_history,
    1204           0 :                               new_warmup_cooldown_rate_epoch,
    1205           0 :                               &epoch_info,
    1206           0 :                               tpool,
    1207           0 :                               exec_spads,
    1208           0 :                               exec_spad_cnt,
    1209           0 :                               runtime_spad );
    1210             : 
    1211             :     /* In future, the calculation will be cached in the snapshot, but for now we just re-calculate it
    1212             :         (as Agave does). */
    1213           0 :     fd_calculate_stake_vote_rewards_result_t calculate_stake_vote_rewards_result[1];
    1214           0 :     calculate_stake_vote_rewards( slot_ctx,
    1215           0 :                                   stake_history,
    1216           0 :                                   rewarded_epoch,
    1217           0 :                                   &point_value,
    1218           0 :                                   calculate_stake_vote_rewards_result,
    1219           0 :                                   &epoch_info,
    1220           0 :                                   tpool,
    1221           0 :                                   exec_spads,
    1222           0 :                                   exec_spad_cnt,
    1223           0 :                                   runtime_spad );
    1224             : 
    1225             :     /* The vote reward map isn't actually used in this code path and will only
    1226             :        be freed after rewards have been distributed. */
    1227             : 
    1228           0 :     fd_stake_reward_calculation_partitioned_t stake_rewards_by_partition[1];
    1229           0 :     hash_rewards_into_partitions( slot_ctx,
    1230           0 :                                   &calculate_stake_vote_rewards_result->stake_reward_calculation,
    1231           0 :                                   &epoch_rewards->parent_blockhash,
    1232           0 :                                   stake_rewards_by_partition,
    1233           0 :                                   runtime_spad );
    1234             : 
    1235             :     /* Update the epoch reward status with the newly re-calculated partitions. */
    1236           0 :     set_epoch_reward_status_active( slot_ctx,
    1237           0 :                                     epoch_rewards->distribution_starting_block_height,
    1238           0 :                                     &stake_rewards_by_partition->partitioned_stake_rewards );
    1239           0 :   } else {
    1240           0 :     set_epoch_reward_status_inactive( slot_ctx, runtime_spad );
    1241           0 :   }
    1242           0 : }

Generated by: LCOV version 1.14