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

Generated by: LCOV version 1.14