LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 586 0.0 %
Date: 2025-10-19 04:31:17 Functions: 0 24 0.0 %

          Line data    Source code
       1             : #include "fd_rewards.h"
       2             : #include <math.h>
       3             : 
       4             : #include "../runtime/fd_acc_mgr.h"
       5             : #include "../runtime/fd_executor_err.h"
       6             : #include "../runtime/program/fd_vote_program.h"
       7             : #include "../runtime/sysvar/fd_sysvar_epoch_rewards.h"
       8             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       9             : #include "../stakes/fd_stakes.h"
      10             : #include "../runtime/program/fd_stake_program.h"
      11             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
      12             : #include "../runtime/context/fd_capture_ctx.h"
      13             : #include "../runtime/fd_runtime.h"
      14             : #include "fd_epoch_rewards.h"
      15             : 
      16             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
      17             : static double
      18           0 : total( fd_inflation_t const * inflation, double year ) {
      19           0 :   if ( FD_UNLIKELY( year == 0.0 ) ) {
      20           0 :     FD_LOG_ERR(( "inflation year 0" ));
      21           0 :   }
      22           0 :   double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
      23           0 :   return (tapered > inflation->terminal) ? tapered : inflation->terminal;
      24           0 : }
      25             : 
      26             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
      27             : static double
      28           0 : foundation( fd_inflation_t const * inflation, double year ) {
      29           0 :   return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
      30           0 : }
      31             : 
      32             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
      33             : static double
      34           0 : validator( fd_inflation_t const * inflation, double year) {
      35             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
      36           0 :   FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
      37           0 :   return total( inflation, year ) - foundation( inflation, year );
      38           0 : }
      39             : 
      40             : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
      41             :     activation slot of the following features:
      42             :     - devnet_and_testnet
      43             :     - full_inflation_enable, if full_inflation_vote has been activated
      44             : 
      45             :     https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
      46             : static FD_FN_CONST ulong
      47           0 : get_inflation_start_slot( fd_bank_t const * bank ) {
      48           0 :   ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
      49           0 :       ? fd_bank_features_query( bank )->devnet_and_testnet
      50           0 :       : ULONG_MAX;
      51             : 
      52           0 :   ulong enable = ULONG_MAX;
      53           0 :   if( FD_FEATURE_ACTIVE_BANK( bank, full_inflation_vote ) &&
      54           0 :       FD_FEATURE_ACTIVE_BANK( bank, full_inflation_enable ) ) {
      55           0 :     enable = fd_bank_features_query( bank )->full_inflation_enable;
      56           0 :   }
      57             : 
      58           0 :   ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
      59           0 :   if( min_slot == ULONG_MAX ) {
      60           0 :     if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
      61           0 :       min_slot = fd_bank_features_query( bank )->pico_inflation;
      62           0 :     } else {
      63           0 :       min_slot = 0;
      64           0 :     }
      65           0 :   }
      66           0 :   return min_slot;
      67           0 : }
      68             : 
      69             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2110 */
      70             : static ulong
      71             : get_inflation_num_slots( fd_bank_t const *           bank,
      72             :                          fd_epoch_schedule_t const * epoch_schedule,
      73           0 :                          ulong                       slot ) {
      74           0 :   ulong inflation_activation_slot = get_inflation_start_slot( bank );
      75           0 :   ulong inflation_start_slot      = fd_epoch_slot0( epoch_schedule,
      76           0 :                                                     fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule,
      77           0 :                                                                                         inflation_activation_slot, NULL ),
      78           0 :                                                                       1UL ) );
      79             : 
      80           0 :   ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, NULL );
      81             : 
      82           0 :   return fd_epoch_slot0( epoch_schedule, epoch ) - inflation_start_slot;
      83           0 : }
      84             : 
      85             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2121 */
      86             : static double
      87           0 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
      88           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
      89           0 :   ulong num_slots = get_inflation_num_slots( bank, epoch_schedule, fd_bank_slot_get( bank ) );
      90           0 :   return (double)num_slots / (double)fd_bank_slots_per_year_get( bank );
      91           0 : }
      92             : 
      93             : /* For a given stake and vote_state, calculate how many points were earned (credits * stake) and new value
      94             :    for credits_observed were the points paid
      95             : 
      96             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
      97             : static void
      98             : calculate_stake_points_and_credits( fd_stake_history_t const *     stake_history,
      99             :                                     fd_stake_delegation_t const *  stake,
     100             :                                     fd_vote_state_ele_t const *    vote_state,
     101             :                                     ulong *                        new_rate_activation_epoch,
     102           0 :                                     fd_calculated_stake_points_t * result ) {
     103             : 
     104           0 :   ulong credits_in_stake = stake->credits_observed;
     105           0 :   ulong credits_in_vote  = 0UL;
     106           0 :   if( FD_LIKELY( vote_state->credits_cnt>0UL ) ) {
     107           0 :     credits_in_vote = vote_state->credits[vote_state->credits_cnt-1UL];
     108           0 :   }
     109             : 
     110             :   /* If the Vote account has less credits observed than the Stake account,
     111             :       something is wrong and we need to force an update.
     112             : 
     113             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
     114           0 :   if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
     115           0 :     result->points = 0;
     116           0 :     result->new_credits_observed = credits_in_vote;
     117           0 :     result->force_credits_update_with_skipped_reward = 1;
     118           0 :     return;
     119           0 :   }
     120             : 
     121             :   /* If the Vote account has the same amount of credits observed as the Stake account,
     122             :       then the Vote account hasn't earnt any credits and so there is nothing to update.
     123             : 
     124             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
     125           0 :   if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
     126           0 :     result->points = 0;
     127           0 :     result->new_credits_observed = credits_in_vote;
     128           0 :     result->force_credits_update_with_skipped_reward = 0;
     129           0 :     return;
     130           0 :   }
     131             : 
     132             :   /* Calculate the points for each epoch credit */
     133           0 :   uint128 points               = 0;
     134           0 :   ulong   new_credits_observed = credits_in_stake;
     135           0 :   for( ulong i=0UL; i<vote_state->credits_cnt; i++ ) {
     136             : 
     137           0 :     ulong final_epoch_credits   = vote_state->credits[i];
     138           0 :     ulong initial_epoch_credits = vote_state->prev_credits[i];
     139           0 :     uint128 earned_credits = 0;
     140           0 :     if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
     141           0 :       earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
     142           0 :     } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
     143           0 :       earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
     144           0 :     }
     145             : 
     146           0 :     new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
     147             : 
     148           0 :     fd_delegation_t delegation = {
     149           0 :       .voter_pubkey         = stake->vote_account,
     150           0 :       .stake                = stake->stake,
     151           0 :       .activation_epoch     = stake->activation_epoch,
     152           0 :       .deactivation_epoch   = stake->deactivation_epoch,
     153           0 :       .warmup_cooldown_rate = stake->warmup_cooldown_rate,
     154           0 :     };
     155             : 
     156           0 :     ulong stake_amount = fd_stake_activating_and_deactivating(
     157           0 :         &delegation,
     158           0 :         vote_state->epoch[i],
     159           0 :         stake_history,
     160           0 :         new_rate_activation_epoch ).effective;
     161             : 
     162           0 :     points += (uint128)stake_amount * earned_credits;
     163             : 
     164           0 :   }
     165             : 
     166             : 
     167           0 :   result->points = points;
     168           0 :   result->new_credits_observed = new_credits_observed;
     169           0 :   result->force_credits_update_with_skipped_reward = 0;
     170           0 : }
     171             : 
     172             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
     173             : static int
     174             : redeem_rewards( fd_stake_history_t const *      stake_history,
     175             :                 fd_stake_delegation_t const *   stake,
     176             :                 fd_vote_state_ele_t const *     vote_state,
     177             :                 ulong                           rewarded_epoch,
     178             :                 ulong                           total_rewards,
     179             :                 uint128                         total_points,
     180             :                 ulong *                         new_rate_activation_epoch,
     181           0 :                 fd_calculated_stake_rewards_t * result ) {
     182             : 
     183             :   /* The firedancer implementation of redeem_rewards inlines a lot of
     184             :      the helper functions that the Agave implementation uses.
     185             :      In Agave: redeem_rewards calls redeem_stake_rewards which calls
     186             :      calculate_stake_rewards. */
     187             : 
     188           0 :   fd_calculated_stake_points_t stake_points_result = {0};
     189           0 :   calculate_stake_points_and_credits(
     190           0 :       stake_history,
     191           0 :       stake,
     192           0 :       vote_state,
     193           0 :       new_rate_activation_epoch,
     194           0 :       &stake_points_result );
     195             : 
     196             :   // Drive credits_observed forward unconditionally when rewards are disabled
     197             :   // or when this is the stake's activation epoch
     198           0 :   if( total_rewards==0UL || stake->activation_epoch==rewarded_epoch ) {
     199           0 :       stake_points_result.force_credits_update_with_skipped_reward = 1;
     200           0 :   }
     201             : 
     202           0 :   if( stake_points_result.force_credits_update_with_skipped_reward ) {
     203           0 :     result->staker_rewards       = 0;
     204           0 :     result->voter_rewards        = 0;
     205           0 :     result->new_credits_observed = stake_points_result.new_credits_observed;
     206           0 :     return 0;
     207           0 :   }
     208           0 :   if( stake_points_result.points==0 || total_points==0 ) {
     209           0 :     return 1;
     210           0 :   }
     211             : 
     212             :   /* FIXME: need to error out if the conversion from uint128 to u64 fails, also use 128 checked mul and div */
     213           0 :   ulong rewards = (ulong)(stake_points_result.points * (uint128)(total_rewards) / (uint128) total_points);
     214           0 :   if( rewards == 0 ) {
     215           0 :     return 1;
     216           0 :   }
     217             : 
     218           0 :   fd_commission_split_t split_result;
     219           0 :   fd_vote_commission_split( vote_state->commission, rewards, &split_result );
     220           0 :   if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
     221           0 :     return 1;
     222           0 :   }
     223             : 
     224           0 :   result->staker_rewards       = split_result.staker_portion;
     225           0 :   result->voter_rewards        = split_result.voter_portion;
     226           0 :   result->new_credits_observed = stake_points_result.new_credits_observed;
     227           0 :   return 0;
     228           0 : }
     229             : 
     230             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L70 */
     231             : static int
     232             : calculate_points( fd_stake_delegation_t const * stake,
     233             :                   fd_vote_state_ele_t const *   vote_state,
     234             :                   fd_stake_history_t const  *   stake_history,
     235             :                   ulong *                       new_rate_activation_epoch,
     236           0 :                   uint128 *                     result ) {
     237           0 :   fd_calculated_stake_points_t stake_point_result;
     238           0 :   calculate_stake_points_and_credits( stake_history,
     239           0 :                                       stake,
     240           0 :                                       vote_state,
     241           0 :                                       new_rate_activation_epoch,
     242           0 :                                       &stake_point_result );
     243           0 :   *result = stake_point_result.points;
     244             : 
     245           0 :   return FD_EXECUTOR_INSTR_SUCCESS;
     246           0 : }
     247             : 
     248             : /* Returns the length of the given epoch in slots
     249             : 
     250             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
     251             : static ulong
     252             : get_slots_in_epoch( ulong                       epoch,
     253           0 :                     fd_epoch_schedule_t const * epoch_schedule ) {
     254           0 :   return (epoch < epoch_schedule->first_normal_epoch) ?
     255           0 :           1UL << fd_ulong_sat_add(epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO) :
     256           0 :           epoch_schedule->slots_per_epoch;
     257           0 : }
     258             : 
     259             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
     260             : static double
     261             : epoch_duration_in_years( fd_bank_t const * bank,
     262           0 :                          ulong             prev_epoch ) {
     263           0 :   ulong slots_in_epoch = get_slots_in_epoch( prev_epoch, fd_bank_epoch_schedule_query( bank ) );
     264           0 :   return (double)slots_in_epoch / (double)fd_bank_slots_per_year_get( bank );
     265           0 : }
     266             : 
     267             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
     268             : static void
     269             : calculate_previous_epoch_inflation_rewards( fd_bank_t const *                   bank,
     270             :                                             ulong                               prev_epoch_capitalization,
     271             :                                             ulong                               prev_epoch,
     272           0 :                                             fd_prev_epoch_inflation_rewards_t * rewards ) {
     273           0 :   double slot_in_year = slot_in_year_for_inflation( bank );
     274             : 
     275           0 :   rewards->validator_rate               = validator( fd_bank_inflation_query( bank ), slot_in_year );
     276           0 :   rewards->foundation_rate              = foundation( fd_bank_inflation_query( bank ), slot_in_year );
     277           0 :   rewards->prev_epoch_duration_in_years = epoch_duration_in_years( bank, prev_epoch );
     278           0 :   rewards->validator_rewards            = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
     279           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 ));
     280           0 : }
     281             : 
     282             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/lib.rs#L29 */
     283             : static ulong
     284           0 : get_minimum_stake_delegation( fd_bank_t * bank ) {
     285           0 :   if( !FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
     286           0 :     return 0UL;
     287           0 :   }
     288             : 
     289           0 :   if( FD_FEATURE_ACTIVE_BANK( bank, stake_raise_minimum_delegation_to_1_sol ) ) {
     290           0 :     return LAMPORTS_PER_SOL;
     291           0 :   }
     292             : 
     293           0 :   return 1;
     294           0 : }
     295             : 
     296             : /* Calculates epoch reward points from stake/vote accounts.
     297             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L445 */
     298             : static uint128
     299             : calculate_reward_points_partitioned( fd_bank_t *                    bank,
     300             :                                      fd_stake_delegations_t const * stake_delegations,
     301           0 :                                      fd_stake_history_t const *     stake_history ) {
     302           0 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
     303             : 
     304             :   /* Calculate the points for each stake delegation */
     305           0 :   int _err[1];
     306           0 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     307           0 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     308           0 :   int is_some = fd_new_warmup_cooldown_rate_epoch(
     309           0 :       fd_bank_epoch_schedule_query( bank ),
     310           0 :       fd_bank_features_query( bank ),
     311           0 :       fd_bank_slot_get( bank ),
     312           0 :       new_warmup_cooldown_rate_epoch,
     313           0 :       _err );
     314           0 :   if( FD_UNLIKELY( !is_some ) ) {
     315           0 :     new_warmup_cooldown_rate_epoch = NULL;
     316           0 :   }
     317             : 
     318           0 :   uint128 total_points = 0;
     319             : 
     320           0 :   fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( bank );
     321             : 
     322           0 :   fd_stake_delegations_iter_t iter_[1];
     323           0 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     324           0 :        !fd_stake_delegations_iter_done( iter );
     325           0 :        fd_stake_delegations_iter_next( iter ) ) {
     326           0 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     327             : 
     328           0 :     if( FD_UNLIKELY( stake_delegation->stake<minimum_stake_delegation ) ) {
     329           0 :       continue;
     330           0 :     }
     331             : 
     332           0 :     fd_vote_state_ele_t * vote_state_ele = fd_vote_states_query( vote_states, &stake_delegation->vote_account );
     333           0 :     if( FD_UNLIKELY( !vote_state_ele ) ) {
     334           0 :       continue;
     335           0 :     }
     336             : 
     337           0 :     uint128 account_points;
     338           0 :     int err = calculate_points(
     339           0 :         stake_delegation,
     340           0 :         vote_state_ele,
     341           0 :         stake_history,
     342           0 :         new_warmup_cooldown_rate_epoch, &account_points );
     343           0 :     if( FD_UNLIKELY( err ) ) {
     344           0 :       FD_LOG_DEBUG(( "failed to calculate points" ));
     345           0 :       continue;
     346           0 :     }
     347             : 
     348           0 :     total_points += account_points;
     349           0 :   }
     350             : 
     351           0 :   fd_bank_vote_states_end_locking_query( bank );
     352             : 
     353           0 :   return total_points;
     354           0 : }
     355             : 
     356             : /* Calculates epoch rewards for stake/vote accounts.
     357             :    Returns vote rewards, stake rewards, and the sum of all stake rewards
     358             :    in lamports.
     359             : 
     360             :    In the future, the calculation will be cached in the snapshot, but
     361             :    for now we just re-calculate it (as Agave does).
     362             :    calculate_stake_vote_rewards is responsible for calculating
     363             :    stake account rewards based off of a combination of the
     364             :    stake delegation state as well as the vote account. If this
     365             :    calculation is done at the end of an epoch, we can just use the
     366             :    vote states at the end of the current epoch. However, because we
     367             :    are presumably booting up a node in the middle of rewards
     368             :    distribution, we need to make sure that we are using the vote
     369             :    states from the end of the previous epoch.
     370             : 
     371             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
     372             : static void
     373             : calculate_stake_vote_rewards( fd_bank_t *                    bank,
     374             :                               fd_stake_delegations_t const * stake_delegations,
     375             :                               fd_capture_ctx_t *             capture_ctx,
     376             :                               fd_stake_history_t const *     stake_history,
     377             :                               ulong                          rewarded_epoch,
     378             :                               ulong                          total_rewards,
     379             :                               uint128                        total_points,
     380           0 :                               int                            is_recalculation ) {
     381             : 
     382           0 :   int _err[1];
     383           0 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     384           0 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     385           0 :   int is_some = fd_new_warmup_cooldown_rate_epoch(
     386           0 :       fd_bank_epoch_schedule_query( bank ),
     387           0 :       fd_bank_features_query( bank ),
     388           0 :       fd_bank_slot_get( bank ),
     389           0 :       new_warmup_cooldown_rate_epoch,
     390           0 :       _err );
     391           0 :   if( FD_UNLIKELY( !is_some ) ) {
     392           0 :     new_warmup_cooldown_rate_epoch = NULL;
     393           0 :   }
     394             : 
     395             : 
     396           0 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
     397           0 :   ulong stake_delegation_cnt     = fd_stake_delegations_cnt( stake_delegations );
     398             : 
     399           0 :   fd_vote_states_t * vote_states = !!is_recalculation ? fd_bank_vote_states_prev_locking_modify( bank ) : fd_bank_vote_states_locking_modify( bank );
     400             : 
     401           0 :   fd_epoch_rewards_t * epoch_rewards = fd_epoch_rewards_join( fd_epoch_rewards_new( fd_bank_epoch_rewards_locking_modify( bank ), stake_delegation_cnt ) );
     402             : 
     403           0 :   fd_stake_delegations_iter_t iter_[1];
     404           0 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     405           0 :        !fd_stake_delegations_iter_done( iter );
     406           0 :        fd_stake_delegations_iter_next( iter ) ) {
     407           0 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     408             : 
     409           0 :     if( FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
     410           0 :       if( stake_delegation->stake<minimum_stake_delegation ) {
     411           0 :         continue;
     412           0 :       }
     413           0 :     }
     414             : 
     415           0 :     fd_pubkey_t const *   voter_acc      = &stake_delegation->vote_account;
     416           0 :     fd_vote_state_ele_t * vote_state_ele = fd_vote_states_query( vote_states, voter_acc );
     417           0 :     if( FD_UNLIKELY( !vote_state_ele ) ) {
     418           0 :       continue;
     419           0 :     }
     420             : 
     421             :     /* redeem_rewards is actually just responisble for calculating the
     422             :        vote and stake rewards for each stake account.  It does not do
     423             :        rewards redemption: it is a misnomer. */
     424           0 :     fd_calculated_stake_rewards_t calculated_stake_rewards[1] = {0};
     425           0 :     int err = redeem_rewards(
     426           0 :         stake_history,
     427           0 :         stake_delegation,
     428           0 :         vote_state_ele,
     429           0 :         rewarded_epoch,
     430           0 :         total_rewards,
     431           0 :         total_points,
     432           0 :         new_warmup_cooldown_rate_epoch,
     433           0 :         calculated_stake_rewards );
     434             : 
     435           0 :     if( FD_UNLIKELY( err!=0 ) ) {
     436           0 :       FD_LOG_DEBUG(( "redeem_rewards failed for %s with error %d", FD_BASE58_ENC_32_ALLOCA( &stake_delegation->stake_account ), err ));
     437           0 :       continue;
     438           0 :     }
     439             : 
     440           0 :     if( capture_ctx ) {
     441           0 :       fd_solcap_write_stake_reward_event( capture_ctx->capture,
     442           0 :           &stake_delegation->stake_account,
     443           0 :           voter_acc,
     444           0 :           vote_state_ele->commission,
     445           0 :           (long)calculated_stake_rewards->voter_rewards,
     446           0 :           (long)calculated_stake_rewards->staker_rewards,
     447           0 :           (long)calculated_stake_rewards->new_credits_observed );
     448           0 :     }
     449             : 
     450           0 :     vote_state_ele->rewards += calculated_stake_rewards->voter_rewards;
     451             : 
     452           0 :     fd_epoch_rewards_insert( epoch_rewards, &stake_delegation->stake_account, calculated_stake_rewards->new_credits_observed, calculated_stake_rewards->staker_rewards );
     453           0 :   }
     454             : 
     455           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
     456             : 
     457           0 :   !!is_recalculation ? fd_bank_vote_states_prev_end_locking_modify( bank ) : fd_bank_vote_states_end_locking_modify( bank );
     458           0 : }
     459             : 
     460             : /* Calculate epoch reward and return vote and stake rewards.
     461             : 
     462             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
     463             : static uint128
     464             : calculate_validator_rewards( fd_bank_t *                    bank,
     465             :                              fd_funk_t *                    funk,
     466             :                              fd_funk_txn_xid_t const *      xid,
     467             :                              fd_stake_delegations_t const * stake_delegations,
     468             :                              fd_capture_ctx_t *             capture_ctx,
     469             :                              ulong                          rewarded_epoch,
     470           0 :                              ulong *                        rewards_out ) {
     471             : 
     472           0 :   fd_stake_history_t stake_history[1];
     473           0 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( funk, xid, stake_history ) ) ) {
     474           0 :     FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
     475           0 :   }
     476             : 
     477             :   /* Calculate the epoch reward points from stake/vote accounts */
     478           0 :   uint128 points = calculate_reward_points_partitioned(
     479           0 :       bank,
     480           0 :       stake_delegations,
     481           0 :       stake_history );
     482             : 
     483             :   /* If there are no points, then we set the rewards to 0. */
     484           0 :   *rewards_out = points>0UL ? *rewards_out: 0UL;
     485             : 
     486           0 :   if( capture_ctx ) {
     487           0 :     ulong const epoch = fd_bank_epoch_get( bank );
     488           0 :     fd_solcap_writer_stake_rewards_begin( capture_ctx->capture,
     489           0 :         epoch,
     490           0 :         epoch-1UL, /* FIXME: this is not strictly correct */
     491           0 :         *rewards_out,
     492           0 :         points );
     493           0 :   }
     494             : 
     495             :   /* Calculate the stake and vote rewards for each account. We want to
     496             :      use the vote states from the end of the current_epoch. */
     497           0 :   calculate_stake_vote_rewards(
     498           0 :       bank,
     499           0 :       stake_delegations,
     500           0 :       capture_ctx,
     501           0 :       stake_history,
     502           0 :       rewarded_epoch,
     503           0 :       *rewards_out,
     504           0 :       points,
     505           0 :       0 );
     506             : 
     507           0 :   return points;
     508           0 : }
     509             : 
     510             : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
     511             : 
     512             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
     513             :  */
     514             : static ulong
     515             : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     516             :                                     ulong                       slot,
     517           0 :                                     ulong                       total_stake_accounts ) {
     518             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
     519           0 :   if( epoch_schedule->warmup &&
     520           0 :       fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
     521           0 :     return 1UL;
     522           0 :   }
     523             : 
     524           0 :   ulong num_chunks = total_stake_accounts / (ulong)STAKE_ACCOUNT_STORES_PER_BLOCK + (total_stake_accounts % STAKE_ACCOUNT_STORES_PER_BLOCK != 0);
     525           0 :   num_chunks       = fd_ulong_max( num_chunks, 1UL );
     526           0 :   num_chunks       = fd_ulong_min( num_chunks,
     527           0 :                                    fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
     528           0 :   return num_chunks;
     529           0 : }
     530             : 
     531             : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
     532             : 
     533             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L277 */
     534             : static void
     535             : calculate_rewards_for_partitioning( fd_bank_t *                            bank,
     536             :                                     fd_funk_t *                            funk,
     537             :                                     fd_funk_txn_xid_t const *              xid,
     538             :                                     fd_stake_delegations_t const *         stake_delegations,
     539             :                                     fd_capture_ctx_t *                     capture_ctx,
     540             :                                     ulong                                  prev_epoch,
     541           0 :                                     fd_partitioned_rewards_calculation_t * result ) {
     542           0 :   fd_prev_epoch_inflation_rewards_t rewards;
     543             : 
     544           0 :   calculate_previous_epoch_inflation_rewards( bank,
     545           0 :                                               fd_bank_capitalization_get( bank ),
     546           0 :                                               prev_epoch,
     547           0 :                                               &rewards );
     548             : 
     549           0 :   ulong total_rewards = rewards.validator_rewards;
     550             : 
     551           0 :   uint128 points = calculate_validator_rewards( bank,
     552           0 :                                                 funk,
     553           0 :                                                 xid,
     554           0 :                                                 stake_delegations,
     555           0 :                                                 capture_ctx,
     556           0 :                                                 prev_epoch,
     557           0 :                                                 &total_rewards );
     558             : 
     559             :   /* The agave client does not partition the stake rewards until the
     560             :      first distribution block.  We calculate the partitions during the
     561             :      boundary. */
     562           0 :   result->validator_points             = points;
     563           0 :   result->validator_rewards            = total_rewards;
     564           0 :   result->validator_rate               = rewards.validator_rate;
     565           0 :   result->foundation_rate              = rewards.foundation_rate;
     566           0 :   result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
     567           0 :   result->capitalization               = fd_bank_capitalization_get( bank );
     568           0 : }
     569             : 
     570             : /* Calculate rewards from previous epoch and distribute vote rewards
     571             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L148 */
     572             : static void
     573             : calculate_rewards_and_distribute_vote_rewards( fd_bank_t *                    bank,
     574             :                                                fd_funk_t *                    funk,
     575             :                                                fd_funk_txn_xid_t const *      xid,
     576             :                                                fd_stake_delegations_t const * stake_delegations,
     577             :                                                fd_capture_ctx_t *             capture_ctx,
     578             :                                                ulong                          prev_epoch,
     579           0 :                                                int                            is_recalculation ) {
     580             : 
     581             :   /* First we must compute the stake and vote rewards for the just
     582             :      completed epoch.  We store the stake account rewards and vote
     583             :      states rewards in the bank */
     584             : 
     585           0 :   fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
     586           0 :   calculate_rewards_for_partitioning( bank,
     587           0 :                                       funk,
     588           0 :                                       xid,
     589           0 :                                       stake_delegations,
     590           0 :                                       capture_ctx,
     591           0 :                                       prev_epoch,
     592           0 :                                       rewards_calc_result );
     593             : 
     594           0 :   fd_vote_states_t const * vote_states = !!is_recalculation ? fd_bank_vote_states_prev_locking_query( bank ) : fd_bank_vote_states_locking_query( bank );
     595             : 
     596             :   /* Iterate over all the vote reward nodes and distribute the rewards
     597             :      to the vote accounts.  After each reward has been paid out,
     598             :      calcualte the lthash for each vote account. */
     599           0 :   ulong distributed_rewards = 0UL;
     600           0 :   fd_vote_states_iter_t iter_[1];
     601           0 :   for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( iter_, vote_states );
     602           0 :        !fd_vote_states_iter_done( iter );
     603           0 :        fd_vote_states_iter_next( iter ) ) {
     604           0 :     fd_vote_state_ele_t * vote_state = fd_vote_states_iter_ele( iter );
     605             : 
     606           0 :     if( vote_state->rewards==0UL ) {
     607           0 :       continue;
     608           0 :     }
     609             : 
     610           0 :     fd_pubkey_t const * vote_pubkey = &vote_state->vote_account;
     611           0 :     fd_txn_account_t vote_rec[1];
     612           0 :     fd_funk_rec_prepare_t prepare = {0};
     613             : 
     614           0 :     if( FD_UNLIKELY( fd_txn_account_init_from_funk_mutable( vote_rec,
     615           0 :                                                             vote_pubkey,
     616           0 :                                                             funk,
     617           0 :                                                             xid,
     618           0 :                                                             1,
     619           0 :                                                             0UL,
     620           0 :                                                             &prepare )!=FD_ACC_MGR_SUCCESS ) ) {
     621           0 :       FD_LOG_ERR(( "Unable to modify vote account" ));
     622           0 :     }
     623             : 
     624           0 :     fd_lthash_value_t prev_hash[1];
     625           0 :     fd_hashes_account_lthash(
     626           0 :       vote_pubkey,
     627           0 :       fd_txn_account_get_meta( vote_rec ),
     628           0 :       fd_txn_account_get_data( vote_rec ),
     629           0 :       prev_hash );
     630             : 
     631           0 :     fd_txn_account_set_slot( vote_rec, fd_bank_slot_get( bank ) );
     632             : 
     633           0 :     if( FD_UNLIKELY( fd_txn_account_checked_add_lamports( vote_rec, vote_state->rewards ) ) ) {
     634           0 :       FD_LOG_ERR(( "Adding lamports to vote account would cause overflow" ));
     635           0 :     }
     636             : 
     637           0 :     fd_hashes_update_lthash( vote_rec, prev_hash,bank, capture_ctx );
     638           0 :     fd_txn_account_mutable_fini( vote_rec, funk, &prepare );
     639             : 
     640           0 :     distributed_rewards = fd_ulong_sat_add( distributed_rewards, vote_state->rewards );
     641             : 
     642           0 :     if( capture_ctx ) {
     643           0 :       fd_solcap_write_vote_account_payout( capture_ctx->capture,
     644           0 :           vote_pubkey,
     645           0 :           fd_bank_slot_get( bank ),
     646           0 :           fd_txn_account_get_lamports( vote_rec ),
     647           0 :           (long)vote_state->rewards );
     648           0 :     }
     649           0 :   }
     650             : 
     651           0 :   !!is_recalculation ? fd_bank_vote_states_prev_end_locking_query( bank ) : fd_bank_vote_states_end_locking_query( bank );
     652             : 
     653             :   /* Verify that we didn't pay any more than we expected to */
     654           0 :   fd_epoch_rewards_t * epoch_rewards = fd_bank_epoch_rewards_locking_modify( bank );
     655             : 
     656           0 :   ulong total_rewards = fd_ulong_sat_add( distributed_rewards, epoch_rewards->total_stake_rewards );
     657           0 :   if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
     658           0 :     FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
     659           0 :   }
     660             : 
     661           0 :   fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + distributed_rewards );
     662             : 
     663           0 :   epoch_rewards->distributed_rewards = distributed_rewards;
     664           0 :   epoch_rewards->total_rewards       = rewards_calc_result->validator_rewards;
     665           0 :   epoch_rewards->total_points        = rewards_calc_result->validator_points;
     666           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
     667           0 : }
     668             : 
     669             : /* Distributes a single partitioned reward to a single stake account */
     670             : static int
     671             : distribute_epoch_reward_to_stake_acc( fd_bank_t *               bank,
     672             :                                       fd_funk_t *               funk,
     673             :                                       fd_funk_txn_xid_t const * xid,
     674             :                                       fd_capture_ctx_t *        capture_ctx,
     675             :                                       fd_pubkey_t *             stake_pubkey,
     676             :                                       ulong                     reward_lamports,
     677           0 :                                       ulong                     new_credits_observed ) {
     678           0 :   fd_txn_account_t stake_acc_rec[1];
     679           0 :   fd_funk_rec_prepare_t prepare = {0};
     680           0 :   if( FD_UNLIKELY( fd_txn_account_init_from_funk_mutable( stake_acc_rec,
     681           0 :                                                           stake_pubkey,
     682           0 :                                                           funk,
     683           0 :                                                           xid,
     684           0 :                                                           0,
     685           0 :                                                           0UL,
     686           0 :                                                           &prepare )!=FD_ACC_MGR_SUCCESS ) ) {
     687           0 :     FD_LOG_ERR(( "Unable to modify stake account" ));
     688           0 :   }
     689             : 
     690           0 :   fd_lthash_value_t prev_hash[1];
     691           0 :   fd_hashes_account_lthash(
     692           0 :     stake_pubkey,
     693           0 :     fd_txn_account_get_meta( stake_acc_rec ),
     694           0 :     fd_txn_account_get_data( stake_acc_rec ),
     695           0 :     prev_hash );
     696             : 
     697           0 :   fd_txn_account_set_slot( stake_acc_rec, fd_bank_slot_get( bank ) );
     698             : 
     699           0 :   fd_stake_state_v2_t stake_state[1] = {0};
     700           0 :   if( fd_stake_get_state( stake_acc_rec, stake_state ) != 0 ) {
     701           0 :     FD_LOG_DEBUG(( "failed to read stake state for %s", FD_BASE58_ENC_32_ALLOCA( stake_pubkey ) ));
     702           0 :     return 1;
     703           0 :   }
     704             : 
     705           0 :   if ( !fd_stake_state_v2_is_stake( stake_state ) ) {
     706           0 :     FD_LOG_DEBUG(( "non-stake stake account, this should never happen" ));
     707           0 :     return 1;
     708           0 :   }
     709             : 
     710           0 :   if( fd_txn_account_checked_add_lamports( stake_acc_rec, reward_lamports ) ) {
     711           0 :     FD_LOG_DEBUG(( "failed to add lamports to stake account" ));
     712           0 :     return 1;
     713           0 :   }
     714             : 
     715           0 :   ulong old_credits_observed = stake_state->inner.stake.stake.credits_observed;
     716           0 :   stake_state->inner.stake.stake.credits_observed = new_credits_observed;
     717           0 :   stake_state->inner.stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->inner.stake.stake.delegation.stake,
     718           0 :                                                                       reward_lamports );
     719             : 
     720             :   /* The stake account has just been updated, so we need to update the
     721             :      stake delegations stored in the bank. */
     722           0 :   fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_delta_locking_modify( bank );
     723           0 :   fd_stake_delegations_update(
     724           0 :       stake_delegations,
     725           0 :       stake_pubkey,
     726           0 :       &stake_state->inner.stake.stake.delegation.voter_pubkey,
     727           0 :       stake_state->inner.stake.stake.delegation.stake,
     728           0 :       stake_state->inner.stake.stake.delegation.activation_epoch,
     729           0 :       stake_state->inner.stake.stake.delegation.deactivation_epoch,
     730           0 :       stake_state->inner.stake.stake.credits_observed,
     731           0 :       stake_state->inner.stake.stake.delegation.warmup_cooldown_rate );
     732           0 :   fd_bank_stake_delegations_delta_end_locking_modify( bank );
     733             : 
     734           0 :   if( capture_ctx ) {
     735           0 :     fd_solcap_write_stake_account_payout( capture_ctx->capture,
     736           0 :         stake_pubkey,
     737           0 :         fd_bank_slot_get( bank ),
     738           0 :         fd_txn_account_get_lamports( stake_acc_rec ),
     739           0 :         (long)reward_lamports,
     740           0 :         new_credits_observed,
     741           0 :         (long)( new_credits_observed-old_credits_observed ),
     742           0 :         stake_state->inner.stake.stake.delegation.stake,
     743           0 :         (long)reward_lamports );
     744           0 :   }
     745             : 
     746           0 :   if( FD_UNLIKELY( write_stake_state( stake_acc_rec, stake_state ) != 0 ) ) {
     747           0 :     FD_LOG_ERR(( "write_stake_state failed" ));
     748           0 :   }
     749             : 
     750           0 :   fd_hashes_update_lthash( stake_acc_rec, prev_hash, bank, capture_ctx );
     751           0 :   fd_txn_account_mutable_fini( stake_acc_rec, funk, &prepare );
     752             : 
     753           0 :   return 0;
     754           0 : }
     755             : 
     756             : /* Process reward credits for a partition of rewards.  Store the rewards
     757             :    to AccountsDB, update reward history record and total capitalization
     758             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
     759             : static void
     760             : distribute_epoch_rewards_in_partition( fd_epoch_rewards_t const * epoch_rewards,
     761             :                                        ulong                      partition_idx,
     762             :                                        fd_bank_t *                bank,
     763             :                                        fd_funk_t *                funk,
     764             :                                        fd_funk_txn_xid_t const *  xid,
     765           0 :                                        fd_capture_ctx_t *         capture_ctx ) {
     766             : 
     767           0 :   ulong lamports_distributed = 0UL;
     768           0 :   ulong lamports_burned      = 0UL;
     769             : 
     770           0 :   fd_epoch_rewards_iter_t iter_[1];
     771           0 :   for( fd_epoch_rewards_iter_t * iter = fd_epoch_rewards_iter_init( iter_, epoch_rewards, partition_idx );
     772           0 :        !fd_epoch_rewards_iter_done( iter );
     773           0 :        fd_epoch_rewards_iter_next( iter ) ) {
     774           0 :     fd_epoch_stake_reward_t * stake_reward = fd_epoch_rewards_iter_ele( iter );
     775             : 
     776           0 :     if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc( bank,
     777           0 :                                                           funk,
     778           0 :                                                           xid,
     779           0 :                                                           capture_ctx,
     780           0 :                                                           &stake_reward->stake_pubkey,
     781           0 :                                                           stake_reward->lamports,
     782           0 :                                                           stake_reward->credits_observed ) )  ) {
     783           0 :       lamports_distributed += stake_reward->lamports;
     784           0 :     } else {
     785           0 :       lamports_burned += stake_reward->lamports;
     786           0 :     }
     787           0 :   }
     788             : 
     789             :   /* Update the epoch rewards sysvar with the amount distributed and burnt */
     790           0 :   fd_sysvar_epoch_rewards_distribute( bank, funk, xid, capture_ctx, lamports_distributed + lamports_burned );
     791             : 
     792           0 :   FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
     793             : 
     794           0 :   fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + lamports_distributed );
     795           0 : }
     796             : 
     797             : /* Process reward distribution for the block if it is inside reward interval.
     798             : 
     799             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L42 */
     800             : void
     801             : fd_distribute_partitioned_epoch_rewards( fd_bank_t *               bank,
     802             :                                          fd_funk_t *               funk,
     803             :                                          fd_funk_txn_xid_t const * xid,
     804           0 :                                          fd_capture_ctx_t *        capture_ctx ) {
     805             : 
     806           0 :   fd_epoch_rewards_t const * epoch_rewards = fd_bank_epoch_rewards_locking_query( bank );
     807           0 :   if( FD_LIKELY( !epoch_rewards ) ) {
     808           0 :     fd_bank_epoch_rewards_end_locking_query( bank );
     809           0 :     return;
     810           0 :   }
     811             : 
     812           0 :   ulong block_height = fd_bank_block_height_get( bank );
     813           0 :   ulong distribution_starting_block_height = epoch_rewards->starting_block_height;
     814           0 :   ulong distribution_end_exclusive         = fd_epoch_rewards_get_exclusive_ending_block_height( epoch_rewards );
     815             : 
     816           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
     817           0 :   ulong                       epoch          = fd_bank_epoch_get( bank );
     818             : 
     819           0 :   if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_schedule ) <= epoch_rewards->num_partitions ) ) {
     820           0 :     FD_LOG_CRIT(( "Should not be distributing rewards" ));
     821           0 :   }
     822             : 
     823           0 :   if( FD_UNLIKELY( block_height>=distribution_starting_block_height && block_height<distribution_end_exclusive ) ) {
     824             : 
     825           0 :     ulong partition_idx = block_height-distribution_starting_block_height;
     826           0 :     distribute_epoch_rewards_in_partition( epoch_rewards, partition_idx, bank, funk, xid, capture_ctx );
     827             : 
     828             :     /* If we have finished distributing rewards, set the status to inactive */
     829           0 :     if( fd_ulong_sat_add( block_height, 1UL )>=distribution_end_exclusive ) {
     830           0 :       fd_sysvar_epoch_rewards_set_inactive( bank, funk, xid, capture_ctx );
     831           0 :     }
     832           0 :   }
     833             : 
     834           0 :   fd_bank_epoch_rewards_end_locking_query( bank );
     835             : 
     836           0 : }
     837             : 
     838             : /* Partitioned epoch rewards entry-point.
     839             : 
     840             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L102
     841             : */
     842             : void
     843             : fd_begin_partitioned_rewards( fd_bank_t *                    bank,
     844             :                               fd_funk_t *                    funk,
     845             :                               fd_funk_txn_xid_t const *      xid,
     846             :                               fd_capture_ctx_t *             capture_ctx,
     847             :                               fd_stake_delegations_t const * stake_delegations,
     848             :                               fd_hash_t const *              parent_blockhash,
     849           0 :                               ulong                          parent_epoch ) {
     850             : 
     851           0 :   calculate_rewards_and_distribute_vote_rewards(
     852           0 :       bank,
     853           0 :       funk,
     854           0 :       xid,
     855           0 :       stake_delegations,
     856           0 :       capture_ctx,
     857           0 :       parent_epoch,
     858           0 :       0 );
     859             : 
     860             :   /* Once the rewards for vote accounts have been distributed and stake
     861             :      account rewards have been calculated, we can now set our epoch
     862             :      reward status to be active and we can initialize the epoch rewards
     863             :      sysvar.  This sysvar is then deleted once all of the partitioned
     864             :      stake rewards have been distributed.
     865             : 
     866             :      The Agave client calculates the partitions for each stake reward
     867             :      when the first distribution block is reached.  The Firedancer
     868             :      client differs here since we hash the partitions during the epoch
     869             :      boundary. */
     870             : 
     871           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
     872           0 :   fd_epoch_rewards_t *        epoch_rewards  = fd_epoch_rewards_join( fd_bank_epoch_rewards_locking_modify( bank ) );
     873             : 
     874           0 :   ulong num_partitions = get_reward_distribution_num_blocks(
     875           0 :       epoch_schedule,
     876           0 :       fd_bank_slot_get( bank ),
     877           0 :       epoch_rewards->stake_rewards_cnt );
     878             : 
     879           0 :   fd_epoch_rewards_hash_into_partitions( epoch_rewards, parent_blockhash, num_partitions );
     880             : 
     881           0 :   ulong distribution_starting_block_height = fd_bank_block_height_get( bank ) + REWARD_CALCULATION_NUM_BLOCKS;
     882             : 
     883           0 :   epoch_rewards->starting_block_height = distribution_starting_block_height;
     884             : 
     885           0 :   fd_sysvar_epoch_rewards_init(
     886           0 :       bank,
     887           0 :       funk,
     888           0 :       xid,
     889           0 :       capture_ctx,
     890           0 :       epoch_rewards->distributed_rewards,
     891           0 :       distribution_starting_block_height,
     892           0 :       epoch_rewards->num_partitions,
     893           0 :       epoch_rewards->total_rewards,
     894           0 :       epoch_rewards->total_points,
     895           0 :       parent_blockhash );
     896           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
     897           0 : }
     898             : 
     899             : /*
     900             :     Re-calculates partitioned stake rewards.
     901             :     This updates the slot context's epoch reward status with the recalculated partitioned rewards.
     902             : 
     903             :     https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521 */
     904             : void
     905             : fd_rewards_recalculate_partitioned_rewards( fd_banks_t *              banks,
     906             :                                             fd_bank_t *               bank,
     907             :                                             fd_funk_t *               funk,
     908             :                                             fd_funk_txn_xid_t const * xid,
     909           0 :                                             fd_capture_ctx_t *        capture_ctx ) {
     910             : 
     911           0 :   fd_sysvar_epoch_rewards_t epoch_rewards_sysvar[1];
     912           0 :   if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read( funk, xid, epoch_rewards_sysvar ) ) ) {
     913           0 :     FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
     914           0 :     return;
     915           0 :   }
     916             : 
     917           0 :   FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
     918             : 
     919           0 :   if( FD_UNLIKELY( !epoch_rewards_sysvar->active ) ) {
     920           0 :     FD_LOG_DEBUG(( "epoch rewards is inactive" ));
     921           0 :     return;
     922           0 :   }
     923             : 
     924             :   /* If partitioned rewards are active, the rewarded epoch is always the immediately
     925             :       preceeding epoch.
     926             : 
     927             :       https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
     928           0 :   FD_LOG_DEBUG(( "epoch rewards is active" ));
     929             : 
     930           0 :   ulong const slot           = fd_bank_slot_get( bank );
     931           0 :   ulong const epoch          = fd_bank_epoch_get( bank );
     932           0 :   ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
     933             : 
     934           0 :   int _err[1] = {0};
     935           0 :   ulong new_warmup_cooldown_rate_epoch_;
     936           0 :   ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_;
     937           0 :   int is_some = fd_new_warmup_cooldown_rate_epoch(
     938           0 :       fd_bank_epoch_schedule_query( bank ),
     939           0 :       fd_bank_features_query( bank ),
     940           0 :       slot,
     941           0 :       new_warmup_cooldown_rate_epoch,
     942           0 :       _err );
     943           0 :   if( FD_UNLIKELY( !is_some ) ) {
     944           0 :     new_warmup_cooldown_rate_epoch = NULL;
     945           0 :   }
     946             : 
     947           0 :   fd_stake_history_t stake_history[1];
     948           0 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( funk, xid, stake_history ) ) ) {
     949           0 :     FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
     950           0 :   }
     951             : 
     952           0 :   fd_stake_delegations_t const * stake_delegations = fd_bank_stake_delegations_frontier_query( banks, bank );
     953           0 :   if( FD_UNLIKELY( !stake_delegations ) ) {
     954           0 :     FD_LOG_CRIT(( "stake_delegations is NULL" ));
     955           0 :   }
     956             : 
     957             :   /* Make sure is_recalculation is ==1 since we are booting up in the
     958             :       middle of rewards distribution (so we should use the epoch
     959             :       stakes for the end of epoch E-1 since we are still distributing
     960             :       rewards for the previous epoch). */
     961           0 :   calculate_stake_vote_rewards(
     962           0 :       bank,
     963           0 :       stake_delegations,
     964           0 :       capture_ctx,
     965           0 :       stake_history,
     966           0 :       rewarded_epoch,
     967           0 :       epoch_rewards_sysvar->total_rewards,
     968           0 :       epoch_rewards_sysvar->total_points,
     969           0 :       1 /* is_recalculation */ );
     970             : 
     971           0 :   fd_epoch_rewards_t * epoch_rewards = fd_epoch_rewards_join( fd_bank_epoch_rewards_locking_modify( bank ) );
     972           0 :   fd_epoch_rewards_hash_into_partitions( epoch_rewards, &epoch_rewards_sysvar->parent_blockhash, epoch_rewards_sysvar->num_partitions );
     973             : 
     974             :   /* Update the epoch reward status with the newly re-calculated partitions. */
     975           0 :   epoch_rewards->starting_block_height = epoch_rewards_sysvar->distribution_starting_block_height;
     976           0 :   fd_bank_epoch_rewards_end_locking_modify( bank );
     977           0 : }

Generated by: LCOV version 1.14