LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 449 622 72.2 %
Date: 2026-05-16 06:43:53 Functions: 24 25 96.0 %

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

Generated by: LCOV version 1.14