LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 345 738 46.7 %
Date: 2026-03-21 05:43:32 Functions: 21 26 80.8 %

          Line data    Source code
       1             : #include "fd_rewards.h"
       2             : #include "fd_stake_rewards.h"
       3             : #include <math.h>
       4             : 
       5             : #include "../runtime/sysvar/fd_sysvar_epoch_rewards.h"
       6             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       7             : #include "../stakes/fd_stakes.h"
       8             : #include "../runtime/program/vote/fd_vote_state_versioned.h"
       9             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
      10             : #include "../capture/fd_capture_ctx.h"
      11             : #include "../runtime/fd_runtime_stack.h"
      12             : #include "../runtime/fd_runtime.h"
      13             : #include "../accdb/fd_accdb_sync.h"
      14             : 
      15             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
      16             : static double
      17          18 : total( fd_inflation_t const * inflation, double year ) {
      18          18 :   if ( FD_UNLIKELY( year == 0.0 ) ) {
      19           0 :     FD_LOG_ERR(( "inflation year 0" ));
      20           0 :   }
      21          18 :   double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
      22          18 :   return (tapered > inflation->terminal) ? tapered : inflation->terminal;
      23          18 : }
      24             : 
      25             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
      26             : static double
      27          27 : foundation( fd_inflation_t const * inflation, double year ) {
      28          27 :   return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
      29          27 : }
      30             : 
      31             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
      32             : static double
      33           9 : validator( fd_inflation_t const * inflation, double year) {
      34             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
      35           9 :   FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
      36           9 :   return total( inflation, year ) - foundation( inflation, year );
      37           9 : }
      38             : 
      39             : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
      40             :     activation slot of the following features:
      41             :     - devnet_and_testnet
      42             :     - full_inflation_enable, if full_inflation_vote has been activated
      43             : 
      44             :     https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
      45             : static FD_FN_CONST ulong
      46           9 : get_inflation_start_slot( fd_bank_t const * bank ) {
      47           9 :   ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
      48           9 :       ? fd_bank_features_query( bank )->devnet_and_testnet
      49           9 :       : ULONG_MAX;
      50             : 
      51           9 :   ulong enable = fd_bank_features_query( bank )->full_inflation_enable;
      52             : 
      53           9 :   ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
      54           9 :   if( min_slot == ULONG_MAX ) {
      55           9 :     if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
      56           0 :       min_slot = fd_bank_features_query( bank )->pico_inflation;
      57           9 :     } else {
      58           9 :       min_slot = 0;
      59           9 :     }
      60           9 :   }
      61           9 :   return min_slot;
      62           9 : }
      63             : 
      64             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2110 */
      65             : static ulong
      66             : get_inflation_num_slots( fd_bank_t const *           bank,
      67             :                          fd_epoch_schedule_t const * epoch_schedule,
      68           9 :                          ulong                       slot ) {
      69           9 :   ulong inflation_activation_slot = get_inflation_start_slot( bank );
      70           9 :   ulong inflation_start_slot      = fd_epoch_slot0( epoch_schedule,
      71           9 :                                                     fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule,
      72           9 :                                                                                         inflation_activation_slot, NULL ),
      73           9 :                                                                       1UL ) );
      74             : 
      75           9 :   ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, NULL );
      76             : 
      77           9 :   return fd_epoch_slot0( epoch_schedule, epoch ) - inflation_start_slot;
      78           9 : }
      79             : 
      80             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2121 */
      81             : static double
      82           9 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
      83           9 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
      84           9 :   ulong num_slots = get_inflation_num_slots( bank, epoch_schedule, fd_bank_slot_get( bank ) );
      85           9 :   return (double)num_slots / (double)fd_bank_slots_per_year_get( bank );
      86           9 : }
      87             : 
      88             : 
      89             : static void
      90             : get_credits( uchar const *       account_data,
      91             :              ulong               account_data_len,
      92             :              uchar *             buf,
      93           0 :              fd_epoch_credits_t * epoch_credits ) {
      94             : 
      95           0 :   fd_bincode_decode_ctx_t ctx = {
      96           0 :     .data    = account_data,
      97           0 :     .dataend = account_data + account_data_len,
      98           0 :   };
      99             : 
     100           0 :   fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( buf, &ctx );
     101           0 :   if( FD_UNLIKELY( vsv==NULL ) ) {
     102           0 :     FD_LOG_CRIT(( "unable to decode vote state versioned" ));
     103           0 :   }
     104             : 
     105           0 :   fd_vote_epoch_credits_t * vote_credits = NULL;
     106             : 
     107           0 :   switch( vsv->discriminant ) {
     108           0 :   case fd_vote_state_versioned_enum_v1_14_11:
     109           0 :     vote_credits = vsv->inner.v1_14_11.epoch_credits;
     110           0 :     break;
     111           0 :   case fd_vote_state_versioned_enum_v3:
     112           0 :     vote_credits = vsv->inner.v3.epoch_credits;
     113           0 :     break;
     114           0 :   case fd_vote_state_versioned_enum_v4:
     115           0 :     vote_credits = vsv->inner.v4.epoch_credits;
     116           0 :     break;
     117           0 :   default:
     118           0 :     FD_LOG_CRIT(( "invalid vote state version %u", vsv->discriminant ));
     119           0 :   }
     120             : 
     121           0 :   epoch_credits->cnt = 0UL;
     122           0 :   for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( vote_credits );
     123           0 :        !deq_fd_vote_epoch_credits_t_iter_done( vote_credits, iter );
     124           0 :        iter = deq_fd_vote_epoch_credits_t_iter_next( vote_credits, iter ) ) {
     125           0 :     fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( vote_credits, iter );
     126           0 :     epoch_credits->epoch[ epoch_credits->cnt ]        = (ushort)ele->epoch;
     127           0 :     epoch_credits->credits[ epoch_credits->cnt ]      = ele->credits;
     128           0 :     epoch_credits->prev_credits[ epoch_credits->cnt ] = ele->prev_credits;
     129           0 :     epoch_credits->cnt++;
     130           0 :   }
     131           0 : }
     132             : 
     133             : /* For a given stake and vote_state, calculate how many points were earned (credits * stake) and new value
     134             :    for credits_observed were the points paid
     135             : 
     136             :     https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
     137             : static void
     138             : calculate_stake_points_and_credits( fd_epoch_credits_t *           epoch_credits,
     139             :                                     fd_stake_history_t const *     stake_history,
     140             :                                     fd_stake_delegation_t const *  stake,
     141             :                                     ulong *                        new_rate_activation_epoch,
     142           9 :                                     fd_calculated_stake_points_t * result ) {
     143             : 
     144           9 :   ulong credits_in_stake = stake->credits_observed;
     145           9 :   ulong credits_cnt      = epoch_credits->cnt;
     146           9 :   ulong credits_in_vote  = credits_cnt > 0UL ? epoch_credits->credits[ credits_cnt - 1UL ] : 0UL;
     147             : 
     148             : 
     149             :   /* If the Vote account has less credits observed than the Stake account,
     150             :       something is wrong and we need to force an update.
     151             : 
     152             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
     153           9 :   if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
     154           0 :     result->points.ud = 0;
     155           0 :     result->new_credits_observed = credits_in_vote;
     156           0 :     result->force_credits_update_with_skipped_reward = 1;
     157           0 :     return;
     158           0 :   }
     159             : 
     160             :   /* If the Vote account has the same amount of credits observed as the Stake account,
     161             :       then the Vote account hasn't earnt any credits and so there is nothing to update.
     162             : 
     163             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
     164           9 :   if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
     165           9 :     result->points.ud = 0;
     166           9 :     result->new_credits_observed = credits_in_vote;
     167           9 :     result->force_credits_update_with_skipped_reward = 0;
     168           9 :     return;
     169           9 :   }
     170             : 
     171             :   /* Calculate the points for each epoch credit */
     172           0 :   uint128 points               = 0;
     173           0 :   ulong   new_credits_observed = credits_in_stake;
     174           0 :   for( ulong i=0UL; i<epoch_credits->cnt; i++ ) {
     175             : 
     176           0 :     ulong final_epoch_credits   = epoch_credits->credits[ i ];
     177           0 :     ulong initial_epoch_credits = epoch_credits->prev_credits[ i ];
     178             : 
     179             :     /* Vote account credits can only increase or stay the same, so
     180             :        initial_epoch_credits <= final_epoch_credits always holds. */
     181           0 :     FD_TEST( initial_epoch_credits<=final_epoch_credits );
     182             : 
     183             :     /* If final_epoch_credits <= credits_in_stake, then:
     184             :         initial_epoch_credits <= final_epoch_credits <= credits_in_stake
     185             : 
     186             :        * earned_credits = 0 since both conditions are false.
     187             :        * new_credits_observed stays the same since it is already set
     188             :          to credits_in_stake and final_epoch_credits <= credits_in_stake
     189             : 
     190             :        Since earned_credits = 0 and new_credits_observed stays the same,
     191             :        points computation can be skipped. */
     192           0 :     if( FD_LIKELY( final_epoch_credits<=credits_in_stake ) ) continue;
     193             : 
     194           0 :     uint128 earned_credits = 0;
     195           0 :     if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
     196           0 :       earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
     197           0 :     } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
     198           0 :       earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
     199           0 :     }
     200             : 
     201           0 :     new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
     202             : 
     203           0 :     ulong stake_amount = fd_stakes_activating_and_deactivating(
     204           0 :         stake,
     205           0 :         epoch_credits->epoch[ i ],
     206           0 :         stake_history,
     207           0 :         new_rate_activation_epoch ).effective;
     208             : 
     209           0 :     points += (uint128)stake_amount * earned_credits;
     210           0 :   }
     211             : 
     212           0 :   result->points.ud = points;
     213           0 :   result->new_credits_observed = new_credits_observed;
     214           0 :   result->force_credits_update_with_skipped_reward = 0;
     215           0 : }
     216             : 
     217             : struct fd_commission_split {
     218             :   ulong voter_portion;
     219             :   ulong staker_portion;
     220             :   uint  is_split;
     221             : };
     222             : typedef struct fd_commission_split fd_commission_split_t;
     223             : 
     224             : /// returns commission split as (voter_portion, staker_portion, was_split) tuple
     225             : ///
     226             : /// if commission calculation is 100% one way or other, indicate with false for was_split
     227             : 
     228             : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L543
     229             : void
     230             : fd_vote_commission_split( uchar                   commission,
     231             :                           ulong                   on,
     232           0 :                           fd_commission_split_t * result ) {
     233           0 :   uint commission_split = fd_uint_min( (uint)commission, 100 );
     234           0 :   result->is_split      = (commission_split != 0 && commission_split != 100);
     235             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L545
     236           0 :   if( commission_split==0U ) {
     237           0 :     result->voter_portion  = 0;
     238           0 :     result->staker_portion = on;
     239           0 :     return;
     240           0 :   }
     241             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L546
     242           0 :   if( commission_split==100U ) {
     243           0 :     result->voter_portion  = on;
     244           0 :     result->staker_portion = 0;
     245           0 :     return;
     246           0 :   }
     247             :   /* Note: order of operations may matter for int division. That's why I didn't make the
     248             :    * optimization of getting out the common calculations */
     249             : 
     250             :   // ... This is copied from the solana comments...
     251             :   //
     252             :   // Calculate mine and theirs independently and symmetrically instead
     253             :   // of using the remainder of the other to treat them strictly
     254             :   // equally. This is also to cancel the rewarding if either of the
     255             :   // parties should receive only fractional lamports, resulting in not
     256             :   // being rewarded at all. Thus, note that we intentionally discard
     257             :   // any residual fractional lamports.
     258             : 
     259             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L548
     260           0 :   result->voter_portion =
     261           0 :       (ulong)((uint128)on * (uint128)commission_split / (uint128)100);
     262           0 :   result->staker_portion =
     263           0 :       (ulong)((uint128)on * (uint128)( 100 - commission_split ) / (uint128)100);
     264           0 : }
     265             : 
     266             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
     267             : static int
     268             : redeem_rewards( fd_stake_delegation_t const *   stake,
     269             :                 ulong                           vote_state_idx,
     270             :                 ulong                           rewarded_epoch,
     271             :                 ulong                           total_rewards,
     272             :                 uint128                         total_points,
     273             :                 fd_runtime_stack_t *            runtime_stack,
     274             :                 fd_calculated_stake_points_t *  stake_points_result,
     275           9 :                 fd_calculated_stake_rewards_t * result ) {
     276             : 
     277             :   /* The firedancer implementation of redeem_rewards inlines a lot of
     278             :      the helper functions that the Agave implementation uses.
     279             :      In Agave: redeem_rewards calls redeem_stake_rewards which calls
     280             :      calculate_stake_rewards. */
     281             : 
     282             :   // Drive credits_observed forward unconditionally when rewards are disabled
     283             :   // or when this is the stake's activation epoch
     284           9 :   if( total_rewards==0UL || stake->activation_epoch==rewarded_epoch ) {
     285           9 :       stake_points_result->force_credits_update_with_skipped_reward = 1;
     286           9 :   }
     287             : 
     288           9 :   if( stake_points_result->force_credits_update_with_skipped_reward ) {
     289           9 :     result->staker_rewards       = 0;
     290           9 :     result->voter_rewards        = 0;
     291           9 :     result->new_credits_observed = stake_points_result->new_credits_observed;
     292           9 :     return 0;
     293           9 :   }
     294           0 :   if( stake_points_result->points.ud==0 || total_points==0 ) {
     295           0 :     return 1;
     296           0 :   }
     297             : 
     298           0 :   uint128 rewards_u128;
     299           0 :   if( FD_UNLIKELY( __builtin_mul_overflow( stake_points_result->points.ud, (uint128)(total_rewards), &rewards_u128 ) ) ) {
     300           0 :     FD_LOG_ERR(( "Rewards intermediate calculation should fit within u128" ));
     301           0 :   }
     302             : 
     303           0 :   FD_TEST( total_points );
     304           0 :   rewards_u128 /=  (uint128) total_points;
     305             : 
     306           0 :   if( FD_UNLIKELY( rewards_u128>(uint128)ULONG_MAX ) ) {
     307           0 :     FD_LOG_ERR(( "Rewards should fit within u64" ));
     308           0 :   }
     309             : 
     310           0 :   ulong rewards = (ulong)rewards_u128;
     311           0 :   if( rewards == 0 ) {
     312           0 :     return 1;
     313           0 :   }
     314             : 
     315           0 :   uchar commission = runtime_stack->stakes.vote_ele[ vote_state_idx ].commission;
     316           0 :   fd_commission_split_t split_result;
     317           0 :   fd_vote_commission_split( commission, rewards, &split_result );
     318           0 :   if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
     319           0 :     return 1;
     320           0 :   }
     321             : 
     322           0 :   result->staker_rewards       = split_result.staker_portion;
     323           0 :   result->voter_rewards        = split_result.voter_portion;
     324           0 :   result->new_credits_observed = stake_points_result->new_credits_observed;
     325           0 :   return 0;
     326           0 : }
     327             : 
     328             : /* Returns the length of the given epoch in slots
     329             : 
     330             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
     331             : static ulong
     332             : get_slots_in_epoch( ulong                       epoch,
     333          30 :                     fd_epoch_schedule_t const * epoch_schedule ) {
     334          30 :   return epoch < epoch_schedule->first_normal_epoch ?
     335           0 :          1UL << fd_ulong_sat_add( epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO ) :
     336          30 :          epoch_schedule->slots_per_epoch;
     337          30 : }
     338             : 
     339             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
     340             : static double
     341             : epoch_duration_in_years( fd_bank_t const * bank,
     342           9 :                          ulong             prev_epoch ) {
     343           9 :   ulong slots_in_epoch = get_slots_in_epoch( prev_epoch, fd_bank_epoch_schedule_query( bank ) );
     344           9 :   return (double)slots_in_epoch / (double)fd_bank_slots_per_year_get( bank );
     345           9 : }
     346             : 
     347             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
     348             : static void
     349             : calculate_previous_epoch_inflation_rewards( fd_bank_t const *                   bank,
     350             :                                             ulong                               prev_epoch_capitalization,
     351             :                                             ulong                               prev_epoch,
     352           9 :                                             fd_prev_epoch_inflation_rewards_t * rewards ) {
     353           9 :   double slot_in_year = slot_in_year_for_inflation( bank );
     354             : 
     355           9 :   rewards->validator_rate               = validator( fd_bank_inflation_query( bank ), slot_in_year );
     356           9 :   rewards->foundation_rate              = foundation( fd_bank_inflation_query( bank ), slot_in_year );
     357           9 :   rewards->prev_epoch_duration_in_years = epoch_duration_in_years( bank, prev_epoch );
     358           9 :   rewards->validator_rewards            = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
     359           9 :   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 ));
     360           9 : }
     361             : 
     362             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/lib.rs#L29 */
     363             : static ulong
     364          18 : get_minimum_stake_delegation( fd_bank_t * bank ) {
     365          18 :   if( !FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
     366          18 :     return 0UL;
     367          18 :   }
     368             : 
     369           0 :   if( FD_FEATURE_ACTIVE_BANK( bank, stake_raise_minimum_delegation_to_1_sol ) ) {
     370           0 :     return LAMPORTS_PER_SOL;
     371           0 :   }
     372             : 
     373           0 :   return 1;
     374           0 : }
     375             : 
     376             : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
     377             : 
     378             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
     379             :  */
     380             :  static uint
     381             :  get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     382             :                                      ulong                       slot,
     383           9 :                                      ulong                       total_stake_accounts ) {
     384             :    /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
     385           9 :    if( epoch_schedule->warmup &&
     386           9 :        fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
     387           0 :      return 1UL;
     388           0 :    }
     389             : 
     390           9 :    ulong num_chunks = total_stake_accounts / (ulong)STAKE_ACCOUNT_STORES_PER_BLOCK + (total_stake_accounts % STAKE_ACCOUNT_STORES_PER_BLOCK != 0);
     391           9 :    num_chunks       = fd_ulong_max( num_chunks, 1UL );
     392           9 :    num_chunks       = fd_ulong_min( num_chunks,
     393           9 :                                     fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
     394           9 :    return (uint)num_chunks;
     395           9 :  }
     396             : 
     397             : /* Calculates epoch reward points from stake/vote accounts.
     398             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L445 */
     399             : static uint128
     400             : calculate_reward_points_partitioned( fd_accdb_user_t *              accdb,
     401             :                                      fd_funk_txn_xid_t const *      xid,
     402             :                                      fd_bank_t *                    bank,
     403             :                                      fd_stake_delegations_t const * stake_delegations,
     404             :                                      fd_stake_history_t const *     stake_history,
     405           9 :                                      fd_runtime_stack_t *           runtime_stack ) {
     406           9 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
     407             : 
     408             :   /* Calculate the points for each stake delegation */
     409           9 :   int _err[1];
     410           9 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     411           9 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     412           9 :   int is_some = fd_stakes_new_warmup_cooldown_rate_epoch(
     413           9 :       fd_bank_epoch_schedule_query( bank ),
     414           9 :       fd_bank_features_query( bank ),
     415           9 :       new_warmup_cooldown_rate_epoch,
     416           9 :       _err );
     417           9 :   if( FD_UNLIKELY( !is_some ) ) {
     418           0 :     new_warmup_cooldown_rate_epoch = NULL;
     419           0 :   }
     420             : 
     421           9 :   uint128 total_points = 0;
     422             : 
     423           9 :   fd_vote_rewards_t *     vote_ele     = runtime_stack->stakes.vote_ele;
     424           9 :   fd_vote_rewards_map_t * vote_ele_map = fd_type_pun( runtime_stack->stakes.vote_map_mem );
     425             : 
     426           9 :   fd_stake_delegations_iter_t iter_[1];
     427           9 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     428          18 :        !fd_stake_delegations_iter_done( iter );
     429           9 :        fd_stake_delegations_iter_next( iter ) ) {
     430           9 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     431           9 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     432             : 
     433           9 :     if( FD_UNLIKELY( stake_delegation->stake<minimum_stake_delegation ) ) {
     434           0 :       continue;
     435           0 :     }
     436             : 
     437           9 :     uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     438           9 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
     439             : 
     440           9 :     fd_calculated_stake_points_t   stake_points_result_[1];
     441           9 :     fd_calculated_stake_points_t * stake_points_result;
     442           9 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) ) {
     443           0 :       stake_points_result = stake_points_result_;
     444           9 :     } else {
     445           9 :       stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
     446           9 :     }
     447             : 
     448           9 :     fd_epoch_credits_t   epoch_credits_;
     449           9 :     fd_epoch_credits_t * epoch_credits = NULL;
     450           9 :     if( idx>=runtime_stack->expected_vote_accounts ) {
     451           0 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ idx ];
     452           0 :       fd_accdb_ro_t vote_ro[1];
     453           0 :       FD_TEST( fd_accdb_open_ro( accdb, vote_ro, xid, &vote_ele->pubkey ) );
     454             : 
     455           0 :       uchar __attribute__((aligned(128))) vsv_buf[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
     456           0 :       get_credits( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), vsv_buf, &epoch_credits_ );
     457           0 :       fd_accdb_close_ro( accdb, vote_ro );
     458           0 :       epoch_credits = &epoch_credits_;
     459           9 :     } else {
     460           9 :       epoch_credits = &runtime_stack->stakes.epoch_credits[ idx ];
     461           9 :     }
     462             : 
     463           9 :     calculate_stake_points_and_credits( epoch_credits,
     464           9 :                                         stake_history,
     465           9 :                                         stake_delegation,
     466           9 :                                         new_warmup_cooldown_rate_epoch,
     467           9 :                                         stake_points_result );
     468             : 
     469           9 :     total_points += stake_points_result->points.ud;
     470           9 :   }
     471             : 
     472           9 :   return total_points;
     473           9 : }
     474             : 
     475             : /* Calculates epoch rewards for stake/vote accounts.
     476             :    Returns vote rewards, stake rewards, and the sum of all stake rewards
     477             :    in lamports.
     478             : 
     479             :    In the future, the calculation will be cached in the snapshot, but
     480             :    for now we just re-calculate it (as Agave does).
     481             :    calculate_stake_vote_rewards is responsible for calculating
     482             :    stake account rewards based off of a combination of the
     483             :    stake delegation state as well as the vote account. If this
     484             :    calculation is done at the end of an epoch, we can just use the
     485             :    vote states at the end of the current epoch. However, because we
     486             :    are presumably booting up a node in the middle of rewards
     487             :    distribution, we need to make sure that we are using the vote
     488             :    states from the end of the previous epoch.
     489             : 
     490             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
     491             : static void
     492             : calculate_stake_vote_rewards( fd_accdb_user_t *              accdb,
     493             :                               fd_funk_txn_xid_t const *      xid,
     494             :                               fd_bank_t *                    bank,
     495             :                               fd_stake_delegations_t const * stake_delegations,
     496             :                               fd_capture_ctx_t *             capture_ctx FD_PARAM_UNUSED,
     497             :                               fd_stake_history_t const *     stake_history,
     498             :                               ulong                          rewarded_epoch,
     499             :                               ulong                          total_rewards,
     500             :                               uint128                        total_points,
     501             :                               fd_runtime_stack_t *           runtime_stack,
     502           9 :                               int                            is_recalculation ) {
     503             : 
     504           9 :   int _err[1];
     505           9 :   ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     506           9 :   ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     507           9 :   int is_some = fd_stakes_new_warmup_cooldown_rate_epoch(
     508           9 :       fd_bank_epoch_schedule_query( bank ),
     509           9 :       fd_bank_features_query( bank ),
     510           9 :       new_warmup_cooldown_rate_epoch,
     511           9 :       _err );
     512           9 :   if( FD_UNLIKELY( !is_some ) ) {
     513           0 :     new_warmup_cooldown_rate_epoch = NULL;
     514           0 :   }
     515             : 
     516           9 :   ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
     517             : 
     518           9 :   runtime_stack->stakes.stake_rewards_cnt = 0UL;
     519             : 
     520           9 :   fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
     521             : 
     522           9 :   uchar __attribute__((aligned(128))) vsv_buf[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
     523             : 
     524           9 :   fd_stake_delegations_iter_t iter_[1];
     525           9 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     526          18 :        !fd_stake_delegations_iter_done( iter );
     527           9 :        fd_stake_delegations_iter_next( iter ) ) {
     528           9 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     529           9 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     530             : 
     531           9 :     if( FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
     532           0 :       if( stake_delegation->stake<minimum_stake_delegation ) {
     533           0 :         continue;
     534           0 :       }
     535           0 :     }
     536             : 
     537           9 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
     538           9 :     if( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) {
     539           0 :       calculated_stake_rewards = calculated_stake_rewards_;
     540           9 :     } else {
     541           9 :       calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
     542           9 :     }
     543           9 :     calculated_stake_rewards->success = 0;
     544             : 
     545           9 :     fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
     546           9 :     fd_vote_rewards_map_t * vote_ele_map = fd_type_pun( runtime_stack->stakes.vote_map_mem );
     547           9 :     uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     548           9 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
     549             : 
     550           9 :     fd_calculated_stake_points_t   stake_points_result_[1];
     551           9 :     fd_calculated_stake_points_t * stake_points_result;
     552           9 :     if( is_recalculation || FD_UNLIKELY( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) ) {
     553           0 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ idx ];
     554             : 
     555           0 :       fd_epoch_credits_t   epoch_credits_;
     556           0 :       fd_epoch_credits_t * epoch_credits = NULL;
     557           0 :       if( idx<runtime_stack->expected_vote_accounts ) {
     558           0 :         epoch_credits = &runtime_stack->stakes.epoch_credits[ idx ];
     559           0 :       } else {
     560           0 :         fd_accdb_ro_t vote_ro[1];
     561           0 :         FD_TEST( fd_accdb_open_ro( accdb, vote_ro, xid, &vote_ele->pubkey ) );
     562           0 :         get_credits( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), vsv_buf, &epoch_credits_ );
     563           0 :         fd_accdb_close_ro( accdb, vote_ro );
     564           0 :         epoch_credits = &epoch_credits_;
     565           0 :       }
     566             : 
     567             :       /* We have not cached the stake points yet if we are recalculating
     568             :          stake rewards so we need to recalculate them. */
     569           0 :       calculate_stake_points_and_credits(
     570           0 :           epoch_credits,
     571           0 :           stake_history,
     572           0 :           stake_delegation,
     573           0 :           new_warmup_cooldown_rate_epoch,
     574           0 :           stake_points_result_ );
     575           0 :       stake_points_result = stake_points_result_;
     576           9 :     } else {
     577           9 :       stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
     578           9 :     }
     579             : 
     580             :     /* redeem_rewards is actually just responsible for calculating the
     581             :        vote and stake rewards for each stake account.  It does not do
     582             :        rewards redemption: it is a misnomer. */
     583           9 :     int err = redeem_rewards(
     584           9 :         stake_delegation,
     585           9 :         idx,
     586           9 :         rewarded_epoch,
     587           9 :         total_rewards,
     588           9 :         total_points,
     589           9 :         runtime_stack,
     590           9 :         stake_points_result,
     591           9 :         calculated_stake_rewards );
     592             : 
     593           9 :     if( FD_UNLIKELY( err!=0 ) ) {
     594           0 :       continue;
     595           0 :     }
     596             : 
     597           9 :     calculated_stake_rewards->success = 1;
     598             : 
     599           9 :     if( capture_ctx && capture_ctx->capture_solcap ) {
     600           0 :       uchar commission = runtime_stack->stakes.vote_ele[ idx ].commission;
     601           0 :       fd_capture_link_write_stake_reward_event( capture_ctx,
     602           0 :                                                 fd_bank_slot_get( bank ),
     603           0 :                                                 stake_delegation->stake_account,
     604           0 :                                                 stake_delegation->vote_account,
     605           0 :                                                 commission,
     606           0 :                                                 (long)calculated_stake_rewards->voter_rewards,
     607           0 :                                                 (long)calculated_stake_rewards->staker_rewards,
     608           0 :                                                 (long)calculated_stake_rewards->new_credits_observed );
     609           0 :     }
     610             : 
     611           9 :     runtime_stack->stakes.vote_ele[ idx ].vote_rewards += calculated_stake_rewards->voter_rewards;
     612           9 :     runtime_stack->stakes.stake_rewards_cnt++;
     613           9 :   }
     614           9 : }
     615             : 
     616             : static void
     617             : setup_stake_partitions( fd_accdb_user_t *              accdb,
     618             :                         fd_funk_txn_xid_t const *      xid,
     619             :                         fd_bank_t *                    bank,
     620             :                         fd_stake_history_t const *     stake_history,
     621             :                         fd_stake_delegations_t const * stake_delegations,
     622             :                         fd_runtime_stack_t *           runtime_stack,
     623             :                         fd_hash_t const *              parent_blockhash,
     624             :                         ulong                          starting_block_height,
     625             :                         uint                           num_partitions,
     626             :                         ulong                          rewarded_epoch,
     627             :                         ulong                          total_rewards,
     628           9 :                         uint128                        total_points ) {
     629             : 
     630           9 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
     631           9 :   uchar fork_idx = fd_stake_rewards_init( stake_rewards, fd_bank_epoch_get( bank ), parent_blockhash, starting_block_height, (uint)num_partitions );
     632           9 :   bank->data->stake_rewards_fork_id = fork_idx;
     633             : 
     634           9 :   uchar __attribute__((aligned(128))) vsv_buf[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
     635             : 
     636           9 :   fd_stake_delegations_iter_t iter_[1];
     637           9 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     638          18 :        !fd_stake_delegations_iter_done( iter );
     639           9 :        fd_stake_delegations_iter_next( iter ) ) {
     640           9 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     641           9 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     642             : 
     643           9 :     fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
     644           9 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
     645             : 
     646           9 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) ) {
     647             : 
     648           0 :       calculated_stake_rewards = calculated_stake_rewards_;
     649             : 
     650           0 :       fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
     651           0 :       fd_vote_rewards_map_t * vote_ele_map = fd_type_pun( runtime_stack->stakes.vote_map_mem );
     652           0 :       uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     653           0 :       if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
     654             : 
     655           0 :       int _err[1];
     656           0 :       ulong   new_warmup_cooldown_rate_epoch_val = 0UL;
     657           0 :       ulong * new_warmup_cooldown_rate_epoch     = &new_warmup_cooldown_rate_epoch_val;
     658           0 :       int is_some = fd_stakes_new_warmup_cooldown_rate_epoch(
     659           0 :           fd_bank_epoch_schedule_query( bank ),
     660           0 :           fd_bank_features_query( bank ),
     661           0 :           new_warmup_cooldown_rate_epoch,
     662           0 :           _err );
     663           0 :       if( FD_UNLIKELY( !is_some ) ) {
     664           0 :         new_warmup_cooldown_rate_epoch = NULL;
     665           0 :       }
     666             : 
     667           0 :       fd_epoch_credits_t   epoch_credits_;
     668           0 :       fd_epoch_credits_t * epoch_credits = NULL;
     669           0 :       if( idx>=runtime_stack->expected_vote_accounts ) {
     670           0 :         fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ idx ];
     671           0 :         fd_accdb_ro_t vote_ro[1];
     672           0 :         FD_TEST( fd_accdb_open_ro( accdb, vote_ro, xid, &vote_ele->pubkey ) );
     673           0 :         get_credits( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), vsv_buf, &epoch_credits_ );
     674           0 :         fd_accdb_close_ro( accdb, vote_ro );
     675           0 :         epoch_credits = &epoch_credits_;
     676           0 :       } else {
     677           0 :         epoch_credits = &runtime_stack->stakes.epoch_credits[ idx ];
     678           0 :       }
     679             : 
     680           0 :       fd_calculated_stake_points_t stake_points_result[1];
     681           0 :       calculate_stake_points_and_credits(
     682           0 :           epoch_credits,
     683           0 :           stake_history,
     684           0 :           stake_delegation,
     685           0 :           new_warmup_cooldown_rate_epoch,
     686           0 :           stake_points_result );
     687             : 
     688             :       /* redeem_rewards is actually just responsible for calculating the
     689             :          vote and stake rewards for each stake account.  It does not do
     690             :          rewards redemption: it is a misnomer. */
     691           0 :       int err = redeem_rewards(
     692           0 :           stake_delegation,
     693           0 :           idx,
     694           0 :           rewarded_epoch,
     695           0 :           total_rewards,
     696           0 :           total_points,
     697           0 :           runtime_stack,
     698           0 :           stake_points_result,
     699           0 :           calculated_stake_rewards );
     700           0 :       calculated_stake_rewards->success = err==0;
     701           9 :     } else {
     702           9 :       calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
     703           9 :     }
     704             : 
     705           9 :     if( FD_UNLIKELY( !calculated_stake_rewards->success ) ) continue;
     706             : 
     707           9 :     fd_stake_rewards_insert(
     708           9 :       stake_rewards,
     709           9 :       fork_idx,
     710           9 :       &stake_delegation->stake_account,
     711           9 :       calculated_stake_rewards->staker_rewards,
     712           9 :       calculated_stake_rewards->new_credits_observed
     713           9 :     );
     714           9 :   }
     715           9 : }
     716             : 
     717             : /* Calculate epoch reward and return vote and stake rewards.
     718             : 
     719             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
     720             : static uint128
     721             : calculate_validator_rewards( fd_bank_t *                    bank,
     722             :                              fd_accdb_user_t *              accdb,
     723             :                              fd_funk_txn_xid_t const *      xid,
     724             :                              fd_runtime_stack_t *           runtime_stack,
     725             :                              fd_stake_delegations_t const * stake_delegations,
     726             :                              fd_capture_ctx_t *             capture_ctx,
     727             :                              ulong                          rewarded_epoch,
     728           9 :                              ulong *                        rewards_out ) {
     729             : 
     730           9 :   fd_stake_history_t stake_history[1];
     731           9 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, xid, stake_history ) ) ) {
     732           0 :     FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
     733           0 :   }
     734             : 
     735             :   /* Calculate the epoch reward points from stake/vote accounts */
     736           9 :   uint128 total_points = calculate_reward_points_partitioned(
     737           9 :       accdb,
     738           9 :       xid,
     739           9 :       bank,
     740           9 :       stake_delegations,
     741           9 :       stake_history,
     742           9 :       runtime_stack );
     743             : 
     744             :   /* If there are no points, then we set the rewards to 0. */
     745           9 :   *rewards_out = total_points>0UL ? *rewards_out: 0UL;
     746             : 
     747           9 :   if( capture_ctx && capture_ctx->capture_solcap ) {
     748           0 :     ulong epoch = fd_bank_epoch_get( bank );
     749           0 :     ulong slot  = fd_bank_slot_get( bank );
     750           0 :     fd_capture_link_write_stake_rewards_begin( capture_ctx,
     751           0 :                                                slot,
     752           0 :                                                epoch,
     753           0 :                                                epoch-1UL, /* FIXME: this is not strictly correct */
     754           0 :                                                *rewards_out,
     755           0 :                                                (ulong)total_points );
     756           0 :   }
     757             : 
     758             :   /* Calculate the stake and vote rewards for each account. We want to
     759             :      use the vote states from the end of the current_epoch. */
     760           9 :   calculate_stake_vote_rewards(
     761           9 :       accdb,
     762           9 :       xid,
     763           9 :       bank,
     764           9 :       stake_delegations,
     765           9 :       capture_ctx,
     766           9 :       stake_history,
     767           9 :       rewarded_epoch,
     768           9 :       *rewards_out,
     769           9 :       total_points,
     770           9 :       runtime_stack,
     771           9 :       0 );
     772             : 
     773           9 :   fd_hash_t const * parent_blockhash      = fd_blockhashes_peek_last_hash( fd_bank_block_hash_queue_query( bank ) );
     774           9 :   ulong             starting_block_height = fd_bank_block_height_get( bank ) + REWARD_CALCULATION_NUM_BLOCKS;
     775           9 :   uint              num_partitions        = get_reward_distribution_num_blocks( fd_bank_epoch_schedule_query( bank ),
     776           9 :                                                                                 fd_bank_slot_get( bank ),
     777           9 :                                                                                 runtime_stack->stakes.stake_rewards_cnt );
     778             : 
     779           9 :   setup_stake_partitions(
     780           9 :       accdb,
     781           9 :       xid,
     782           9 :       bank,
     783           9 :       stake_history,
     784           9 :       stake_delegations,
     785           9 :       runtime_stack,
     786           9 :       parent_blockhash,
     787           9 :       starting_block_height,
     788           9 :       num_partitions,
     789           9 :       rewarded_epoch,
     790           9 :       *rewards_out,
     791           9 :       total_points );
     792             : 
     793           9 :   return total_points;
     794           9 : }
     795             : 
     796             : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
     797             : 
     798             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L277 */
     799             : static void
     800             : calculate_rewards_for_partitioning( fd_bank_t *                            bank,
     801             :                                     fd_accdb_user_t *                      accdb,
     802             :                                     fd_funk_txn_xid_t const *              xid,
     803             :                                     fd_runtime_stack_t *                   runtime_stack,
     804             :                                     fd_stake_delegations_t const *         stake_delegations,
     805             :                                     fd_capture_ctx_t *                     capture_ctx,
     806             :                                     ulong                                  prev_epoch,
     807           9 :                                     fd_partitioned_rewards_calculation_t * result ) {
     808           9 :   fd_prev_epoch_inflation_rewards_t rewards;
     809             : 
     810           9 :   calculate_previous_epoch_inflation_rewards( bank,
     811           9 :                                               fd_bank_capitalization_get( bank ),
     812           9 :                                               prev_epoch,
     813           9 :                                               &rewards );
     814             : 
     815           9 :   ulong total_rewards = rewards.validator_rewards;
     816             : 
     817           9 :   uint128 points = calculate_validator_rewards( bank,
     818           9 :                                                 accdb,
     819           9 :                                                 xid,
     820           9 :                                                 runtime_stack,
     821           9 :                                                 stake_delegations,
     822           9 :                                                 capture_ctx,
     823           9 :                                                 prev_epoch,
     824           9 :                                                 &total_rewards );
     825             : 
     826             :   /* The agave client does not partition the stake rewards until the
     827             :      first distribution block.  We calculate the partitions during the
     828             :      boundary. */
     829           9 :   result->validator_points             = points;
     830           9 :   result->validator_rewards            = total_rewards;
     831           9 :   result->validator_rate               = rewards.validator_rate;
     832           9 :   result->foundation_rate              = rewards.foundation_rate;
     833           9 :   result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
     834           9 :   result->capitalization               = fd_bank_capitalization_get( bank );
     835           9 : }
     836             : 
     837             : /* Calculate rewards from previous epoch and distribute vote rewards
     838             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L148 */
     839             : static void
     840             : calculate_rewards_and_distribute_vote_rewards( fd_bank_t *                    bank,
     841             :                                                fd_accdb_user_t *              accdb,
     842             :                                                fd_funk_txn_xid_t const *      xid,
     843             :                                                fd_runtime_stack_t *           runtime_stack,
     844             :                                                fd_stake_delegations_t const * stake_delegations,
     845             :                                                fd_capture_ctx_t *             capture_ctx,
     846           9 :                                                ulong                          prev_epoch ) {
     847             : 
     848           9 :   fd_vote_rewards_t *     vote_ele_pool = runtime_stack->stakes.vote_ele;
     849           9 :   fd_vote_rewards_map_t * vote_ele_map  = fd_type_pun( runtime_stack->stakes.vote_map_mem );
     850             : 
     851             :   /* First we must compute the stake and vote rewards for the just
     852             :      completed epoch.  We store the stake account rewards and vote
     853             :      states rewards in the bank */
     854             : 
     855           9 :   fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
     856           9 :   calculate_rewards_for_partitioning( bank,
     857           9 :                                       accdb,
     858           9 :                                       xid,
     859           9 :                                       runtime_stack,
     860           9 :                                       stake_delegations,
     861           9 :                                       capture_ctx,
     862           9 :                                       prev_epoch,
     863           9 :                                       rewards_calc_result );
     864             : 
     865             : 
     866             :   /* Iterate over all the vote reward nodes and distribute the rewards
     867             :      to the vote accounts.  After each reward has been paid out,
     868             :      calcualte the lthash for each vote account. */
     869           9 :   ulong distributed_rewards = 0UL;
     870           9 :   for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
     871          18 :        !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
     872           9 :        iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
     873             : 
     874           9 :     uint idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
     875           9 :     fd_vote_rewards_t * ele = &vote_ele_pool[idx];
     876             : 
     877           9 :     ulong rewards = runtime_stack->stakes.vote_ele[ idx ].vote_rewards;
     878           9 :     if( rewards==0UL ) {
     879           9 :       continue;
     880           9 :     }
     881             : 
     882             :     /* Credit rewards to vote account (creating a new system account if
     883             :        it does not exist) */
     884           0 :     fd_pubkey_t const * vote_pubkey = &ele->pubkey;
     885           0 :     fd_accdb_rw_t rw[1];
     886           0 :     fd_accdb_open_rw( accdb, rw, xid, vote_pubkey, 0UL, FD_ACCDB_FLAG_CREATE );
     887           0 :     fd_lthash_value_t prev_hash[1];
     888           0 :     fd_hashes_account_lthash( vote_pubkey, rw->meta, fd_accdb_ref_data_const( rw->ro ), prev_hash );
     889           0 :     ulong acc_lamports = fd_accdb_ref_lamports( rw->ro );
     890           0 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( acc_lamports, rewards, &acc_lamports ) ) ) {
     891           0 :       FD_BASE58_ENCODE_32_BYTES( vote_pubkey->key, addr_b58 );
     892           0 :       FD_LOG_EMERG(( "integer overflow while crediting %lu vote reward lamports to %s (previous balance %lu)",
     893           0 :                      rewards, addr_b58, fd_accdb_ref_lamports( rw->ro ) ));
     894           0 :     }
     895           0 :     fd_accdb_ref_lamports_set( rw, acc_lamports );
     896           0 :     fd_hashes_update_lthash( vote_pubkey, rw->meta, prev_hash,bank, capture_ctx );
     897           0 :     fd_accdb_close_rw( accdb, rw );
     898             : 
     899           0 :     distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards );
     900           0 :   }
     901             : 
     902             :   /* Verify that we didn't pay any more than we expected to */
     903           9 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
     904           9 :   ulong total_stake_rewards = fd_stake_rewards_total_rewards( stake_rewards, bank->data->stake_rewards_fork_id );
     905             : 
     906           9 :   ulong total_rewards = fd_ulong_sat_add( distributed_rewards, total_stake_rewards );
     907           9 :   if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
     908           0 :     FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
     909           0 :   }
     910             : 
     911           9 :   fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + distributed_rewards );
     912             : 
     913           9 :   runtime_stack->stakes.distributed_rewards = distributed_rewards;
     914           9 :   runtime_stack->stakes.total_rewards       = rewards_calc_result->validator_rewards;
     915           9 :   runtime_stack->stakes.total_points.ud     = rewards_calc_result->validator_points;
     916           9 : }
     917             : 
     918             : /* Distributes a single partitioned reward to a single stake account */
     919             : static int
     920             : distribute_epoch_reward_to_stake_acc( fd_bank_t *               bank,
     921             :                                       fd_accdb_user_t *         accdb,
     922             :                                       fd_funk_txn_xid_t const * xid,
     923             :                                       fd_capture_ctx_t *        capture_ctx,
     924             :                                       fd_pubkey_t *             stake_pubkey,
     925             :                                       ulong                     reward_lamports,
     926           0 :                                       ulong                     new_credits_observed ) {
     927             : 
     928           0 :   fd_accdb_rw_t rw[1];
     929           0 :   if( FD_UNLIKELY( !fd_accdb_open_rw( accdb, rw, xid, stake_pubkey, 0UL, 0 ) ) ) {
     930           0 :     return 1;  /* account does not exist */
     931           0 :   }
     932             : 
     933           0 :   fd_lthash_value_t prev_hash[1];
     934           0 :   fd_hashes_account_lthash( stake_pubkey, rw->meta, fd_accdb_ref_data_const( rw->ro ), prev_hash );
     935           0 :   fd_stake_state_v2_t stake_state[1] = {0};
     936           0 :   if( 0!=fd_stakes_get_state( rw->meta, stake_state ) ||
     937           0 :       !fd_stake_state_v2_is_stake( stake_state ) ) {
     938           0 :     fd_accdb_close_rw( accdb, rw );
     939           0 :     return 1;  /* not a valid stake account */
     940           0 :   }
     941             : 
     942             :   /* Credit rewards to stake account */
     943           0 :   ulong acc_lamports = fd_accdb_ref_lamports( rw->ro );
     944           0 :   if( FD_UNLIKELY( __builtin_uaddl_overflow( acc_lamports, reward_lamports, &acc_lamports ) ) ) {
     945           0 :     FD_BASE58_ENCODE_32_BYTES( stake_pubkey->key, addr_b58 );
     946           0 :     FD_LOG_EMERG(( "integer overflow while crediting %lu stake reward lamports to %s (previous balance %lu)",
     947           0 :                     reward_lamports, addr_b58, fd_accdb_ref_lamports( rw->ro ) ));
     948           0 :   }
     949           0 :   fd_accdb_ref_lamports_set( rw, acc_lamports );
     950             : 
     951           0 :   ulong old_credits_observed                      = stake_state->inner.stake.stake.credits_observed;
     952           0 :   stake_state->inner.stake.stake.credits_observed = new_credits_observed;
     953           0 :   stake_state->inner.stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->inner.stake.stake.delegation.stake,
     954           0 :                                                                       reward_lamports );
     955             : 
     956           0 :   fd_stake_delegations_t * stake_delegations_upd = fd_bank_stake_delegations_modify( bank );
     957           0 :   fd_stake_delegations_fork_update( stake_delegations_upd,
     958           0 :                                     bank->data->stake_delegations_fork_id,
     959           0 :                                     stake_pubkey,
     960           0 :                                     &stake_state->inner.stake.stake.delegation.voter_pubkey,
     961           0 :                                     stake_state->inner.stake.stake.delegation.stake,
     962           0 :                                     stake_state->inner.stake.stake.delegation.activation_epoch,
     963           0 :                                     stake_state->inner.stake.stake.delegation.deactivation_epoch,
     964           0 :                                     stake_state->inner.stake.stake.credits_observed,
     965           0 :                                     stake_state->inner.stake.stake.delegation.warmup_cooldown_rate );
     966             : 
     967           0 :   if( capture_ctx && capture_ctx->capture_solcap ) {
     968           0 :     fd_capture_link_write_stake_account_payout( capture_ctx,
     969           0 :                                                 fd_bank_slot_get( bank ),
     970           0 :                                                 *stake_pubkey,
     971           0 :                                                 fd_bank_slot_get( bank ),
     972           0 :                                                 acc_lamports,
     973           0 :                                                 (long)reward_lamports,
     974           0 :                                                 new_credits_observed,
     975           0 :                                                 (long)( new_credits_observed - old_credits_observed ),
     976           0 :                                                 stake_state->inner.stake.stake.delegation.stake,
     977           0 :                                                 (long)reward_lamports );
     978           0 :   }
     979             : 
     980           0 :   fd_bincode_encode_ctx_t ctx = { .data=fd_accdb_ref_data( rw ), .dataend=(uchar *)fd_accdb_ref_data( rw )+fd_accdb_ref_data_sz( rw->ro ) };
     981           0 :   if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx )!=FD_BINCODE_SUCCESS ) ) {
     982           0 :     FD_LOG_ERR(( "fd_stake_state_encode failed" ));
     983           0 :   }
     984             : 
     985           0 :   fd_hashes_update_lthash( stake_pubkey, rw->meta, prev_hash, bank, capture_ctx );
     986           0 :   fd_accdb_close_rw( accdb, rw );
     987             : 
     988           0 :   return 0;
     989           0 : }
     990             : 
     991             : /* Process reward credits for a partition of rewards.  Store the rewards
     992             :    to AccountsDB, update reward history record and total capitalization
     993             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
     994             : static void
     995             : distribute_epoch_rewards_in_partition( fd_stake_rewards_t *      stake_rewards,
     996             :                                        ulong                     partition_idx,
     997             :                                        fd_bank_t *               bank,
     998             :                                        fd_accdb_user_t *         accdb,
     999             :                                        fd_funk_txn_xid_t const * xid,
    1000           0 :                                        fd_capture_ctx_t *        capture_ctx ) {
    1001             : 
    1002           0 :   ulong lamports_distributed = 0UL;
    1003           0 :   ulong lamports_burned      = 0UL;
    1004             : 
    1005           0 :   for( fd_stake_rewards_iter_init( stake_rewards, bank->data->stake_rewards_fork_id, (ushort)partition_idx );
    1006           0 :        !fd_stake_rewards_iter_done( stake_rewards );
    1007           0 :        fd_stake_rewards_iter_next( stake_rewards, bank->data->stake_rewards_fork_id ) ) {
    1008           0 :     fd_pubkey_t pubkey;
    1009           0 :     ulong       lamports;
    1010           0 :     ulong       credits_observed;
    1011           0 :     fd_stake_rewards_iter_ele( stake_rewards, bank->data->stake_rewards_fork_id, &pubkey, &lamports, &credits_observed );
    1012             : 
    1013           0 :     if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc( bank,
    1014           0 :                                                           accdb,
    1015           0 :                                                           xid,
    1016           0 :                                                           capture_ctx,
    1017           0 :                                                           &pubkey,
    1018           0 :                                                           lamports,
    1019           0 :                                                           credits_observed ) )  ) {
    1020           0 :       lamports_distributed += lamports;
    1021           0 :     } else {
    1022           0 :       lamports_burned += lamports;
    1023           0 :     }
    1024           0 :   }
    1025             : 
    1026             :   /* Update the epoch rewards sysvar with the amount distributed and burnt */
    1027           0 :   fd_sysvar_epoch_rewards_distribute( bank, accdb, xid, capture_ctx, lamports_distributed + lamports_burned );
    1028             : 
    1029           0 :   FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
    1030             : 
    1031           0 :   fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + lamports_distributed );
    1032           0 : }
    1033             : 
    1034             : /* Process reward distribution for the block if it is inside reward interval.
    1035             : 
    1036             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L42 */
    1037             : void
    1038             : fd_distribute_partitioned_epoch_rewards( fd_bank_t *               bank,
    1039             :                                          fd_accdb_user_t *         accdb,
    1040             :                                          fd_funk_txn_xid_t const * xid,
    1041         108 :                                          fd_capture_ctx_t *        capture_ctx ) {
    1042         108 :   if( FD_LIKELY( bank->data->stake_rewards_fork_id==UCHAR_MAX ) ) return;
    1043             : 
    1044          21 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1045             : 
    1046          21 :   ulong block_height                       = fd_bank_block_height_get( bank );
    1047          21 :   ulong distribution_starting_block_height = fd_stake_rewards_starting_block_height( stake_rewards, bank->data->stake_rewards_fork_id );
    1048          21 :   ulong distribution_end_exclusive         = fd_stake_rewards_exclusive_ending_block_height( stake_rewards, bank->data->stake_rewards_fork_id );
    1049             : 
    1050          21 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
    1051          21 :   ulong                       epoch          = fd_bank_epoch_get( bank );
    1052             : 
    1053          21 :   if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_schedule ) <= fd_stake_rewards_num_partitions( stake_rewards, bank->data->stake_rewards_fork_id ) ) ) {
    1054           0 :     FD_LOG_CRIT(( "Should not be distributing rewards" ));
    1055           0 :   }
    1056             : 
    1057          21 :   if( FD_UNLIKELY( block_height>=distribution_starting_block_height && block_height<distribution_end_exclusive ) ) {
    1058             : 
    1059           0 :     ulong partition_idx = block_height-distribution_starting_block_height;
    1060           0 :     distribute_epoch_rewards_in_partition( stake_rewards, partition_idx, bank, accdb, xid, capture_ctx );
    1061             : 
    1062             :     /* If we have finished distributing rewards, set the status to inactive */
    1063           0 :     if( fd_ulong_sat_add( block_height, 1UL )>=distribution_end_exclusive ) {
    1064           0 :       fd_sysvar_epoch_rewards_set_inactive( bank, accdb, xid, capture_ctx );
    1065           0 :       bank->data->stake_rewards_fork_id = UCHAR_MAX;
    1066           0 :     }
    1067           0 :   }
    1068          21 : }
    1069             : 
    1070             : /* Partitioned epoch rewards entry-point.
    1071             : 
    1072             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L102
    1073             : */
    1074             : void
    1075             : fd_begin_partitioned_rewards( fd_bank_t *                    bank,
    1076             :                               fd_accdb_user_t *              accdb,
    1077             :                               fd_funk_txn_xid_t const *      xid,
    1078             :                               fd_runtime_stack_t *           runtime_stack,
    1079             :                               fd_capture_ctx_t *             capture_ctx,
    1080             :                               fd_stake_delegations_t const * stake_delegations,
    1081             :                               fd_hash_t const *              parent_blockhash,
    1082           9 :                               ulong                          parent_epoch ) {
    1083             : 
    1084           9 :   calculate_rewards_and_distribute_vote_rewards(
    1085           9 :       bank,
    1086           9 :       accdb,
    1087           9 :       xid,
    1088           9 :       runtime_stack,
    1089           9 :       stake_delegations,
    1090           9 :       capture_ctx,
    1091           9 :       parent_epoch );
    1092             : 
    1093             :   /* Once the rewards for vote accounts have been distributed and stake
    1094             :      account rewards have been calculated, we can now set our epoch
    1095             :      reward status to be active and we can initialize the epoch rewards
    1096             :      sysvar.  This sysvar is then deleted once all of the partitioned
    1097             :      stake rewards have been distributed.
    1098             : 
    1099             :      The Agave client calculates the partitions for each stake reward
    1100             :      when the first distribution block is reached.  The Firedancer
    1101             :      client differs here since we hash the partitions during the epoch
    1102             :      boundary. */
    1103             : 
    1104           9 :   ulong distribution_starting_block_height = fd_bank_block_height_get( bank ) + REWARD_CALCULATION_NUM_BLOCKS;
    1105           9 :   uint  num_partitions                     = fd_stake_rewards_num_partitions( fd_bank_stake_rewards_query( bank ), bank->data->stake_rewards_fork_id );
    1106             : 
    1107           9 :   fd_sysvar_epoch_rewards_init(
    1108           9 :       bank,
    1109           9 :       accdb,
    1110           9 :       xid,
    1111           9 :       capture_ctx,
    1112           9 :       runtime_stack->stakes.distributed_rewards,
    1113           9 :       distribution_starting_block_height,
    1114           9 :       num_partitions,
    1115           9 :       runtime_stack->stakes.total_rewards,
    1116           9 :       runtime_stack->stakes.total_points.ud,
    1117           9 :       parent_blockhash );
    1118           9 : }
    1119             : 
    1120             : /*
    1121             :     Re-calculates partitioned stake rewards.
    1122             :     This updates the slot context's epoch reward status with the recalculated partitioned rewards.
    1123             : 
    1124             :     https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521 */
    1125             : void
    1126             : fd_rewards_recalculate_partitioned_rewards( fd_banks_t *              banks,
    1127             :                                             fd_bank_t *               bank,
    1128             :                                             fd_accdb_user_t *         accdb,
    1129             :                                             fd_funk_txn_xid_t const * xid,
    1130             :                                             fd_runtime_stack_t *      runtime_stack,
    1131           0 :                                             fd_capture_ctx_t *        capture_ctx ) {
    1132             : 
    1133           0 :   fd_sysvar_epoch_rewards_t epoch_rewards_sysvar[1];
    1134           0 :   if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read( accdb, xid, epoch_rewards_sysvar ) ) ) {
    1135           0 :     FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
    1136           0 :     return;
    1137           0 :   }
    1138             : 
    1139           0 :   FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
    1140             : 
    1141           0 :   if( FD_UNLIKELY( !epoch_rewards_sysvar->active ) ) {
    1142           0 :     FD_LOG_DEBUG(( "epoch rewards is inactive" ));
    1143           0 :     return;
    1144           0 :   }
    1145             : 
    1146             :   /* If partitioned rewards are active, the rewarded epoch is always the immediately
    1147             :       preceeding epoch.
    1148             : 
    1149             :       https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
    1150           0 :   FD_LOG_DEBUG(( "epoch rewards is active" ));
    1151             : 
    1152           0 :   ulong const epoch          = fd_bank_epoch_get( bank );
    1153           0 :   ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
    1154             : 
    1155           0 :   int _err[1] = {0};
    1156           0 :   ulong new_warmup_cooldown_rate_epoch_;
    1157           0 :   ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_;
    1158           0 :   int is_some = fd_stakes_new_warmup_cooldown_rate_epoch(
    1159           0 :       fd_bank_epoch_schedule_query( bank ),
    1160           0 :       fd_bank_features_query( bank ),
    1161           0 :       new_warmup_cooldown_rate_epoch,
    1162           0 :       _err );
    1163           0 :   if( FD_UNLIKELY( !is_some ) ) {
    1164           0 :     new_warmup_cooldown_rate_epoch = NULL;
    1165           0 :   }
    1166             : 
    1167           0 :   fd_stake_history_t stake_history[1];
    1168           0 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, xid, stake_history ) ) ) {
    1169           0 :     FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
    1170           0 :   }
    1171             : 
    1172           0 :   fd_stake_delegations_t const * stake_delegations = fd_bank_stake_delegations_frontier_query( banks, bank );
    1173           0 :   if( FD_UNLIKELY( !stake_delegations ) ) {
    1174           0 :     FD_LOG_CRIT(( "stake_delegations is NULL" ));
    1175           0 :   }
    1176             : 
    1177           0 :   calculate_stake_vote_rewards(
    1178           0 :       accdb,
    1179           0 :       xid,
    1180           0 :       bank,
    1181           0 :       stake_delegations,
    1182           0 :       capture_ctx,
    1183           0 :       stake_history,
    1184           0 :       rewarded_epoch,
    1185           0 :       epoch_rewards_sysvar->total_rewards,
    1186           0 :       epoch_rewards_sysvar->total_points.ud,
    1187           0 :       runtime_stack,
    1188           0 :       1 );
    1189             : 
    1190           0 :   setup_stake_partitions(
    1191           0 :       accdb,
    1192           0 :       xid,
    1193           0 :       bank,
    1194           0 :       stake_history,
    1195           0 :       stake_delegations,
    1196           0 :       runtime_stack,
    1197           0 :       &epoch_rewards_sysvar->parent_blockhash,
    1198           0 :       epoch_rewards_sysvar->distribution_starting_block_height,
    1199           0 :       (uint)epoch_rewards_sysvar->num_partitions,
    1200           0 :       rewarded_epoch,
    1201           0 :       epoch_rewards_sysvar->total_rewards,
    1202           0 :       epoch_rewards_sysvar->total_points.ud );
    1203             : 
    1204           0 :   fd_bank_stake_delegations_end_frontier_query( banks, bank );
    1205           0 : }

Generated by: LCOV version 1.14