LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 866 1018 85.1 %
Date: 2026-11-12 04:25:03 Functions: 49 99 49.5 %

          Line data    Source code
       1             : #include "fd_rewards.h"
       2             : #include "fd_stake_rewards.h"
       3             : #include "fd_reward_epoch_delegated_stakes.h"
       4             : #include "fd_epoch_inflation_account.h"
       5             : #include "fd_integer_inflation.h"
       6             : 
       7             : #include "../runtime/sysvar/fd_sysvar_epoch_rewards.h"
       8             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       9             : #include "../runtime/sysvar/fd_sysvar_rent.h"
      10             : #include "../runtime/fd_hashes.h"
      11             : #include "../../ballet/lthash/fd_lthash_adder.h"
      12             : #include "../stakes/fd_stakes.h"
      13             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
      14             : #include "../runtime/fd_system_ids.h"
      15             : #include "../capture/fd_capture_ctx.h"
      16             : #include "../runtime/fd_runtime_stack_tmpl.h"
      17             : #include "../runtime/fd_accdb_svm.h"
      18             : #include "fd_rewards_base.h"
      19             : #include "../events/fd_event_runtime.h"
      20             : 
      21             : #include <math.h>
      22             : 
      23             : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/feature-set/src/lib.rs#L1537-L1541 */
      24           3 : #define FD_DOUBLE_DISINFLATION_TAPER (0.30)
      25             : 
      26             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
      27             : static double
      28         900 : total( fd_inflation_t const * inflation, double year ) {
      29         900 :   double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
      30         900 :   return (tapered > inflation->terminal) ? tapered : inflation->terminal;
      31         900 : }
      32             : 
      33             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
      34             : static double
      35         687 : foundation( fd_inflation_t const * inflation, double year ) {
      36         687 :   return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
      37         687 : }
      38             : 
      39             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
      40             : static double
      41         342 : validator( fd_inflation_t const * inflation, double year) {
      42             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
      43         342 :   FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
      44             :   /* We need 2 independent rounded ops like in Agave. Volatile blocks FMA contraction.
      45             :      https://github.com/anza-xyz/solana-sdk/blob/inflation%40v3.1.1/inflation/src/lib.rs#L105-L116 */
      46         342 :   volatile double foundation_portion = foundation( inflation, year );
      47         342 :   return total( inflation, year ) - foundation_portion;
      48         342 : }
      49             : 
      50             : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
      51             :     activation slot of the following features:
      52             :     - devnet_and_testnet
      53             :     - full_inflation_enable, if full_inflation_vote has been activated
      54             : 
      55             :     https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
      56             : static FD_FN_PURE ulong
      57        2067 : get_inflation_start_slot( fd_bank_t const * bank ) {
      58        2067 :   ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
      59        2067 :       ? bank->f.features.devnet_and_testnet
      60        2067 :       : ULONG_MAX;
      61             : 
      62        2067 :   ulong enable = bank->f.features.full_inflation_enable;
      63             : 
      64        2067 :   ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
      65        2067 :   if( min_slot == ULONG_MAX ) {
      66        1548 :     if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
      67          12 :       min_slot = bank->f.features.pico_inflation;
      68        1536 :     } else {
      69        1536 :       min_slot = 0;
      70        1536 :     }
      71        1548 :   }
      72        2067 :   return min_slot;
      73        2067 : }
      74             : 
      75             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2921-L2928 */
      76             : static ulong
      77             : inflation_start_slot_aligned_to_rewards( fd_bank_t const *           bank,
      78        2067 :                                          fd_epoch_schedule_t const * epoch_schedule ) {
      79        2067 :   ulong inflation_activation_slot = get_inflation_start_slot( bank );
      80        2067 :   return fd_epoch_slot0( epoch_schedule,
      81        2067 :                          fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule, inflation_activation_slot, NULL ), 1UL ) );
      82        2067 : }
      83             : 
      84             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2915-L2918 */
      85             : static ulong
      86             : get_inflation_num_slots( fd_bank_t const *           bank,
      87             :                          fd_epoch_schedule_t const * epoch_schedule,
      88         324 :                          ulong                       slot ) {
      89         324 :   ulong inflation_start_slot = inflation_start_slot_aligned_to_rewards( bank, epoch_schedule );
      90         324 :   return fd_epoch_slot0( epoch_schedule, fd_slot_to_epoch( epoch_schedule, slot, NULL ) ) - inflation_start_slot;
      91         324 : }
      92             : 
      93             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2931-L2935 */
      94             : static double
      95         324 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
      96         324 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
      97         324 :   ulong num_slots            = get_inflation_num_slots( bank, epoch_schedule, bank->f.slot );
      98         324 :   ulong inflation_start_slot = inflation_start_slot_aligned_to_rewards( bank, epoch_schedule );
      99         324 :   return fd_slot_params_slot_range_duration_years( bank,
     100         324 :                                                    inflation_start_slot, inflation_start_slot + num_slots );
     101         324 : }
     102             : 
     103             : /* https://github.com/anza-xyz/agave/blob/9bcf93e368b5fb52c21677ed8c7f0360eb93b5b7/runtime/src/bank.rs#L6265-L6270 */
     104             : void
     105           3 : fd_apply_double_disinflation_rate( fd_bank_t * bank ) {
     106           3 :   bank->f.inflation.taper = FD_DOUBLE_DISINFLATION_TAPER;
     107           3 : }
     108             : 
     109             : /* https://github.com/anza-xyz/agave/blob/9bcf93e368b5fb52c21677ed8c7f0360eb93b5b7/runtime/src/bank.rs#L6257-L6261 */
     110             : void
     111           3 : fd_apply_integer_inflation_initial_rate( fd_bank_t * bank ) {
     112             :   /* Nothing reads this value after remove_runtime_float_ops is active,
     113             :      but we write it here so that our snapshot manifest is consistent
     114             :      with Agave's. */
     115           3 :   bank->f.inflation.initial = 0.08;
     116           3 : }
     117             : 
     118             : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/inflation_rewards/mod.rs#L412-L440 */
     119             : static void
     120             : fd_vote_commission_split_preserve_lamports( ushort                  commission,
     121             :                                             ulong                   on,
     122         126 :                                             fd_commission_split_t * result ) {
     123         126 :   ushort commission_split = fd_ushort_min( commission, 10000U );
     124         126 :   switch( commission_split ) {
     125          57 :   case 0U:
     126          57 :     *result = (fd_commission_split_t){ .voter_portion=0UL, .staker_portion=on, .is_split=0U };
     127          57 :     break;
     128          54 :   case 10000U:
     129          54 :     *result = (fd_commission_split_t){ .voter_portion=on, .staker_portion=0UL, .is_split=0U };
     130          54 :     break;
     131          15 :   default:
     132          15 :     result->staker_portion = (ulong)( (uint128)on *
     133          15 :                                       (uint128)(10000U-commission_split) /
     134          15 :                                       (uint128)10000U );
     135          15 :     result->voter_portion  = on-result->staker_portion;
     136          15 :     result->is_split       = 1U;
     137          15 :     break;
     138         126 :   }
     139         126 : }
     140             : 
     141             : /* Validates an external SIMD-0232 inflation commission collector
     142             :    after all rewards routed to it have been added.  Returns 0 to
     143             :    deposit, 1 to burn.  The vote account itself is always valid and
     144             :    must not be passed here.
     145             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/fee_distribution.rs#L231-L270 */
     146             : static int
     147             : fd_rewards_validate_commission_collector( fd_bank_t const *   bank,
     148             :                                           fd_pubkey_t const * collector,
     149             :                                           fd_acc_t const *    account,
     150          42 :                                           ulong               rewards ) {
     151             :   /* Must be a system program owned account. */
     152          42 :   if( FD_UNLIKELY( memcmp( account->owner, fd_solana_system_program_id.uc, sizeof(fd_pubkey_t) ) ) ) return 1;
     153             : 
     154             :   /* Must not be a reserved account. */
     155          33 :   if( FD_UNLIKELY( fd_pubkey_is_active_reserved_key( collector ) ||
     156          33 :                    fd_pubkey_is_pending_reserved_key( collector ) ) ) return 1;
     157             : 
     158          30 :   ulong post_balance;
     159          30 :   if( FD_UNLIKELY( __builtin_uaddl_overflow( account->lamports, rewards, &post_balance ) ) ) return 1;
     160             : 
     161             :   /* The incinerator is exempt from the rent check so the deposit
     162             :      always works.  Incinerator funds are burned at the end of the
     163             :      rewards distribution block. */
     164          27 :   if( FD_UNLIKELY( fd_pubkey_eq( collector, &fd_sysvar_incinerator_id ) ) ) return 0;
     165             : 
     166             :   /* Must be rent-exempt after the deposit.  With
     167             :      relax_post_exec_min_balance_check (SIMD-0392) a pre-existing
     168             :      account may stay rent-paying. */
     169          24 :   int is_rent_exempt = post_balance>=fd_rent_exempt_minimum_balance( &bank->f.rent, account->data_len );
     170          24 :   return !is_rent_exempt &&
     171          24 :          ( !FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) || !account->lamports );
     172          27 : }
     173             : 
     174             : /* Resolves the SIMD-0232 inflation commission collector from the vote
     175             :    account state at the start of the distribution epoch (the t-1 vote
     176             :    stakes set); defaults to the vote account.
     177             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L667-L672 */
     178             : static void
     179             : fd_rewards_inflation_collector( fd_bank_t *         bank,
     180             :                                 fd_pubkey_t const * vote_pubkey,
     181          60 :                                 fd_pubkey_t *       collector_out ) {
     182          60 :   fd_vote_stakes_ele_t ele[1];
     183          60 :   if( fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_pubkey, ele ) ) {
     184          60 :     *collector_out = ele->inflation_collector;
     185          60 :   } else {
     186           0 :     *collector_out = *vote_pubkey;
     187           0 :   }
     188          60 : }
     189             : 
     190             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33
     191             :    https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/inflation_rewards/mod.rs#L204-L371 */
     192             : static int
     193             : redeem_rewards( fd_stake_delegation_t const *   stake,
     194             :                 ulong                           vote_state_idx,
     195             :                 ulong                           rewarded_epoch,
     196             :                 ulong                           total_rewards,
     197             :                 fd_runtime_stack_t *            runtime_stack,
     198             :                 fd_calculated_stake_points_t *  stake_points_result,
     199         420 :                 fd_calculated_stake_rewards_t * result ) {
     200             : 
     201             :   /* The firedancer implementation of redeem_rewards inlines a lot of
     202             :      the helper functions that the Agave implementation uses.
     203             :      In Agave: redeem_rewards calls redeem_stake_rewards which calls
     204             :      calculate_stake_rewards. */
     205             : 
     206             :   // Drive credits_observed forward unconditionally when rewards are disabled
     207             :   // or when this is the stake's activation epoch
     208         420 :   if( total_rewards==0UL || stake->activation_epoch==rewarded_epoch ) {
     209         282 :     stake_points_result->force_credits_update_with_skipped_reward = 1;
     210         282 :   }
     211             : 
     212         420 :   if( stake_points_result->force_credits_update_with_skipped_reward ) {
     213         285 :     result->staker_rewards       = 0;
     214         285 :     result->voter_rewards        = 0;
     215         285 :     result->new_credits_observed = stake_points_result->new_credits_observed;
     216         285 :     return 0;
     217         285 :   }
     218         135 :   if( stake_points_result->points.ud==0 ) {
     219           9 :     if( stake_points_result->new_credits_observed!=stake->credits_observed ) {
     220             :       /* Don't update credits_observed if the delegation is inactive.
     221             : 
     222             :          TODO: remove this after remove_inactive_stakes is active
     223             :          everywhere.
     224             : 
     225             :          https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/inflation_rewards/mod.rs#L255-L258 */
     226           0 :       if( FD_LIKELY( stake_points_result->inactive ) ) {
     227           0 :         return 1;
     228           0 :       }
     229           0 :       result->staker_rewards       = 0UL;
     230           0 :       result->voter_rewards        = 0UL;
     231           0 :       result->new_credits_observed = stake_points_result->new_credits_observed;
     232           0 :       return 0;
     233           0 :     }
     234           9 :     return 1;
     235           9 :   }
     236             : 
     237         126 :   if( FD_UNLIKELY( stake_points_result->points.ud>(uint128)ULONG_MAX ) ) {
     238           0 :     FD_LOG_ERR(( "Rewards should fit within u64" ));
     239           0 :   }
     240             : 
     241         126 :   fd_commission_split_t split_result;
     242         126 :   fd_vote_commission_split_preserve_lamports( runtime_stack->stakes.vote_ele[ vote_state_idx ].commission,
     243         126 :                                               (ulong)stake_points_result->points.ud,
     244         126 :                                               &split_result );
     245             : 
     246         126 :   result->staker_rewards       = split_result.staker_portion;
     247         126 :   result->voter_rewards        = split_result.voter_portion;
     248         126 :   result->new_credits_observed = stake_points_result->new_credits_observed;
     249         126 :   return 0;
     250         126 : }
     251             : 
     252             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
     253             : static double
     254             : epoch_duration_in_years( fd_bank_t const * bank,
     255         324 :                          ulong             prev_epoch ) {
     256         324 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
     257         324 :   ulong                       slots_in_epoch = fd_epoch_slot_cnt( epoch_schedule, prev_epoch );
     258         324 :   double                      slots_per_year = fd_slot_params_at_slot( bank,
     259         324 :                                                                        fd_epoch_slot0( epoch_schedule, prev_epoch ) ).slots_per_year;
     260         324 :   return (double)slots_in_epoch / slots_per_year;
     261         324 : }
     262             : 
     263             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128
     264             :    https://github.com/anza-xyz/agave/blob/9bcf93e368b5fb52c21677ed8c7f0360eb93b5b7/runtime/src/bank.rs#L3150-L3177 */
     265             : static ulong
     266             : calculate_epoch_inflation_rewards( fd_bank_t const * bank,
     267             :                                    ulong             capitalization,
     268        1242 :                                    ulong             epoch ) {
     269        1242 :   if( FD_FEATURE_ACTIVE_BANK( bank, remove_runtime_float_ops ) ) {
     270         921 :     ulong inflation_start_slot = inflation_start_slot_aligned_to_rewards( bank, &bank->f.epoch_schedule );
     271         921 :     return fd_integer_inflation_epoch_rewards( bank, inflation_start_slot, capitalization, epoch );
     272         921 :   }
     273             : 
     274         321 :   double slot_in_year     = slot_in_year_for_inflation( bank );
     275         321 :   double validator_rate   = validator( &bank->f.inflation, slot_in_year );
     276         321 :   double epoch_duration   = epoch_duration_in_years( bank, epoch );
     277         321 :   ulong  validator_reward = (ulong)( ( validator_rate * (double)capitalization ) * epoch_duration );
     278         321 :   return validator_reward;
     279        1242 : }
     280             : 
     281             : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
     282             : 
     283             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
     284             :  */
     285             : static uint
     286             : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     287             :                                     ulong                       slot,
     288             :                                     ulong                       total_stake_accounts,
     289         330 :                                     ulong                       stake_account_stores_per_block ) {
     290             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
     291         330 :   if( epoch_schedule->warmup &&
     292         330 :       fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
     293           3 :     return 1UL;
     294           3 :   }
     295             : 
     296         327 :   FD_TEST( stake_account_stores_per_block );
     297         327 :   ulong num_chunks = total_stake_accounts / stake_account_stores_per_block + (total_stake_accounts % stake_account_stores_per_block != 0);
     298         327 :   num_chunks       = fd_ulong_max( num_chunks, 1UL );
     299         327 :   num_chunks       = fd_ulong_min( num_chunks,
     300         327 :                                    fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
     301         327 :   return (uint)num_chunks;
     302         327 : }
     303             : 
     304             : uint
     305             : fd_rewards_get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     306             :                                                ulong                       slot,
     307             :                                                ulong                       total_stake_accounts,
     308          12 :                                                ulong                       stake_account_stores_per_block ) {
     309          12 :   return get_reward_distribution_num_blocks( epoch_schedule, slot, total_stake_accounts, stake_account_stores_per_block );
     310          12 : }
     311             : 
     312             : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/inflation_rewards/points.rs#L236-L312 */
     313             : static void
     314             : calculate_alpenglow_points( fd_epoch_credits_t const *    epoch_credits,
     315             :                             fd_stake_history_t const *    stake_history,
     316             :                             fd_stake_delegation_t const * stake,
     317             :                             ulong *                       new_rate_activation_epoch,
     318             :                             ulong                         rewarded_epoch,
     319             :                             ulong                         validator_reward_epoch_stake,
     320         420 :                             fd_calculated_stake_points_t * result ) {
     321         420 :   ulong credits_in_stake = stake->credits_observed;
     322         420 :   ulong cnt              = epoch_credits->cnt;
     323         420 :   ulong base             = epoch_credits->base_credits;
     324         420 :   ulong credits_in_vote  = cnt ? base+epoch_credits->credits_delta[ cnt-1UL ] : 0UL;
     325             : 
     326         420 :   result->points.ud                                = 0;
     327         420 :   result->new_credits_observed                     = credits_in_stake;
     328         420 :   result->force_credits_update_with_skipped_reward = 0;
     329         420 :   result->inactive                                 = 0;
     330             : 
     331         420 :   if( FD_UNLIKELY( credits_in_vote<credits_in_stake ) ) {
     332           3 :     result->new_credits_observed                     = credits_in_vote;
     333           3 :     result->force_credits_update_with_skipped_reward = 1;
     334           3 :     return;
     335           3 :   }
     336         417 :   if( FD_UNLIKELY( credits_in_vote==credits_in_stake || !cnt ) ) return;
     337             : 
     338         141 :   ulong idx = cnt-1UL;
     339         141 :   if( FD_UNLIKELY( (ulong)epoch_credits->epoch[ idx ]!=rewarded_epoch ) ) return;
     340             : 
     341         138 :   ulong initial_epoch_credits = base+epoch_credits->prev_credits_delta[ idx ];
     342         138 :   ulong earned_credits;
     343         138 :   if( credits_in_stake<initial_epoch_credits ) {
     344           0 :     earned_credits = credits_in_vote-initial_epoch_credits;
     345         138 :   } else {
     346         138 :     earned_credits = credits_in_vote-credits_in_stake;
     347         138 :   }
     348         138 :   result->new_credits_observed = credits_in_vote;
     349             : 
     350         138 :   fd_stake_history_entry_t status = fd_stake_delegation_activation_status(
     351         138 :       stake,
     352         138 :       rewarded_epoch,
     353         138 :       stake_history,
     354         138 :       new_rate_activation_epoch );
     355         138 :   result->inactive = (uchar)( !status.effective && !status.activating );
     356             : 
     357         138 :   if( FD_UNLIKELY( !earned_credits ) ) return;
     358         138 :   if( FD_UNLIKELY( !status.effective ) ) return;
     359             : 
     360         132 :   if( FD_UNLIKELY( !validator_reward_epoch_stake ) ) {
     361           0 :     result->force_credits_update_with_skipped_reward = 1;
     362           0 :     return;
     363           0 :   }
     364             : 
     365         132 :   result->points.ud = (uint128)earned_credits *
     366         132 :                       (uint128)status.effective /
     367         132 :                       (uint128)validator_reward_epoch_stake;
     368         132 : }
     369             : 
     370             : static inline void
     371             : calculate_stake_points_for_reward( fd_runtime_stack_t *           runtime_stack,
     372             :                                    fd_epoch_credits_t const *     epoch_credits,
     373             :                                    fd_stake_history_t const *     stake_history,
     374             :                                    fd_stake_delegation_t const *  stake,
     375             :                                    ulong *                        new_rate_activation_epoch,
     376             :                                    ulong                          rewarded_epoch,
     377         420 :                                    fd_calculated_stake_points_t * result ) {
     378         420 :   ulong                    validator_reward_epoch_stake = 0UL;
     379         420 :   fd_stake_accum_t const * accumulated                  = fd_stake_accum_map_ele_query_const(
     380         420 :       runtime_stack->stakes.stake_accum_map,
     381         420 :       &stake->vote_account,
     382         420 :       NULL,
     383         420 :       runtime_stack->stakes.stake_accum );
     384         420 :   if( accumulated ) validator_reward_epoch_stake = accumulated->reward_stake;
     385         420 :   calculate_alpenglow_points( epoch_credits,
     386         420 :                               stake_history,
     387         420 :                               stake,
     388         420 :                               new_rate_activation_epoch,
     389         420 :                               rewarded_epoch,
     390         420 :                               validator_reward_epoch_stake,
     391         420 :                               result );
     392         420 : }
     393             : 
     394             : /* Calculates the reward points of the first max_stake_accounts stake
     395             :    delegations into runtime_stack's stake points cache.
     396             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L445 */
     397             : static void
     398             : calculate_reward_points_partitioned( fd_bank_t *                    bank,
     399             :                                      fd_stake_delegations_t const * stake_delegations,
     400             :                                      fd_stake_history_t const *     stake_history,
     401             :                                      ulong                          rewarded_epoch,
     402         318 :                                      fd_runtime_stack_t *           runtime_stack ) {
     403         318 :   fd_vote_rewards_t *     vote_ele     = runtime_stack->stakes.vote_ele;
     404         318 :   fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
     405             : 
     406         318 :   fd_epoch_credits_view_t epoch_credits_view[1];
     407         318 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
     408         318 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
     409             : 
     410         318 :   fd_stake_delegations_iter_t iter_[1];
     411         318 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     412         780 :        !fd_stake_delegations_iter_done( iter );
     413         462 :        fd_stake_delegations_iter_next( iter ) ) {
     414         462 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     415         462 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     416         462 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->max_stake_accounts ) ) continue;
     417         462 :     fd_calculated_stake_points_t * stake_points_result = &runtime_stack->stakes.stake_results[ stake_delegation_idx ].points;
     418             : 
     419         462 :     if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) &&
     420         462 :                      fd_stake_delegation_is_inactive( stake_delegation, rewarded_epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch ) ) ) {
     421           3 :       stake_points_result->vote_idx = UINT_MAX;
     422           3 :       continue;
     423           3 :     }
     424             : 
     425             :     /* Note that we don't check minimum delegation here, as there are
     426             :        no plans to activate stake_minimum_delegation_for_rewards.
     427             :        If this changes we need to skip stake accounts that are
     428             :        below the minimum delegation here. However we don't do this yet,
     429             :        to ensure that we audit the feature properly if this happens. */
     430             : 
     431         459 :     uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     432         459 :     stake_points_result->vote_idx = idx;
     433         459 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
     434             : 
     435         402 :     calculate_stake_points_for_reward( runtime_stack,
     436         402 :                                        &epoch_credits_arr[ idx ],
     437         402 :                                        stake_history,
     438         402 :                                        stake_delegation,
     439         402 :                                        &bank->f.warmup_cooldown_rate_epoch,
     440         402 :                                        rewarded_epoch,
     441         402 :                                        stake_points_result );
     442         402 :   }
     443             : 
     444         318 :   fd_epoch_credits_view_fini( epoch_credits_view );
     445         318 : }
     446             : 
     447             : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L161-L173 */
     448             : static int
     449             : delegation_may_need_adjustment( fd_bank_t *                   bank,
     450             :                                 fd_stake_delegation_t const * stake_delegation,
     451             :                                 fd_stake_history_t const *    stake_history,
     452             :                                 ulong                         rewarded_epoch,
     453             :                                 ulong                         new_delegation_with_rewards,
     454             :                                 ulong                         lamports_with_rewards,
     455          66 :                                 ulong                         minimum_lamports ) {
     456          66 :   ulong new_delegation = fd_ulong_min(
     457          66 :     new_delegation_with_rewards,
     458          66 :     fd_ulong_sat_sub( lamports_with_rewards, minimum_lamports )
     459          66 :   );
     460             : 
     461          66 :   if( FD_LIKELY( new_delegation==stake_delegation->stake ) ) return 0;
     462             : 
     463             :   /* Delegations that are neither effective nor activating are left
     464             :      alone.  This is checked last because it can walk many stake history
     465             :      entries. */
     466           9 :   fd_stake_history_entry_t status = fd_stake_delegation_activation_status(
     467           9 :     stake_delegation,
     468           9 :     rewarded_epoch,
     469           9 :     stake_history,
     470           9 :     &bank->f.warmup_cooldown_rate_epoch );
     471             : 
     472           9 :   return !( status.effective==0UL && status.activating==0UL );
     473          66 : }
     474             : 
     475             : /* Calculates block reward for a stake account based on SIMD-0123.
     476             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L173 */
     477             : static ulong
     478             : calculate_block_reward( fd_bank_t *                   bank,
     479             :                         fd_runtime_stack_t *          runtime_stack,
     480             :                         fd_stake_history_t const *    stake_history,
     481             :                         fd_stake_delegation_t const * stake_delegation,
     482          24 :                         ulong                         rewarded_epoch ) {
     483          24 :   fd_pubkey_t const * vote_pubkey = &stake_delegation->vote_account;
     484          24 :   fd_vote_stakes_ele_t ele[1];
     485          24 :   if( !fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_pubkey, ele ) ) {
     486           3 :     return 0UL;
     487           3 :   }
     488          21 :   ulong pending_delegator_rewards = ele->pending_delegator_rewards;
     489          21 :   fd_stake_accum_t const * accumulated = fd_stake_accum_map_ele_query_const(
     490          21 :       runtime_stack->stakes.stake_accum_map, vote_pubkey, NULL, runtime_stack->stakes.stake_accum );
     491          21 :   ulong total_active_stake = accumulated ? accumulated->reward_stake : 0UL;
     492          21 :   if( total_active_stake==0UL ) return 0UL;
     493             : 
     494          18 :   ulong stake = fd_stake_delegation_activation_status( stake_delegation,
     495          18 :                                                        rewarded_epoch,
     496          18 :                                                        stake_history,
     497          18 :                                                        &bank->f.warmup_cooldown_rate_epoch ).effective;
     498             :   /* During recalculation, if stake account has already received rewards,
     499             :      it's possible to have `stake > total_active_stake`. If
     500             :      `pending_delegator_rewards` is a huge number, we could potentially
     501             :      overflow a `u64`. We can also have individual rewards look greater
     502             :      than the pending rewards. This is harmless in practice, but we
     503             :      clamp it just to be safe */
     504          18 :   uint128 reward = (uint128)pending_delegator_rewards * (uint128)stake / (uint128)total_active_stake;
     505          18 :   return fd_ulong_min( reward>(uint128)ULONG_MAX ? ULONG_MAX : (ulong)reward, pending_delegator_rewards );
     506          21 : }
     507             : 
     508             : /* Calculates epoch rewards for stake/vote accounts.
     509             :    Returns vote rewards, stake rewards, and the sum of all stake rewards
     510             :    in lamports.
     511             : 
     512             :    In the future, the calculation will be cached in the snapshot, but
     513             :    for now we just re-calculate it (as Agave does).
     514             :    calculate_stake_vote_rewards is responsible for calculating
     515             :    stake account rewards based off of a combination of the
     516             :    stake delegation state as well as the vote account. If this
     517             :    calculation is done at the end of an epoch, we can just use the
     518             :    vote states at the end of the current epoch. However, because we
     519             :    are presumably booting up a node in the middle of rewards
     520             :    distribution, we need to make sure that we are using the vote
     521             :    states from the end of the previous epoch.
     522             : 
     523             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
     524             : static void
     525             : calculate_stake_vote_rewards( fd_bank_t *                    bank,
     526             :                               fd_stake_delegations_t const * stake_delegations,
     527             :                               fd_capture_ctx_t *             capture_ctx FD_PARAM_UNUSED,
     528             :                               fd_stake_history_t const *     stake_history,
     529             :                               ulong                          rewarded_epoch,
     530             :                               ulong                          total_rewards,
     531             :                               fd_runtime_stack_t *           runtime_stack,
     532         336 :                               int                            is_recalculation ) {
     533             : 
     534         336 :   runtime_stack->stakes.stake_rewards_cnt = 0UL;
     535             : 
     536         336 :   int block_revenue_sharing = FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing );
     537             : 
     538         336 :   fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
     539         336 :   fd_epoch_credits_view_t       epoch_credits_view[1];
     540         336 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
     541         336 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
     542             : 
     543         336 :   fd_stake_delegations_iter_t iter_[1];
     544         336 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     545         819 :        !fd_stake_delegations_iter_done( iter );
     546         483 :        fd_stake_delegations_iter_next( iter ) ) {
     547         483 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     548         483 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     549             : 
     550             :     /* Note that we don't check minimum delegation here, as there are
     551             :        no plans to activate stake_minimum_delegation_for_rewards.
     552             :        If this changes we need to skip stake accounts that are
     553             :        below the minimum delegation here. However we don't do this yet,
     554             :        to ensure that we audit the feature properly if this happens. */
     555             : 
     556             :     /* The rewards overwrite the cached points, so copy them out first. */
     557         483 :     int                          cached = !is_recalculation && stake_delegation_idx<runtime_stack->max_stake_accounts;
     558         483 :     fd_calculated_stake_points_t stake_points_result[1] = {0};
     559         483 :     if( FD_LIKELY( cached ) ) *stake_points_result = runtime_stack->stakes.stake_results[ stake_delegation_idx ].points;
     560             : 
     561         483 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
     562         483 :     if( stake_delegation_idx>=runtime_stack->max_stake_accounts ) {
     563           0 :       calculated_stake_rewards = calculated_stake_rewards_;
     564         483 :     } else {
     565         483 :       calculated_stake_rewards = &runtime_stack->stakes.stake_results[ stake_delegation_idx ].rewards;
     566         483 :     }
     567         483 :     calculated_stake_rewards->success = 0;
     568             : 
     569             : 
     570         483 :     if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) &&
     571         483 :                      fd_stake_delegation_is_inactive( stake_delegation, rewarded_epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch ) ) ) {
     572           6 :       continue;
     573           6 :     }
     574             : 
     575             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L856-L867 */
     576         477 :     ulong block_reward = block_revenue_sharing ? calculate_block_reward( bank, runtime_stack, stake_history, stake_delegation, rewarded_epoch ) : 0UL;
     577             : 
     578         477 :     uint idx;
     579         477 :     if( FD_LIKELY( cached ) ) {
     580         459 :       idx = stake_points_result->vote_idx;
     581         459 :     } else {
     582          18 :       fd_vote_rewards_t *     vote_ele     = runtime_stack->stakes.vote_ele;
     583          18 :       fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
     584          18 :       idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     585          18 :     }
     586             : 
     587             :     /* Stake account may need to be adjusted to meet rent-exempt minimum
     588             :        balance requirements based on new rent and delegation parameters.
     589             :        https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L568-L608 */
     590         477 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) {
     591             :       /* If the stake account's resulting lamports would cause it to be
     592             :          below the rent exempt minimum balance, it needs to be queued
     593             :          for update (and thus affects the epoch reward partitions). */
     594          57 :       int may_need_adjustment = delegation_may_need_adjustment(
     595          57 :           bank,
     596          57 :           stake_delegation,
     597          57 :           stake_history,
     598          57 :           rewarded_epoch,
     599          57 :           stake_delegation->stake,
     600          57 :           stake_delegation->lamports,
     601          57 :           fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
     602          57 :       if( !may_need_adjustment && !block_reward ) continue;
     603             : 
     604             :       /* Place an empty entry for this stake delegation idx so that
     605             :          the partitioning logic factors it in. */
     606           0 :       *calculated_stake_rewards = (fd_calculated_stake_rewards_t){
     607           0 :         .success              = 1,
     608           0 :         .staker_rewards       = 0,
     609           0 :         .voter_rewards        = 0,
     610           0 :         .new_credits_observed = stake_delegation->credits_observed,
     611           0 :         .block_reward         = block_reward
     612           0 :       };
     613           0 :       runtime_stack->stakes.stake_rewards_cnt++;
     614           0 :       continue;
     615          57 :     }
     616             : 
     617         420 :     if( FD_UNLIKELY( !cached ) ) {
     618             :       /* We have not cached the stake points yet if we are recalculating
     619             :          stake rewards so we need to recalculate them. */
     620          18 :       calculate_stake_points_for_reward( runtime_stack,
     621          18 :                                          &epoch_credits_arr[ idx ],
     622          18 :                                          stake_history,
     623          18 :                                          stake_delegation,
     624          18 :                                          &bank->f.warmup_cooldown_rate_epoch,
     625          18 :                                          rewarded_epoch,
     626          18 :                                          stake_points_result );
     627          18 :     }
     628             : 
     629             :     /* redeem_rewards is actually just responsible for calculating the
     630             :        vote and stake rewards for each stake account.  It does not do
     631             :        rewards redemption: it is a misnomer. */
     632         420 :     int err = redeem_rewards(
     633         420 :         stake_delegation,
     634         420 :         idx,
     635         420 :         rewarded_epoch,
     636         420 :         total_rewards,
     637         420 :         runtime_stack,
     638         420 :         stake_points_result,
     639         420 :         calculated_stake_rewards );
     640             : 
     641         420 :     if( FD_UNLIKELY( err!=0 ) ) {
     642             :       /* Even if there is an error computing rewards for the stake
     643             :          account, there may be a required balance update for the stake
     644             :          account if rent increased.
     645             :          https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L132-L152 */
     646           9 :       int may_need_adjustment = delegation_may_need_adjustment(
     647           9 :           bank,
     648           9 :           stake_delegation,
     649           9 :           stake_history,
     650           9 :           rewarded_epoch,
     651           9 :           stake_delegation->stake,
     652           9 :           stake_delegation->lamports,
     653           9 :           fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
     654           9 :       if( !may_need_adjustment && !block_reward ) continue;
     655             : 
     656           9 :       *calculated_stake_rewards = (fd_calculated_stake_rewards_t){
     657           9 :         .success              = 1,
     658           9 :         .staker_rewards       = 0,
     659           9 :         .voter_rewards        = 0,
     660           9 :         .new_credits_observed = stake_delegation->credits_observed,
     661           9 :         .block_reward         = block_reward
     662           9 :       };
     663         411 :     } else {
     664         411 :       calculated_stake_rewards->success      = 1;
     665         411 :       calculated_stake_rewards->block_reward = block_reward;
     666         411 :     }
     667             : 
     668         420 :     if( capture_ctx && capture_ctx->capture_solcap ) {
     669           0 :       fd_capture_link_write_stake_reward_event( capture_ctx,
     670           0 :                                                 bank->f.slot,
     671           0 :                                                 stake_delegation->stake_account,
     672           0 :                                                 stake_delegation->vote_account,
     673           0 :                                                 runtime_stack->stakes.vote_ele[ idx ].commission,
     674           0 :                                                 (long)calculated_stake_rewards->voter_rewards,
     675           0 :                                                 (long)calculated_stake_rewards->staker_rewards,
     676           0 :                                                 (long)calculated_stake_rewards->new_credits_observed );
     677           0 :     }
     678             : 
     679         420 :     runtime_stack->stakes.vote_ele[ idx ].vote_rewards += calculated_stake_rewards->voter_rewards;
     680         420 :     runtime_stack->stakes.stake_rewards_cnt++;
     681         420 :   }
     682         336 :   fd_epoch_credits_view_fini( epoch_credits_view );
     683         336 : }
     684             : 
     685             : /* setup_stake_partitions hashes every stake reward of the epoch into
     686             :    its partition.  Only the rewards landing inside the fork's current
     687             :    window are retained; see fd_stake_rewards.h.  The fork must already
     688             :    have been initialized and its window positioned by the caller. */
     689             : 
     690             : static void
     691             : setup_stake_partitions( fd_bank_t *                    bank,
     692             :                         fd_stake_history_t const *     stake_history,
     693             :                         fd_stake_delegations_t const * stake_delegations,
     694             :                         fd_runtime_stack_t *           runtime_stack,
     695             :                         ushort                         fork_idx,
     696             :                         ulong                          rewarded_epoch,
     697         336 :                         ulong                          total_rewards ) {
     698             : 
     699         336 :   int block_revenue_sharing = FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing );
     700             : 
     701         336 :   fd_stake_rewards_t * stake_rewards     = fd_bank_stake_rewards_modify( bank );
     702         336 :   fd_epoch_credits_view_t epoch_credits_view[1];
     703         336 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
     704         336 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
     705             : 
     706         336 :   fd_stake_delegations_iter_t iter_[1];
     707         336 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     708         819 :        !fd_stake_delegations_iter_done( iter );
     709         483 :        fd_stake_delegations_iter_next( iter ) ) {
     710         483 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     711         483 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     712             : 
     713         483 :     if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) &&
     714         483 :                      fd_stake_delegation_is_inactive( stake_delegation, rewarded_epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch ) ) ) {
     715           6 :       continue;
     716           6 :     }
     717             : 
     718         477 :     fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
     719         477 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
     720             : 
     721         477 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->max_stake_accounts ) ) {
     722             : 
     723           0 :       calculated_stake_rewards = calculated_stake_rewards_;
     724             : 
     725             :       /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L856-L867 */
     726           0 :       ulong block_reward = block_revenue_sharing ? calculate_block_reward( bank, runtime_stack, stake_history, stake_delegation, rewarded_epoch ) : 0UL;
     727             : 
     728           0 :       fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
     729           0 :       fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
     730           0 :       uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     731           0 :       if( FD_UNLIKELY( idx==UINT_MAX ) ) {
     732             :         /* If the stake account's resulting lamports would cause it to be
     733             :            below the rent exempt minimum balance, it needs to be queued
     734             :            for update (and thus affects the epoch reward partitions). */
     735           0 :         int may_need_adjustment = delegation_may_need_adjustment(
     736           0 :             bank,
     737           0 :             stake_delegation,
     738           0 :             stake_history,
     739           0 :             rewarded_epoch,
     740           0 :             stake_delegation->stake,
     741           0 :             stake_delegation->lamports,
     742           0 :             fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
     743           0 :         if( !may_need_adjustment && !block_reward ) continue;
     744             : 
     745           0 :         fd_stake_rewards_insert( stake_rewards, fork_idx, &stake_delegation->stake_account, 0UL, stake_delegation->credits_observed, block_reward );
     746           0 :         continue;
     747           0 :       }
     748             : 
     749           0 :       fd_calculated_stake_points_t stake_points_result[1];
     750           0 :       calculate_stake_points_for_reward( runtime_stack,
     751           0 :                                          &epoch_credits_arr[ idx ],
     752           0 :                                          stake_history,
     753           0 :                                          stake_delegation,
     754           0 :                                          &bank->f.warmup_cooldown_rate_epoch,
     755           0 :                                          rewarded_epoch,
     756           0 :                                          stake_points_result );
     757             : 
     758             :       /* redeem_rewards is actually just responsible for calculating the
     759             :          vote and stake rewards for each stake account.  It does not do
     760             :          rewards redemption: it is a misnomer. */
     761           0 :       int err = redeem_rewards(
     762           0 :           stake_delegation,
     763           0 :           idx,
     764           0 :           rewarded_epoch,
     765           0 :           total_rewards,
     766           0 :           runtime_stack,
     767           0 :           stake_points_result,
     768           0 :           calculated_stake_rewards );
     769             : 
     770           0 :       if( FD_UNLIKELY( err!=0 ) ) {
     771             :         /* Even if there is an error computing rewards for the stake
     772             :            account, there may be a required balance update for the stake
     773             :            account if rent increased.
     774             :            https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L132-L152 */
     775           0 :         int may_need_adjustment = delegation_may_need_adjustment(
     776           0 :             bank,
     777           0 :             stake_delegation,
     778           0 :             stake_history,
     779           0 :             rewarded_epoch,
     780           0 :             stake_delegation->stake,
     781           0 :             stake_delegation->lamports,
     782           0 :             fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
     783           0 :         if( !may_need_adjustment && !block_reward ) continue;
     784             : 
     785           0 :         fd_stake_rewards_insert( stake_rewards, fork_idx, &stake_delegation->stake_account, 0UL, stake_delegation->credits_observed, block_reward );
     786           0 :         continue;
     787           0 :       } else {
     788           0 :         calculated_stake_rewards->success      = 1;
     789           0 :         calculated_stake_rewards->block_reward = block_reward;
     790           0 :       }
     791         477 :     } else {
     792         477 :       calculated_stake_rewards = &runtime_stack->stakes.stake_results[ stake_delegation_idx ].rewards;
     793         477 :     }
     794             : 
     795         477 :     if( FD_UNLIKELY( !calculated_stake_rewards->success ) ) continue;
     796             : 
     797         420 :     fd_stake_rewards_insert(
     798         420 :       stake_rewards,
     799         420 :       fork_idx,
     800         420 :       &stake_delegation->stake_account,
     801         420 :       calculated_stake_rewards->staker_rewards,
     802         420 :       calculated_stake_rewards->new_credits_observed,
     803         420 :       calculated_stake_rewards->block_reward
     804         420 :     );
     805         420 :   }
     806             : 
     807         336 :   fd_stake_rewards_fini( stake_rewards, fork_idx );
     808         336 :   fd_epoch_credits_view_fini( epoch_credits_view );
     809         336 : }
     810             : 
     811             : /* Calculate epoch reward and return vote and stake rewards.
     812             : 
     813             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
     814             : static void
     815             : calculate_validator_rewards( fd_bank_t *                    bank,
     816             :                              fd_accdb_t *                   accdb,
     817             :                              fd_runtime_stack_t *           runtime_stack,
     818             :                              fd_stake_delegations_t const * stake_delegations,
     819             :                              fd_capture_ctx_t *             capture_ctx,
     820             :                              ulong                          rewarded_epoch,
     821         318 :                              ulong                          total_rewards ) {
     822         318 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
     823         318 :   fd_stake_history_t stake_history[1];
     824         318 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, bank->accdb_fork_id, stake_history_data, stake_history ) ) ) {
     825           0 :     FD_LOG_ERR(( "Unable to read stake history sysvar" ));
     826           0 :   }
     827             : 
     828             :   /* Calculate the epoch reward points from stake/vote accounts */
     829         318 :   calculate_reward_points_partitioned(
     830         318 :       bank,
     831         318 :       stake_delegations,
     832         318 :       stake_history,
     833         318 :       rewarded_epoch,
     834         318 :       runtime_stack );
     835             : 
     836         318 :   if( FD_UNLIKELY( capture_ctx && capture_ctx->capture_solcap ) ) {
     837           0 :     ulong epoch = bank->f.epoch;
     838           0 :     ulong slot  = bank->f.slot;
     839           0 :     fd_capture_link_write_stake_rewards_begin( capture_ctx,
     840           0 :                                                slot,
     841           0 :                                                epoch,
     842           0 :                                                epoch-1UL, /* FIXME: this is not strictly correct */
     843           0 :                                                total_rewards,
     844           0 :                                                0UL );
     845           0 :   }
     846             : 
     847             :   /* Calculate the stake and vote rewards for each account. We want to
     848             :      use the vote states from the end of the current_epoch. */
     849         318 :   calculate_stake_vote_rewards(
     850         318 :       bank,
     851         318 :       stake_delegations,
     852         318 :       capture_ctx,
     853         318 :       stake_history,
     854         318 :       rewarded_epoch,
     855         318 :       total_rewards,
     856         318 :       runtime_stack,
     857         318 :       0 );
     858             : 
     859         318 :   fd_hash_t const * parent_blockhash      = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
     860         318 :   ulong             starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
     861         318 :   uint              num_partitions        = get_reward_distribution_num_blocks( &bank->f.epoch_schedule,
     862         318 :                                                                                 bank->f.slot,
     863         318 :                                                                                 runtime_stack->stakes.stake_rewards_cnt,
     864         318 :                                                                                 bank->f.slot_params.stake_account_stores_per_block );
     865             : 
     866         318 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
     867         318 :   ushort               fork_idx      = fd_stake_rewards_init( stake_rewards,
     868         318 :                                                               parent_blockhash,
     869         318 :                                                               starting_block_height,
     870         318 :                                                               num_partitions,
     871         318 :                                                               0U,
     872         318 :                                                               runtime_stack->stakes.stake_rewards_cnt );
     873         318 :   if( FD_UNLIKELY( bank->stake_rewards_fork_id!=USHORT_MAX ) ) {
     874           3 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
     875           3 :   }
     876         318 :   bank->stake_rewards_fork_id = fork_idx;
     877             : 
     878         318 :   setup_stake_partitions( bank,
     879         318 :                           stake_history,
     880         318 :                           stake_delegations,
     881         318 :                           runtime_stack,
     882         318 :                           fork_idx,
     883         318 :                           rewarded_epoch,
     884         318 :                           total_rewards );
     885         318 : }
     886             : 
     887             : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
     888             : 
     889             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L277
     890             :    https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L470-L495 */
     891             : static void
     892             : calculate_rewards_for_partitioning( fd_bank_t *                            bank,
     893             :                                     fd_accdb_t *                           accdb,
     894             :                                     fd_runtime_stack_t *                   runtime_stack,
     895             :                                     fd_stake_delegations_t const *         stake_delegations,
     896             :                                     fd_capture_ctx_t *                     capture_ctx,
     897             :                                     ulong                                  prev_epoch,
     898         318 :                                     fd_partitioned_rewards_calculation_t * result ) {
     899         318 :   ulong total_rewards = fd_epoch_inflation_rewards_for_epoch( bank, accdb, prev_epoch );
     900             : 
     901         318 :   calculate_validator_rewards( bank,
     902         318 :                                accdb,
     903         318 :                                runtime_stack,
     904         318 :                                stake_delegations,
     905         318 :                                capture_ctx,
     906         318 :                                prev_epoch,
     907         318 :                                total_rewards );
     908             : 
     909             :   /* The agave client does not partition the stake rewards until the
     910             :      first distribution block.  We calculate the partitions during the
     911             :      boundary. */
     912         318 :   result->validator_rewards = total_rewards;
     913         318 :   result->capitalization    = bank->f.capitalization;
     914         318 : }
     915             : 
     916             : /* Sweeps the given vote account's pending delegator rewards.
     917             :    maybe_stake is the validator's reward epoch delegated stake, NULL if
     918             :    unknown.  Only accounts holding pending rewards are rewritten.
     919             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L233 */
     920             : static void
     921             : sweep_vote_account( fd_bank_t *         bank,
     922             :                     fd_accdb_t *        accdb,
     923             :                     fd_capture_ctx_t *  capture_ctx,
     924             :                     fd_pubkey_t const * vote_address,
     925             :                     ulong const *       maybe_stake,
     926          21 :                     ulong *             total_block_reward_lamports ) {
     927          21 :   ulong pending_delegator_rewards = 0UL;
     928          21 :   {
     929          21 :     fd_acc_t ro = fd_accdb_read_one( accdb, bank->accdb_fork_id, vote_address->uc );
     930          21 :     int skip = !ro.lamports ||
     931          21 :                memcmp( ro.owner, fd_solana_vote_program_id.uc, sizeof(fd_pubkey_t) ) ||
     932          21 :                fd_vote_account_pending_delegator_rewards( ro.data, ro.data_len, &pending_delegator_rewards ) ||
     933          21 :                !pending_delegator_rewards;
     934          21 :     fd_accdb_unread_one( accdb, &ro );
     935          21 :     if( FD_LIKELY( skip ) ) return;
     936          21 :   }
     937             : 
     938          12 :   fd_accdb_svm_update_t update[1];
     939          12 :   fd_acc_t              acc = fd_accdb_svm_open_rw( bank, accdb, update, vote_address, 0 );
     940          12 :   ulong                 pending_reset;
     941             :   /* The read above already parsed this data as a v4 state. */
     942          12 :   FD_TEST( !fd_vote_account_reset_pending_delegator_rewards( acc.data, acc.data_len, &pending_reset ) );
     943             :   /* If validator has no stake, delegator rewards go back to the validator, no
     944             :      distribution needed.
     945             :      If the validator's stake is not known, it likely did not pay VAT, so that
     946             :      validator's block rewards get burned. */
     947          12 :   if( !( maybe_stake && *maybe_stake==0UL ) ) {
     948             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L249-L259 */
     949           9 :     FD_TEST( acc.lamports>=pending_delegator_rewards );
     950           9 :     acc.lamports                 -= pending_delegator_rewards;
     951           9 :     *total_block_reward_lamports += pending_delegator_rewards;
     952           9 :   }
     953          12 :   fd_accdb_svm_close_rw( bank, accdb, capture_ctx, &acc, update );
     954          12 : }
     955             : 
     956             : /* reward_epoch_delegated_stakes lookup: the t-1 set decides whether
     957             :    the validator is known.  The stake is the accumulator's reward epoch
     958             :    stake.  Returns stake_out or NULL if the validator is unknown.
     959             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/alpenglow_epoch_type.rs#L72 */
     960             : static ulong const *
     961             : reward_epoch_delegated_stake( fd_bank_t *          bank,
     962             :                               fd_runtime_stack_t * runtime_stack,
     963             :                               fd_pubkey_t const *  vote_address,
     964          21 :                               ulong *              stake_out ) {
     965          21 :   fd_stake_accum_t const * accumulated = fd_stake_accum_map_ele_query_const(
     966          21 :       runtime_stack->stakes.stake_accum_map, vote_address, NULL, runtime_stack->stakes.stake_accum );
     967          21 :   *stake_out = accumulated ? accumulated->reward_stake : 0UL;
     968          21 :   return fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_address, NULL ) ? stake_out : NULL;
     969          21 : }
     970             : 
     971             : /* Sweeps pending delegator rewards out of the vote accounts.
     972             :    Returns the swept lamports.
     973             : 
     974             :    Important. We must iterate the t-3 set, because both the fee settlement
     975             :    and the vote program limit pending_delegator_rewards to leaders, i.e. t-2 set
     976             :    (t-2 set has already rotated to t-3 when we call sweep_pending_delegator_rewards).
     977             : 
     978             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1258-L1290 */
     979             : static ulong
     980             : sweep_pending_delegator_rewards( fd_bank_t *          bank,
     981             :                                  fd_accdb_t *         accdb,
     982             :                                  fd_capture_ctx_t *   capture_ctx,
     983          12 :                                  fd_runtime_stack_t * runtime_stack ) {
     984          12 :   ulong total_block_reward_lamports = 0UL;
     985             : 
     986          12 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     987          12 :   ushort             fork_id     = bank->vote_stakes_fork_id;
     988          12 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     989          12 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter_mem );
     990          33 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter );
     991          21 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter ) ) {
     992          21 :     fd_vote_stakes_ele_t ele[1];
     993          21 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter, ele );
     994             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1276-L1278 */
     995          21 :     ulong         stake;
     996          21 :     ulong const * maybe_stake = reward_epoch_delegated_stake( bank, runtime_stack, &ele->pubkey, &stake );
     997             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1279-L1285 */
     998          21 :     sweep_vote_account( bank, accdb, capture_ctx, &ele->pubkey, maybe_stake, &total_block_reward_lamports );
     999          21 :   }
    1000             : 
    1001          12 :   return total_block_reward_lamports;
    1002          12 : }
    1003             : 
    1004             : /* Calculate rewards from previous epoch and distribute vote rewards
    1005             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L148
    1006             :    https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L234-L296 */
    1007             : static ulong
    1008             : calculate_rewards_and_distribute_vote_rewards( fd_bank_t *                    bank,
    1009             :                                                fd_accdb_t *                   accdb,
    1010             :                                                fd_runtime_stack_t *           runtime_stack,
    1011             :                                                fd_stake_delegations_t const * stake_delegations,
    1012             :                                                fd_capture_ctx_t *             capture_ctx,
    1013         318 :                                                ulong                          prev_epoch ) {
    1014             : 
    1015         318 :   fd_vote_rewards_t *     vote_ele_pool = runtime_stack->stakes.vote_ele;
    1016         318 :   fd_vote_rewards_map_t * vote_ele_map  = runtime_stack->stakes.vote_map;
    1017             : 
    1018             :   /* First we must compute the stake and vote rewards for the just
    1019             :      completed epoch.  We store the stake account rewards and vote
    1020             :      states rewards in the bank */
    1021             : 
    1022         318 :   fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
    1023         318 :   calculate_rewards_for_partitioning( bank,
    1024         318 :                                       accdb,
    1025         318 :                                       runtime_stack,
    1026         318 :                                       stake_delegations,
    1027         318 :                                       capture_ctx,
    1028         318 :                                       prev_epoch,
    1029         318 :                                       rewards_calc_result );
    1030             : 
    1031             :   /* Sweep while stake_accum still holds the reward epoch delegated stakes.
    1032             :      https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1258-L1290 */
    1033         318 :   runtime_stack->stakes.block_rewards = 0UL;
    1034         318 :   if( FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing ) ) {
    1035          12 :     runtime_stack->stakes.block_rewards = sweep_pending_delegator_rewards( bank, accdb, capture_ctx, runtime_stack );
    1036          12 :   }
    1037             : 
    1038             : 
    1039             :   /* Distribute the commission rewards.  distributed_rewards includes
    1040             :      burned and incinerated amounts: the epoch rewards sysvar counts
    1041             :      them as distributed.
    1042             :      https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L186-L217 */
    1043         318 :   ulong distributed_rewards   = 0UL;
    1044         318 :   ulong credited_vote_rewards = 0UL;
    1045         318 :   if( FD_FEATURE_ACTIVE_BANK( bank, custom_commission_collector ) ) {
    1046             :     /* Aggregate rewards per collector before validation: several small
    1047             :        rewards may collectively make a new collector rent-exempt.
    1048             :        stake_accum is free for reuse as scratch here. */
    1049          69 :     fd_stake_accum_t *     collector_pool = runtime_stack->stakes.stake_accum;
    1050          69 :     fd_stake_accum_map_t * collector_map  = runtime_stack->stakes.stake_accum_map;
    1051          69 :     fd_stake_accum_map_reset( collector_map );
    1052          69 :     ulong collector_cnt = 0UL;
    1053             : 
    1054          69 :     for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
    1055         150 :          !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
    1056          81 :          iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
    1057          81 :       uint                idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
    1058          81 :       fd_vote_rewards_t * ele = &vote_ele_pool[idx];
    1059             : 
    1060          81 :       ulong rewards = ele->vote_rewards;
    1061          81 :       if( FD_UNLIKELY( !rewards ) ) continue;
    1062          54 :       distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards );
    1063             : 
    1064          54 :       fd_pubkey_t collector;
    1065          54 :       fd_rewards_inflation_collector( bank, &ele->pubkey, &collector );
    1066             : 
    1067          54 :       fd_stake_accum_t * collector_reward = fd_stake_accum_map_ele_query( collector_map, &collector, NULL, collector_pool );
    1068          54 :       if( FD_UNLIKELY( !collector_reward ) ) {
    1069          48 :         FD_TEST( collector_cnt<runtime_stack->max_vote_accounts );
    1070          48 :         collector_reward         = &collector_pool[ collector_cnt++ ];
    1071          48 :         collector_reward->pubkey = collector;
    1072          48 :         collector_reward->stake  = rewards;
    1073          48 :         fd_stake_accum_map_ele_insert( collector_map, collector_reward, collector_pool );
    1074          48 :       } else {
    1075           6 :         collector_reward->stake = fd_ulong_sat_add( collector_reward->stake, rewards );
    1076           6 :       }
    1077          54 :     }
    1078             : 
    1079          69 :     for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( collector_map, collector_pool );
    1080         117 :          !fd_stake_accum_map_iter_done( iter, collector_map, collector_pool );
    1081          69 :          iter = fd_stake_accum_map_iter_next( iter, collector_map, collector_pool ) ) {
    1082          48 :       fd_stake_accum_t * collector_reward = fd_stake_accum_map_iter_ele( iter, collector_map, collector_pool );
    1083             : 
    1084             :       /* A collector that is itself a self-collecting vote account is
    1085             :          paid only its own reward; rewards routed to it by other vote
    1086             :          accounts are burned (it is not system-owned).
    1087             :          https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L70-L124 */
    1088          48 :       fd_vote_rewards_t * self_ele = fd_vote_rewards_map_ele_query( vote_ele_map, &collector_reward->pubkey, NULL, vote_ele_pool );
    1089          48 :       int is_vote_account = 0;
    1090          48 :       if( self_ele && self_ele->vote_rewards ) {
    1091           6 :         fd_pubkey_t self_collector;
    1092           6 :         fd_rewards_inflation_collector( bank, &self_ele->pubkey, &self_collector );
    1093           6 :         is_vote_account = fd_pubkey_eq( &self_collector, &self_ele->pubkey );
    1094           6 :       }
    1095          48 :       ulong rewards = is_vote_account ? self_ele->vote_rewards : collector_reward->stake;
    1096             : 
    1097          48 :       fd_acc_t account = fd_accdb_read_one( accdb, bank->accdb_fork_id, collector_reward->pubkey.uc );
    1098          48 :       int      burn;
    1099          48 :       if( is_vote_account ) {
    1100             :         /* The vote account itself only needs the overflow check. */
    1101           6 :         ulong post_balance;
    1102           6 :         burn = __builtin_uaddl_overflow( account.lamports, rewards, &post_balance );
    1103          42 :       } else {
    1104          42 :         burn = fd_rewards_validate_commission_collector( bank, &collector_reward->pubkey, &account, rewards );
    1105          42 :       }
    1106          48 :       fd_accdb_unread_one( accdb, &account );
    1107             : 
    1108          48 :       if( FD_UNLIKELY( burn ) ) continue;
    1109             : 
    1110             :       /* Credit rewards to the collector (creating a new system account
    1111             :          if it does not exist).  This is always a vote reward. */
    1112          27 :       fd_accdb_svm_credit( bank, accdb, capture_ctx, &collector_reward->pubkey, rewards, 1 );
    1113          27 :       if( FD_LIKELY( !fd_pubkey_eq( &collector_reward->pubkey, &fd_sysvar_incinerator_id ) ) ) {
    1114          24 :         credited_vote_rewards = fd_ulong_sat_add( credited_vote_rewards, rewards );
    1115          24 :       }
    1116          27 :     }
    1117         249 :   } else {
    1118         249 :     for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
    1119         564 :          !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
    1120         315 :          iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
    1121             : 
    1122         315 :       uint idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
    1123         315 :       fd_vote_rewards_t * ele = &vote_ele_pool[idx];
    1124             : 
    1125         315 :       ulong rewards = ele->vote_rewards;
    1126         315 :       if( rewards==0UL ) {
    1127         306 :         continue;
    1128         306 :       }
    1129             : 
    1130             :       /* Credit rewards to vote account (creating a new system account if
    1131             :          it does not exist) */
    1132           9 :       fd_accdb_svm_credit( bank, accdb, capture_ctx, &ele->pubkey, rewards, 1 );
    1133           9 :       distributed_rewards   = fd_ulong_sat_add( distributed_rewards, rewards );
    1134           9 :       credited_vote_rewards = fd_ulong_sat_add( credited_vote_rewards, rewards );
    1135           9 :     }
    1136         249 :   }
    1137             : 
    1138             :   /* Verify that we didn't pay any more than we expected to */
    1139         318 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1140         318 :   ulong total_stake_rewards = fd_stake_rewards_total_rewards( stake_rewards, bank->stake_rewards_fork_id );
    1141             : 
    1142         318 :   ulong total_rewards = fd_ulong_sat_add( distributed_rewards, total_stake_rewards );
    1143         318 :   if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
    1144           0 :     FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
    1145           0 :   }
    1146             : 
    1147         318 :   runtime_stack->stakes.distributed_rewards = distributed_rewards;
    1148         318 :   runtime_stack->stakes.total_rewards       = rewards_calc_result->validator_rewards;
    1149         318 :   return fd_ulong_sat_add( credited_vote_rewards, total_stake_rewards );
    1150         318 : }
    1151             : 
    1152             : /* Note: modifies delegation in-place, adjusting it for rent-exempt
    1153             :    minimum balance requirements.
    1154             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L55-L76 */
    1155             : static void
    1156             : adjust_delegation_for_rent( fd_delegation_t * delegation,
    1157             :                             ulong             rewarded_epoch,
    1158             :                             ulong             new_delegation_with_rewards,
    1159             :                             ulong             lamports_with_rewards,
    1160           3 :                             ulong             minimum_lamports ) {
    1161           3 :   ulong new_delegation = fd_ulong_min( new_delegation_with_rewards,
    1162           3 :                                        fd_ulong_sat_sub( lamports_with_rewards, minimum_lamports ) );
    1163             : 
    1164           3 :   if( new_delegation!=delegation->stake ) {
    1165           3 :     delegation->stake = new_delegation;
    1166           3 :     if( FD_UNLIKELY( new_delegation==0UL ) ) {
    1167           0 :       delegation->deactivation_epoch = rewarded_epoch;
    1168           0 :     }
    1169           3 :   }
    1170           3 : }
    1171             : 
    1172             : /* Distributes a single partitioned reward to a single stake account.  acc was
    1173             :    acquired by the caller as part of a batch and is released with it, so the
    1174             :    early returns below leave commit at 0, which fd_accdb_release skips.
    1175             :    The adders and sums are used only when lthash_mode is INBAND. */
    1176             : 
    1177             : static int
    1178             : distribute_epoch_reward_to_stake_acc( fd_bank_t *         bank,
    1179             :                                       fd_capture_ctx_t *  capture_ctx,
    1180             :                                       ulong               reward_lamports,
    1181             :                                       ulong               block_reward,
    1182             :                                       ulong               new_credits_observed,
    1183             :                                       ulong               partition_idx,
    1184             :                                       fd_acc_t *          acc,
    1185             :                                       uchar               lthash_mode,
    1186             :                                       fd_lthash_adder_t * adder_pre,
    1187             :                                       fd_lthash_value_t * sum_pre,
    1188             :                                       fd_lthash_adder_t * adder_post,
    1189         228 :                                       fd_lthash_value_t * sum_post ) {
    1190         228 :   if( FD_UNLIKELY( !acc->lamports ) ) {
    1191           0 :     return 1; /* account does not exist */
    1192           0 :   }
    1193             : 
    1194         228 :   fd_stake_state_t const * stake_state_orig = fd_stakes_get_state( acc );
    1195         228 :   if( FD_UNLIKELY( !stake_state_orig || stake_state_orig->stake_type!=FD_STAKE_STATE_STAKE ) ) {
    1196           0 :     return 1; /* not a valid stake account */
    1197           0 :   }
    1198             : 
    1199         228 :   fd_pubkey_t const * stake_pubkey   = fd_type_pun_const(acc->pubkey);
    1200         228 :   fd_stake_state_t    stake_state[1] = { *stake_state_orig };
    1201             : 
    1202         228 :   if( lthash_mode==FD_BANK_LTHASH_MODE_INBAND ) {
    1203         228 :     fd_lthash_adder_push_solana_account( adder_pre, sum_pre, stake_pubkey->uc, acc->data, acc->data_len, acc->lamports, (uchar)!!acc->executable, acc->owner );
    1204         228 :   }
    1205             : 
    1206             :   /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L263-L268 */
    1207         228 :   ulong lamports_pre = acc->lamports;
    1208         228 :   FD_TEST( !__builtin_add_overflow( acc->lamports, reward_lamports, &acc->lamports ) );
    1209         228 :   FD_TEST( !__builtin_add_overflow( acc->lamports, block_reward,    &acc->lamports ) );
    1210             : 
    1211         228 :   ulong old_credits_observed                = stake_state->stake.stake.credits_observed;
    1212         228 :   stake_state->stake.stake.credits_observed = new_credits_observed;
    1213         228 :   stake_state->stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->stake.stake.delegation.stake, reward_lamports );
    1214             : 
    1215             :   /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L259-L283 */
    1216         228 :   fd_stake_t * new_stake = &stake_state->stake.stake;
    1217         228 :   if( FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) ) {
    1218           3 :     ulong minimum_balance = fd_rent_exempt_minimum_balance( &bank->f.rent, acc->data_len );
    1219           3 :     adjust_delegation_for_rent(
    1220           3 :       &new_stake->delegation,
    1221           3 :       fd_ulong_sat_sub( bank->f.epoch, 1UL ),
    1222           3 :       new_stake->delegation.stake,
    1223           3 :       acc->lamports,
    1224           3 :       minimum_balance );
    1225           3 :   }
    1226             : 
    1227         228 :   fd_stake_delegations_t * stake_delegations_upd = fd_bank_stake_delegations_modify( bank );
    1228         228 :   fd_stake_delegations_fork_update( stake_delegations_upd,
    1229         228 :                                     bank->stake_delegations_fork_id, 0UL,
    1230         228 :                                     stake_pubkey,
    1231         228 :                                     &stake_state->stake.stake.delegation.voter_pubkey,
    1232         228 :                                     stake_state->stake.stake.delegation.stake,
    1233         228 :                                     stake_state->stake.stake.delegation.activation_epoch,
    1234         228 :                                     stake_state->stake.stake.delegation.deactivation_epoch,
    1235         228 :                                     stake_state->stake.stake.credits_observed,
    1236         228 :                                     acc->lamports,
    1237         228 :                                     (uint)acc->data_len );
    1238         228 :   if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) {
    1239           0 :     fd_event_runtime_stake_delegation_payout_emit( bank,
    1240           0 :                                                    stake_pubkey->uc,
    1241           0 :                                                    stake_state->stake.stake.delegation.voter_pubkey.uc,
    1242           0 :                                                    stake_state->stake.stake.delegation.stake,
    1243           0 :                                                    stake_state->stake.stake.delegation.activation_epoch,
    1244           0 :                                                    stake_state->stake.stake.delegation.deactivation_epoch,
    1245           0 :                                                    stake_state->stake.stake.credits_observed );
    1246           0 :   }
    1247             : 
    1248         228 :   if( FD_UNLIKELY( capture_ctx && capture_ctx->capture_solcap ) ) {
    1249           0 :     fd_capture_link_write_stake_account_payout( capture_ctx,
    1250           0 :                                                 bank->f.slot,
    1251           0 :                                                 *stake_pubkey,
    1252           0 :                                                 bank->f.slot,
    1253           0 :                                                 acc->lamports,
    1254           0 :                                                 (long)( reward_lamports+block_reward ),
    1255           0 :                                                 new_credits_observed,
    1256           0 :                                                 (long)( new_credits_observed - old_credits_observed ),
    1257           0 :                                                 stake_state->stake.stake.delegation.stake,
    1258           0 :                                                 (long)( reward_lamports+block_reward ) );
    1259           0 :   }
    1260             : 
    1261         228 :   FD_STORE( fd_stake_state_t, acc->data, *stake_state );
    1262         228 :   if( lthash_mode==FD_BANK_LTHASH_MODE_INBAND ) {
    1263         228 :     fd_lthash_adder_push_solana_account( adder_post, sum_post, stake_pubkey->uc, acc->data, acc->data_len, acc->lamports, (uchar)!!acc->executable, acc->owner );
    1264         228 :   } else if( lthash_mode==FD_BANK_LTHASH_MODE_OOB_RECORD ) {
    1265           0 :     fd_bank_lthash_record( bank, acc->pubkey );
    1266           0 :   }
    1267         228 :   fd_hashes_capture_account( stake_pubkey->uc, acc->owner, acc->lamports, acc->executable, acc->data, acc->data_len, bank, capture_ctx );
    1268         228 :   if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) {
    1269           0 :     fd_event_runtime_reward_emit( bank, FD_EVENT_RUNTIME_REWARD_KIND_STAKE, acc->pubkey, acc->owner,
    1270           0 :                                   lamports_pre, acc->lamports, partition_idx, new_credits_observed,
    1271           0 :                                   stake_state->stake.stake.delegation.stake );
    1272           0 :   }
    1273         228 :   acc->commit = 1;
    1274             : 
    1275         228 :   return 0;
    1276         228 : }
    1277             : 
    1278             : /* Accdb batch size for updating stake accounts */
    1279        6114 : #define STAKE_REWARD_ACC_BATCH_SZ (32UL)
    1280             : 
    1281             : /* Process reward credits for a partition of rewards.  Store the rewards
    1282             :    to AccountsDB, update reward history record and total capitalization
    1283             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
    1284             : static void
    1285             : distribute_epoch_rewards_in_partition( fd_stake_rewards_t *      stake_rewards,
    1286             :                                        ulong                     partition_idx,
    1287             :                                        fd_bank_t *               bank,
    1288             :                                        fd_accdb_t *              accdb,
    1289         162 :                                        fd_capture_ctx_t *        capture_ctx ) {
    1290             : 
    1291         162 :   ulong lamports_distributed     = 0UL;
    1292         162 :   ulong lamports_burned          = 0UL;
    1293         162 :   ulong block_reward_distributed = 0UL;
    1294         162 :   ulong block_reward_burned      = 0UL;
    1295             : 
    1296             :   /* The mode does not change during distribution. */
    1297         162 :   uchar lthash_mode = bank->lthash_mode;
    1298             : 
    1299         162 :   fd_lthash_adder_t adder_pre[1], adder_post[1];
    1300         162 :   fd_lthash_value_t sum_pre[1], sum_post[1];
    1301         162 :   if( lthash_mode==FD_BANK_LTHASH_MODE_INBAND ) {
    1302         162 :     fd_lthash_adder_new( adder_pre  );
    1303         162 :     fd_lthash_adder_new( adder_post );
    1304         162 :     fd_lthash_zero( sum_pre  );
    1305         162 :     fd_lthash_zero( sum_post );
    1306         162 :   }
    1307             : 
    1308             :   /* Acquire and process stake accounts in batches of 32 */
    1309             : 
    1310         162 :   fd_pubkey_t   pubkeys         [ STAKE_REWARD_ACC_BATCH_SZ ];
    1311         162 :   uchar const * pubkey_ptrs     [ STAKE_REWARD_ACC_BATCH_SZ ];
    1312         162 :   int           writable        [ STAKE_REWARD_ACC_BATCH_SZ ];
    1313         162 :   fd_acc_t      accs            [ STAKE_REWARD_ACC_BATCH_SZ ];
    1314         162 :   ulong         reward_lamports [ STAKE_REWARD_ACC_BATCH_SZ ];
    1315         162 :   ulong         credits_observed[ STAKE_REWARD_ACC_BATCH_SZ ];
    1316         162 :   ulong         block_rewards   [ STAKE_REWARD_ACC_BATCH_SZ ];
    1317             : 
    1318        5346 :   for( ulong i=0UL; i<STAKE_REWARD_ACC_BATCH_SZ; i++ ) {
    1319        5184 :     pubkey_ptrs[ i ] = pubkeys[ i ].uc;
    1320        5184 :     writable   [ i ] = 1;
    1321        5184 :   }
    1322             : 
    1323         162 :   fd_stake_rewards_iter_init( stake_rewards, bank->stake_rewards_fork_id, (ushort)partition_idx );
    1324         318 :   while( !fd_stake_rewards_iter_done( stake_rewards ) ) {
    1325             : 
    1326             :     /* Gather the next batch of rewards out of the partition. */
    1327         156 :     ulong batch_cnt = 0UL;
    1328         384 :     for( ; batch_cnt<STAKE_REWARD_ACC_BATCH_SZ && !fd_stake_rewards_iter_done( stake_rewards );
    1329         228 :          batch_cnt++, fd_stake_rewards_iter_next( stake_rewards, bank->stake_rewards_fork_id ) ) {
    1330         228 :       fd_stake_rewards_iter_ele( stake_rewards,
    1331         228 :                                  bank->stake_rewards_fork_id,
    1332         228 :                                  &pubkeys         [ batch_cnt ],
    1333         228 :                                  &reward_lamports [ batch_cnt ],
    1334         228 :                                  &credits_observed[ batch_cnt ],
    1335         228 :                                  &block_rewards   [ batch_cnt ] );
    1336         228 :     }
    1337             : 
    1338         156 :     fd_accdb_acquire( accdb, bank->accdb_fork_id, batch_cnt, pubkey_ptrs, writable, accs );
    1339             : 
    1340             :     /* Calculate and flush stake account updates */
    1341         384 :     for( ulong i=0UL; i<batch_cnt; i++ ) {
    1342         228 :       if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc( bank,
    1343         228 :                                                             capture_ctx,
    1344         228 :                                                             reward_lamports[ i ],
    1345         228 :                                                             block_rewards[ i ],
    1346         228 :                                                             credits_observed[ i ],
    1347         228 :                                                             partition_idx,
    1348         228 :                                                             &accs[ i ],
    1349         228 :                                                             lthash_mode,
    1350         228 :                                                             adder_pre, sum_pre,
    1351         228 :                                                             adder_post, sum_post ) ) ) {
    1352         228 :         lamports_distributed     += reward_lamports[ i ];
    1353         228 :         block_reward_distributed += block_rewards[ i ];
    1354         228 :       } else {
    1355           0 :         lamports_burned     += reward_lamports[ i ];
    1356           0 :         block_reward_burned += block_rewards[ i ];
    1357           0 :       }
    1358         228 :     }
    1359             : 
    1360         156 :     fd_accdb_release( accdb, batch_cnt, accs );
    1361         156 :   }
    1362             : 
    1363         162 :   if( lthash_mode==FD_BANK_LTHASH_MODE_INBAND ) {
    1364         162 :     fd_lthash_adder_flush( adder_pre,  sum_pre  );
    1365         162 :     fd_lthash_adder_flush( adder_post, sum_post );
    1366             : 
    1367         162 :     fd_lthash_value_t * bank_lthash = fd_bank_lthash_locking_modify( bank );
    1368         162 :     fd_lthash_sub( bank_lthash, sum_pre  );
    1369         162 :     fd_lthash_add( bank_lthash, sum_post );
    1370         162 :     fd_bank_lthash_end_locking_modify( bank );
    1371         162 :   }
    1372             : 
    1373             :   /* Update the epoch rewards sysvar with the amount distributed and burnt */
    1374         162 :   fd_sysvar_epoch_rewards_distribute( bank, accdb, capture_ctx,
    1375         162 :                                       lamports_distributed + lamports_burned,
    1376         162 :                                       block_reward_distributed + block_reward_burned );
    1377             : 
    1378         162 :   FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu, block rewards burned: %lu, block rewards distributed: %lu",
    1379         162 :                  lamports_burned, lamports_distributed, block_reward_burned, block_reward_distributed ));
    1380             : 
    1381         162 :   bank->f.capitalization = bank->f.capitalization + lamports_distributed + block_reward_distributed;
    1382         162 : }
    1383             : 
    1384             : static int
    1385             : recalculate_partitioned_rewards( fd_bank_t *          bank,
    1386             :                                  fd_accdb_t *         accdb,
    1387             :                                  fd_runtime_stack_t * runtime_stack,
    1388             :                                  fd_capture_ctx_t *   capture_ctx,
    1389             :                                  int                  snapshot_boot,
    1390             :                                  uint                 win_lo );
    1391             : 
    1392             : /* Process reward distribution for the block if it is inside reward interval.
    1393             : 
    1394             :    https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L45-L136 */
    1395             : void
    1396             : fd_distribute_partitioned_epoch_rewards( fd_bank_t *          bank,
    1397             :                                          fd_accdb_t *         accdb,
    1398             :                                          fd_runtime_stack_t * runtime_stack,
    1399        7575 :                                          fd_capture_ctx_t *   capture_ctx ) {
    1400             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L46-L48 */
    1401        7575 :   if( FD_LIKELY( bank->stake_rewards_fork_id==USHORT_MAX ) ) return;
    1402             : 
    1403         480 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1404             : 
    1405         480 :   ulong block_height                       = bank->f.block_height;
    1406         480 :   ulong distribution_starting_block_height = fd_stake_rewards_starting_block_height( stake_rewards, bank->stake_rewards_fork_id );
    1407         480 :   ulong distribution_end_exclusive         = fd_stake_rewards_exclusive_ending_block_height( stake_rewards, bank->stake_rewards_fork_id );
    1408             : 
    1409             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L55-L58 */
    1410         480 :   if( FD_UNLIKELY( block_height<distribution_starting_block_height ) ) {
    1411         318 :     return;
    1412         318 :   }
    1413             : 
    1414             :   /* The logic in Agave for EpochRewardPhase::Calculation has no direct
    1415             :      equivalent in Firedancer, because reward calculation is done
    1416             :      eagerly at the epoch boundary, whereas for Agave it's done at the
    1417             :      first distribution block.
    1418             :      https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L60-L90 */
    1419             : 
    1420         162 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
    1421         162 :   ulong                       epoch          = bank->f.epoch;
    1422             : 
    1423         162 :   if( FD_UNLIKELY( fd_epoch_slot_cnt( epoch_schedule, epoch ) <= fd_stake_rewards_num_partitions( stake_rewards, bank->stake_rewards_fork_id ) ) ) {
    1424           0 :     FD_LOG_CRIT(( "Should not be distributing rewards" ));
    1425           0 :   }
    1426             : 
    1427             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L110-L114 */
    1428         162 :   if( FD_LIKELY( block_height>=distribution_starting_block_height && block_height<distribution_end_exclusive ) ) {
    1429         162 :     ulong partition_idx = block_height-distribution_starting_block_height;
    1430             : 
    1431             :     /* The rewards of this partition are only in memory if the window
    1432             :        covers it.  If they are not, re-derive the window that does. */
    1433         162 :     ushort fork_id = bank->stake_rewards_fork_id;
    1434         162 :     if( FD_UNLIKELY( partition_idx<(ulong)fd_stake_rewards_window_lo( stake_rewards, fork_id ) ||
    1435         162 :                      partition_idx>(ulong)fd_stake_rewards_window_hi( stake_rewards, fork_id ) ) ) {
    1436           3 :       FD_LOG_INFO(( "reward partition is not in the window, recalculating" ));
    1437           3 :       if( FD_UNLIKELY( !recalculate_partitioned_rewards( bank, accdb, runtime_stack, capture_ctx, 0, (uint)partition_idx ) ) ) {
    1438           0 :         FD_LOG_CRIT(( "failed to recalculate partitioned rewards "
    1439           0 :                       "(slot=%lu block_height=%lu partition=%lu fork=%u)",
    1440           0 :                       bank->f.slot,
    1441           0 :                       block_height,
    1442           0 :                       partition_idx,
    1443           0 :                       (uint)fork_id ));
    1444           0 :       }
    1445           3 :       stake_rewards = fd_bank_stake_rewards_modify( bank );
    1446           3 :     }
    1447             : 
    1448         162 :     distribute_epoch_rewards_in_partition( stake_rewards, partition_idx, bank, accdb, capture_ctx );
    1449         162 :   }
    1450             : 
    1451             :   /* If we have finished distributing rewards, set the status to inactive
    1452             :      https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L116-L135 */
    1453         162 :   if( fd_ulong_sat_add( block_height, 1UL )>=distribution_end_exclusive ) {
    1454         156 :     fd_sysvar_epoch_rewards_set_inactive( bank, accdb, capture_ctx );
    1455         156 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    1456         156 :     bank->stake_rewards_fork_id = USHORT_MAX;
    1457         156 :   }
    1458         162 : }
    1459             : 
    1460             : /* Partitioned epoch rewards entry-point.
    1461             : 
    1462             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L102
    1463             :    https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank.rs#L1748-L1899
    1464             : */
    1465             : void
    1466             : fd_begin_partitioned_rewards( fd_bank_t *                    bank,
    1467             :                               fd_accdb_t *                   accdb,
    1468             :                               fd_runtime_stack_t *           runtime_stack,
    1469             :                               fd_capture_ctx_t *             capture_ctx,
    1470             :                               fd_stake_delegations_t const * stake_delegations,
    1471             :                               fd_hash_t const *              parent_blockhash,
    1472             :                               ulong                          parent_epoch,
    1473         318 :                               ulong                          parent_capitalization ) {
    1474             : 
    1475         318 :   fd_reward_epoch_stakes_set( bank,
    1476         318 :                               accdb,
    1477         318 :                               capture_ctx,
    1478         318 :                               parent_epoch,
    1479         318 :                               runtime_stack );
    1480             : 
    1481         318 :   ulong capitalization_rewards = calculate_rewards_and_distribute_vote_rewards(
    1482         318 :       bank,
    1483         318 :       accdb,
    1484         318 :       runtime_stack,
    1485         318 :       stake_delegations,
    1486         318 :       capture_ctx,
    1487         318 :       parent_epoch );
    1488             : 
    1489             :   /* Once the rewards for vote accounts have been distributed and stake
    1490             :      account rewards have been calculated, we can now set our epoch
    1491             :      reward status to be active and we can initialize the epoch rewards
    1492             :      sysvar.  This sysvar is then deleted once all of the partitioned
    1493             :      stake rewards have been distributed.
    1494             : 
    1495             :      The Agave client calculates the partitions for each stake reward
    1496             :      when the first distribution block is reached.  The Firedancer
    1497             :      client differs here since we hash the partitions during the epoch
    1498             :      boundary. */
    1499             : 
    1500         318 :   ulong distribution_starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
    1501         318 :   uint  num_partitions                     = fd_stake_rewards_num_partitions( fd_bank_stake_rewards_query( bank ), bank->stake_rewards_fork_id );
    1502             : 
    1503         318 :   fd_sysvar_epoch_rewards_init(
    1504         318 :       bank,
    1505         318 :       accdb,
    1506         318 :       capture_ctx,
    1507         318 :       runtime_stack->stakes.distributed_rewards,
    1508         318 :       distribution_starting_block_height,
    1509         318 :       num_partitions,
    1510         318 :       runtime_stack->stakes.total_rewards,
    1511         318 :       0UL, /* Alpenglow rewards are not points based */
    1512         318 :       parent_blockhash,
    1513         318 :       runtime_stack->stakes.block_rewards );
    1514             : 
    1515         318 :   ulong capitalization;
    1516         318 :   FD_TEST( !__builtin_uaddl_overflow( parent_capitalization, capitalization_rewards, &capitalization ) );
    1517         318 :   fd_epoch_inflation_account_update( bank, accdb, capture_ctx, calculate_epoch_inflation_rewards( bank, capitalization, bank->f.epoch ) );
    1518         318 : }
    1519             : 
    1520             : /*
    1521             :     Re-calculates partitioned stake rewards.
    1522             :     This updates the slot context's epoch reward status with the recalculated partitioned rewards.
    1523             : 
    1524             :     Everything the calculation needs survives in the epoch rewards
    1525             :     sysvar and in the bank, so the partitions can be rebuilt at any
    1526             :     point during the distribution interval.  The rebuilt partitions
    1527             :     always go into a freshly acquired fork, with its window starting at
    1528             :     win_lo, so that banks reading the fork this bank was using keep the
    1529             :     entries they computed for themselves.  snapshot_boot selects where
    1530             :     the vote commissions come from: a bank restored from a snapshot has
    1531             :     to resolve them, every other caller reuses the ones the epoch
    1532             :     boundary resolved.
    1533             : 
    1534             :     https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521
    1535             :     https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1038-L1095 */
    1536             : static int
    1537             : recalculate_partitioned_rewards( fd_bank_t *          bank,
    1538             :                                  fd_accdb_t *         accdb,
    1539             :                                  fd_runtime_stack_t * runtime_stack,
    1540             :                                  fd_capture_ctx_t *   capture_ctx,
    1541             :                                  int                  snapshot_boot,
    1542          18 :                                  uint                 win_lo ) {
    1543             : 
    1544          18 :   fd_sysvar_epoch_rewards_t epoch_rewards_sysvar[1];
    1545          18 :   if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read(
    1546          18 :           accdb, bank->accdb_fork_id, epoch_rewards_sysvar ) ) ) {
    1547           0 :     FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
    1548           0 :     return 0;
    1549           0 :   }
    1550             : 
    1551          18 :   FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
    1552             : 
    1553          18 :   if( FD_UNLIKELY( !epoch_rewards_sysvar->active ) ) {
    1554           0 :     FD_LOG_DEBUG(( "epoch rewards is inactive" ));
    1555           0 :     return 0;
    1556           0 :   }
    1557             : 
    1558          18 :   ulong partition_cnt = epoch_rewards_sysvar->num_partitions;
    1559          18 :   ulong starting_block_height =
    1560          18 :       epoch_rewards_sysvar->distribution_starting_block_height;
    1561          18 :   if( FD_UNLIKELY( !partition_cnt ||
    1562          18 :                    partition_cnt>MAX_PARTITIONS_PER_EPOCH ||
    1563          18 :                    starting_block_height>ULONG_MAX-partition_cnt ||
    1564          18 :                    (ulong)win_lo>=partition_cnt ) ) {
    1565           0 :     FD_LOG_WARNING(( "invalid epoch rewards partition window" ));
    1566           0 :     return 0;
    1567           0 :   }
    1568             : 
    1569          18 :   fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
    1570          18 :   fd_vote_rewards_map_reset( vote_ele_map );
    1571             : 
    1572          18 :   fd_epoch_credits_view_t epoch_credits_view[1];
    1573          18 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
    1574          18 :   ulong                epoch_credits_len = epoch_credits_view->len;
    1575          18 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
    1576             : 
    1577          18 :   if( FD_LIKELY( !snapshot_boot ) ) {
    1578             : 
    1579             :     /* The rewards of this epoch were computed once already, at the epoch
    1580             :        boundary or at snapshot load, and only the window is moving.  The
    1581             :        commissions resolved back then are stored with the epoch credits
    1582             :        and must be reused. */
    1583             : 
    1584           6 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    1585           3 :       fd_epoch_credits_t const * epoch_credits = &epoch_credits_arr[i];
    1586             : 
    1587           3 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
    1588           3 :       vote_ele->pubkey       = *(fd_pubkey_t const *)epoch_credits->pubkey;
    1589           3 :       vote_ele->vote_rewards = 0UL;
    1590           3 :       vote_ele->commission   = epoch_credits->commission;
    1591           3 :       fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
    1592           3 :     }
    1593             : 
    1594          15 :   } else {
    1595             : 
    1596             :     /* If the snapshot was loaded while partitioned epoch rewards is
    1597             :        active, then the vote rewards map must be populated with the state
    1598             :        of the vote accounts as of the end of the previous epoch boundary.
    1599             :        The epoch credits for these accounts are stored in the bank, and
    1600             :        the vote stakes retain the t-3 commission.  With this, it's
    1601             :        possible to recalculate the rewards for the previous epoch
    1602             :        boundary.  We need the commission from the end of the t-3 epoch
    1603             :        if we are calculating rewards for the transition from epoch t-1
    1604             :        to t since there needs to be a 2 epoch commission gap for the
    1605             :        delay_commission_updates feature. */
    1606             : 
    1607          15 :     fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
    1608          15 :     ushort             fork_id     = bank->vote_stakes_fork_id;
    1609             : 
    1610          30 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    1611          15 :       fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[i];
    1612          15 :       fd_pubkey_t const *  pubkey        = (fd_pubkey_t const *)epoch_credits->pubkey;
    1613             : 
    1614             :       /* Get the t-1 stake account information.  This is guaranteed to be
    1615             :          valid since the epoch credits are populated from the t-1 stakes
    1616             :          in the snapshot manifest. */
    1617          15 :       fd_vote_stakes_ele_t ele_t_1[1];
    1618          15 :       FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, fork_id, pubkey, ele_t_1 ) );
    1619             : 
    1620             :       /* Get the t-3 information before the t-2 fallback.  Either may be
    1621             :          absent if the vote account was created in a later epoch. */
    1622          15 :       fd_vote_stakes_ele_t ele_t_3[1];
    1623          15 :       int exists_t_3 = fd_vote_stakes_query_t_3( vote_stakes, fork_id, pubkey, ele_t_3 );
    1624             : 
    1625             :       /* Now get the t-2 information (if it exists).  This is not
    1626             :          guaranteed to be valid since it's possible for a vote account to
    1627             :          have been created in the last epoch. */
    1628          15 :       fd_vote_stakes_ele_t ele_t_2[1];
    1629          15 :       int exists_t_2 = fd_vote_stakes_query_t_2( vote_stakes, fork_id, pubkey, ele_t_2 );
    1630             : 
    1631          15 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
    1632          15 :       vote_ele->pubkey       = *(fd_pubkey_t *)epoch_credits->pubkey;
    1633          15 :       vote_ele->vote_rewards = 0UL;
    1634          15 :       if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
    1635           3 :         vote_ele->commission = exists_t_3 ? ele_t_3->commission : (exists_t_2 ? ele_t_2->commission : ele_t_1->commission);
    1636          12 :       } else {
    1637          12 :         vote_ele->commission = ele_t_1->commission;
    1638          12 :       }
    1639          15 :       fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
    1640          15 :     }
    1641             : 
    1642             :     /* Publish the resolved commissions so that any later repositioning
    1643             :        of this fork's window reuses them. */
    1644          30 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    1645          15 :       epoch_credits_arr[i].commission = runtime_stack->stakes.vote_ele[i].commission;
    1646          15 :     }
    1647          15 :   }
    1648          18 :   fd_epoch_credits_view_fini( epoch_credits_view );
    1649             : 
    1650             :   /* If partitioned rewards are active, the rewarded epoch is always the immediately
    1651             :       preceding epoch.
    1652             : 
    1653             :       https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
    1654          18 :   FD_LOG_DEBUG(( "epoch rewards is active" ));
    1655             : 
    1656          18 :   ulong const epoch          = bank->f.epoch;
    1657          18 :   ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
    1658             : 
    1659          18 :   fd_reward_epoch_stakes_restore( bank, accdb, rewarded_epoch, runtime_stack );
    1660             : 
    1661          18 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    1662          18 :   fd_stake_history_t stake_history[1];
    1663          18 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, bank->accdb_fork_id, stake_history_data, stake_history ) ) ) {
    1664           0 :     FD_LOG_ERR(( "Unable to read stake history sysvar" ));
    1665           0 :   }
    1666             : 
    1667          18 :   fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
    1668          18 :   fd_stake_delegations_view_begin( stake_delegations,
    1669          18 :                                    bank->f.epoch,
    1670          18 :                                    stake_history,
    1671          18 :                                    &bank->f.warmup_cooldown_rate_epoch,
    1672          18 :                                    bank->stake_delegations_fork_id );
    1673             : 
    1674          18 :   calculate_stake_vote_rewards(
    1675          18 :       bank,
    1676          18 :       stake_delegations,
    1677          18 :       capture_ctx,
    1678          18 :       stake_history,
    1679          18 :       rewarded_epoch,
    1680          18 :       epoch_rewards_sysvar->total_rewards,
    1681          18 :       runtime_stack,
    1682          18 :       1 );
    1683             : 
    1684             :   /* The recomputed rewards go into a fork of their own rather than over
    1685             :      the window the bank is holding: banks that branched off this one
    1686             :      share that window and are entitled to the entries they already have,
    1687             :      which were computed from their own delegations. */
    1688             : 
    1689          18 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1690          18 :   ushort               fork_idx      = fd_stake_rewards_init( stake_rewards,
    1691          18 :                                                               &epoch_rewards_sysvar->parent_blockhash,
    1692          18 :                                                               starting_block_height,
    1693          18 :                                                               (uint)partition_cnt,
    1694          18 :                                                               win_lo,
    1695          18 :                                                               runtime_stack->stakes.stake_rewards_cnt );
    1696          18 :   if( FD_LIKELY( bank->stake_rewards_fork_id!=USHORT_MAX ) ) {
    1697           3 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    1698           3 :   }
    1699          18 :   bank->stake_rewards_fork_id = fork_idx;
    1700             : 
    1701          18 :   setup_stake_partitions(
    1702          18 :       bank,
    1703          18 :       stake_history,
    1704          18 :       stake_delegations,
    1705          18 :       runtime_stack,
    1706          18 :       fork_idx,
    1707          18 :       rewarded_epoch,
    1708          18 :       epoch_rewards_sysvar->total_rewards );
    1709             : 
    1710          18 :   fd_stake_delegations_view_end( stake_delegations,
    1711          18 :                                  stake_history,
    1712          18 :                                  &bank->f.warmup_cooldown_rate_epoch );
    1713          18 :   return 1;
    1714          18 : }
    1715             : 
    1716             : void
    1717             : fd_rewards_recalculate_partitioned_rewards( fd_bank_t *          bank,
    1718             :                                             fd_accdb_t *         accdb,
    1719             :                                             fd_runtime_stack_t * runtime_stack,
    1720          15 :                                             fd_capture_ctx_t *   capture_ctx ) {
    1721          15 :   recalculate_partitioned_rewards( bank, accdb, runtime_stack, capture_ctx, 1, 0U );
    1722          15 : }

Generated by: LCOV version 1.14