LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 993 1409 70.5 %
Date: 2026-10-22 18:10:43 Functions: 39 86 45.3 %

          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 "../alpenglow/fd_alpenglow.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           0 : #define FD_DOUBLE_DISINFLATION_TAPER (0.30)
      25             : 
      26             : /* A note on the calculation of points for inflation rewards at the
      27             :    epoch boundary.
      28             : 
      29             :    As of today there are more than 1.5 million stake delegations on
      30             :    mainnet (1,583,562 at the 987->988 boundary).  Each and every one of
      31             :    them could in theory earn some lamports of inflation rewards, so at
      32             :    the boundary each and every delegation is looked at to compute its
      33             :    share.  A delegation's share is directly proportional to its
      34             :    "points", and the points are essentially the area under a curve where
      35             :    the y axis is the delegation's effective stake and the x axis is the
      36             :    monotonically increasing vote credits.
      37             : 
      38             :      points = SUM over eligible epochs e of ( effective(e) * credits_owed(e) )
      39             : 
      40             :    There are two dimensions to this summation formula.  (1) A vote
      41             :    account monotonically ticks up its vote credit as it votes, and
      42             :    records its per-epoch credit history as (epoch,final,initial) tuples,
      43             :    where the [initial,final] ranges are contiguous and non-overlapping.
      44             :    That is to say, initial[n]=final[n-1].  A stake delegation stores a
      45             :    watermark, credits_observed, of how far it has already been paid
      46             :    along the corresponding vote account's credit history.  What the
      47             :    stake delegation is owed for an epoch is the part of that epoch's
      48             :    [initial,final] that sits above the credits_observed.  The credit
      49             :    history is capped at 64 epochs, so the sum is never more than 64
      50             :    terms long.  The epochs that overlap [credits_observed,final[last]]
      51             :    are the ones that contribute to the sum.  This is the x axis.  (2)
      52             :    Delegated stake ramps up to the full delegated amount over one or
      53             :    more epochs when it's activating, and ramps down gradually to 0 when
      54             :    it's deactivating.  So the effective stake at an epoch, aka
      55             :    effective(e), is not always simply the delegated amount.  Computing
      56             :    effective(e) involves running a simulation of the ramp, encoded in
      57             :    the activating_and_deactivating() function.  This is the y axis.
      58             : 
      59             :      stake |        ______________P________________
      60             :            |       /                               \
      61             :            | L   U/  warmup               cooldown  \D     R
      62             :          0 |_____/                                   \____________
      63             :            +-----+---------------------------------+--------------->  vote credits
      64             :             activation         activated          deactivation    cooled down
      65             :             (ramps up)   (effective=delegated)    (ramps down)    (effective=0)
      66             : 
      67             :    So under a reference implementation, the worst case is 64 effective()
      68             :    computations per delegation.  The delegations that tend to hit this
      69             :    case are dust delegations, either activated or deactivated, whose
      70             :    tiny or zero points share keeps rounding their rewards down to zero.
      71             :    As a result, reward doesn't pay out to a dust delegation, so it never
      72             :    advances its credits_observed, and over time it gets pegged at the
      73             :    worst case 64-term sum.  Roughly 14% of delegations are multi-term
      74             :    evaluations like this, and they account for ~89% (11.3M of 12.6M) of
      75             :    all the effective() invocations the points pass does.  A minority of
      76             :    delegations demanding the overwhelming majority of work, and they
      77             :    barely get any rewards, if at all.
      78             : 
      79             :    The good news is that the shape of the warmup/cooldown curve isn't
      80             :    arbitrary.  Observe that within a given boundary, a delegation's
      81             :    (activation_epoch,deactivation_epoch,stake) are fixed, and on this
      82             :    frozen tuple the effective stake curve is a single hump aka at most
      83             :    one peak.  The plot above shows the fullest warmup/cooldown curve
      84             :    within a boundary.  It rises during warmup (U) from 0 (L,
      85             :    epoch<=activation), sits on a flat plateau (P) at exactly the height
      86             :    of delegated stake, optionally ramps back down (D) after
      87             :    deactivation, and rests on a flat zero floor (R).  It never goes down
      88             :    and then back up within a given boundary.  In practice, the summation
      89             :    usually runs over just a subsection of this full curve.  Depending on
      90             :    which of the five zones {L,U,P,D,R} the first and the last
      91             :    contributing terms (the "o"s below) sit on the warmup/cooldown curve,
      92             :    there can be up to 15 unique possible subsection spans.  As of the
      93             :    987->988 boundary, the following three cases cover almost the entire
      94             :    points pass of a reference implementation.  The other spans are
      95             :    either rare or already cheap to evaluate.  We exploit the shape of
      96             :    each case to short circuit step-by-step summation.
      97             : 
      98             :    Case 1: Every contributing term sits on the zero floor.  The
      99             :    delegation deactivated at or before the epoch its watermark froze.
     100             :    This is the common fate of deactivated and abandoned dust stake whose
     101             :    reward payout stopped at deactivation.  This accounts for 79.8% of
     102             :    the effective() invocations and 88.4% of the effective() iterations
     103             :    in the points pass.  This is the R->R span.
     104             : 
     105             :            stake |
     106             :                  |
     107             :                0 | o--o--o--o--o
     108             :                  '--------------->  vote credits
     109             :                    ^ every contributing term sits past full deactivation
     110             : 
     111             :    Case 2: The contributing terms straddle the hump.  A few terms ride
     112             :    the hump, and the rest sit on the zero floor.  This accounts for just
     113             :    0.05% of the effective() invocations and 0.04% of the effective()
     114             :    iterations in the points pass.  This is a tiny population (230
     115             :    delegations) but catching the 0-tail of this case is a free side
     116             :    effect of trying to short circuit Case 1.  This is the {L,U,P,D}->R
     117             :    spans.
     118             : 
     119             :            stake | o--o--.
     120             :                  |        \
     121             :                0 |         o--o--o
     122             :                  '----------------->  vote credits
     123             :                            ^ first full deactivation term
     124             : 
     125             :    Case 3: Every contributing term sits on the plateau.  The delegation
     126             :    is fully activated and, almost always, never deactivated.  This is
     127             :    the fate of activated dust whose reward keeps rounding down to zero
     128             :    while its vote account keeps voting.  This accounts for 19.8% of the
     129             :    effective() invocations and 11.6% of the effective() iterations in
     130             :    the points pass.  This is the P->P span.  As a side note, P->P also
     131             :    includes a small sliver, ~14.8K delegations here, that deactivated no
     132             :    earlier than the epoch of its last contributing term.  The stake is
     133             :    still fully effective at the deactivation epoch itself.  The fast
     134             :    path below doesn't cover these.
     135             : 
     136             :            stake | o--o--o--o--o
     137             :                  |
     138             :                0 |________________
     139             :                  '--------------->  vote credits
     140             : 
     141             :    We try to short circuit multi-term Cases 1 and 3, as well as the
     142             :    multi-term tail floor of Case 2.  The short circuit conditions do not
     143             :    have to be fully precise, they just need to be conservative but not
     144             :    overly conservative and ideally cheap so as to net a performance win
     145             :    for most of the case population.
     146             : 
     147             :    Further observe that the vast majority (84%, 1.33M of 1.58M) of stake
     148             :    delegations have an up-to-date (>=initial[last]) credits watermark
     149             :    and they are almost all either single-term Case 1 or single-term Case
     150             :    3.  We fast path 1.27M of these with delegation state tags.  The
     151             :    small delta is almost entirely fresh delegations in the just-ended
     152             :    epoch.
     153             : 
     154             :    Note that VAT doesn't make the problem of dust points go away.  If we
     155             :    were to apply VAT on the 987->988 boundary, 96.3% of effective() and
     156             :    98.5% of iterations in effective() would still survive.
     157             :    Unfortunately, there's just a lot of abandoned dust stake pointing at
     158             :    validators that are still live and voting.
     159             : 
     160             :    ===
     161             : 
     162             :    For the data minded, the full census of the possible spans at the
     163             :    reference points pass of the 987->988 boundary:
     164             : 
     165             :      span   delegations    invocations            iterations
     166             :      R->R       182,920     10,067,849 (79.8%)    14,585,005 (88.4%)  Case 1
     167             :      P->R           225          5,745 (0.05%)         6,902 (0.04%)  Case 2
     168             :      L->R             5            164                     0          Case 2
     169             :      P->P     1,327,803      2,500,408 (19.8%)     1,905,636 (11.6%)  Case 3
     170             :      L->L        49,087         49,087 (0.4%)              0
     171             :      L->P             3             42                    11
     172             :      other            0              0                     0
     173             :      none        23,519              0                     0
     174             :      total    1,583,562     12,623,295            16,497,554
     175             : 
     176             :    The L->L span is fresh delegations from the just-ended epoch.  Their
     177             :    contributing term sits at or before the activation epoch, so every
     178             :    effective() invocation early exits from the all-activating branch
     179             :    without entering the simulation loop, hence zero iterations.  "other"
     180             :    is the nine spans that include either the U or D ramp zones.  They
     181             :    are all empty, because under today's mainnet warmup/cooldown budget
     182             :    both ramps complete in a single epoch step, so no term ever observes
     183             :    a partially warmed or partially cooled stake.  "none" is delegations
     184             :    with no contributing terms, i.e. their watermark already caught up to
     185             :    the vote credits.
     186             : 
     187             :    And broken down by how much each fast/slow path covers:
     188             : 
     189             :      span  fast/slow path                delegations  invocations   iterations
     190             :      P->P  single-term state tag           1,268,371 /  1,268,371 /  1,094,607
     191             :            single-term effective()            12,745 /     12,745 /     12,265
     192             :            multi-term is_warmed_plateau()     44,683 /  1,179,761 /    777,412
     193             :            slow path                           2,004 /     39,531 /     21,352
     194             :      R->R  early exit at 0-tail              181,263 / 10,066,192 / 14,581,965
     195             :            single-term state tag                  22 /         22 /         22
     196             :            single-term effective()             1,635 /      1,635 /      3,018
     197             :      L->L  single-term effective()            49,006 /     49,006 /          0
     198             :            single-term state tag                  81 /         81 /          0
     199             :      P->R  early exit at 0-tail                  225 /      5,745 /      6,902
     200             :      L->R  early exit at 0-tail                    5 /        164 /          0
     201             :      L->P  slow path                               3 /         42 /         11
     202             : 
     203             :    The single-term effective() branch gets taken on state tag misses,
     204             :    including fresh L->L delegations whose tag is still WARMING/UNKNOWN,
     205             :    P->P stakes that deactivated during the rewarded epoch (tag COOLING,
     206             :    though every term is still fully effective) or whose delinquent
     207             :    vote's last credit entry predates the rewarded epoch, and a few stale
     208             :    R->R.  The 81 L->L state tag hits are accounts that delegated and
     209             :    deactivated in the same epoch because activation==deactivation
     210             :    classifies as COOLED with effective 0.  The P->P slow path is taken
     211             :    by multi-term stakes that deactivated no earlier than their last
     212             :    contributing term, so they get rejected by is_warmed_plateau() and
     213             :    the 0-tail exit doesn't happen either. */
     214             : 
     215             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
     216             : static double
     217         162 : total( fd_inflation_t const * inflation, double year ) {
     218         162 :   double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
     219         162 :   return (tapered > inflation->terminal) ? tapered : inflation->terminal;
     220         162 : }
     221             : 
     222             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
     223             : static double
     224         108 : foundation( fd_inflation_t const * inflation, double year ) {
     225         108 :   return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
     226         108 : }
     227             : 
     228             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
     229             : static double
     230          36 : validator( fd_inflation_t const * inflation, double year) {
     231             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
     232          36 :   FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
     233             :   /* We need 2 independent rounded ops like in Agave. Volatile blocks FMA contraction.
     234             :      https://github.com/anza-xyz/solana-sdk/blob/inflation%40v3.1.1/inflation/src/lib.rs#L105-L116 */
     235          36 :   volatile double foundation_portion = foundation( inflation, year );
     236          36 :   return total( inflation, year ) - foundation_portion;
     237          36 : }
     238             : 
     239             : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
     240             :     activation slot of the following features:
     241             :     - devnet_and_testnet
     242             :     - full_inflation_enable, if full_inflation_vote has been activated
     243             : 
     244             :     https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
     245             : static FD_FN_PURE ulong
     246          72 : get_inflation_start_slot( fd_bank_t const * bank ) {
     247          72 :   ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
     248          72 :       ? bank->f.features.devnet_and_testnet
     249          72 :       : ULONG_MAX;
     250             : 
     251          72 :   ulong enable = bank->f.features.full_inflation_enable;
     252             : 
     253          72 :   ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
     254          72 :   if( min_slot == ULONG_MAX ) {
     255           0 :     if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
     256           0 :       min_slot = bank->f.features.pico_inflation;
     257           0 :     } else {
     258           0 :       min_slot = 0;
     259           0 :     }
     260           0 :   }
     261          72 :   return min_slot;
     262          72 : }
     263             : 
     264             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2921-L2928 */
     265             : static ulong
     266             : inflation_start_slot_aligned_to_rewards( fd_bank_t const *           bank,
     267          72 :                                          fd_epoch_schedule_t const * epoch_schedule ) {
     268          72 :   ulong inflation_activation_slot = get_inflation_start_slot( bank );
     269          72 :   return fd_epoch_slot0( epoch_schedule,
     270          72 :                          fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule, inflation_activation_slot, NULL ), 1UL ) );
     271          72 : }
     272             : 
     273             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2915-L2918 */
     274             : static ulong
     275             : get_inflation_num_slots( fd_bank_t const *           bank,
     276             :                          fd_epoch_schedule_t const * epoch_schedule,
     277          36 :                          ulong                       slot ) {
     278          36 :   ulong inflation_start_slot = inflation_start_slot_aligned_to_rewards( bank, epoch_schedule );
     279          36 :   return fd_epoch_slot0( epoch_schedule, fd_slot_to_epoch( epoch_schedule, slot, NULL ) ) - inflation_start_slot;
     280          36 : }
     281             : 
     282             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2931-L2935 */
     283             : static double
     284          36 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
     285          36 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
     286          36 :   ulong num_slots            = get_inflation_num_slots( bank, epoch_schedule, bank->f.slot );
     287          36 :   ulong inflation_start_slot = inflation_start_slot_aligned_to_rewards( bank, epoch_schedule );
     288          36 :   return fd_slot_params_slot_range_duration_years( bank,
     289          36 :                                                    inflation_start_slot, inflation_start_slot + num_slots );
     290          36 : }
     291             : 
     292             : void
     293           0 : fd_apply_double_disinflation_rate( fd_bank_t * bank ) {
     294           0 :   double           year        = slot_in_year_for_inflation( bank );
     295           0 :   fd_inflation_t * inflation   = &bank->f.inflation;
     296           0 :   double           anchor_rate = total( inflation, year );
     297             : 
     298           0 :   inflation->taper   = FD_DOUBLE_DISINFLATION_TAPER;
     299             : 
     300             :   /* Matches Agave's calculation, as this is floating-point calculation
     301             :      https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank.rs#L6125 */
     302           0 :   inflation->initial = anchor_rate / pow( 1.0-FD_DOUBLE_DISINFLATION_TAPER, year );
     303           0 : }
     304             : 
     305             : /* Returns 1 if effective stake is clearly a warmed plateau.
     306             : 
     307             :    This function seeks to conservatively prove that the given stake has
     308             :    effective=delegated throughout its entire multi-term points
     309             :    calculation.  Concretely, this boils down to the following.
     310             : 
     311             :    - The stake is not slated for deactivation.  This removes the
     312             :      downramp.
     313             :    - The first contributing epoch is > the activation epoch.
     314             :    - The stake easily activated at activation epoch+1.  This removes the
     315             :      upramp.
     316             : 
     317             :    These conditions constrain the warmup/cooldown curve to a flat
     318             :    plateau at effective=delegated.  What makes this conservative is that
     319             :    there could be false negatives only: this function says that a stake
     320             :    is not a multi-term plateau when in fact it is.  We do a single-step
     321             :    warmup simulation in this function, and the stake is rejected if it
     322             :    failed to easily warm up in a single epoch one past the activation
     323             :    epoch.  So a stake that took >=2 epochs to warm up may well be fully
     324             :    warmed up by the time of its first contributing epoch.  We make this
     325             :    tradeoff because this function is meant to be a fast detector and in
     326             :    practice most stake activate quickly under today's mainnet warmup
     327             :    budget.  Another case of false negative rejections is for multi-term
     328             :    stakes that deactivated no earlier than the last contributing epoch.
     329             :    We could easily eliminate this class of false negatives by passing in
     330             :    the last contributing epoch and comparing against deactivation epoch.
     331             :    We make this tradeoff because this class is empirically small and
     332             :    this function becomes that much easier to reason about by virtue of
     333             :    cleanly eliminating the deactivation simulation. */
     334             : static inline int
     335             : is_warmed_plateau( fd_stake_delegation_t const * stake,
     336             :                    ulong                         first_contributing_epoch,
     337             :                    fd_stake_history_t const *    stake_history,
     338             :                    ulong *                       new_rate_activation_epoch,
     339           2 :                    int                           use_fixed_point_stake_math ) {
     340             :   /* Slated for deactivation. */
     341           2 :   if( stake->deactivation_epoch!=USHORT_MAX ) return 0;
     342             : 
     343             :   /* is_bootstrap(): https://github.com/solana-program/stake/blob/interface%40v4.3.1/interface/src/state.rs#L892
     344             :      Stake activated as per protocol. */
     345           2 :   if( stake->activation_epoch==USHORT_MAX ) {
     346           0 :     return 1;
     347           0 :   }
     348             : 
     349           2 :   ulong ae = stake->activation_epoch;
     350           2 :   if( ae>=first_contributing_epoch ) return 0;
     351             : 
     352             :   /* Dropped out of history: https://github.com/solana-program/stake/blob/interface%40v4.3.1/interface/src/state.rs#L969
     353             :      Stake activated as per protocol. */
     354           2 :   fd_stake_history_entry_t const * e = fd_sysvar_stake_history_query( stake_history, ae );
     355           2 :   if( FD_UNLIKELY( !e ) ) {
     356           1 :     return 1;
     357           1 :   }
     358             : 
     359             :   /* Note that e may not actually be epoch ae's entry on a
     360             :      non-contiguous window.  That is fine here because the reference
     361             :      simulation's first step reads the exact same entry via the exact
     362             :      same query and the exact same allowance function below, and
     363             :      acceptance means the simulation completes warmup on that first
     364             :      step, before reading any other entry.  Similarly, the NULL return
     365             :      from the query above may also be spurious, and that's fine because
     366             :      Agave also just assumes fully effective. */
     367             : 
     368             :   /* Agave claims this is a "should have been fully effective" branch.
     369             :      Practically this branch probably won't be taken and so we will
     370             :      conservatively reject the fast path in this branch. */
     371           1 :   if( FD_UNLIKELY( e->activating==0UL ) ) return 0;
     372             : 
     373             :   /* Run a single-step simulation and see if stake easily activated. */
     374           1 :   ulong newly_effective;
     375           1 :   if( use_fixed_point_stake_math ) {
     376           0 :     newly_effective = fd_ulong_max( fd_stake_calculate_activation_allowance( ae+1UL, stake->stake, e, new_rate_activation_epoch ), 1UL );
     377           1 :   } else {
     378           1 : #if FD_HAS_DOUBLE
     379           1 :     newly_effective = fd_ulong_max( fd_stake_calculate_change_allowance_float( ae+1UL, stake->stake, e->activating, e->effective, new_rate_activation_epoch ), 1UL );
     380             : #else
     381             :     return 0;
     382             : #endif
     383           1 :   }
     384           1 :   if( newly_effective>=stake->stake ) {
     385           0 :     return 1;
     386           0 :   }
     387           1 :   return 0;
     388           1 : }
     389             : 
     390             : /* Inverted activation and deactivation epochs shouldn't really be
     391             :    possible on delegations created by the stake program.  To be safe, we
     392             :    will fall back to the slow path if any of these are detected. */
     393             : static inline int
     394         247 : stake_epochs_are_normal( fd_stake_delegation_t const * stake ) {
     395         247 :   return stake->activation_epoch==USHORT_MAX   ||
     396         247 :          stake->deactivation_epoch==USHORT_MAX ||
     397         247 :          stake->activation_epoch<=stake->deactivation_epoch;
     398         247 : }
     399             : 
     400             : /* For a given stake and epoch credit history, calculate how many
     401             :    points, aka (credits * stake) were earned and the new value for
     402             :    credits_observed if the points were to materialize to non-zero
     403             :    inflation rewards.
     404             : 
     405             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
     406             : static void
     407             : calculate_stake_points_and_credits( fd_epoch_credits_t const *     epoch_credits,
     408             :                                     fd_stake_history_t const *     stake_history,
     409             :                                     fd_stake_delegation_t const *  stake,
     410             :                                     ulong *                        new_rate_activation_epoch,
     411             :                                     int                            use_fixed_point_stake_math,
     412           5 :                                     fd_calculated_stake_points_t * result ) {
     413             : 
     414           5 :   ulong credits_in_stake = stake->credits_observed;
     415           5 :   ulong credits_cnt      = epoch_credits->cnt;
     416           5 :   ulong base             = epoch_credits->base_credits;
     417           5 :   ulong credits_in_vote  = credits_cnt > 0UL ? base + epoch_credits->credits_delta[ credits_cnt - 1UL ] : 0UL;
     418             : 
     419             : 
     420             :   /* If the Vote account has less credits observed than the Stake account,
     421             :       something is wrong and we need to force an update.
     422             : 
     423             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
     424           5 :   if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
     425           3 :     result->points.ud = 0;
     426           3 :     result->new_credits_observed = credits_in_vote;
     427           3 :     result->force_credits_update_with_skipped_reward = 1;
     428           3 :     return;
     429           3 :   }
     430             : 
     431             :   /* If the Vote account has the same amount of credits observed as the Stake account,
     432             :       then the Vote account hasn't earned any credits and so there is nothing to update.
     433             : 
     434             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
     435           2 :   if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
     436           0 :     result->points.ud = 0;
     437           0 :     result->new_credits_observed = credits_in_vote;
     438           0 :     result->force_credits_update_with_skipped_reward = 0;
     439           0 :     return;
     440           0 :   }
     441             : 
     442           2 :   int coalesce_eligible = stake_epochs_are_normal( stake ) && epoch_credits->fast_path_ok;
     443             : 
     444             :   /* Calculate the points for each Tower epoch credit.
     445             :      If the Alpenglow migration marker is present, then the last entry
     446             :      is the Alpenglow credits earnt during the migration epoch, which
     447             :      we should skip.
     448             :      https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/points.rs#L183-L234 */
     449           2 :   ulong   tower_entries        = fd_ulong_sat_sub( (ulong)epoch_credits->cnt, (ulong)epoch_credits->has_ag_migration_marker );
     450           2 :   uint128 points               = 0;
     451           2 :   ulong   new_credits_observed = credits_in_stake;
     452           6 :   for( ulong i=0UL; i<tower_entries; i++ ) {
     453             : 
     454           4 :     ulong final_epoch_credits   = base + epoch_credits->credits_delta[ i ];
     455           4 :     ulong initial_epoch_credits = base + epoch_credits->prev_credits_delta[ i ];
     456             : 
     457             :     /* All production inputs should satisfy
     458             :        initial_epoch_credits <= final_epoch_credits
     459             : 
     460             :        If final_epoch_credits <= credits_in_stake, then:
     461             :         initial_epoch_credits <= final_epoch_credits <= credits_in_stake
     462             : 
     463             :        * earned_credits = 0 since both conditions are false.
     464             :        * new_credits_observed stays the same since it is already set
     465             :          to credits_in_stake and final_epoch_credits <= credits_in_stake
     466             : 
     467             :        Since earned_credits = 0 and new_credits_observed stays the same,
     468             :        points computation can be skipped. */
     469           4 :     if( FD_LIKELY( epoch_credits->fast_path_ok && final_epoch_credits<=credits_in_stake ) ) continue;
     470             : 
     471           4 :     uint128 earned_credits = 0;
     472           4 :     if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
     473           2 :       earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
     474           2 :     } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
     475           2 :       earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
     476           2 :     }
     477             : 
     478           4 :     new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
     479             : 
     480           4 :     ulong stake_amount = fd_stake_delegation_activation_status( stake, epoch_credits->epoch[ i ], stake_history, new_rate_activation_epoch, use_fixed_point_stake_math ).effective;
     481           4 :     if( coalesce_eligible && stake_amount==0UL && epoch_credits->epoch[ i ]>stake->deactivation_epoch ) {
     482             :       /* Multi-term Cases 1 and 2.  Note that
     483             :          deactivation_epoch!=USHORT_MAX is implied since epoch[ i ] is
     484             :          also a ushort. */
     485           0 :       new_credits_observed = base + epoch_credits->credits_delta[ tower_entries-1UL ];
     486           0 :       break;
     487           0 :     }
     488             : 
     489           4 :     points += (uint128)stake_amount * earned_credits;
     490           4 :   }
     491             : 
     492           2 :   result->points.ud = points;
     493           2 :   result->new_credits_observed = new_credits_observed;
     494           2 :   result->force_credits_update_with_skipped_reward = 0;
     495           2 : }
     496             : 
     497             : /* Returns commission split as
     498             :    (voter_portion, staker_portion, was_split) tuple.  If commission
     499             :    calculation is 10000 (100%) one way or other, indicate with false for
     500             :    was_split.
     501             : 
     502             :    https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L237-L272 */
     503             : void
     504             : fd_vote_commission_split( ushort                  commission,
     505             :                           ulong                   on,
     506         236 :                           fd_commission_split_t * result ) {
     507             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L244-L245 */
     508         873 :   #define MAX_BPS (10000)
     509             : 
     510             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L246-L271 */
     511         236 :   ushort commission_split = fd_ushort_min( commission, MAX_BPS );
     512         236 :   switch( commission_split ) {
     513           7 :     case 0: {
     514             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L246 */
     515           7 :       result->voter_portion  = 0UL;
     516           7 :       result->staker_portion = on;
     517           7 :       result->is_split       = 0;
     518           7 :       break;
     519           0 :     }
     520          25 :     case MAX_BPS: {
     521             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L247 */
     522          25 :       result->voter_portion  = on;
     523          25 :       result->staker_portion = 0UL;
     524          25 :       result->is_split       = 0;
     525          25 :       break;
     526           0 :     }
     527         204 :     default: {
     528             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L256-L259 */
     529         204 :       result->voter_portion  = (ulong)((uint128)on * (uint128)commission_split / (uint128)MAX_BPS);
     530         204 :       result->staker_portion = (ulong)((uint128)on * (uint128)(MAX_BPS-commission_split) / (uint128)MAX_BPS);
     531         204 :       result->is_split       = 1;
     532         204 :       break;
     533           0 :     }
     534         236 :   }
     535             : 
     536         236 :   #undef MAX_BPS
     537         236 : }
     538             : 
     539             : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/inflation_rewards/mod.rs#L412-L440 */
     540             : static void
     541             : fd_vote_commission_split_preserve_lamports( ushort                  commission,
     542             :                                             ulong                   on,
     543           0 :                                             fd_commission_split_t * result ) {
     544           0 :   ushort commission_split = fd_ushort_min( commission, 10000U );
     545           0 :   switch( commission_split ) {
     546           0 :   case 0U:
     547           0 :     *result = (fd_commission_split_t){ .voter_portion=0UL, .staker_portion=on, .is_split=0U };
     548           0 :     break;
     549           0 :   case 10000U:
     550           0 :     *result = (fd_commission_split_t){ .voter_portion=on, .staker_portion=0UL, .is_split=0U };
     551           0 :     break;
     552           0 :   default:
     553           0 :     result->staker_portion = (ulong)( (uint128)on *
     554           0 :                                       (uint128)(10000U-commission_split) /
     555           0 :                                       (uint128)10000U );
     556           0 :     result->voter_portion  = on-result->staker_portion;
     557           0 :     result->is_split       = 1U;
     558           0 :     break;
     559           0 :   }
     560           0 : }
     561             : 
     562             : /* Validates an external SIMD-0232 inflation commission collector
     563             :    after all rewards routed to it have been added.  Returns 0 to
     564             :    deposit, 1 to burn.  The vote account itself is always valid and
     565             :    must not be passed here.
     566             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/fee_distribution.rs#L231-L270 */
     567             : static int
     568             : fd_rewards_validate_commission_collector( fd_bank_t const *   bank,
     569             :                                           fd_pubkey_t const * collector,
     570             :                                           fd_acc_t const *    account,
     571          18 :                                           ulong               rewards ) {
     572             :   /* Must be a system program owned account. */
     573          18 :   if( FD_UNLIKELY( memcmp( account->owner, fd_solana_system_program_id.uc, sizeof(fd_pubkey_t) ) ) ) return 1;
     574             : 
     575             :   /* Must not be a reserved account. */
     576          15 :   if( FD_UNLIKELY( fd_pubkey_is_active_reserved_key( collector ) ||
     577          15 :                    fd_pubkey_is_pending_reserved_key( collector ) ) ) return 1;
     578             : 
     579          15 :   ulong post_balance;
     580          15 :   if( FD_UNLIKELY( __builtin_uaddl_overflow( account->lamports, rewards, &post_balance ) ) ) return 1;
     581             : 
     582             :   /* The incinerator is exempt from the rent check so the deposit
     583             :      always works.  Incinerator funds are burned at the end of the
     584             :      rewards distribution block. */
     585          15 :   if( FD_UNLIKELY( fd_pubkey_eq( collector, &fd_sysvar_incinerator_id ) ) ) return 0;
     586             : 
     587             :   /* Must be rent-exempt after the deposit.  With
     588             :      relax_post_exec_min_balance_check (SIMD-0392) a pre-existing
     589             :      account may stay rent-paying. */
     590          12 :   int is_rent_exempt = post_balance>=fd_rent_exempt_minimum_balance( &bank->f.rent, account->data_len );
     591          12 :   return !is_rent_exempt &&
     592          12 :          ( !FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) || !account->lamports );
     593          15 : }
     594             : 
     595             : /* Resolves the SIMD-0232 inflation commission collector from the vote
     596             :    account state at the start of the distribution epoch (the t-1 vote
     597             :    stakes set); defaults to the vote account.
     598             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L667-L672 */
     599             : static void
     600             : fd_rewards_inflation_collector( fd_bank_t *         bank,
     601             :                                 fd_pubkey_t const * vote_pubkey,
     602          37 :                                 fd_pubkey_t *       collector_out ) {
     603          37 :   fd_vote_stakes_ele_t ele[1];
     604          37 :   if( fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_pubkey, ele ) ) {
     605          37 :     *collector_out = ele->inflation_collector;
     606          37 :   } else {
     607           0 :     *collector_out = *vote_pubkey;
     608           0 :   }
     609          37 : }
     610             : 
     611             : /* rewarded_epoch_is_alpenglow checks if the rewarded
     612             :    epoch contains or follows the alpenglow migration.  This is distinct
     613             :    from the alpenglow feature being active: between feature activation
     614             :    and the migration slot (and for the epoch that ended at the
     615             :    activation boundary) stake rewards are still computed the tower way.
     616             : 
     617             :    The migration epoch itself is a mixed Tower+Alpenglow epoch, where
     618             :    credits earned before the migration slot are Tower credits and
     619             :    credits earned after the migration slot are Alpenglow credits.
     620             : 
     621             :    See https://github.com/anza-xyz/agave/blob/ef22c39d51b90c1f4cfccfe1f9fde94471c6242a/runtime/src/alpenglow_epoch_type.rs#L154 */
     622             : 
     623             : static int
     624             : rewarded_epoch_is_alpenglow( fd_bank_t *  bank,
     625             :                              fd_accdb_t * accdb,
     626         312 :                              ulong        rewarded_epoch ) {
     627         312 :   ulong migration_slot = fd_alpenglow_migration_slot( bank, accdb );
     628         312 :   if( FD_LIKELY( migration_slot==ULONG_MAX ) ) return 0;
     629           0 :   return fd_slot_to_epoch( &bank->f.epoch_schedule, migration_slot, NULL )<=rewarded_epoch;
     630         312 : }
     631             : 
     632             : /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/alpenglow_epoch_type.rs#L197-L223 */
     633             : static int
     634             : rewarded_epoch_is_alpenglow_migration( fd_bank_t *  bank,
     635             :                                        fd_accdb_t * accdb,
     636         212 :                                        ulong        rewarded_epoch ) {
     637         212 :   ulong migration_slot = fd_alpenglow_migration_slot( bank, accdb );
     638         212 :   if( FD_LIKELY( migration_slot==ULONG_MAX ) ) return 0;
     639           0 :   return fd_slot_to_epoch( &bank->f.epoch_schedule, migration_slot, NULL )==rewarded_epoch;
     640         212 : }
     641             : 
     642             : /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/alpenglow_epoch_type.rs#L198-L202 */
     643             : static ulong
     644             : migration_num_tower_slots( fd_bank_t *  bank,
     645             :                            fd_accdb_t * accdb,
     646           0 :                            ulong        rewarded_epoch ) {
     647           0 :   ulong migration_slot = fd_alpenglow_migration_slot( bank, accdb );
     648             :   /* +1 because the migration slot is counted as a Tower slot */
     649           0 :   return migration_slot - fd_epoch_slot0( &bank->f.epoch_schedule, rewarded_epoch ) + 1UL;
     650           0 : }
     651             : 
     652             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33
     653             :    https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/mod.rs#L204-L372 */
     654             : static int
     655             : redeem_rewards( fd_stake_delegation_t const *   stake,
     656             :                 ulong                           vote_state_idx,
     657             :                 ulong                           rewarded_epoch,
     658             :                 ulong                           total_rewards,
     659             :                 uint128                         total_points,
     660             :                 int                             alpenglow_enabled,
     661             :                 int                             alpenglow_migration,
     662             :                 ulong                           num_tower_slots,
     663             :                 ulong                           slots_in_epoch,
     664             :                 fd_runtime_stack_t *            runtime_stack,
     665             :                 fd_calculated_stake_points_t *  stake_points_result,
     666             :                 uint128                         ag_migration_points,
     667         245 :                 fd_calculated_stake_rewards_t * result ) {
     668             : 
     669             :   /* The firedancer implementation of redeem_rewards inlines a lot of
     670             :      the helper functions that the Agave implementation uses.
     671             :      In Agave: redeem_rewards calls redeem_stake_rewards which calls
     672             :      calculate_stake_rewards. */
     673             : 
     674             :   // Drive credits_observed forward unconditionally when rewards are disabled
     675             :   // or when this is the stake's activation epoch
     676         245 :   if( total_rewards==0UL || stake->activation_epoch==rewarded_epoch ) {
     677           5 :     stake_points_result->force_credits_update_with_skipped_reward = 1;
     678           5 :   }
     679             : 
     680         245 :   if( stake_points_result->force_credits_update_with_skipped_reward ) {
     681           8 :     result->staker_rewards       = 0;
     682           8 :     result->voter_rewards        = 0;
     683           8 :     result->new_credits_observed = stake_points_result->new_credits_observed;
     684           8 :     return 0;
     685           8 :   }
     686             :   /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/mod.rs#L312-L322 */
     687         237 :   int zero_points = alpenglow_migration ?
     688           0 :       ( ( stake_points_result->points.ud==0 && ag_migration_points==0 ) || ( ag_migration_points==0 && total_points==0 ) ) :
     689         237 :       ( stake_points_result->points.ud==0 || (!alpenglow_enabled && total_points==0) );
     690         237 :   if( zero_points ) {
     691           1 :     if( alpenglow_enabled && stake_points_result->new_credits_observed!=stake->credits_observed ) {
     692             :       /* Don't update credits_observed if the delegation is inactive.
     693             : 
     694             :          TODO: remove this after remove_inactive_stakes is active
     695             :          everywhere.
     696             : 
     697             :          https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/inflation_rewards/mod.rs#L255-L258 */
     698           0 :       if( FD_LIKELY( stake_points_result->inactive ) ) {
     699           0 :         return 1;
     700           0 :       }
     701           0 :       result->staker_rewards       = 0UL;
     702           0 :       result->voter_rewards        = 0UL;
     703           0 :       result->new_credits_observed = stake_points_result->new_credits_observed;
     704           0 :       return 0;
     705           0 :     }
     706           1 :     return 1;
     707           1 :   }
     708             : 
     709         236 :   uint128 rewards_u128 = stake_points_result->points.ud;
     710         236 :   if( !alpenglow_enabled || alpenglow_migration ) {
     711         236 :     if( FD_UNLIKELY( __builtin_mul_overflow( rewards_u128, (uint128)(total_rewards), &rewards_u128 ) ) ) {
     712           0 :       FD_LOG_ERR(( "Rewards intermediate calculation should fit within u128" ));
     713           0 :     }
     714         236 :     FD_TEST( total_points );
     715         236 :     rewards_u128 /= (uint128)total_points;
     716         236 :   }
     717         236 :   if( alpenglow_migration ) {
     718             :     /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/mod.rs#L322-L332 */
     719           0 :     if( FD_UNLIKELY( __builtin_mul_overflow( rewards_u128, (uint128)num_tower_slots, &rewards_u128 ) ) ) {
     720           0 :       FD_LOG_ERR(( "migration epoch tower rewards should fit within u128" ));
     721           0 :     }
     722             :     /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/mod.rs#L331-L333 */
     723           0 :     rewards_u128 /= (uint128)slots_in_epoch;
     724           0 :     if( FD_UNLIKELY( __builtin_add_overflow( rewards_u128, ag_migration_points, &rewards_u128 ) ) ) {
     725           0 :       FD_LOG_ERR(( "migration epoch rewards intermediate calculation should fit within u128" ));
     726           0 :     }
     727           0 :   }
     728             : 
     729         236 :   if( FD_UNLIKELY( rewards_u128>(uint128)ULONG_MAX ) ) {
     730           0 :     FD_LOG_ERR(( "Rewards should fit within u64" ));
     731           0 :   }
     732             : 
     733         236 :   ulong rewards = (ulong)rewards_u128;
     734         236 :   if( rewards == 0 ) {
     735           0 :     if( alpenglow_enabled &&
     736           0 :         stake_points_result->new_credits_observed!=stake->credits_observed &&
     737           0 :         !stake_points_result->inactive ) {
     738           0 :       result->staker_rewards       = 0UL;
     739           0 :       result->voter_rewards        = 0UL;
     740           0 :       result->new_credits_observed = stake_points_result->new_credits_observed;
     741           0 :       return 0;
     742           0 :     }
     743           0 :     return 1;
     744           0 :   }
     745             : 
     746         236 :   fd_commission_split_t split_result;
     747         236 :   if( alpenglow_enabled ) {
     748           0 :     fd_vote_commission_split_preserve_lamports( runtime_stack->stakes.vote_ele[ vote_state_idx ].commission,
     749           0 :                                                 rewards,
     750           0 :                                                 &split_result );
     751         236 :   } else {
     752         236 :     fd_vote_commission_split( runtime_stack->stakes.vote_ele[ vote_state_idx ].commission, rewards, &split_result );
     753         236 :   }
     754         236 :   if( !alpenglow_enabled && split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
     755           0 :     return 1;
     756           0 :   }
     757             : 
     758         236 :   result->staker_rewards       = split_result.staker_portion;
     759         236 :   result->voter_rewards        = split_result.voter_portion;
     760         236 :   result->new_credits_observed = stake_points_result->new_credits_observed;
     761         236 :   return 0;
     762         236 : }
     763             : 
     764             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
     765             : static double
     766             : epoch_duration_in_years( fd_bank_t const * bank,
     767          36 :                          ulong             prev_epoch ) {
     768          36 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
     769          36 :   ulong                       slots_in_epoch = fd_epoch_slot_cnt( epoch_schedule, prev_epoch );
     770          36 :   double                      slots_per_year = fd_slot_params_at_slot( bank,
     771          36 :                                                                        fd_epoch_slot0( epoch_schedule, prev_epoch ) ).slots_per_year;
     772          36 :   return (double)slots_in_epoch / slots_per_year;
     773          36 : }
     774             : 
     775             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
     776             : static void
     777             : calculate_previous_epoch_inflation_rewards( fd_bank_t const *                   bank,
     778             :                                             ulong                               prev_epoch_capitalization,
     779             :                                             ulong                               prev_epoch,
     780          36 :                                             fd_prev_epoch_inflation_rewards_t * rewards ) {
     781          36 :   double slot_in_year = slot_in_year_for_inflation( bank );
     782             : 
     783          36 :   rewards->validator_rate               = validator( &bank->f.inflation, slot_in_year );
     784          36 :   rewards->foundation_rate              = foundation( &bank->f.inflation, slot_in_year );
     785          36 :   rewards->prev_epoch_duration_in_years = epoch_duration_in_years( bank, prev_epoch );
     786          36 :   rewards->validator_rewards            = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
     787          36 :   FD_LOG_DEBUG(( "Rewards %lu, Rate %.16f, Duration %.18f Capitalization %lu Slot in year %.16f", rewards->validator_rewards, rewards->validator_rate, rewards->prev_epoch_duration_in_years, prev_epoch_capitalization, slot_in_year ));
     788          36 : }
     789             : 
     790             : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
     791             : 
     792             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
     793             :  */
     794             : static uint
     795             : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     796             :                                     ulong                       slot,
     797             :                                     ulong                       total_stake_accounts,
     798          32 :                                     ulong                       stake_account_stores_per_block ) {
     799             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
     800          32 :   if( epoch_schedule->warmup &&
     801          32 :       fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
     802           3 :     return 1UL;
     803           3 :   }
     804             : 
     805          29 :   FD_TEST( stake_account_stores_per_block );
     806          29 :   ulong num_chunks = total_stake_accounts / stake_account_stores_per_block + (total_stake_accounts % stake_account_stores_per_block != 0);
     807          29 :   num_chunks       = fd_ulong_max( num_chunks, 1UL );
     808          29 :   num_chunks       = fd_ulong_min( num_chunks,
     809          29 :                                    fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
     810          29 :   return (uint)num_chunks;
     811          29 : }
     812             : 
     813             : uint
     814             : fd_rewards_get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     815             :                                                ulong                       slot,
     816             :                                                ulong                       total_stake_accounts,
     817           0 :                                                ulong                       stake_account_stores_per_block ) {
     818           0 :   return get_reward_distribution_num_blocks( epoch_schedule, slot, total_stake_accounts, stake_account_stores_per_block );
     819           0 : }
     820             : 
     821             : static void
     822             : read_stake_history( fd_accdb_t *         accdb,
     823             :                     fd_accdb_fork_id_t   fork_id,
     824             :                     uchar                data[ static FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ],
     825          74 :                     fd_stake_history_t * stake_history ) {
     826          74 :   fd_acc_t ro = fd_accdb_read_one( accdb, fork_id, fd_sysvar_stake_history_id.uc );
     827          74 :   if( FD_UNLIKELY( !ro.lamports ) ) FD_LOG_ERR(( "Unable to read stake history sysvar" ));
     828          74 :   ulong copy_sz = fd_ulong_min( ro.data_len, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
     829          74 :   fd_memcpy( data, ro.data, copy_sz );
     830          74 :   fd_accdb_unread_one( accdb, &ro );
     831          74 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_view( stake_history, data, copy_sz ) ) ) {
     832           0 :     FD_LOG_ERR(( "Unable to decode stake history sysvar" ));
     833           0 :   }
     834          74 : }
     835             : 
     836             : /* calculate_stake_points_and_credits() with some fast paths. */
     837             : static inline void
     838             : calculate_stake_points_fast( fd_epoch_credits_t *           epoch_credits,
     839             :                              fd_stake_history_t const *     stake_history,
     840             :                              fd_stake_delegation_t const *  stake,
     841             :                              ulong *                        new_rate_activation_epoch,
     842             :                              int                            use_fixed_point_stake_math,
     843             :                              ulong                          rewarded_epoch,
     844         245 :                              fd_calculated_stake_points_t * result ) {
     845         245 :   result->inactive = 0; /* only read in Alpenglow path */
     846         245 :   if( FD_UNLIKELY( !stake_epochs_are_normal( stake ) ) ) {
     847           0 :     FD_BASE58_ENCODE_32_BYTES( stake->stake_account.uc, stake_account_str );
     848           0 :     FD_BASE58_ENCODE_32_BYTES( stake->vote_account.uc,  vote_account_str  );
     849           0 :     FD_LOG_INFO(( "stake delegation (stake_account=%s vote_account=%s delegated=%lu balance=%lu credits_observed=%lu) activation epoch %u > deactivation epoch %u",
     850           0 :                   stake_account_str, vote_account_str, stake->stake, stake->lamports, stake->credits_observed, stake->activation_epoch, stake->deactivation_epoch ));
     851           0 :     calculate_stake_points_and_credits( epoch_credits, stake_history, stake, new_rate_activation_epoch, use_fixed_point_stake_math, result );
     852           0 :     return;
     853           0 :   }
     854             : 
     855             :   /* If the Alpenglow migration marker is present, then the last entry
     856             :      is the Alpenglow credits earnt during the migration epoch, which
     857             :      we should skip. */
     858         245 :   ulong cnt = fd_ulong_sat_sub( (ulong)epoch_credits->cnt, (ulong)epoch_credits->has_ag_migration_marker );
     859         245 :   if( FD_LIKELY( epoch_credits->fast_path_ok && cnt ) ) {
     860         244 :     ulong base             = epoch_credits->base_credits;
     861         244 :     ulong credits_in_stake = stake->credits_observed;
     862         244 :     ulong credits_in_vote  = base+epoch_credits->credits_delta[ cnt-1UL ];
     863         244 :     if( FD_LIKELY( credits_in_vote>credits_in_stake ) ) {
     864         241 :       ulong initial_last = base+epoch_credits->prev_credits_delta[ cnt-1UL ];
     865         241 :       int fast = 0;
     866             : 
     867         241 :       if( FD_LIKELY( credits_in_stake>=initial_last ) ) {
     868             :         /* Single-term. */
     869         239 :         ulong target_epoch = epoch_credits->epoch[ cnt-1UL ];
     870         239 :         ulong effective_stake;
     871         239 :         if( FD_LIKELY( target_epoch==rewarded_epoch && (stake->state==FD_STAKE_DELEGATION_STATE_WARMED||stake->state==FD_STAKE_DELEGATION_STATE_COOLED) ) ) { /* See the block comment for state tags for why we need target_epoch==rewarded_epoch. */
     872             :           /* Single-term Case 3 or Case 1. */
     873          35 :           effective_stake = stake->state==FD_STAKE_DELEGATION_STATE_WARMED ? stake->stake : 0UL;
     874         204 :         } else {
     875             :           /* We could let this branch fall through to the slow path,
     876             :              whose loop will skip a whole bunch of epoch credits only to
     877             :              get to the final and only contributing term.  Computing it
     878             :              right here reduces about 800 instructions retired per such
     879             :              delegation. */
     880         204 :           effective_stake = fd_stake_delegation_activation_status( stake, target_epoch, stake_history, new_rate_activation_epoch, use_fixed_point_stake_math ).effective;
     881         204 :         }
     882         239 :         result->points.ud            = (uint128)effective_stake * (uint128)( credits_in_vote - credits_in_stake );
     883         239 :         result->new_credits_observed = credits_in_vote;
     884         239 :         fast = 1;
     885         239 :       } else {
     886             :         /* Multi-term. */
     887           2 :         ulong first_contributing_idx = 0UL;
     888           2 :         while( base+epoch_credits->credits_delta[ first_contributing_idx ]<=credits_in_stake ) first_contributing_idx++;
     889           2 :         FD_TEST( first_contributing_idx<cnt ); /* Guaranteed found because of the earlier credits_in_vote>credits_in_stake gate. */
     890             :         /* Multi-term Case 3.  effective=delegated at the earliest
     891             :            contributing term, and no deactivation at all, so the plateau
     892             :            simplifies the points calculation to a single closed form
     893             :            multiplication. */
     894           2 :         if( FD_LIKELY( is_warmed_plateau( stake, epoch_credits->epoch[ first_contributing_idx ], stake_history, new_rate_activation_epoch, use_fixed_point_stake_math ) ) ) {
     895           1 :           ulong start_credits          = fd_ulong_max( credits_in_stake, base+epoch_credits->prev_credits_delta[ 0UL ] );
     896           1 :           result->points.ud            = (uint128)stake->stake*(uint128)(credits_in_vote-start_credits);
     897           1 :           result->new_credits_observed = credits_in_vote;
     898           1 :           fast = 1;
     899           1 :         }
     900           2 :       }
     901             : 
     902         241 :       if( FD_LIKELY( fast ) ) {
     903         240 :         result->force_credits_update_with_skipped_reward = 0;
     904         240 :         return;
     905         240 :       }
     906         241 :     }
     907         244 :   }
     908             :   /* Potentially term-by-term slow path fallback for anything we can't
     909             :      conservatively prove to take the fast paths so far.  In this
     910             :      callee, early exit at the first fully deactivated term is the
     911             :      0-tail short circuit fast path for multi-term Cases 1 and 2. */
     912           5 :   calculate_stake_points_and_credits( epoch_credits, stake_history, stake, new_rate_activation_epoch, use_fixed_point_stake_math, result );
     913           5 : }
     914             : 
     915             : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/inflation_rewards/points.rs#L236-L312 */
     916             : static void
     917             : calculate_alpenglow_points( fd_epoch_credits_t const *    epoch_credits,
     918             :                             fd_stake_history_t const *    stake_history,
     919             :                             fd_stake_delegation_t const * stake,
     920             :                             ulong *                       new_rate_activation_epoch,
     921             :                             int                           use_fixed_point_stake_math,
     922             :                             ulong                         rewarded_epoch,
     923             :                             ulong                         validator_reward_epoch_stake,
     924           0 :                             fd_calculated_stake_points_t * result ) {
     925           0 :   ulong credits_in_stake = stake->credits_observed;
     926           0 :   ulong cnt              = epoch_credits->cnt;
     927           0 :   ulong base             = epoch_credits->base_credits;
     928           0 :   ulong credits_in_vote  = cnt ? base+epoch_credits->credits_delta[ cnt-1UL ] : 0UL;
     929             : 
     930           0 :   result->points.ud                                = 0;
     931           0 :   result->new_credits_observed                     = credits_in_stake;
     932           0 :   result->force_credits_update_with_skipped_reward = 0;
     933           0 :   result->inactive                                 = 0;
     934             : 
     935           0 :   if( FD_UNLIKELY( credits_in_vote<credits_in_stake ) ) {
     936           0 :     result->new_credits_observed                     = credits_in_vote;
     937           0 :     result->force_credits_update_with_skipped_reward = 1;
     938           0 :     return;
     939           0 :   }
     940           0 :   if( FD_UNLIKELY( credits_in_vote==credits_in_stake || !cnt ) ) return;
     941             : 
     942           0 :   ulong idx = cnt-1UL;
     943           0 :   if( FD_UNLIKELY( (ulong)epoch_credits->epoch[ idx ]!=rewarded_epoch ) ) return;
     944             : 
     945           0 :   ulong initial_epoch_credits = base+epoch_credits->prev_credits_delta[ idx ];
     946           0 :   ulong earned_credits;
     947           0 :   if( credits_in_stake<initial_epoch_credits ) {
     948           0 :     earned_credits = credits_in_vote-initial_epoch_credits;
     949           0 :   } else {
     950           0 :     earned_credits = credits_in_vote-credits_in_stake;
     951           0 :   }
     952           0 :   result->new_credits_observed = credits_in_vote;
     953             : 
     954           0 :   fd_stake_history_entry_t status = fd_stake_delegation_activation_status(
     955           0 :       stake,
     956           0 :       rewarded_epoch,
     957           0 :       stake_history,
     958           0 :       new_rate_activation_epoch,
     959           0 :       use_fixed_point_stake_math );
     960           0 :   result->inactive = (uchar)( !status.effective && !status.activating );
     961             : 
     962           0 :   if( FD_UNLIKELY( !earned_credits ) ) return;
     963           0 :   if( FD_UNLIKELY( !status.effective ) ) return;
     964             : 
     965           0 :   if( FD_UNLIKELY( !validator_reward_epoch_stake ) ) {
     966           0 :     result->force_credits_update_with_skipped_reward = 1;
     967           0 :     return;
     968           0 :   }
     969             : 
     970           0 :   result->points.ud = (uint128)earned_credits *
     971           0 :                       (uint128)status.effective /
     972           0 :                       (uint128)validator_reward_epoch_stake;
     973           0 : }
     974             : 
     975             : /* calculate_ag_migration_points is our implementation of Agave's
     976             :    calculate_migration_points.
     977             : 
     978             :    https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/points.rs#L314-L352 */
     979             : static uint128
     980             : calculate_ag_migration_points( fd_epoch_credits_t *           epoch_credits,
     981             :                                fd_stake_history_t const *     stake_history,
     982             :                                fd_stake_delegation_t const *  stake,
     983             :                                ulong *                        new_rate_activation_epoch,
     984             :                                int                            use_fixed_point_stake_math,
     985             :                                ulong                          rewarded_epoch,
     986             :                                ulong                          tower_fast_path_epoch,
     987             :                                ulong                          validator_reward_epoch_stake,
     988           0 :                                fd_calculated_stake_points_t * result ) {
     989           0 :   uint128 ag_migration_points = 0;
     990           0 :   calculate_stake_points_fast( epoch_credits, stake_history, stake, new_rate_activation_epoch, use_fixed_point_stake_math, tower_fast_path_epoch, result );
     991             : 
     992           0 :   ulong credits_in_stake = stake->credits_observed;
     993           0 :   ulong cnt              = epoch_credits->cnt;
     994           0 :   ulong base             = epoch_credits->base_credits;
     995           0 :   ulong credits_in_vote  = cnt ? base+epoch_credits->credits_delta[ cnt-1UL ] : 0UL;
     996             : 
     997             :   /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/points.rs#L368-L398 */
     998           0 :   if( credits_in_vote<=credits_in_stake ) return 0;
     999             : 
    1000           0 :   fd_stake_history_entry_t status;
    1001           0 :   int   have_status             = 0;
    1002           0 :   ulong ag_new_credits_observed = credits_in_stake;
    1003           0 :   ulong idx                     = fd_ulong_sat_sub( cnt, (ulong)epoch_credits->has_ag_migration_marker );
    1004           0 :   if( idx<cnt && (ulong)epoch_credits->epoch[ idx ]==rewarded_epoch ) {
    1005           0 :     ulong final_epoch_credits   = base+epoch_credits->credits_delta     [ idx ];
    1006           0 :     ulong initial_epoch_credits = base+epoch_credits->prev_credits_delta[ idx ];
    1007           0 :     ulong earned_credits        = 0UL;
    1008           0 :     if( credits_in_stake<initial_epoch_credits ) {
    1009           0 :       earned_credits = final_epoch_credits-initial_epoch_credits;
    1010           0 :     } else if( credits_in_stake<final_epoch_credits ) {
    1011           0 :       earned_credits = final_epoch_credits-credits_in_stake;
    1012           0 :     }
    1013           0 :     ag_new_credits_observed = fd_ulong_max( credits_in_stake, final_epoch_credits );
    1014             : 
    1015           0 :     status      = fd_stake_delegation_activation_status( stake, rewarded_epoch, stake_history, new_rate_activation_epoch, use_fixed_point_stake_math );
    1016           0 :     have_status = 1;
    1017           0 :     if( earned_credits && status.effective ) {
    1018           0 :       if( FD_UNLIKELY( !validator_reward_epoch_stake ) ) {
    1019             :         /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/points.rs#L283-L299 */
    1020           0 :         result->points.ud                                = 0;
    1021           0 :         result->new_credits_observed                     = ag_new_credits_observed;
    1022           0 :         result->force_credits_update_with_skipped_reward = 1;
    1023           0 :         return ag_migration_points;
    1024           0 :       }
    1025           0 :       ag_migration_points = (uint128)earned_credits *
    1026           0 :                             (uint128)status.effective /
    1027           0 :                             (uint128)validator_reward_epoch_stake;
    1028           0 :     }
    1029           0 :   }
    1030             : 
    1031           0 :   result->new_credits_observed = fd_ulong_max( result->new_credits_observed, ag_new_credits_observed );
    1032           0 :   if( result->new_credits_observed!=credits_in_stake ) {
    1033           0 :     if( !have_status ) status = fd_stake_delegation_activation_status( stake, rewarded_epoch, stake_history, new_rate_activation_epoch, use_fixed_point_stake_math );
    1034           0 :     result->inactive = (uchar)( !status.effective && !status.activating );
    1035           0 :   }
    1036           0 :   return ag_migration_points;
    1037           0 : }
    1038             : 
    1039             : static inline void
    1040             : calculate_stake_points_for_reward( int                            alpenglow_enabled,
    1041             :                                    int                            alpenglow_migration,
    1042             :                                    fd_runtime_stack_t *           runtime_stack,
    1043             :                                    fd_epoch_credits_t *           epoch_credits,
    1044             :                                    fd_stake_history_t const *     stake_history,
    1045             :                                    fd_stake_delegation_t const *  stake,
    1046             :                                    ulong *                        new_rate_activation_epoch,
    1047             :                                    int                            use_fixed_point_stake_math,
    1048             :                                    ulong                          rewarded_epoch,
    1049             :                                    ulong                          tower_fast_path_epoch,
    1050             :                                    fd_calculated_stake_points_t * result,
    1051         245 :                                    uint128 *                      ag_migration_points ) {
    1052         245 :   if( FD_UNLIKELY( alpenglow_enabled ) ) {
    1053           0 :     ulong                    validator_reward_epoch_stake = 0UL;
    1054           0 :     fd_stake_accum_t const * accumulated                  = fd_stake_accum_map_ele_query_const(
    1055           0 :         runtime_stack->stakes.stake_accum_map,
    1056           0 :         &stake->vote_account,
    1057           0 :         NULL,
    1058           0 :         runtime_stack->stakes.stake_accum );
    1059           0 :     if( accumulated ) validator_reward_epoch_stake = accumulated->reward_stake;
    1060           0 :     if( FD_UNLIKELY( alpenglow_migration ) ) {
    1061           0 :       *ag_migration_points = calculate_ag_migration_points( epoch_credits,
    1062           0 :                                                             stake_history,
    1063           0 :                                                             stake,
    1064           0 :                                                             new_rate_activation_epoch,
    1065           0 :                                                             use_fixed_point_stake_math,
    1066           0 :                                                             rewarded_epoch,
    1067           0 :                                                             tower_fast_path_epoch,
    1068           0 :                                                             validator_reward_epoch_stake,
    1069           0 :                                                             result );
    1070           0 :     } else {
    1071           0 :       calculate_alpenglow_points( epoch_credits,
    1072           0 :                                   stake_history,
    1073           0 :                                   stake,
    1074           0 :                                   new_rate_activation_epoch,
    1075           0 :                                   use_fixed_point_stake_math,
    1076           0 :                                   rewarded_epoch,
    1077           0 :                                   validator_reward_epoch_stake,
    1078           0 :                                   result );
    1079           0 :     }
    1080         245 :   } else {
    1081         245 :     calculate_stake_points_fast( epoch_credits,
    1082         245 :                                  stake_history,
    1083         245 :                                  stake,
    1084         245 :                                  new_rate_activation_epoch,
    1085         245 :                                  use_fixed_point_stake_math,
    1086         245 :                                  tower_fast_path_epoch,
    1087         245 :                                  result );
    1088         245 :   }
    1089         245 : }
    1090             : 
    1091             : /* Calculates epoch reward points from stake/vote accounts.
    1092             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L445 */
    1093             : static uint128
    1094             : calculate_reward_points_partitioned( fd_bank_t *                    bank,
    1095             :                                      fd_accdb_t *                   accdb,
    1096             :                                      fd_stake_delegations_t const * stake_delegations,
    1097             :                                      fd_stake_history_t const *     stake_history,
    1098             :                                      ulong                          rewarded_epoch,
    1099          32 :                                      fd_runtime_stack_t *           runtime_stack ) {
    1100             :   /* Calculate the points for each stake delegation */
    1101          32 :   uint128  total_points        = 0;
    1102          32 :   int      alpenglow_enabled   = rewarded_epoch_is_alpenglow( bank, accdb, rewarded_epoch );
    1103          32 :   int      alpenglow_migration = rewarded_epoch_is_alpenglow_migration( bank, accdb, rewarded_epoch );
    1104             : 
    1105          32 :   fd_vote_rewards_t *     vote_ele     = runtime_stack->stakes.vote_ele;
    1106          32 :   fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
    1107             : 
    1108          32 :   fd_epoch_credits_view_t epoch_credits_view[1];
    1109          32 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
    1110          32 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
    1111             : 
    1112          32 :   fd_stake_delegations_iter_t iter_[1];
    1113          32 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
    1114         248 :        !fd_stake_delegations_iter_done( iter );
    1115         216 :        fd_stake_delegations_iter_next( iter ) ) {
    1116         216 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
    1117         216 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
    1118             : 
    1119         216 :     if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) &&
    1120         216 :                      fd_stake_delegation_is_inactive( stake_delegation, rewarded_epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch,
    1121         216 :                                                       FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) ) ) ) {
    1122           0 :       if( FD_LIKELY( stake_delegation_idx<runtime_stack->max_stake_accounts ) ) {
    1123           0 :         runtime_stack->stakes.stake_points_result[ stake_delegation_idx ].vote_idx = UINT_MAX;
    1124           0 :       }
    1125           0 :       continue;
    1126           0 :     }
    1127             : 
    1128             :     /* Note that we don't check minimum delegation here, as there are
    1129             :        no plans to activate stake_minimum_delegation_for_rewards.
    1130             :        If this changes we need to skip stake accounts that are
    1131             :        below the minimum delegation here. However we don't do this yet,
    1132             :        to ensure that we audit the feature properly if this happens. */
    1133             : 
    1134         216 :     uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
    1135             : 
    1136         216 :     if( FD_LIKELY( stake_delegation_idx<runtime_stack->max_stake_accounts ) ) {
    1137         216 :       runtime_stack->stakes.stake_points_result[ stake_delegation_idx ].vote_idx = idx;
    1138         216 :     }
    1139             : 
    1140         216 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
    1141             : 
    1142         106 :     fd_calculated_stake_points_t   stake_points_result_[1];
    1143         106 :     fd_calculated_stake_points_t * stake_points_result;
    1144         106 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->max_stake_accounts ) ) {
    1145           0 :       stake_points_result = stake_points_result_;
    1146         106 :     } else {
    1147         106 :       stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
    1148         106 :     }
    1149             : 
    1150         106 :     fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[ idx ];
    1151             : 
    1152             :     /* In the migration epoch, the total_points is only used to scale
    1153             :        the Tower rewards, so we calculate them here as if we were in a
    1154             :        Tower-only epoch. This mirrors Agave's structure.
    1155             : 
    1156             :        https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/inflation_rewards/points.rs#L124-L144 */
    1157         106 :     uint128 ag_migration_points = 0;
    1158         106 :     calculate_stake_points_for_reward( alpenglow_enabled && !alpenglow_migration,
    1159         106 :                                        0,
    1160         106 :                                        runtime_stack,
    1161         106 :                                        epoch_credits,
    1162         106 :                                        stake_history,
    1163         106 :                                        stake_delegation,
    1164         106 :                                        &bank->f.warmup_cooldown_rate_epoch,
    1165         106 :                                        FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
    1166         106 :                                        rewarded_epoch,
    1167         106 :                                        rewarded_epoch,
    1168         106 :                                        stake_points_result,
    1169         106 :                                        &ag_migration_points );
    1170             : 
    1171             :     /* https://github.com/anza-xyz/agave/blob/v4.3.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L972-L1003 */
    1172         106 :     if( FD_LIKELY( !alpenglow_enabled || alpenglow_migration ) ) {
    1173         106 :       total_points += stake_points_result->points.ud;
    1174         106 :     }
    1175         106 :   }
    1176             : 
    1177          32 :   fd_epoch_credits_view_fini( epoch_credits_view );
    1178          32 :   return total_points;
    1179          32 : }
    1180             : 
    1181             : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L161-L173 */
    1182             : static int
    1183             : delegation_may_need_adjustment( fd_bank_t *                   bank,
    1184             :                                 fd_stake_delegation_t const * stake_delegation,
    1185             :                                 fd_stake_history_t const *    stake_history,
    1186             :                                 ulong                         rewarded_epoch,
    1187             :                                 ulong                         new_delegation_with_rewards,
    1188             :                                 ulong                         lamports_with_rewards,
    1189          62 :                                 ulong                         minimum_lamports ) {
    1190          62 :   ulong new_delegation = fd_ulong_min(
    1191          62 :     new_delegation_with_rewards,
    1192          62 :     fd_ulong_sat_sub( lamports_with_rewards, minimum_lamports )
    1193          62 :   );
    1194             : 
    1195          62 :   if( FD_LIKELY( new_delegation==stake_delegation->stake ) ) return 0;
    1196             : 
    1197             :   /* Delegations that are neither effective nor activating are left
    1198             :      alone.  This is checked last because it can walk many stake history
    1199             :      entries. */
    1200          29 :   fd_stake_history_entry_t status = fd_stake_delegation_activation_status(
    1201          29 :     stake_delegation,
    1202          29 :     rewarded_epoch,
    1203          29 :     stake_history,
    1204          29 :     &bank->f.warmup_cooldown_rate_epoch,
    1205          29 :     FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) );
    1206             : 
    1207          29 :   return !( status.effective==0UL && status.activating==0UL );
    1208          62 : }
    1209             : 
    1210             : /* Calculates block reward for a stake account based on SIMD-0123.
    1211             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L173 */
    1212             : static ulong
    1213             : calculate_block_reward( fd_bank_t *                   bank,
    1214             :                         fd_runtime_stack_t *          runtime_stack,
    1215             :                         fd_stake_history_t const *    stake_history,
    1216             :                         fd_stake_delegation_t const * stake_delegation,
    1217          13 :                         ulong                         rewarded_epoch ) {
    1218          13 :   fd_pubkey_t const * vote_pubkey = &stake_delegation->vote_account;
    1219          13 :   fd_vote_stakes_ele_t ele[1];
    1220          13 :   if( !fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_pubkey, ele ) ) {
    1221          12 :     return 0UL;
    1222          12 :   }
    1223           1 :   ulong pending_delegator_rewards = ele->pending_delegator_rewards;
    1224           1 :   fd_stake_accum_t const * accumulated = fd_stake_accum_map_ele_query_const(
    1225           1 :       runtime_stack->stakes.stake_accum_map, vote_pubkey, NULL, runtime_stack->stakes.stake_accum );
    1226           1 :   ulong total_active_stake = accumulated ? accumulated->reward_stake : 0UL;
    1227           1 :   if( total_active_stake==0UL ) return 0UL;
    1228             : 
    1229           0 :   ulong stake = fd_stake_delegation_activation_status( stake_delegation,
    1230           0 :                                                        rewarded_epoch,
    1231           0 :                                                        stake_history,
    1232           0 :                                                        &bank->f.warmup_cooldown_rate_epoch,
    1233           0 :                                                        FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) ).effective;
    1234             :   /* During recalculation, if stake account has already received rewards,
    1235             :      it's possible to have `stake > total_active_stake`. If
    1236             :      `pending_delegator_rewards` is a huge number, we could potentially
    1237             :      overflow a `u64`. We can also have individual rewards look greater
    1238             :      than the pending rewards. This is harmless in practice, but we
    1239             :      clamp it just to be safe */
    1240           0 :   uint128 reward = (uint128)pending_delegator_rewards * (uint128)stake / (uint128)total_active_stake;
    1241           0 :   return fd_ulong_min( reward>(uint128)ULONG_MAX ? ULONG_MAX : (ulong)reward, pending_delegator_rewards );
    1242           1 : }
    1243             : 
    1244             : /* Calculates epoch rewards for stake/vote accounts.
    1245             :    Returns vote rewards, stake rewards, and the sum of all stake rewards
    1246             :    in lamports.
    1247             : 
    1248             :    In the future, the calculation will be cached in the snapshot, but
    1249             :    for now we just re-calculate it (as Agave does).
    1250             :    calculate_stake_vote_rewards is responsible for calculating
    1251             :    stake account rewards based off of a combination of the
    1252             :    stake delegation state as well as the vote account. If this
    1253             :    calculation is done at the end of an epoch, we can just use the
    1254             :    vote states at the end of the current epoch. However, because we
    1255             :    are presumably booting up a node in the middle of rewards
    1256             :    distribution, we need to make sure that we are using the vote
    1257             :    states from the end of the previous epoch.
    1258             : 
    1259             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
    1260             : static void
    1261             : calculate_stake_vote_rewards( fd_bank_t *                    bank,
    1262             :                               fd_accdb_t *                   accdb,
    1263             :                               fd_stake_delegations_t const * stake_delegations,
    1264             :                               fd_capture_ctx_t *             capture_ctx FD_PARAM_UNUSED,
    1265             :                               fd_stake_history_t const *     stake_history,
    1266             :                               ulong                          rewarded_epoch,
    1267             :                               ulong                          total_rewards,
    1268             :                               uint128                        total_points,
    1269             :                               fd_runtime_stack_t *           runtime_stack,
    1270          69 :                               int                            is_recalculation ) {
    1271             : 
    1272          69 :   runtime_stack->stakes.stake_rewards_cnt = 0UL;
    1273             : 
    1274          69 :   int   alpenglow_enabled           = rewarded_epoch_is_alpenglow( bank, accdb, rewarded_epoch );
    1275          69 :   int   alpenglow_migration         = rewarded_epoch_is_alpenglow_migration( bank, accdb, rewarded_epoch );
    1276          69 :   ulong num_tower_slots             = alpenglow_migration ? migration_num_tower_slots( bank, accdb, rewarded_epoch ) : 0UL;
    1277          69 :   ulong slots_in_epoch              = fd_epoch_slot_cnt( &bank->f.epoch_schedule, rewarded_epoch );
    1278          69 :   int   block_revenue_sharing       = FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing );
    1279             : 
    1280          69 :   fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
    1281          69 :   fd_epoch_credits_view_t       epoch_credits_view[1];
    1282          69 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
    1283          69 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
    1284             : 
    1285          69 :   fd_stake_delegations_iter_t iter_[1];
    1286          69 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
    1287         375 :        !fd_stake_delegations_iter_done( iter );
    1288         306 :        fd_stake_delegations_iter_next( iter ) ) {
    1289         306 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
    1290         306 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
    1291             : 
    1292             :     /* Note that we don't check minimum delegation here, as there are
    1293             :        no plans to activate stake_minimum_delegation_for_rewards.
    1294             :        If this changes we need to skip stake accounts that are
    1295             :        below the minimum delegation here. However we don't do this yet,
    1296             :        to ensure that we audit the feature properly if this happens. */
    1297             : 
    1298         306 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
    1299         306 :     if( stake_delegation_idx>=runtime_stack->max_stake_accounts ) {
    1300           0 :       calculated_stake_rewards = calculated_stake_rewards_;
    1301         306 :     } else {
    1302         306 :       calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
    1303         306 :     }
    1304         306 :     calculated_stake_rewards->success = 0;
    1305             : 
    1306             : 
    1307         306 :     if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) &&
    1308         306 :                      fd_stake_delegation_is_inactive( stake_delegation, rewarded_epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch,
    1309         306 :                                                       FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) ) ) ) {
    1310           0 :       continue;
    1311           0 :     }
    1312             : 
    1313             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L856-L867 */
    1314         306 :     ulong block_reward = block_revenue_sharing ? calculate_block_reward( bank, runtime_stack, stake_history, stake_delegation, rewarded_epoch ) : 0UL;
    1315             : 
    1316             :     /* For the Alpenglow migration, we re-calculate the points for all
    1317             :        stake delegations because we haven't calculated the Alpenglow
    1318             :        points yet. Agave has the same semantics. We could compute and
    1319             :        cache the Alpenglow points in the first pass, but it would mean
    1320             :        bloating the cache for a one-off event. */
    1321         306 :     int cached = !is_recalculation &&
    1322         306 :                  stake_delegation_idx<runtime_stack->max_stake_accounts &&
    1323         306 :                  !alpenglow_migration;
    1324         306 :     uint idx;
    1325         306 :     if( FD_LIKELY( cached ) ) {
    1326         167 :       idx = runtime_stack->stakes.stake_points_result[ stake_delegation_idx ].vote_idx;
    1327         167 :     } else {
    1328         139 :       fd_vote_rewards_t *     vote_ele     = runtime_stack->stakes.vote_ele;
    1329         139 :       fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
    1330         139 :       idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
    1331         139 :     }
    1332             : 
    1333             :     /* Stake account may need to be adjusted to meet rent-exempt minimum
    1334             :        balance requirements based on new rent and delegation parameters.
    1335             :        https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L568-L608 */
    1336         306 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) {
    1337             :       /* If the stake account's resulting lamports would cause it to be
    1338             :          below the rent exempt minimum balance, it needs to be queued
    1339             :          for update (and thus affects the epoch reward partitions). */
    1340          61 :       int may_need_adjustment = delegation_may_need_adjustment(
    1341          61 :           bank,
    1342          61 :           stake_delegation,
    1343          61 :           stake_history,
    1344          61 :           rewarded_epoch,
    1345          61 :           stake_delegation->stake,
    1346          61 :           stake_delegation->lamports,
    1347          61 :           fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
    1348          61 :       if( !may_need_adjustment && !block_reward ) continue;
    1349             : 
    1350             :       /* Place an empty entry for this stake delegation idx so that
    1351             :          the partitioning logic factors it in. */
    1352          11 :       *calculated_stake_rewards = (fd_calculated_stake_rewards_t){
    1353          11 :         .success              = 1,
    1354          11 :         .staker_rewards       = 0,
    1355          11 :         .voter_rewards        = 0,
    1356          11 :         .new_credits_observed = stake_delegation->credits_observed,
    1357          11 :         .block_reward         = block_reward
    1358          11 :       };
    1359          11 :       runtime_stack->stakes.stake_rewards_cnt++;
    1360          11 :       continue;
    1361          61 :     }
    1362             : 
    1363         245 :     fd_calculated_stake_points_t   stake_points_result_[1];
    1364         245 :     fd_calculated_stake_points_t * stake_points_result;
    1365         245 :     uint128                        ag_migration_points = 0;
    1366         245 :     if( FD_LIKELY( cached ) ) {
    1367         106 :       stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
    1368         139 :     } else {
    1369         139 :       fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[ idx ];
    1370             : 
    1371             :       /* We have not cached the stake points yet if we are recalculating
    1372             :          stake rewards so we need to recalculate them.  ULONG_MAX
    1373             :          disables the tag fast path. */
    1374         139 :       calculate_stake_points_for_reward( alpenglow_enabled,
    1375         139 :                                          alpenglow_migration,
    1376         139 :                                          runtime_stack,
    1377         139 :                                          epoch_credits,
    1378         139 :                                          stake_history,
    1379         139 :                                          stake_delegation,
    1380         139 :                                          &bank->f.warmup_cooldown_rate_epoch,
    1381         139 :                                          FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
    1382         139 :                                          rewarded_epoch,
    1383         139 :                                          ULONG_MAX,
    1384         139 :                                          stake_points_result_,
    1385         139 :                                          &ag_migration_points );
    1386         139 :       stake_points_result = stake_points_result_;
    1387         139 :     }
    1388             : 
    1389             :     /* redeem_rewards is actually just responsible for calculating the
    1390             :        vote and stake rewards for each stake account.  It does not do
    1391             :        rewards redemption: it is a misnomer. */
    1392         245 :     int err = redeem_rewards(
    1393         245 :         stake_delegation,
    1394         245 :         idx,
    1395         245 :         rewarded_epoch,
    1396         245 :         total_rewards,
    1397         245 :         total_points,
    1398         245 :         alpenglow_enabled,
    1399         245 :         alpenglow_migration,
    1400         245 :         num_tower_slots,
    1401         245 :         slots_in_epoch,
    1402         245 :         runtime_stack,
    1403         245 :         stake_points_result,
    1404         245 :         ag_migration_points,
    1405         245 :         calculated_stake_rewards );
    1406             : 
    1407         245 :     if( FD_UNLIKELY( err!=0 ) ) {
    1408             :       /* Even if there is an error computing rewards for the stake
    1409             :          account, there may be a required balance update for the stake
    1410             :          account if rent increased.
    1411             :          https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L132-L152 */
    1412           1 :       int may_need_adjustment = delegation_may_need_adjustment(
    1413           1 :           bank,
    1414           1 :           stake_delegation,
    1415           1 :           stake_history,
    1416           1 :           rewarded_epoch,
    1417           1 :           stake_delegation->stake,
    1418           1 :           stake_delegation->lamports,
    1419           1 :           fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
    1420           1 :       if( !may_need_adjustment && !block_reward ) continue;
    1421             : 
    1422           0 :       *calculated_stake_rewards = (fd_calculated_stake_rewards_t){
    1423           0 :         .success              = 1,
    1424           0 :         .staker_rewards       = 0,
    1425           0 :         .voter_rewards        = 0,
    1426           0 :         .new_credits_observed = stake_delegation->credits_observed,
    1427           0 :         .block_reward         = block_reward
    1428           0 :       };
    1429         244 :     } else {
    1430         244 :       calculated_stake_rewards->success      = 1;
    1431         244 :       calculated_stake_rewards->block_reward = block_reward;
    1432         244 :     }
    1433             : 
    1434         244 :     if( capture_ctx && capture_ctx->capture_solcap ) {
    1435           0 :       fd_capture_link_write_stake_reward_event( capture_ctx,
    1436           0 :                                                 bank->f.slot,
    1437           0 :                                                 stake_delegation->stake_account,
    1438           0 :                                                 stake_delegation->vote_account,
    1439           0 :                                                 runtime_stack->stakes.vote_ele[ idx ].commission,
    1440           0 :                                                 (long)calculated_stake_rewards->voter_rewards,
    1441           0 :                                                 (long)calculated_stake_rewards->staker_rewards,
    1442           0 :                                                 (long)calculated_stake_rewards->new_credits_observed );
    1443           0 :     }
    1444             : 
    1445         244 :     runtime_stack->stakes.vote_ele[ idx ].vote_rewards += calculated_stake_rewards->voter_rewards;
    1446         244 :     runtime_stack->stakes.stake_rewards_cnt++;
    1447         244 :   }
    1448          69 :   fd_epoch_credits_view_fini( epoch_credits_view );
    1449          69 : }
    1450             : 
    1451             : /* setup_stake_partitions hashes every stake reward of the epoch into
    1452             :    its partition.  Only the rewards landing inside the fork's current
    1453             :    window are retained; see fd_stake_rewards.h.  The fork must already
    1454             :    have been initialized and its window positioned by the caller. */
    1455             : 
    1456             : static void
    1457             : setup_stake_partitions( fd_bank_t *                    bank,
    1458             :                         fd_accdb_t *                   accdb,
    1459             :                         fd_stake_history_t const *     stake_history,
    1460             :                         fd_stake_delegations_t const * stake_delegations,
    1461             :                         fd_runtime_stack_t *           runtime_stack,
    1462             :                         ushort                         fork_idx,
    1463             :                         ulong                          rewarded_epoch,
    1464             :                         ulong                          total_rewards,
    1465          69 :                         uint128                        total_points ) {
    1466             : 
    1467          69 :   int   alpenglow_enabled           = rewarded_epoch_is_alpenglow( bank, accdb, rewarded_epoch );
    1468          69 :   int   alpenglow_migration         = rewarded_epoch_is_alpenglow_migration( bank, accdb, rewarded_epoch );
    1469          69 :   ulong num_tower_slots             = alpenglow_migration ? migration_num_tower_slots( bank, accdb, rewarded_epoch ) : 0UL;
    1470          69 :   ulong slots_in_epoch              = fd_epoch_slot_cnt( &bank->f.epoch_schedule, rewarded_epoch );
    1471          69 :   int   block_revenue_sharing       = FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing );
    1472             : 
    1473          69 :   fd_stake_rewards_t * stake_rewards     = fd_bank_stake_rewards_modify( bank );
    1474          69 :   fd_epoch_credits_view_t epoch_credits_view[1];
    1475          69 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
    1476          69 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
    1477             : 
    1478          69 :   fd_stake_delegations_iter_t iter_[1];
    1479          69 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
    1480         375 :        !fd_stake_delegations_iter_done( iter );
    1481         306 :        fd_stake_delegations_iter_next( iter ) ) {
    1482         306 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
    1483         306 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
    1484             : 
    1485         306 :     if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) &&
    1486         306 :                      fd_stake_delegation_is_inactive( stake_delegation, rewarded_epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch,
    1487         306 :                                                       FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) ) ) ) {
    1488           0 :       continue;
    1489           0 :     }
    1490             : 
    1491         306 :     fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
    1492         306 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
    1493             : 
    1494         306 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->max_stake_accounts ) ) {
    1495             : 
    1496           0 :       calculated_stake_rewards = calculated_stake_rewards_;
    1497             : 
    1498             :       /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L856-L867 */
    1499           0 :       ulong block_reward = block_revenue_sharing ? calculate_block_reward( bank, runtime_stack, stake_history, stake_delegation, rewarded_epoch ) : 0UL;
    1500             : 
    1501           0 :       fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
    1502           0 :       fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
    1503           0 :       uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
    1504           0 :       if( FD_UNLIKELY( idx==UINT_MAX ) ) {
    1505             :         /* If the stake account's resulting lamports would cause it to be
    1506             :            below the rent exempt minimum balance, it needs to be queued
    1507             :            for update (and thus affects the epoch reward partitions). */
    1508           0 :         int may_need_adjustment = delegation_may_need_adjustment(
    1509           0 :             bank,
    1510           0 :             stake_delegation,
    1511           0 :             stake_history,
    1512           0 :             rewarded_epoch,
    1513           0 :             stake_delegation->stake,
    1514           0 :             stake_delegation->lamports,
    1515           0 :             fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
    1516           0 :         if( !may_need_adjustment && !block_reward ) continue;
    1517             : 
    1518           0 :         fd_stake_rewards_insert( stake_rewards, fork_idx, &stake_delegation->stake_account, 0UL, stake_delegation->credits_observed, block_reward );
    1519           0 :         continue;
    1520           0 :       }
    1521             : 
    1522           0 :       fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[ idx ];
    1523             : 
    1524           0 :       fd_calculated_stake_points_t stake_points_result[1];
    1525           0 :       uint128                      ag_migration_points = 0;
    1526           0 :       calculate_stake_points_for_reward( alpenglow_enabled,
    1527           0 :                                          alpenglow_migration,
    1528           0 :                                          runtime_stack,
    1529           0 :                                          epoch_credits,
    1530           0 :                                          stake_history,
    1531           0 :                                          stake_delegation,
    1532           0 :                                          &bank->f.warmup_cooldown_rate_epoch,
    1533           0 :                                          FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
    1534           0 :                                          rewarded_epoch,
    1535           0 :                                          ULONG_MAX,
    1536           0 :                                          stake_points_result,
    1537           0 :                                          &ag_migration_points );
    1538             : 
    1539             :       /* redeem_rewards is actually just responsible for calculating the
    1540             :          vote and stake rewards for each stake account.  It does not do
    1541             :          rewards redemption: it is a misnomer. */
    1542           0 :       int err = redeem_rewards(
    1543           0 :           stake_delegation,
    1544           0 :           idx,
    1545           0 :           rewarded_epoch,
    1546           0 :           total_rewards,
    1547           0 :           total_points,
    1548           0 :           alpenglow_enabled,
    1549           0 :           alpenglow_migration,
    1550           0 :           num_tower_slots,
    1551           0 :           slots_in_epoch,
    1552           0 :           runtime_stack,
    1553           0 :           stake_points_result,
    1554           0 :           ag_migration_points,
    1555           0 :           calculated_stake_rewards );
    1556             : 
    1557           0 :       if( FD_UNLIKELY( err!=0 ) ) {
    1558             :         /* Even if there is an error computing rewards for the stake
    1559             :            account, there may be a required balance update for the stake
    1560             :            account if rent increased.
    1561             :            https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L132-L152 */
    1562           0 :         int may_need_adjustment = delegation_may_need_adjustment(
    1563           0 :             bank,
    1564           0 :             stake_delegation,
    1565           0 :             stake_history,
    1566           0 :             rewarded_epoch,
    1567           0 :             stake_delegation->stake,
    1568           0 :             stake_delegation->lamports,
    1569           0 :             fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) );
    1570           0 :         if( !may_need_adjustment && !block_reward ) continue;
    1571             : 
    1572           0 :         fd_stake_rewards_insert( stake_rewards, fork_idx, &stake_delegation->stake_account, 0UL, stake_delegation->credits_observed, block_reward );
    1573           0 :         continue;
    1574           0 :       } else {
    1575           0 :         calculated_stake_rewards->success      = 1;
    1576           0 :         calculated_stake_rewards->block_reward = block_reward;
    1577           0 :       }
    1578         306 :     } else {
    1579         306 :       calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
    1580         306 :     }
    1581             : 
    1582         306 :     if( FD_UNLIKELY( !calculated_stake_rewards->success ) ) continue;
    1583             : 
    1584         255 :     fd_stake_rewards_insert(
    1585         255 :       stake_rewards,
    1586         255 :       fork_idx,
    1587         255 :       &stake_delegation->stake_account,
    1588         255 :       calculated_stake_rewards->staker_rewards,
    1589         255 :       calculated_stake_rewards->new_credits_observed,
    1590         255 :       calculated_stake_rewards->block_reward
    1591         255 :     );
    1592         255 :   }
    1593             : 
    1594          69 :   fd_stake_rewards_fini( stake_rewards, fork_idx );
    1595          69 :   fd_epoch_credits_view_fini( epoch_credits_view );
    1596          69 : }
    1597             : 
    1598             : /* Calculate epoch reward and return vote and stake rewards.
    1599             : 
    1600             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
    1601             : static uint128
    1602             : calculate_validator_rewards( fd_bank_t *                    bank,
    1603             :                              fd_accdb_t *                   accdb,
    1604             :                              fd_runtime_stack_t *           runtime_stack,
    1605             :                              fd_stake_delegations_t const * stake_delegations,
    1606             :                              fd_capture_ctx_t *             capture_ctx,
    1607             :                              ulong                          rewarded_epoch,
    1608          32 :                              ulong *                        rewards_out ) {
    1609          32 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    1610          32 :   fd_stake_history_t stake_history[1];
    1611          32 :   read_stake_history( accdb, bank->accdb_fork_id, stake_history_data, stake_history );
    1612             : 
    1613             :   /* Calculate the epoch reward points from stake/vote accounts */
    1614          32 :   uint128 total_points = calculate_reward_points_partitioned(
    1615          32 :       bank,
    1616          32 :       accdb,
    1617          32 :       stake_delegations,
    1618          32 :       stake_history,
    1619          32 :       rewarded_epoch,
    1620          32 :       runtime_stack );
    1621             : 
    1622             :   /* In a Tower or migration epoch, if there have been no Tower points
    1623             :      then no rewards are paid out (even Alpenglow rewards).
    1624             :      https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L959-L968
    1625             :      https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1005-L1008
    1626             :      https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L502-L518 */
    1627          32 :   int skip_rewards = ( !rewarded_epoch_is_alpenglow( bank, accdb, rewarded_epoch ) ||
    1628          32 :                        rewarded_epoch_is_alpenglow_migration( bank, accdb, rewarded_epoch ) ) &&
    1629          32 :                      !total_points;
    1630          32 :   if( FD_UNLIKELY( skip_rewards ) ) {
    1631           5 :     *rewards_out                            = 0UL;
    1632           5 :     runtime_stack->stakes.stake_rewards_cnt = 0UL;
    1633           5 :   }
    1634             : 
    1635          32 :   if( FD_UNLIKELY( capture_ctx && capture_ctx->capture_solcap ) ) {
    1636           0 :     ulong epoch = bank->f.epoch;
    1637           0 :     ulong slot  = bank->f.slot;
    1638           0 :     fd_capture_link_write_stake_rewards_begin( capture_ctx,
    1639           0 :                                                slot,
    1640           0 :                                                epoch,
    1641           0 :                                                epoch-1UL, /* FIXME: this is not strictly correct */
    1642           0 :                                                *rewards_out,
    1643           0 :                                                (ulong)total_points );
    1644           0 :   }
    1645             : 
    1646             :   /* Calculate the stake and vote rewards for each account. We want to
    1647             :      use the vote states from the end of the current_epoch. */
    1648          32 :   if( FD_LIKELY( !skip_rewards ) ) {
    1649          27 :     calculate_stake_vote_rewards(
    1650          27 :         bank,
    1651          27 :         accdb,
    1652          27 :         stake_delegations,
    1653          27 :         capture_ctx,
    1654          27 :         stake_history,
    1655          27 :         rewarded_epoch,
    1656          27 :         *rewards_out,
    1657          27 :         total_points,
    1658          27 :         runtime_stack,
    1659          27 :         0 );
    1660          27 :   }
    1661             : 
    1662          32 :   fd_hash_t const * parent_blockhash      = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
    1663          32 :   ulong             starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
    1664          32 :   uint              num_partitions        = get_reward_distribution_num_blocks( &bank->f.epoch_schedule,
    1665          32 :                                                                                 bank->f.slot,
    1666          32 :                                                                                 runtime_stack->stakes.stake_rewards_cnt,
    1667          32 :                                                                                 bank->f.slot_params.stake_account_stores_per_block );
    1668             : 
    1669          32 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1670          32 :   ushort               fork_idx      = fd_stake_rewards_init( stake_rewards,
    1671          32 :                                                               parent_blockhash,
    1672          32 :                                                               starting_block_height,
    1673          32 :                                                               num_partitions,
    1674          32 :                                                               0U,
    1675          32 :                                                               runtime_stack->stakes.stake_rewards_cnt );
    1676          32 :   if( FD_UNLIKELY( bank->stake_rewards_fork_id!=USHORT_MAX ) ) {
    1677           0 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    1678           0 :   }
    1679          32 :   bank->stake_rewards_fork_id = fork_idx;
    1680             : 
    1681          32 :   if( FD_LIKELY( !skip_rewards ) ) {
    1682          27 :     setup_stake_partitions( bank,
    1683          27 :                             accdb,
    1684          27 :                             stake_history,
    1685          27 :                             stake_delegations,
    1686          27 :                             runtime_stack,
    1687          27 :                             fork_idx,
    1688          27 :                             rewarded_epoch,
    1689          27 :                             *rewards_out,
    1690          27 :                             total_points );
    1691          27 :   } else {
    1692           5 :     fd_stake_rewards_fini( stake_rewards, fork_idx );
    1693           5 :   }
    1694             : 
    1695          32 :   return total_points;
    1696          32 : }
    1697             : 
    1698             : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
    1699             : 
    1700             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L277
    1701             :    https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L470-L495 */
    1702             : static void
    1703             : calculate_rewards_for_partitioning( fd_bank_t *                            bank,
    1704             :                                     fd_accdb_t *                           accdb,
    1705             :                                     fd_runtime_stack_t *                   runtime_stack,
    1706             :                                     fd_stake_delegations_t const *         stake_delegations,
    1707             :                                     fd_capture_ctx_t *                     capture_ctx,
    1708             :                                     ulong                                  prev_epoch,
    1709          32 :                                     fd_partitioned_rewards_calculation_t * result ) {
    1710          32 :   fd_prev_epoch_inflation_rewards_t rewards;
    1711             : 
    1712          32 :   calculate_previous_epoch_inflation_rewards( bank,
    1713          32 :                                               bank->f.capitalization,
    1714          32 :                                               prev_epoch,
    1715          32 :                                               &rewards );
    1716          32 :   if( FD_UNLIKELY( rewarded_epoch_is_alpenglow( bank, accdb, prev_epoch ) ) ) {
    1717           0 :     rewards.validator_rewards = fd_epoch_inflation_rewards_for_epoch( bank, accdb, prev_epoch );
    1718           0 :   }
    1719             : 
    1720          32 :   ulong total_rewards = rewards.validator_rewards;
    1721             : 
    1722          32 :   uint128 points = calculate_validator_rewards( bank,
    1723          32 :                                                 accdb,
    1724          32 :                                                 runtime_stack,
    1725          32 :                                                 stake_delegations,
    1726          32 :                                                 capture_ctx,
    1727          32 :                                                 prev_epoch,
    1728          32 :                                                 &total_rewards );
    1729             : 
    1730             :   /* The agave client does not partition the stake rewards until the
    1731             :      first distribution block.  We calculate the partitions during the
    1732             :      boundary. */
    1733          32 :   result->validator_points             = points;
    1734          32 :   result->validator_rewards            = total_rewards;
    1735          32 :   result->validator_rate               = rewards.validator_rate;
    1736          32 :   result->foundation_rate              = rewards.foundation_rate;
    1737          32 :   result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
    1738          32 :   result->capitalization               = bank->f.capitalization;
    1739          32 : }
    1740             : 
    1741             : /* Sweeps the given vote account's pending delegator rewards.
    1742             :    maybe_stake is the validator's reward epoch delegated stake, NULL if
    1743             :    unknown.  Only accounts holding pending rewards are rewritten.
    1744             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L233 */
    1745             : static void
    1746             : sweep_vote_account( fd_bank_t *         bank,
    1747             :                     fd_accdb_t *        accdb,
    1748             :                     fd_capture_ctx_t *  capture_ctx,
    1749             :                     fd_pubkey_t const * vote_address,
    1750             :                     ulong const *       maybe_stake,
    1751          59 :                     ulong *             total_block_reward_lamports ) {
    1752          59 :   ulong pending_delegator_rewards = 0UL;
    1753          59 :   {
    1754          59 :     fd_acc_t ro = fd_accdb_read_one( accdb, bank->accdb_fork_id, vote_address->uc );
    1755          59 :     int skip = !ro.lamports ||
    1756          59 :                memcmp( ro.owner, fd_solana_vote_program_id.uc, sizeof(fd_pubkey_t) ) ||
    1757          59 :                fd_vote_account_pending_delegator_rewards( ro.data, ro.data_len, &pending_delegator_rewards ) ||
    1758          59 :                !pending_delegator_rewards;
    1759          59 :     fd_accdb_unread_one( accdb, &ro );
    1760          59 :     if( FD_LIKELY( skip ) ) return;
    1761          59 :   }
    1762             : 
    1763          57 :   fd_accdb_svm_update_t update[1];
    1764          57 :   fd_acc_t              acc = fd_accdb_svm_open_rw( bank, accdb, update, vote_address, 0 );
    1765          57 :   ulong                 pending_reset;
    1766             :   /* The read above already parsed this data as a v4 state. */
    1767          57 :   FD_TEST( !fd_vote_account_reset_pending_delegator_rewards( acc.data, acc.data_len, &pending_reset ) );
    1768             :   /* If validator has no stake, delegator rewards go back to the validator, no
    1769             :      distribution needed.
    1770             :      If the validator's stake is not known, it likely did not pay VAT, so that
    1771             :      validator's block rewards get burned. */
    1772          57 :   if( !( maybe_stake && *maybe_stake==0UL ) ) {
    1773             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L249-L259 */
    1774           4 :     FD_TEST( acc.lamports>=pending_delegator_rewards );
    1775           4 :     acc.lamports                 -= pending_delegator_rewards;
    1776           4 :     *total_block_reward_lamports += pending_delegator_rewards;
    1777           4 :   }
    1778          57 :   fd_accdb_svm_close_rw( bank, accdb, capture_ctx, &acc, update );
    1779          57 : }
    1780             : 
    1781             : /* reward_epoch_delegated_stakes lookup: under Alpenglow the t-1 set
    1782             :    decides whether the validator is known, otherwise the stake
    1783             :    accumulator does.  The stake is the accumulator's reward epoch stake.
    1784             :    Returns stake_out or NULL if the validator is unknown.
    1785             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/alpenglow_epoch_type.rs#L72 */
    1786             : static ulong const *
    1787             : reward_epoch_delegated_stake( fd_bank_t *          bank,
    1788             :                               fd_runtime_stack_t * runtime_stack,
    1789             :                               fd_pubkey_t const *  vote_address,
    1790             :                               int                  alpenglow_enabled,
    1791          59 :                               ulong *              stake_out ) {
    1792          59 :   fd_stake_accum_t const * accumulated = fd_stake_accum_map_ele_query_const(
    1793          59 :       runtime_stack->stakes.stake_accum_map, vote_address, NULL, runtime_stack->stakes.stake_accum );
    1794          59 :   int known = alpenglow_enabled
    1795          59 :             ? fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_address, NULL )
    1796          59 :             : !!accumulated;
    1797          59 :   *stake_out = accumulated ? accumulated->reward_stake : 0UL;
    1798          59 :   return known ? stake_out : NULL;
    1799          59 : }
    1800             : 
    1801             : /* Sweeps pending delegator rewards out of the vote accounts.
    1802             :    Returns the swept lamports.
    1803             : 
    1804             :    Important. We must iterate the t-3 set, because both the fee settlement
    1805             :    and the vote program limit pending_delegator_rewards to leaders, i.e. t-2 set
    1806             :    (t-2 set has already rotated to t-3 when we call sweep_pending_delegator_rewards).
    1807             : 
    1808             :    https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1258-L1290 */
    1809             : static ulong
    1810             : sweep_pending_delegator_rewards( fd_bank_t *          bank,
    1811             :                                  fd_accdb_t *         accdb,
    1812             :                                  fd_capture_ctx_t *   capture_ctx,
    1813             :                                  fd_runtime_stack_t * runtime_stack,
    1814           4 :                                  int                  alpenglow_enabled ) {
    1815           4 :   ulong total_block_reward_lamports = 0UL;
    1816             : 
    1817           4 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
    1818           4 :   ulong              fork_id     = bank->vote_stakes_fork_id;
    1819           4 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
    1820           4 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter_mem );
    1821          63 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter );
    1822          59 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter ) ) {
    1823          59 :     fd_vote_stakes_ele_t ele[1];
    1824          59 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_3, iter, ele );
    1825             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1276-L1278 */
    1826          59 :     ulong         stake;
    1827          59 :     ulong const * maybe_stake = reward_epoch_delegated_stake( bank, runtime_stack, &ele->pubkey, alpenglow_enabled, &stake );
    1828             :     /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1279-L1285 */
    1829          59 :     sweep_vote_account( bank, accdb, capture_ctx, &ele->pubkey, maybe_stake, &total_block_reward_lamports );
    1830          59 :   }
    1831             : 
    1832           4 :   return total_block_reward_lamports;
    1833           4 : }
    1834             : 
    1835             : /* Calculate rewards from previous epoch and distribute vote rewards
    1836             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L148
    1837             :    https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L234-L296 */
    1838             : static ulong
    1839             : calculate_rewards_and_distribute_vote_rewards( fd_bank_t *                    bank,
    1840             :                                                fd_accdb_t *                   accdb,
    1841             :                                                fd_runtime_stack_t *           runtime_stack,
    1842             :                                                fd_stake_delegations_t const * stake_delegations,
    1843             :                                                fd_capture_ctx_t *             capture_ctx,
    1844          32 :                                                ulong                          prev_epoch ) {
    1845             : 
    1846          32 :   fd_vote_rewards_t *     vote_ele_pool = runtime_stack->stakes.vote_ele;
    1847          32 :   fd_vote_rewards_map_t * vote_ele_map  = runtime_stack->stakes.vote_map;
    1848             : 
    1849             :   /* First we must compute the stake and vote rewards for the just
    1850             :      completed epoch.  We store the stake account rewards and vote
    1851             :      states rewards in the bank */
    1852             : 
    1853          32 :   fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
    1854          32 :   calculate_rewards_for_partitioning( bank,
    1855          32 :                                       accdb,
    1856          32 :                                       runtime_stack,
    1857          32 :                                       stake_delegations,
    1858          32 :                                       capture_ctx,
    1859          32 :                                       prev_epoch,
    1860          32 :                                       rewards_calc_result );
    1861             : 
    1862             :   /* Sweep while stake_accum still holds the reward epoch delegated stakes.
    1863             :      https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1258-L1290 */
    1864          32 :   runtime_stack->stakes.block_rewards = 0UL;
    1865          32 :   if( FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing ) ) {
    1866           4 :     runtime_stack->stakes.block_rewards = sweep_pending_delegator_rewards( bank,
    1867           4 :                                                                            accdb,
    1868           4 :                                                                            capture_ctx,
    1869           4 :                                                                            runtime_stack,
    1870           4 :                                                                            rewarded_epoch_is_alpenglow( bank, accdb, prev_epoch ) );
    1871           4 :   }
    1872             : 
    1873             : 
    1874             :   /* Distribute the commission rewards.  distributed_rewards includes
    1875             :      burned and incinerated amounts: the epoch rewards sysvar counts
    1876             :      them as distributed.
    1877             :      https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L186-L217 */
    1878          32 :   ulong distributed_rewards   = 0UL;
    1879          32 :   ulong credited_vote_rewards = 0UL;
    1880          32 :   if( FD_FEATURE_ACTIVE_BANK( bank, custom_commission_collector ) ) {
    1881             :     /* Aggregate rewards per collector before validation: several small
    1882             :        rewards may collectively make a new collector rent-exempt.
    1883             :        stake_accum is free for reuse as scratch here. */
    1884           9 :     fd_stake_accum_t *     collector_pool = runtime_stack->stakes.stake_accum;
    1885           9 :     fd_stake_accum_map_t * collector_map  = runtime_stack->stakes.stake_accum_map;
    1886           9 :     fd_stake_accum_map_reset( collector_map );
    1887           9 :     ulong collector_cnt = 0UL;
    1888             : 
    1889           9 :     for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
    1890          44 :          !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
    1891          35 :          iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
    1892          35 :       uint                idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
    1893          35 :       fd_vote_rewards_t * ele = &vote_ele_pool[idx];
    1894             : 
    1895          35 :       ulong rewards = ele->vote_rewards;
    1896          35 :       if( FD_UNLIKELY( !rewards ) ) continue;
    1897          29 :       distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards );
    1898             : 
    1899          29 :       fd_pubkey_t collector;
    1900          29 :       fd_rewards_inflation_collector( bank, &ele->pubkey, &collector );
    1901             : 
    1902          29 :       fd_stake_accum_t * collector_reward = fd_stake_accum_map_ele_query( collector_map, &collector, NULL, collector_pool );
    1903          29 :       if( FD_UNLIKELY( !collector_reward ) ) {
    1904          26 :         FD_TEST( collector_cnt<runtime_stack->max_vote_accounts );
    1905          26 :         collector_reward         = &collector_pool[ collector_cnt++ ];
    1906          26 :         collector_reward->pubkey = collector;
    1907          26 :         collector_reward->stake  = rewards;
    1908          26 :         fd_stake_accum_map_ele_insert( collector_map, collector_reward, collector_pool );
    1909          26 :       } else {
    1910           3 :         collector_reward->stake = fd_ulong_sat_add( collector_reward->stake, rewards );
    1911           3 :       }
    1912          29 :     }
    1913             : 
    1914           9 :     for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( collector_map, collector_pool );
    1915          35 :          !fd_stake_accum_map_iter_done( iter, collector_map, collector_pool );
    1916          26 :          iter = fd_stake_accum_map_iter_next( iter, collector_map, collector_pool ) ) {
    1917          26 :       fd_stake_accum_t * collector_reward = fd_stake_accum_map_iter_ele( iter, collector_map, collector_pool );
    1918             : 
    1919             :       /* A collector that is itself a self-collecting vote account is
    1920             :          paid only its own reward; rewards routed to it by other vote
    1921             :          accounts are burned (it is not system-owned).
    1922             :          https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L70-L124 */
    1923          26 :       fd_vote_rewards_t * self_ele = fd_vote_rewards_map_ele_query( vote_ele_map, &collector_reward->pubkey, NULL, vote_ele_pool );
    1924          26 :       int is_vote_account = 0;
    1925          26 :       if( self_ele && self_ele->vote_rewards ) {
    1926           8 :         fd_pubkey_t self_collector;
    1927           8 :         fd_rewards_inflation_collector( bank, &self_ele->pubkey, &self_collector );
    1928           8 :         is_vote_account = fd_pubkey_eq( &self_collector, &self_ele->pubkey );
    1929           8 :       }
    1930          26 :       ulong rewards = is_vote_account ? self_ele->vote_rewards : collector_reward->stake;
    1931             : 
    1932          26 :       fd_acc_t account = fd_accdb_read_one( accdb, bank->accdb_fork_id, collector_reward->pubkey.uc );
    1933          26 :       int      burn;
    1934          26 :       if( is_vote_account ) {
    1935             :         /* The vote account itself only needs the overflow check. */
    1936           8 :         ulong post_balance;
    1937           8 :         burn = __builtin_uaddl_overflow( account.lamports, rewards, &post_balance );
    1938          18 :       } else {
    1939          18 :         burn = fd_rewards_validate_commission_collector( bank, &collector_reward->pubkey, &account, rewards );
    1940          18 :       }
    1941          26 :       fd_accdb_unread_one( accdb, &account );
    1942             : 
    1943          26 :       if( FD_UNLIKELY( burn ) ) continue;
    1944             : 
    1945             :       /* Credit rewards to the collector (creating a new system account
    1946             :          if it does not exist).  This is always a vote reward. */
    1947          22 :       fd_accdb_svm_credit( bank, accdb, capture_ctx, &collector_reward->pubkey, rewards, 1 );
    1948          22 :       if( FD_LIKELY( !fd_pubkey_eq( &collector_reward->pubkey, &fd_sysvar_incinerator_id ) ) ) {
    1949          19 :         credited_vote_rewards = fd_ulong_sat_add( credited_vote_rewards, rewards );
    1950          19 :       }
    1951          22 :     }
    1952          23 :   } else {
    1953          23 :     for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
    1954          92 :          !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
    1955          69 :          iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
    1956             : 
    1957          69 :       uint idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
    1958          69 :       fd_vote_rewards_t * ele = &vote_ele_pool[idx];
    1959             : 
    1960          69 :       ulong rewards = ele->vote_rewards;
    1961          69 :       if( rewards==0UL ) {
    1962           8 :         continue;
    1963           8 :       }
    1964             : 
    1965             :       /* Credit rewards to vote account (creating a new system account if
    1966             :          it does not exist) */
    1967          61 :       fd_accdb_svm_credit( bank, accdb, capture_ctx, &ele->pubkey, rewards, 1 );
    1968          61 :       distributed_rewards   = fd_ulong_sat_add( distributed_rewards, rewards );
    1969          61 :       credited_vote_rewards = fd_ulong_sat_add( credited_vote_rewards, rewards );
    1970          61 :     }
    1971          23 :   }
    1972             : 
    1973             :   /* Verify that we didn't pay any more than we expected to */
    1974          32 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1975          32 :   ulong total_stake_rewards = fd_stake_rewards_total_rewards( stake_rewards, bank->stake_rewards_fork_id );
    1976             : 
    1977          32 :   ulong total_rewards = fd_ulong_sat_add( distributed_rewards, total_stake_rewards );
    1978          32 :   if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
    1979           0 :     FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
    1980           0 :   }
    1981             : 
    1982          32 :   runtime_stack->stakes.distributed_rewards = distributed_rewards;
    1983          32 :   runtime_stack->stakes.total_rewards       = rewards_calc_result->validator_rewards;
    1984          32 :   runtime_stack->stakes.total_points.ud     = rewards_calc_result->validator_points;
    1985          32 :   return fd_ulong_sat_add( credited_vote_rewards, total_stake_rewards );
    1986          32 : }
    1987             : 
    1988             : /* Note: modifies delegation in-place, adjusting it for rent-exempt
    1989             :    minimum balance requirements.
    1990             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L55-L76 */
    1991             : static void
    1992             : adjust_delegation_for_rent( fd_delegation_t * delegation,
    1993             :                             ulong             rewarded_epoch,
    1994             :                             ulong             new_delegation_with_rewards,
    1995             :                             ulong             lamports_with_rewards,
    1996           0 :                             ulong             minimum_lamports ) {
    1997           0 :   ulong new_delegation = fd_ulong_min( new_delegation_with_rewards,
    1998           0 :                                        fd_ulong_sat_sub( lamports_with_rewards, minimum_lamports ) );
    1999             : 
    2000           0 :   if( new_delegation!=delegation->stake ) {
    2001           0 :     delegation->stake = new_delegation;
    2002           0 :     if( FD_UNLIKELY( new_delegation==0UL ) ) {
    2003           0 :       delegation->deactivation_epoch = rewarded_epoch;
    2004           0 :     }
    2005           0 :   }
    2006           0 : }
    2007             : 
    2008             : /* Distributes a single partitioned reward to a single stake account.  acc was
    2009             :    acquired by the caller as part of a batch and is released with it, so the
    2010             :    early returns below leave commit at 0, which fd_accdb_release skips. */
    2011             : 
    2012             : static int
    2013             : distribute_epoch_reward_to_stake_acc( fd_bank_t *         bank,
    2014             :                                       fd_capture_ctx_t *  capture_ctx,
    2015             :                                       ulong               reward_lamports,
    2016             :                                       ulong               block_reward,
    2017             :                                       ulong               new_credits_observed,
    2018             :                                       ulong               partition_idx,
    2019             :                                       fd_acc_t *          acc,
    2020             :                                       fd_lthash_adder_t * adder_pre,
    2021             :                                       fd_lthash_value_t * sum_pre,
    2022             :                                       fd_lthash_adder_t * adder_post,
    2023         136 :                                       fd_lthash_value_t * sum_post ) {
    2024         136 :   if( FD_UNLIKELY( !acc->lamports ) ) {
    2025           0 :     return 1; /* account does not exist */
    2026           0 :   }
    2027             : 
    2028         136 :   fd_stake_state_t const * stake_state_orig = fd_stakes_get_state( acc );
    2029         136 :   if( FD_UNLIKELY( !stake_state_orig || stake_state_orig->stake_type!=FD_STAKE_STATE_STAKE ) ) {
    2030           0 :     return 1; /* not a valid stake account */
    2031           0 :   }
    2032             : 
    2033         136 :   fd_pubkey_t const * stake_pubkey   = fd_type_pun_const(acc->pubkey);
    2034         136 :   fd_stake_state_t    stake_state[1] = { *stake_state_orig };
    2035             : 
    2036         136 :   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 );
    2037             : 
    2038             :   /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.5/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L263-L268 */
    2039         136 :   ulong lamports_pre = acc->lamports;
    2040         136 :   FD_TEST( !__builtin_add_overflow( acc->lamports, reward_lamports, &acc->lamports ) );
    2041         136 :   FD_TEST( !__builtin_add_overflow( acc->lamports, block_reward,    &acc->lamports ) );
    2042             : 
    2043         136 :   ulong old_credits_observed                = stake_state->stake.stake.credits_observed;
    2044         136 :   stake_state->stake.stake.credits_observed = new_credits_observed;
    2045         136 :   stake_state->stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->stake.stake.delegation.stake, reward_lamports );
    2046             : 
    2047             :   /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L259-L283 */
    2048         136 :   fd_stake_t * new_stake = &stake_state->stake.stake;
    2049         136 :   if( FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) ) {
    2050           0 :     ulong minimum_balance = fd_rent_exempt_minimum_balance( &bank->f.rent, acc->data_len );
    2051           0 :     adjust_delegation_for_rent(
    2052           0 :       &new_stake->delegation,
    2053           0 :       fd_ulong_sat_sub( bank->f.epoch, 1UL ),
    2054           0 :       new_stake->delegation.stake,
    2055           0 :       acc->lamports,
    2056           0 :       minimum_balance );
    2057           0 :   }
    2058             : 
    2059         136 :   fd_stake_delegations_t * stake_delegations_upd = fd_bank_stake_delegations_modify( bank );
    2060         136 :   fd_stake_delegations_fork_update( stake_delegations_upd,
    2061         136 :                                     bank->stake_delegations_fork_id, 0UL,
    2062         136 :                                     stake_pubkey,
    2063         136 :                                     &stake_state->stake.stake.delegation.voter_pubkey,
    2064         136 :                                     stake_state->stake.stake.delegation.stake,
    2065         136 :                                     stake_state->stake.stake.delegation.activation_epoch,
    2066         136 :                                     stake_state->stake.stake.delegation.deactivation_epoch,
    2067         136 :                                     stake_state->stake.stake.credits_observed,
    2068         136 :                                     acc->lamports,
    2069         136 :                                     (uint)acc->data_len );
    2070         136 :   if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) {
    2071           0 :     fd_event_runtime_stake_delegation_payout_emit( bank,
    2072           0 :                                                    stake_pubkey->uc,
    2073           0 :                                                    stake_state->stake.stake.delegation.voter_pubkey.uc,
    2074           0 :                                                    stake_state->stake.stake.delegation.stake,
    2075           0 :                                                    stake_state->stake.stake.delegation.activation_epoch,
    2076           0 :                                                    stake_state->stake.stake.delegation.deactivation_epoch,
    2077           0 :                                                    stake_state->stake.stake.credits_observed );
    2078           0 :   }
    2079             : 
    2080         136 :   if( FD_UNLIKELY( capture_ctx && capture_ctx->capture_solcap ) ) {
    2081           0 :     fd_capture_link_write_stake_account_payout( capture_ctx,
    2082           0 :                                                 bank->f.slot,
    2083           0 :                                                 *stake_pubkey,
    2084           0 :                                                 bank->f.slot,
    2085           0 :                                                 acc->lamports,
    2086           0 :                                                 (long)( reward_lamports+block_reward ),
    2087           0 :                                                 new_credits_observed,
    2088           0 :                                                 (long)( new_credits_observed - old_credits_observed ),
    2089           0 :                                                 stake_state->stake.stake.delegation.stake,
    2090           0 :                                                 (long)( reward_lamports+block_reward ) );
    2091           0 :   }
    2092             : 
    2093         136 :   FD_STORE( fd_stake_state_t, acc->data, *stake_state );
    2094         136 :   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 );
    2095         136 :   fd_hashes_capture_account( stake_pubkey->uc, acc->owner, acc->lamports, acc->executable, acc->data, acc->data_len, bank, capture_ctx );
    2096         136 :   if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) {
    2097           0 :     fd_event_runtime_reward_emit( bank, FD_EVENT_RUNTIME_REWARD_KIND_STAKE, acc->pubkey, acc->owner,
    2098           0 :                                   lamports_pre, acc->lamports, partition_idx, new_credits_observed,
    2099           0 :                                   stake_state->stake.stake.delegation.stake );
    2100           0 :   }
    2101         136 :   acc->commit = 1;
    2102             : 
    2103         136 :   return 0;
    2104         136 : }
    2105             : 
    2106             : /* Accdb batch size for updating stake accounts */
    2107        1707 : #define STAKE_REWARD_ACC_BATCH_SZ (32UL)
    2108             : 
    2109             : /* Process reward credits for a partition of rewards.  Store the rewards
    2110             :    to AccountsDB, update reward history record and total capitalization
    2111             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
    2112             : static void
    2113             : distribute_epoch_rewards_in_partition( fd_stake_rewards_t *      stake_rewards,
    2114             :                                        ulong                     partition_idx,
    2115             :                                        fd_bank_t *               bank,
    2116             :                                        fd_accdb_t *              accdb,
    2117          41 :                                        fd_capture_ctx_t *        capture_ctx ) {
    2118             : 
    2119          41 :   ulong lamports_distributed     = 0UL;
    2120          41 :   ulong lamports_burned          = 0UL;
    2121          41 :   ulong block_reward_distributed = 0UL;
    2122          41 :   ulong block_reward_burned      = 0UL;
    2123             : 
    2124          41 :   fd_lthash_adder_t adder_pre[1], adder_post[1];
    2125          41 :   fd_lthash_adder_new( adder_pre  );
    2126          41 :   fd_lthash_adder_new( adder_post );
    2127          41 :   fd_lthash_value_t sum_pre[1], sum_post[1];
    2128          41 :   fd_lthash_zero( sum_pre  );
    2129          41 :   fd_lthash_zero( sum_post );
    2130             : 
    2131             :   /* Acquire and process stake accounts in batches of 32 */
    2132             : 
    2133          41 :   fd_pubkey_t   pubkeys         [ STAKE_REWARD_ACC_BATCH_SZ ];
    2134          41 :   uchar const * pubkey_ptrs     [ STAKE_REWARD_ACC_BATCH_SZ ];
    2135          41 :   int           writable        [ STAKE_REWARD_ACC_BATCH_SZ ];
    2136          41 :   fd_acc_t      accs            [ STAKE_REWARD_ACC_BATCH_SZ ];
    2137          41 :   ulong         reward_lamports [ STAKE_REWARD_ACC_BATCH_SZ ];
    2138          41 :   ulong         credits_observed[ STAKE_REWARD_ACC_BATCH_SZ ];
    2139          41 :   ulong         block_rewards   [ STAKE_REWARD_ACC_BATCH_SZ ];
    2140             : 
    2141        1353 :   for( ulong i=0UL; i<STAKE_REWARD_ACC_BATCH_SZ; i++ ) {
    2142        1312 :     pubkey_ptrs[ i ] = pubkeys[ i ].uc;
    2143        1312 :     writable   [ i ] = 1;
    2144        1312 :   }
    2145             : 
    2146          41 :   fd_stake_rewards_iter_init( stake_rewards, bank->stake_rewards_fork_id, (ushort)partition_idx );
    2147          82 :   while( !fd_stake_rewards_iter_done( stake_rewards ) ) {
    2148             : 
    2149             :     /* Gather the next batch of rewards out of the partition. */
    2150          41 :     ulong batch_cnt = 0UL;
    2151         177 :     for( ; batch_cnt<STAKE_REWARD_ACC_BATCH_SZ && !fd_stake_rewards_iter_done( stake_rewards );
    2152         136 :          batch_cnt++, fd_stake_rewards_iter_next( stake_rewards, bank->stake_rewards_fork_id ) ) {
    2153         136 :       fd_stake_rewards_iter_ele( stake_rewards,
    2154         136 :                                  bank->stake_rewards_fork_id,
    2155         136 :                                  &pubkeys         [ batch_cnt ],
    2156         136 :                                  &reward_lamports [ batch_cnt ],
    2157         136 :                                  &credits_observed[ batch_cnt ],
    2158         136 :                                  &block_rewards   [ batch_cnt ] );
    2159         136 :     }
    2160             : 
    2161          41 :     fd_accdb_acquire( accdb, bank->accdb_fork_id, batch_cnt, pubkey_ptrs, writable, accs );
    2162             : 
    2163             :     /* Calculate and flush stake account updates */
    2164         177 :     for( ulong i=0UL; i<batch_cnt; i++ ) {
    2165         136 :       if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc( bank,
    2166         136 :                                                             capture_ctx,
    2167         136 :                                                             reward_lamports[ i ],
    2168         136 :                                                             block_rewards[ i ],
    2169         136 :                                                             credits_observed[ i ],
    2170         136 :                                                             partition_idx,
    2171         136 :                                                             &accs[ i ],
    2172         136 :                                                             adder_pre, sum_pre,
    2173         136 :                                                             adder_post, sum_post ) ) ) {
    2174         136 :         lamports_distributed     += reward_lamports[ i ];
    2175         136 :         block_reward_distributed += block_rewards[ i ];
    2176         136 :       } else {
    2177           0 :         lamports_burned     += reward_lamports[ i ];
    2178           0 :         block_reward_burned += block_rewards[ i ];
    2179           0 :       }
    2180         136 :     }
    2181             : 
    2182          41 :     fd_accdb_release( accdb, batch_cnt, accs );
    2183          41 :   }
    2184             : 
    2185          41 :   fd_lthash_adder_flush( adder_pre,  sum_pre  );
    2186          41 :   fd_lthash_adder_flush( adder_post, sum_post );
    2187             : 
    2188          41 :   fd_lthash_value_t * bank_lthash = fd_bank_lthash_locking_modify( bank );
    2189          41 :   fd_lthash_sub( bank_lthash, sum_pre  );
    2190          41 :   fd_lthash_add( bank_lthash, sum_post );
    2191          41 :   fd_bank_lthash_end_locking_modify( bank );
    2192             : 
    2193             :   /* Update the epoch rewards sysvar with the amount distributed and burnt */
    2194          41 :   fd_sysvar_epoch_rewards_distribute( bank, accdb, capture_ctx,
    2195          41 :                                       lamports_distributed + lamports_burned,
    2196          41 :                                       block_reward_distributed + block_reward_burned );
    2197             : 
    2198          41 :   FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu, block rewards burned: %lu, block rewards distributed: %lu",
    2199          41 :                  lamports_burned, lamports_distributed, block_reward_burned, block_reward_distributed ));
    2200             : 
    2201          41 :   bank->f.capitalization = bank->f.capitalization + lamports_distributed + block_reward_distributed;
    2202          41 : }
    2203             : 
    2204             : static int
    2205             : recalculate_partitioned_rewards( fd_bank_t *          bank,
    2206             :                                  fd_accdb_t *         accdb,
    2207             :                                  fd_runtime_stack_t * runtime_stack,
    2208             :                                  fd_capture_ctx_t *   capture_ctx,
    2209             :                                  int                  snapshot_boot,
    2210             :                                  uint                 win_lo );
    2211             : 
    2212             : /* Process reward distribution for the block if it is inside reward interval.
    2213             : 
    2214             :    https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L45-L136 */
    2215             : void
    2216             : fd_distribute_partitioned_epoch_rewards( fd_bank_t *          bank,
    2217             :                                          fd_accdb_t *         accdb,
    2218             :                                          fd_runtime_stack_t * runtime_stack,
    2219         615 :                                          fd_capture_ctx_t *   capture_ctx ) {
    2220             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L46-L48 */
    2221         615 :   if( FD_LIKELY( bank->stake_rewards_fork_id==USHORT_MAX ) ) return;
    2222             : 
    2223          74 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    2224             : 
    2225          74 :   ulong block_height                       = bank->f.block_height;
    2226          74 :   ulong distribution_starting_block_height = fd_stake_rewards_starting_block_height( stake_rewards, bank->stake_rewards_fork_id );
    2227          74 :   ulong distribution_end_exclusive         = fd_stake_rewards_exclusive_ending_block_height( stake_rewards, bank->stake_rewards_fork_id );
    2228             : 
    2229             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L55-L58 */
    2230          74 :   if( FD_UNLIKELY( block_height<distribution_starting_block_height ) ) {
    2231          32 :     return;
    2232          32 :   }
    2233             : 
    2234             :   /* The logic in Agave for EpochRewardPhase::Calculation has no direct
    2235             :      equivalent in Firedancer, because reward calculation is done
    2236             :      eagerly at the epoch boundary, whereas for Agave it's done at the
    2237             :      first distribution block.
    2238             :      https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L60-L90 */
    2239             : 
    2240          42 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
    2241          42 :   ulong                       epoch          = bank->f.epoch;
    2242             : 
    2243          42 :   if( FD_UNLIKELY( fd_epoch_slot_cnt( epoch_schedule, epoch ) <= fd_stake_rewards_num_partitions( stake_rewards, bank->stake_rewards_fork_id ) ) ) {
    2244           0 :     FD_LOG_CRIT(( "Should not be distributing rewards" ));
    2245           0 :   }
    2246             : 
    2247             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L110-L114 */
    2248          42 :   if( FD_LIKELY( block_height>=distribution_starting_block_height && block_height<distribution_end_exclusive ) ) {
    2249          41 :     ulong partition_idx = block_height-distribution_starting_block_height;
    2250             : 
    2251             :     /* The rewards of this partition are only in memory if the window
    2252             :        covers it.  If they are not, re-derive the window that does. */
    2253          41 :     ushort fork_id = bank->stake_rewards_fork_id;
    2254          41 :     if( FD_UNLIKELY( partition_idx<(ulong)fd_stake_rewards_window_lo( stake_rewards, fork_id ) ||
    2255          41 :                      partition_idx>(ulong)fd_stake_rewards_window_hi( stake_rewards, fork_id ) ) ) {
    2256           0 :       FD_LOG_INFO(( "reward partition is not in the window, recalculating" ));
    2257           0 :       if( FD_UNLIKELY( !recalculate_partitioned_rewards( bank, accdb, runtime_stack, capture_ctx, 0, (uint)partition_idx ) ) ) {
    2258           0 :         FD_LOG_CRIT(( "failed to recalculate partitioned rewards "
    2259           0 :                       "(slot=%lu block_height=%lu partition=%lu fork=%u)",
    2260           0 :                       bank->f.slot,
    2261           0 :                       block_height,
    2262           0 :                       partition_idx,
    2263           0 :                       (uint)fork_id ));
    2264           0 :       }
    2265           0 :       stake_rewards = fd_bank_stake_rewards_modify( bank );
    2266           0 :     }
    2267             : 
    2268          41 :     distribute_epoch_rewards_in_partition( stake_rewards, partition_idx, bank, accdb, capture_ctx );
    2269          41 :   }
    2270             : 
    2271             :   /* If we have finished distributing rewards, set the status to inactive
    2272             :      https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L116-L135 */
    2273          42 :   if( fd_ulong_sat_add( block_height, 1UL )>=distribution_end_exclusive ) {
    2274          42 :     fd_sysvar_epoch_rewards_set_inactive( bank, accdb, capture_ctx );
    2275          42 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    2276          42 :     bank->stake_rewards_fork_id = USHORT_MAX;
    2277          42 :   }
    2278          42 : }
    2279             : 
    2280             : /* Partitioned epoch rewards entry-point.
    2281             : 
    2282             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L102
    2283             :    https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank.rs#L1748-L1899
    2284             : */
    2285             : void
    2286             : fd_begin_partitioned_rewards( fd_bank_t *                    bank,
    2287             :                               fd_accdb_t *                   accdb,
    2288             :                               fd_runtime_stack_t *           runtime_stack,
    2289             :                               fd_capture_ctx_t *             capture_ctx,
    2290             :                               fd_stake_delegations_t const * stake_delegations,
    2291             :                               fd_hash_t const *              parent_blockhash,
    2292             :                               ulong                          parent_epoch,
    2293          32 :                               ulong                          parent_capitalization ) {
    2294             : 
    2295          32 :   if( FD_UNLIKELY( rewarded_epoch_is_alpenglow( bank, accdb, parent_epoch ) ) ) {
    2296           0 :     fd_reward_epoch_stakes_set( bank,
    2297           0 :                                 accdb,
    2298           0 :                                 capture_ctx,
    2299           0 :                                 parent_epoch,
    2300           0 :                                 runtime_stack );
    2301           0 :   }
    2302             : 
    2303          32 :   ulong capitalization_rewards = calculate_rewards_and_distribute_vote_rewards(
    2304          32 :       bank,
    2305          32 :       accdb,
    2306          32 :       runtime_stack,
    2307          32 :       stake_delegations,
    2308          32 :       capture_ctx,
    2309          32 :       parent_epoch );
    2310             : 
    2311             :   /* Once the rewards for vote accounts have been distributed and stake
    2312             :      account rewards have been calculated, we can now set our epoch
    2313             :      reward status to be active and we can initialize the epoch rewards
    2314             :      sysvar.  This sysvar is then deleted once all of the partitioned
    2315             :      stake rewards have been distributed.
    2316             : 
    2317             :      The Agave client calculates the partitions for each stake reward
    2318             :      when the first distribution block is reached.  The Firedancer
    2319             :      client differs here since we hash the partitions during the epoch
    2320             :      boundary. */
    2321             : 
    2322          32 :   ulong distribution_starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
    2323          32 :   uint  num_partitions                     = fd_stake_rewards_num_partitions( fd_bank_stake_rewards_query( bank ), bank->stake_rewards_fork_id );
    2324             : 
    2325          32 :   fd_sysvar_epoch_rewards_init(
    2326          32 :       bank,
    2327          32 :       accdb,
    2328          32 :       capture_ctx,
    2329          32 :       runtime_stack->stakes.distributed_rewards,
    2330          32 :       distribution_starting_block_height,
    2331          32 :       num_partitions,
    2332          32 :       runtime_stack->stakes.total_rewards,
    2333          32 :       runtime_stack->stakes.total_points.ud,
    2334          32 :       parent_blockhash,
    2335          32 :       runtime_stack->stakes.block_rewards );
    2336             : 
    2337          32 :   if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, alpenglow ) ) ) {
    2338           4 :     ulong capitalization;
    2339           4 :     FD_TEST( !__builtin_uaddl_overflow( parent_capitalization, capitalization_rewards, &capitalization ) );
    2340           4 :     fd_prev_epoch_inflation_rewards_t rewards[1];
    2341           4 :     calculate_previous_epoch_inflation_rewards( bank, capitalization, bank->f.epoch, rewards );
    2342           4 :     fd_epoch_inflation_account_update( bank, accdb, capture_ctx, rewards->validator_rewards );
    2343           4 :   }
    2344          32 : }
    2345             : 
    2346             : /*
    2347             :     Re-calculates partitioned stake rewards.
    2348             :     This updates the slot context's epoch reward status with the recalculated partitioned rewards.
    2349             : 
    2350             :     Everything the calculation needs survives in the epoch rewards
    2351             :     sysvar and in the bank, so the partitions can be rebuilt at any
    2352             :     point during the distribution interval.  The rebuilt partitions
    2353             :     always go into a freshly acquired fork, with its window starting at
    2354             :     win_lo, so that banks reading the fork this bank was using keep the
    2355             :     entries they computed for themselves.  snapshot_boot selects where
    2356             :     the vote commissions come from: a bank restored from a snapshot has
    2357             :     to resolve them, every other caller reuses the ones the epoch
    2358             :     boundary resolved.
    2359             : 
    2360             :     https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521
    2361             :     https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L1038-L1095 */
    2362             : static int
    2363             : recalculate_partitioned_rewards( fd_bank_t *          bank,
    2364             :                                  fd_accdb_t *         accdb,
    2365             :                                  fd_runtime_stack_t * runtime_stack,
    2366             :                                  fd_capture_ctx_t *   capture_ctx,
    2367             :                                  int                  snapshot_boot,
    2368         615 :                                  uint                 win_lo ) {
    2369             : 
    2370         615 :   fd_sysvar_epoch_rewards_t epoch_rewards_sysvar[1];
    2371         615 :   if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read(
    2372         615 :           accdb, bank->accdb_fork_id, epoch_rewards_sysvar ) ) ) {
    2373          46 :     FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
    2374          46 :     return 0;
    2375          46 :   }
    2376             : 
    2377         569 :   FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
    2378             : 
    2379         569 :   if( FD_UNLIKELY( !epoch_rewards_sysvar->active ) ) {
    2380         527 :     FD_LOG_DEBUG(( "epoch rewards is inactive" ));
    2381         527 :     return 0;
    2382         527 :   }
    2383             : 
    2384          42 :   ulong partition_cnt = epoch_rewards_sysvar->num_partitions;
    2385          42 :   ulong starting_block_height =
    2386          42 :       epoch_rewards_sysvar->distribution_starting_block_height;
    2387          42 :   if( FD_UNLIKELY( !partition_cnt ||
    2388          42 :                    partition_cnt>MAX_PARTITIONS_PER_EPOCH ||
    2389          42 :                    starting_block_height>ULONG_MAX-partition_cnt ||
    2390          42 :                    (ulong)win_lo>=partition_cnt ) ) {
    2391           0 :     FD_LOG_WARNING(( "invalid epoch rewards partition window" ));
    2392           0 :     return 0;
    2393           0 :   }
    2394             : 
    2395          42 :   fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
    2396          42 :   fd_vote_rewards_map_reset( vote_ele_map );
    2397             : 
    2398          42 :   fd_epoch_credits_view_t epoch_credits_view[1];
    2399          42 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
    2400          42 :   ulong                epoch_credits_len = epoch_credits_view->len;
    2401          42 :   fd_epoch_credits_t * epoch_credits_arr = epoch_credits_view->credits;
    2402             : 
    2403          42 :   if( FD_LIKELY( !snapshot_boot ) ) {
    2404             : 
    2405             :     /* The rewards of this epoch were computed once already, at the epoch
    2406             :        boundary or at snapshot load, and only the window is moving.  The
    2407             :        commissions resolved back then are stored with the epoch credits
    2408             :        and must be reused: they come from a vote account snapshot two
    2409             :        epochs behind the rewarded epoch, which the top votes sets no
    2410             :        longer retain. */
    2411             : 
    2412           0 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    2413           0 :       fd_epoch_credits_t const * epoch_credits = &epoch_credits_arr[i];
    2414             : 
    2415           0 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
    2416           0 :       vote_ele->pubkey       = *(fd_pubkey_t const *)epoch_credits->pubkey;
    2417           0 :       vote_ele->vote_rewards = 0UL;
    2418           0 :       vote_ele->commission   = epoch_credits->commission;
    2419           0 :       fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
    2420           0 :     }
    2421             : 
    2422          42 :   } else {
    2423             : 
    2424             :     /* If the snapshot was loaded while partitioned epoch rewards is
    2425             :        active, then the vote rewards map must be populated with the state
    2426             :        of the vote accounts as of the end of the previous epoch boundary.
    2427             :        The epoch credits for these accounts are stored in the bank, and
    2428             :        the vote stakes retain the t-3 commission.  With this, it's
    2429             :        possible to recalculate the rewards for the previous epoch
    2430             :        boundary.  We need the commission from the end of the t-3 epoch
    2431             :        if we are calculating rewards for the transition from epoch t-1
    2432             :        to t since there needs to be a 2 epoch commission gap for the
    2433             :        delay_commission_updates feature. */
    2434             : 
    2435          42 :     fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
    2436          42 :     ulong              fork_id     = bank->vote_stakes_fork_id;
    2437             : 
    2438         181 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    2439         139 :       fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[i];
    2440         139 :       fd_pubkey_t const *  pubkey        = (fd_pubkey_t const *)epoch_credits->pubkey;
    2441             : 
    2442             :       /* Get the t-1 stake account information.  This is guaranteed to be
    2443             :          valid since the epoch credits are populated from the t-1 stakes
    2444             :          in the snapshot manifest. */
    2445         139 :       fd_vote_stakes_ele_t ele_t_1[1];
    2446         139 :       FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, fork_id, pubkey, ele_t_1 ) );
    2447             : 
    2448             :       /* Get the t-3 information before the t-2 fallback.  Either may be
    2449             :          absent if the vote account was created in a later epoch. */
    2450         139 :       fd_vote_stakes_ele_t ele_t_3[1];
    2451         139 :       int exists_t_3 = fd_vote_stakes_query_t_3( vote_stakes, fork_id, pubkey, ele_t_3 );
    2452             : 
    2453             :       /* Now get the t-2 information (if it exists).  This is not
    2454             :          guaranteed to be valid since it's possible for a vote account to
    2455             :          have been created in the last epoch. */
    2456         139 :       fd_vote_stakes_ele_t ele_t_2[1];
    2457         139 :       int exists_t_2 = fd_vote_stakes_query_t_2( vote_stakes, fork_id, pubkey, ele_t_2 );
    2458             : 
    2459         139 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
    2460         139 :       vote_ele->pubkey       = *(fd_pubkey_t *)epoch_credits->pubkey;
    2461         139 :       vote_ele->vote_rewards = 0UL;
    2462         139 :       if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
    2463         139 :         vote_ele->commission = exists_t_3 ? ele_t_3->commission : (exists_t_2 ? ele_t_2->commission : ele_t_1->commission);
    2464         139 :       } else {
    2465           0 :         vote_ele->commission = ele_t_1->commission;
    2466           0 :       }
    2467         139 :       fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
    2468         139 :     }
    2469             : 
    2470             :     /* Publish the resolved commissions so that any later repositioning
    2471             :        of this fork's window reuses them. */
    2472         181 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    2473         139 :       epoch_credits_arr[i].commission = runtime_stack->stakes.vote_ele[i].commission;
    2474         139 :     }
    2475          42 :   }
    2476          42 :   fd_epoch_credits_view_fini( epoch_credits_view );
    2477             : 
    2478             :   /* If partitioned rewards are active, the rewarded epoch is always the immediately
    2479             :       preceding epoch.
    2480             : 
    2481             :       https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
    2482          42 :   FD_LOG_DEBUG(( "epoch rewards is active" ));
    2483             : 
    2484          42 :   ulong const epoch          = bank->f.epoch;
    2485          42 :   ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
    2486             : 
    2487          42 :   int const is_alpenglow = rewarded_epoch_is_alpenglow( bank, accdb, rewarded_epoch );
    2488          42 :   if( FD_UNLIKELY( is_alpenglow ) ) {
    2489           0 :     fd_reward_epoch_stakes_restore( bank, accdb, rewarded_epoch, runtime_stack );
    2490           0 :   }
    2491             : 
    2492          42 :   int const is_migration = rewarded_epoch_is_alpenglow_migration( bank, accdb, rewarded_epoch );
    2493          42 :   int const skip_rewards = ( !is_alpenglow || is_migration ) && !epoch_rewards_sysvar->total_points.ud;
    2494          42 :   if( FD_UNLIKELY( skip_rewards ) ) {
    2495           0 :     runtime_stack->stakes.stake_rewards_cnt = 0UL;
    2496           0 :   }
    2497             : 
    2498          42 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    2499          42 :   fd_stake_history_t stake_history[1];
    2500          42 :   read_stake_history( accdb, bank->accdb_fork_id, stake_history_data, stake_history );
    2501             : 
    2502          42 :   fd_stake_history_t   frontier_stake_history_[1];
    2503          42 :   fd_stake_history_t * frontier_stake_history = fd_sysvar_cache_stake_history_view( &bank->f.sysvar_cache, frontier_stake_history_ );
    2504             : 
    2505          42 :   fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
    2506          42 :   fd_stake_delegations_view_begin( stake_delegations,
    2507          42 :                                    bank->f.epoch,
    2508          42 :                                    frontier_stake_history,
    2509          42 :                                    &bank->f.warmup_cooldown_rate_epoch,
    2510          42 :                                    FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
    2511          42 :                                    bank->stake_delegations_fork_id );
    2512             : 
    2513          42 :   if( FD_LIKELY( !skip_rewards ) ) {
    2514          42 :     calculate_stake_vote_rewards(
    2515          42 :         bank,
    2516          42 :         accdb,
    2517          42 :         stake_delegations,
    2518          42 :         capture_ctx,
    2519          42 :         stake_history,
    2520          42 :         rewarded_epoch,
    2521          42 :         epoch_rewards_sysvar->total_rewards,
    2522          42 :         epoch_rewards_sysvar->total_points.ud,
    2523          42 :         runtime_stack,
    2524          42 :         1 );
    2525          42 :   }
    2526             : 
    2527             :   /* The recomputed rewards go into a fork of their own rather than over
    2528             :      the window the bank is holding: banks that branched off this one
    2529             :      share that window and are entitled to the entries they already have,
    2530             :      which were computed from their own delegations. */
    2531             : 
    2532          42 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    2533          42 :   ushort               fork_idx      = fd_stake_rewards_init( stake_rewards,
    2534          42 :                                                               &epoch_rewards_sysvar->parent_blockhash,
    2535          42 :                                                               starting_block_height,
    2536          42 :                                                               (uint)partition_cnt,
    2537          42 :                                                               win_lo,
    2538          42 :                                                               runtime_stack->stakes.stake_rewards_cnt );
    2539          42 :   if( FD_LIKELY( bank->stake_rewards_fork_id!=USHORT_MAX ) ) {
    2540           0 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    2541           0 :   }
    2542          42 :   bank->stake_rewards_fork_id = fork_idx;
    2543             : 
    2544          42 :   if( FD_LIKELY( !skip_rewards ) ) {
    2545          42 :     setup_stake_partitions(
    2546          42 :         bank,
    2547          42 :         accdb,
    2548          42 :         stake_history,
    2549          42 :         stake_delegations,
    2550          42 :         runtime_stack,
    2551          42 :         fork_idx,
    2552          42 :         rewarded_epoch,
    2553          42 :         epoch_rewards_sysvar->total_rewards,
    2554          42 :         epoch_rewards_sysvar->total_points.ud );
    2555          42 :   } else {
    2556           0 :     fd_stake_rewards_fini( stake_rewards, fork_idx );
    2557           0 :   }
    2558             : 
    2559          42 :   fd_stake_delegations_view_end( stake_delegations,
    2560          42 :                                  frontier_stake_history,
    2561          42 :                                  &bank->f.warmup_cooldown_rate_epoch,
    2562          42 :                                  FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) );
    2563          42 :   return 1;
    2564          42 : }
    2565             : 
    2566             : void
    2567             : fd_rewards_recalculate_partitioned_rewards( fd_bank_t *          bank,
    2568             :                                             fd_accdb_t *         accdb,
    2569             :                                             fd_runtime_stack_t * runtime_stack,
    2570         615 :                                             fd_capture_ctx_t *   capture_ctx ) {
    2571         615 :   recalculate_partitioned_rewards( bank, accdb, runtime_stack, capture_ctx, 1, 0U );
    2572         615 : }

Generated by: LCOV version 1.14