LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 710 972 73.0 %
Date: 2026-08-21 04:38:30 Functions: 36 65 55.4 %

          Line data    Source code
       1             : #include "fd_rewards.h"
       2             : #include "fd_stake_rewards.h"
       3             : 
       4             : #include "../runtime/sysvar/fd_sysvar_epoch_rewards.h"
       5             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       6             : #include "../runtime/sysvar/fd_sysvar_rent.h"
       7             : #include "../runtime/fd_hashes.h"
       8             : #include "../../ballet/lthash/fd_lthash_adder.h"
       9             : #include "../stakes/fd_stakes.h"
      10             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
      11             : #include "../runtime/fd_system_ids.h"
      12             : #include "../capture/fd_capture_ctx.h"
      13             : #include "../runtime/fd_runtime_stack.h"
      14             : #include "../runtime/fd_accdb_svm.h"
      15             : #include "fd_rewards_base.h"
      16             : 
      17             : #include <math.h>
      18             : 
      19             : /* A note on the calculation of points for inflation rewards at the
      20             :    epoch boundary.
      21             : 
      22             :    As of today there are more than 1.5 million stake delegations on
      23             :    mainnet (1,583,562 at the 987->988 boundary).  Each and every one of
      24             :    them could in theory earn some lamports of inflation rewards, so at
      25             :    the boundary each and every delegation is looked at to compute its
      26             :    share.  A delegation's share is directly proportional to its
      27             :    "points", and the points are essentially the area under a curve where
      28             :    the y axis is the delegation's effective stake and the x axis is the
      29             :    monotonically increasing vote credits.
      30             : 
      31             :      points = SUM over eligible epochs e of ( effective(e) * credits_owed(e) )
      32             : 
      33             :    There are two dimensions to this summation formula.  (1) A vote
      34             :    account monotonically ticks up its vote credit as it votes, and
      35             :    records its per-epoch credit history as (epoch,final,initial) tuples,
      36             :    where the [initial,final] ranges are contiguous and non-overlapping.
      37             :    That is to say, initial[n]=final[n-1].  A stake delegation stores a
      38             :    watermark, credits_observed, of how far it has already been paid
      39             :    along the corresponding vote account's credit history.  What the
      40             :    stake delegation is owed for an epoch is the part of that epoch's
      41             :    [initial,final] that sits above the credits_observed.  The credit
      42             :    history is capped at 64 epochs, so the sum is never more than 64
      43             :    terms long.  The epochs that overlap [credits_observed,final[last]]
      44             :    are the ones that contribute to the sum.  This is the x axis.  (2)
      45             :    Delegated stake ramps up to the full delegated amount over one or
      46             :    more epochs when it's activating, and ramps down gradually to 0 when
      47             :    it's deactivating.  So the effective stake at an epoch, aka
      48             :    effective(e), is not always simply the delegated amount.  Computing
      49             :    effective(e) involves running a simulation of the ramp, encoded in
      50             :    the activating_and_deactivating() function.  This is the y axis.
      51             : 
      52             :      stake |        ______________P________________
      53             :            |       /                               \
      54             :            | L   U/  warmup               cooldown  \D     R
      55             :          0 |_____/                                   \____________
      56             :            +-----+---------------------------------+--------------->  vote credits
      57             :             activation         activated          deactivation    cooled down
      58             :             (ramps up)   (effective=delegated)    (ramps down)    (effective=0)
      59             : 
      60             :    So under a reference implementation, the worst case is 64 effective()
      61             :    computations per delegation.  The delegations that tend to hit this
      62             :    case are dust delegations, either activated or deactivated, whose
      63             :    tiny or zero points share keeps rounding their rewards down to zero.
      64             :    As a result, reward doesn't pay out to a dust delegation, so it never
      65             :    advances its credits_observed, and over time it gets pegged at the
      66             :    worst case 64-term sum.  Roughly 14% of delegations are multi-term
      67             :    evaluations like this, and they account for ~89% (11.3M of 12.6M) of
      68             :    all the effective() invocations the points pass does.  A minority of
      69             :    delegations demanding the overwhelming majority of work, and they
      70             :    barely get any rewards, if at all.
      71             : 
      72             :    The good news is that the shape of the warmup/cooldown curve isn't
      73             :    arbitrary.  Observe that within a given boundary, a delegation's
      74             :    (activation_epoch,deactivation_epoch,stake) are fixed, and on this
      75             :    frozen tuple the effective stake curve is a single hump aka at most
      76             :    one peak.  The plot above shows the fullest warmup/cooldown curve
      77             :    within a boundary.  It rises during warmup (U) from 0 (L,
      78             :    epoch<=activation), sits on a flat plateau (P) at exactly the height
      79             :    of delegated stake, optionally ramps back down (D) after
      80             :    deactivation, and rests on a flat zero floor (R).  It never goes down
      81             :    and then back up within a given boundary.  In practice, the summation
      82             :    usually runs over just a subsection of this full curve.  Depending on
      83             :    which of the five zones {L,U,P,D,R} the first and the last
      84             :    contributing terms (the "o"s below) sit on the warmup/cooldown curve,
      85             :    there can be up to 15 unique possible subsection spans.  As of the
      86             :    987->988 boundary, the following three cases cover almost the entire
      87             :    points pass of a reference implementation.  The other spans are
      88             :    either rare or already cheap to evaluate.  We exploit the shape of
      89             :    each case to short circuit step-by-step summation.
      90             : 
      91             :    Case 1: Every contributing term sits on the zero floor.  The
      92             :    delegation deactivated at or before the epoch its watermark froze.
      93             :    This is the common fate of deactivated and abandoned dust stake whose
      94             :    reward payout stopped at deactivation.  This accounts for 79.8% of
      95             :    the effective() invocations and 88.4% of the effective() iterations
      96             :    in the points pass.  This is the R->R span.
      97             : 
      98             :            stake |
      99             :                  |
     100             :                0 | o--o--o--o--o
     101             :                  '--------------->  vote credits
     102             :                    ^ every contributing term sits past full deactivation
     103             : 
     104             :    Case 2: The contributing terms straddle the hump.  A few terms ride
     105             :    the hump, and the rest sit on the zero floor.  This accounts for just
     106             :    0.05% of the effective() invocations and 0.04% of the effective()
     107             :    iterations in the points pass.  This is a tiny population (230
     108             :    delegations) but catching the 0-tail of this case is a free side
     109             :    effect of trying to short circuit Case 1.  This is the {L,U,P,D}->R
     110             :    spans.
     111             : 
     112             :            stake | o--o--.
     113             :                  |        \
     114             :                0 |         o--o--o
     115             :                  '----------------->  vote credits
     116             :                            ^ first full deactivation term
     117             : 
     118             :    Case 3: Every contributing term sits on the plateau.  The delegation
     119             :    is fully activated and, almost always, never deactivated.  This is
     120             :    the fate of activated dust whose reward keeps rounding down to zero
     121             :    while its vote account keeps voting.  This accounts for 19.8% of the
     122             :    effective() invocations and 11.6% of the effective() iterations in
     123             :    the points pass.  This is the P->P span.  As a side note, P->P also
     124             :    includes a small sliver, ~14.8K delegations here, that deactivated no
     125             :    earlier than the epoch of its last contributing term.  The stake is
     126             :    still fully effective at the deactivation epoch itself.  The fast
     127             :    path below doesn't cover these.
     128             : 
     129             :            stake | o--o--o--o--o
     130             :                  |
     131             :                0 |________________
     132             :                  '--------------->  vote credits
     133             : 
     134             :    We try to short circuit multi-term Cases 1 and 3, as well as the
     135             :    multi-term tail floor of Case 2.  The short circuit conditions do not
     136             :    have to be fully precise, they just need to be conservative but not
     137             :    overly conservative and ideally cheap so as to net a performance win
     138             :    for most of the case population.
     139             : 
     140             :    Further observe that the vast majority (84%, 1.33M of 1.58M) of stake
     141             :    delegations have an up-to-date (>=initial[last]) credits watermark
     142             :    and they are almost all either single-term Case 1 or single-term Case
     143             :    3.  We fast path 1.27M of these with delegation state tags.  The
     144             :    small delta is almost entirely fresh delegations in the just-ended
     145             :    epoch.
     146             : 
     147             :    Note that VAT doesn't make the problem of dust points go away.  If we
     148             :    were to apply VAT on the 987->988 boundary, 96.3% of effective() and
     149             :    98.5% of iterations in effective() would still survive.
     150             :    Unfortunately, there's just a lot of abandoned dust stake pointing at
     151             :    validators that are still live and voting.
     152             : 
     153             :    ===
     154             : 
     155             :    For the data minded, the full census of the possible spans at the
     156             :    reference points pass of the 987->988 boundary:
     157             : 
     158             :      span   delegations    invocations            iterations
     159             :      R->R       182,920     10,067,849 (79.8%)    14,585,005 (88.4%)  Case 1
     160             :      P->R           225          5,745 (0.05%)         6,902 (0.04%)  Case 2
     161             :      L->R             5            164                     0          Case 2
     162             :      P->P     1,327,803      2,500,408 (19.8%)     1,905,636 (11.6%)  Case 3
     163             :      L->L        49,087         49,087 (0.4%)              0
     164             :      L->P             3             42                    11
     165             :      other            0              0                     0
     166             :      none        23,519              0                     0
     167             :      total    1,583,562     12,623,295            16,497,554
     168             : 
     169             :    The L->L span is fresh delegations from the just-ended epoch.  Their
     170             :    contributing term sits at or before the activation epoch, so every
     171             :    effective() invocation early exits from the all-activating branch
     172             :    without entering the simulation loop, hence zero iterations.  "other"
     173             :    is the nine spans that include either the U or D ramp zones.  They
     174             :    are all empty, because under today's mainnet warmup/cooldown budget
     175             :    both ramps complete in a single epoch step, so no term ever observes
     176             :    a partially warmed or partially cooled stake.  "none" is delegations
     177             :    with no contributing terms, i.e. their watermark already caught up to
     178             :    the vote credits.
     179             : 
     180             :    And broken down by how much each fast/slow path covers:
     181             : 
     182             :      span  fast/slow path                delegations  invocations   iterations
     183             :      P->P  single-term state tag           1,268,371 /  1,268,371 /  1,094,607
     184             :            single-term effective()            12,745 /     12,745 /     12,265
     185             :            multi-term is_warmed_plateau()     44,683 /  1,179,761 /    777,412
     186             :            slow path                           2,004 /     39,531 /     21,352
     187             :      R->R  early exit at 0-tail              181,263 / 10,066,192 / 14,581,965
     188             :            single-term state tag                  22 /         22 /         22
     189             :            single-term effective()             1,635 /      1,635 /      3,018
     190             :      L->L  single-term effective()            49,006 /     49,006 /          0
     191             :            single-term state tag                  81 /         81 /          0
     192             :      P->R  early exit at 0-tail                  225 /      5,745 /      6,902
     193             :      L->R  early exit at 0-tail                    5 /        164 /          0
     194             :      L->P  slow path                               3 /         42 /         11
     195             : 
     196             :    The single-term effective() branch gets taken on state tag misses,
     197             :    including fresh L->L delegations whose tag is still WARMING/UNKNOWN,
     198             :    P->P stakes that deactivated during the rewarded epoch (tag COOLING,
     199             :    though every term is still fully effective) or whose delinquent
     200             :    vote's last credit entry predates the rewarded epoch, and a few stale
     201             :    R->R.  The 81 L->L state tag hits are accounts that delegated and
     202             :    deactivated in the same epoch because activation==deactivation
     203             :    classifies as COOLED with effective 0.  The P->P slow path is taken
     204             :    by multi-term stakes that deactivated no earlier than their last
     205             :    contributing term, so they get rejected by is_warmed_plateau() and
     206             :    the 0-tail exit doesn't happen either. */
     207             : 
     208             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
     209             : static double
     210         849 : total( fd_inflation_t const * inflation, double year ) {
     211         849 :   double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
     212         849 :   return (tapered > inflation->terminal) ? tapered : inflation->terminal;
     213         849 : }
     214             : 
     215             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
     216             : static double
     217         831 : foundation( fd_inflation_t const * inflation, double year ) {
     218         831 :   return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
     219         831 : }
     220             : 
     221             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
     222             : static double
     223         282 : validator( fd_inflation_t const * inflation, double year) {
     224             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
     225         282 :   FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
     226             :   /* We need 2 independent rounded ops like in Agave. Volatile blocks FMA contraction.
     227             :      https://github.com/anza-xyz/solana-sdk/blob/inflation%40v3.1.1/inflation/src/lib.rs#L105-L116 */
     228         282 :   volatile double foundation_portion = foundation( inflation, year );
     229         282 :   return total( inflation, year ) - foundation_portion;
     230         282 : }
     231             : 
     232             : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
     233             :     activation slot of the following features:
     234             :     - devnet_and_testnet
     235             :     - full_inflation_enable, if full_inflation_vote has been activated
     236             : 
     237             :     https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
     238             : static FD_FN_CONST ulong
     239         528 : get_inflation_start_slot( fd_bank_t const * bank ) {
     240         528 :   ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
     241         528 :       ? bank->f.features.devnet_and_testnet
     242         528 :       : ULONG_MAX;
     243             : 
     244         528 :   ulong enable = bank->f.features.full_inflation_enable;
     245             : 
     246         528 :   ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
     247         528 :   if( min_slot == ULONG_MAX ) {
     248         528 :     if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
     249          12 :       min_slot = bank->f.features.pico_inflation;
     250         516 :     } else {
     251         516 :       min_slot = 0;
     252         516 :     }
     253         528 :   }
     254         528 :   return min_slot;
     255         528 : }
     256             : 
     257             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2921-L2928 */
     258             : static ulong
     259             : inflation_start_slot_aligned_to_rewards( fd_bank_t const *           bank,
     260         528 :                                          fd_epoch_schedule_t const * epoch_schedule ) {
     261         528 :   ulong inflation_activation_slot = get_inflation_start_slot( bank );
     262         528 :   return fd_epoch_slot0( epoch_schedule,
     263         528 :                          fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule, inflation_activation_slot, NULL ), 1UL ) );
     264         528 : }
     265             : 
     266             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2915-L2918 */
     267             : static ulong
     268             : get_inflation_num_slots( fd_bank_t const *           bank,
     269             :                          fd_epoch_schedule_t const * epoch_schedule,
     270         264 :                          ulong                       slot ) {
     271         264 :   ulong inflation_start_slot = inflation_start_slot_aligned_to_rewards( bank, epoch_schedule );
     272         264 :   return fd_epoch_slot0( epoch_schedule, fd_slot_to_epoch( epoch_schedule, slot, NULL ) ) - inflation_start_slot;
     273         264 : }
     274             : 
     275             : /* https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L2931-L2935 */
     276             : static double
     277         264 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
     278         264 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
     279         264 :   ulong num_slots            = get_inflation_num_slots( bank, epoch_schedule, bank->f.slot );
     280         264 :   ulong inflation_start_slot = inflation_start_slot_aligned_to_rewards( bank, epoch_schedule );
     281         264 :   return fd_slot_params_slot_range_duration_years( bank,
     282         264 :                                                    inflation_start_slot, inflation_start_slot + num_slots );
     283         264 : }
     284             : 
     285             : /* Returns 1 if effective stake is clearly a warmed plateau.
     286             : 
     287             :    This function seeks to conservatively prove that the given stake has
     288             :    effective=delegated throughout its entire multi-term points
     289             :    calculation.  Concretely, this boils down to the following.
     290             : 
     291             :    - The stake is not slated for deactivation.  This removes the
     292             :      downramp.
     293             :    - The first contributing epoch is > the activation epoch.
     294             :    - The stake easily activated at activation epoch+1.  This removes the
     295             :      upramp.
     296             : 
     297             :    These conditions constrain the warmup/cooldown curve to a flat
     298             :    plateau at effective=delegated.  What makes this conservative is that
     299             :    there could be false negatives only: this function says that a stake
     300             :    is not a multi-term plateau when in fact it is.  We do a single-step
     301             :    warmup simulation in this function, and the stake is rejected if it
     302             :    failed to easily warm up in a single epoch one past the activation
     303             :    epoch.  So a stake that took >=2 epochs to warm up may well be fully
     304             :    warmed up by the time of its first contributing epoch.  We make this
     305             :    tradeoff because this function is meant to be a fast detector and in
     306             :    practice most stake activate quickly under today's mainnet warmup
     307             :    budget.  Another case of false negative rejections is for multi-term
     308             :    stakes that deactivated no earlier than the last contributing epoch.
     309             :    We could easily eliminate this class of false negatives by passing in
     310             :    the last contributing epoch and comparing against deactivation epoch.
     311             :    We make this tradeoff because this class is empirically small and
     312             :    this function becomes that much easier to reason about by virtue of
     313             :    cleanly eliminating the deactivation simulation. */
     314             : static inline int
     315             : is_warmed_plateau( fd_stake_delegation_t const * stake,
     316             :                    ulong                         first_contributing_epoch,
     317             :                    fd_stake_history_t const *    stake_history,
     318             :                    ulong *                       new_rate_activation_epoch,
     319           0 :                    int                           use_fixed_point_stake_math ) {
     320             :   /* Slated for deactivation. */
     321           0 :   if( stake->deactivation_epoch!=USHORT_MAX ) return 0;
     322             : 
     323             :   /* is_bootstrap(): https://github.com/solana-program/stake/blob/interface%40v4.3.1/interface/src/state.rs#L892
     324             :      Stake activated as per protocol. */
     325           0 :   if( stake->activation_epoch==USHORT_MAX ) {
     326           0 :     return 1;
     327           0 :   }
     328             : 
     329           0 :   ulong ae = stake->activation_epoch;
     330           0 :   if( ae>=first_contributing_epoch ) return 0;
     331             : 
     332             :   /* Dropped out of history: https://github.com/solana-program/stake/blob/interface%40v4.3.1/interface/src/state.rs#L969
     333             :      Stake activated as per protocol. */
     334           0 :   fd_stake_history_entry_t const * e = fd_sysvar_stake_history_query( stake_history, ae );
     335           0 :   if( FD_UNLIKELY( !e ) ) {
     336           0 :     return 1;
     337           0 :   }
     338             : 
     339             :   /* Note that e may not actually be epoch ae's entry on a
     340             :      non-contiguous window.  That is fine here because the reference
     341             :      simulation's first step reads the exact same entry via the exact
     342             :      same query and the exact same allowance function below, and
     343             :      acceptance means the simulation completes warmup on that first
     344             :      step, before reading any other entry.  Similarly, the NULL return
     345             :      from the query above may also be spurious, and that's fine because
     346             :      Agave also just assumes fully effective. */
     347             : 
     348             :   /* Agave claims this is a "should have been fully effective" branch.
     349             :      Practically this branch probably won't be taken and so we will
     350             :      conservatively reject the fast path in this branch. */
     351           0 :   if( FD_UNLIKELY( e->activating==0UL ) ) return 0;
     352             : 
     353             :   /* Run a single-step simulation and see if stake easily activated. */
     354           0 :   ulong newly_effective;
     355           0 :   if( use_fixed_point_stake_math ) {
     356           0 :     newly_effective = fd_ulong_max( fd_stake_calculate_activation_allowance( ae+1UL, stake->stake, e, new_rate_activation_epoch ), 1UL );
     357           0 :   } else {
     358           0 : #if FD_HAS_DOUBLE
     359           0 :     newly_effective = fd_ulong_max( fd_stake_calculate_change_allowance_float( ae+1UL, stake->stake, e->activating, e->effective, new_rate_activation_epoch ), 1UL );
     360             : #else
     361             :     return 0;
     362             : #endif
     363           0 :   }
     364           0 :   if( newly_effective>=stake->stake ) {
     365           0 :     return 1;
     366           0 :   }
     367           0 :   return 0;
     368           0 : }
     369             : 
     370             : /* Inverted activation and deactivation epochs shouldn't really be
     371             :    possible on delegations created by the stake program.  To be safe, we
     372             :    will fall back to the slow path if any of these are detected. */
     373             : static inline int
     374         327 : stake_epochs_are_normal( fd_stake_delegation_t const * stake ) {
     375         327 :   return stake->activation_epoch==USHORT_MAX   ||
     376         327 :          stake->deactivation_epoch==USHORT_MAX ||
     377         327 :          stake->activation_epoch<=stake->deactivation_epoch;
     378         327 : }
     379             : 
     380             : /* For a given stake and epoch credit history, calculate how many
     381             :    points, aka (credits * stake) were earned and the new value for
     382             :    credits_observed if the points were to materialize to non-zero
     383             :    inflation rewards.
     384             : 
     385             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
     386             : static void
     387             : calculate_stake_points_and_credits( fd_epoch_credits_t const *     epoch_credits,
     388             :                                     fd_stake_history_t const *     stake_history,
     389             :                                     fd_stake_delegation_t const *  stake,
     390             :                                     ulong *                        new_rate_activation_epoch,
     391             :                                     int                            use_fixed_point_stake_math,
     392         228 :                                     fd_calculated_stake_points_t * result ) {
     393             : 
     394         228 :   ulong credits_in_stake = stake->credits_observed;
     395         228 :   ulong credits_cnt      = epoch_credits->cnt;
     396         228 :   ulong base             = epoch_credits->base_credits;
     397         228 :   ulong credits_in_vote  = credits_cnt > 0UL ? base + epoch_credits->credits_delta[ credits_cnt - 1UL ] : 0UL;
     398             : 
     399             : 
     400             :   /* If the Vote account has less credits observed than the Stake account,
     401             :       something is wrong and we need to force an update.
     402             : 
     403             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
     404         228 :   if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
     405           3 :     result->points.ud = 0;
     406           3 :     result->new_credits_observed = credits_in_vote;
     407           3 :     result->force_credits_update_with_skipped_reward = 1;
     408           3 :     return;
     409           3 :   }
     410             : 
     411             :   /* If the Vote account has the same amount of credits observed as the Stake account,
     412             :       then the Vote account hasn't earned any credits and so there is nothing to update.
     413             : 
     414             :       https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
     415         225 :   if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
     416         225 :     result->points.ud = 0;
     417         225 :     result->new_credits_observed = credits_in_vote;
     418         225 :     result->force_credits_update_with_skipped_reward = 0;
     419         225 :     return;
     420         225 :   }
     421             : 
     422           0 :   int coalesce_eligible = stake_epochs_are_normal( stake ) && epoch_credits->fast_path_ok;
     423             : 
     424             :   /* Calculate the points for each epoch credit */
     425           0 :   uint128 points               = 0;
     426           0 :   ulong   new_credits_observed = credits_in_stake;
     427           0 :   for( ulong i=0UL; i<epoch_credits->cnt; i++ ) {
     428             : 
     429           0 :     ulong final_epoch_credits   = base + epoch_credits->credits_delta[ i ];
     430           0 :     ulong initial_epoch_credits = base + epoch_credits->prev_credits_delta[ i ];
     431             : 
     432             :     /* All production inputs should satisfy
     433             :        initial_epoch_credits <= final_epoch_credits
     434             : 
     435             :        If final_epoch_credits <= credits_in_stake, then:
     436             :         initial_epoch_credits <= final_epoch_credits <= credits_in_stake
     437             : 
     438             :        * earned_credits = 0 since both conditions are false.
     439             :        * new_credits_observed stays the same since it is already set
     440             :          to credits_in_stake and final_epoch_credits <= credits_in_stake
     441             : 
     442             :        Since earned_credits = 0 and new_credits_observed stays the same,
     443             :        points computation can be skipped. */
     444           0 :     if( FD_LIKELY( epoch_credits->fast_path_ok && final_epoch_credits<=credits_in_stake ) ) continue;
     445             : 
     446           0 :     uint128 earned_credits = 0;
     447           0 :     if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
     448           0 :       earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
     449           0 :     } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
     450           0 :       earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
     451           0 :     }
     452             : 
     453           0 :     new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
     454             : 
     455           0 :     ulong stake_amount = fd_stakes_activating_and_deactivating( stake, epoch_credits->epoch[ i ], stake_history, new_rate_activation_epoch, use_fixed_point_stake_math ).effective;
     456           0 :     if( coalesce_eligible && stake_amount==0UL && epoch_credits->epoch[ i ]>stake->deactivation_epoch ) {
     457             :       /* Multi-term Cases 1 and 2.  Note that
     458             :          deactivation_epoch!=USHORT_MAX is implied since epoch[ i ] is
     459             :          also a ushort. */
     460           0 :       new_credits_observed = credits_in_vote;
     461           0 :       break;
     462           0 :     }
     463             : 
     464           0 :     points += (uint128)stake_amount * earned_credits;
     465           0 :   }
     466             : 
     467           0 :   result->points.ud = points;
     468           0 :   result->new_credits_observed = new_credits_observed;
     469           0 :   result->force_credits_update_with_skipped_reward = 0;
     470           0 : }
     471             : 
     472             : /* Returns commission split as
     473             :    (voter_portion, staker_portion, was_split) tuple.  If commission
     474             :    calculation is 10000 (100%) one way or other, indicate with false for
     475             :    was_split.
     476             : 
     477             :    https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L237-L272 */
     478             : void
     479             : fd_vote_commission_split( ushort                  commission,
     480             :                           ulong                   on,
     481         129 :                           fd_commission_split_t * result ) {
     482             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L244-L245 */
     483         324 :   #define MAX_BPS (10000)
     484             : 
     485             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L246-L271 */
     486         129 :   ushort commission_split = fd_ushort_min( commission, MAX_BPS );
     487         129 :   switch( commission_split ) {
     488          24 :     case 0: {
     489             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L246 */
     490          24 :       result->voter_portion  = 0UL;
     491          24 :       result->staker_portion = on;
     492          24 :       result->is_split       = 0;
     493          24 :       break;
     494           0 :     }
     495          60 :     case MAX_BPS: {
     496             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L247 */
     497          60 :       result->voter_portion  = on;
     498          60 :       result->staker_portion = 0UL;
     499          60 :       result->is_split       = 0;
     500          60 :       break;
     501           0 :     }
     502          45 :     default: {
     503             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/inflation_rewards/mod.rs#L256-L259 */
     504          45 :       result->voter_portion  = (ulong)((uint128)on * (uint128)commission_split / (uint128)MAX_BPS);
     505          45 :       result->staker_portion = (ulong)((uint128)on * (uint128)(MAX_BPS-commission_split) / (uint128)MAX_BPS);
     506          45 :       result->is_split       = 1;
     507          45 :       break;
     508           0 :     }
     509         129 :   }
     510             : 
     511         129 :   #undef MAX_BPS
     512         129 : }
     513             : 
     514             : /* Validates an external SIMD-0232 inflation commission collector
     515             :    after all rewards routed to it have been added.  Returns 0 to
     516             :    deposit, 1 to burn.  The vote account itself is always valid and
     517             :    must not be passed here.
     518             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/fee_distribution.rs#L231-L270 */
     519             : static int
     520             : fd_rewards_validate_commission_collector( fd_bank_t const *   bank,
     521             :                                           fd_pubkey_t const * collector,
     522             :                                           fd_acc_t const *    account,
     523          42 :                                           ulong               rewards ) {
     524             :   /* Must be a system program owned account. */
     525          42 :   if( FD_UNLIKELY( memcmp( account->owner, fd_solana_system_program_id.uc, sizeof(fd_pubkey_t) ) ) ) return 1;
     526             : 
     527             :   /* Must not be a reserved account. */
     528          33 :   if( FD_UNLIKELY( fd_pubkey_is_active_reserved_key( collector ) ||
     529          33 :                    fd_pubkey_is_pending_reserved_key( collector ) ) ) return 1;
     530             : 
     531          30 :   ulong post_balance;
     532          30 :   if( FD_UNLIKELY( __builtin_uaddl_overflow( account->lamports, rewards, &post_balance ) ) ) return 1;
     533             : 
     534             :   /* The incinerator is exempt from the rent check so the deposit
     535             :      always works.  Incinerator funds are burned at the end of the
     536             :      rewards distribution block. */
     537          27 :   if( FD_UNLIKELY( fd_pubkey_eq( collector, &fd_sysvar_incinerator_id ) ) ) return 0;
     538             : 
     539             :   /* Must be rent-exempt after the deposit.  With
     540             :      relax_post_exec_min_balance_check (SIMD-0392) a pre-existing
     541             :      account may stay rent-paying. */
     542          24 :   int is_rent_exempt = post_balance>=fd_rent_exempt_minimum_balance( &bank->f.rent, account->data_len );
     543          24 :   return !is_rent_exempt &&
     544          24 :          ( !FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) || !account->lamports );
     545          27 : }
     546             : 
     547             : /* Resolves the SIMD-0232 inflation commission collector from the vote
     548             :    account state at the start of the distribution epoch (tag
     549             :    bank->f.epoch); defaults to the vote account.
     550             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L667-L672 */
     551             : static void
     552             : fd_rewards_inflation_collector( fd_bank_t *         bank,
     553             :                                 fd_pubkey_t const * vote_pubkey,
     554          60 :                                 fd_pubkey_t *       collector_out ) {
     555          60 :   *collector_out = *vote_pubkey;
     556          60 :   fd_collector_overrides_query( fd_bank_collector_overrides( bank ),
     557          60 :                                 bank->collector_overrides_fork_id,
     558          60 :                                 bank->f.epoch,
     559          60 :                                 vote_pubkey,
     560          60 :                                 collector_out,
     561          60 :                                 NULL );
     562          60 : }
     563             : 
     564             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
     565             : static int
     566             : redeem_rewards( fd_stake_delegation_t const *   stake,
     567             :                 ulong                           vote_state_idx,
     568             :                 ulong                           rewarded_epoch,
     569             :                 ulong                           total_rewards,
     570             :                 uint128                         total_points,
     571             :                 fd_runtime_stack_t *            runtime_stack,
     572             :                 fd_calculated_stake_points_t *  stake_points_result,
     573         327 :                 fd_calculated_stake_rewards_t * result ) {
     574             : 
     575             :   /* The firedancer implementation of redeem_rewards inlines a lot of
     576             :      the helper functions that the Agave implementation uses.
     577             :      In Agave: redeem_rewards calls redeem_stake_rewards which calls
     578             :      calculate_stake_rewards. */
     579             : 
     580             :   // Drive credits_observed forward unconditionally when rewards are disabled
     581             :   // or when this is the stake's activation epoch
     582         327 :   if( total_rewards==0UL || stake->activation_epoch==rewarded_epoch ) {
     583         240 :     stake_points_result->force_credits_update_with_skipped_reward = 1;
     584         240 :   }
     585             : 
     586         327 :   if( stake_points_result->force_credits_update_with_skipped_reward ) {
     587         240 :     result->staker_rewards       = 0;
     588         240 :     result->voter_rewards        = 0;
     589         240 :     result->new_credits_observed = stake_points_result->new_credits_observed;
     590         240 :     return 0;
     591         240 :   }
     592          87 :   if( stake_points_result->points.ud==0 || total_points==0 ) {
     593           0 :     return 1;
     594           0 :   }
     595             : 
     596          87 :   uint128 rewards_u128;
     597          87 :   if( FD_UNLIKELY( __builtin_mul_overflow( stake_points_result->points.ud, (uint128)(total_rewards), &rewards_u128 ) ) ) {
     598           0 :     FD_LOG_ERR(( "Rewards intermediate calculation should fit within u128" ));
     599           0 :   }
     600             : 
     601          87 :   FD_TEST( total_points );
     602          87 :   rewards_u128 /=  (uint128) total_points;
     603             : 
     604          87 :   if( FD_UNLIKELY( rewards_u128>(uint128)ULONG_MAX ) ) {
     605           0 :     FD_LOG_ERR(( "Rewards should fit within u64" ));
     606           0 :   }
     607             : 
     608          87 :   ulong rewards = (ulong)rewards_u128;
     609          87 :   if( rewards == 0 ) {
     610           0 :     return 1;
     611           0 :   }
     612             : 
     613          87 :   fd_commission_split_t split_result;
     614          87 :   fd_vote_commission_split( runtime_stack->stakes.vote_ele[ vote_state_idx ].commission, rewards, &split_result );
     615          87 :   if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
     616           3 :     return 1;
     617           3 :   }
     618             : 
     619          84 :   result->staker_rewards       = split_result.staker_portion;
     620          84 :   result->voter_rewards        = split_result.voter_portion;
     621          84 :   result->new_credits_observed = stake_points_result->new_credits_observed;
     622          84 :   return 0;
     623          87 : }
     624             : 
     625             : /* Returns the length of the given epoch in slots
     626             : 
     627             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
     628             : static ulong
     629             : get_slots_in_epoch( ulong                       epoch,
     630         399 :                     fd_epoch_schedule_t const * epoch_schedule ) {
     631         399 :   return epoch < epoch_schedule->first_normal_epoch ?
     632           0 :          1UL << fd_ulong_sat_add( epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO ) :
     633         399 :          epoch_schedule->slots_per_epoch;
     634         399 : }
     635             : 
     636             : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
     637             : static double
     638             : epoch_duration_in_years( fd_bank_t const * bank,
     639         264 :                          ulong             prev_epoch ) {
     640         264 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
     641         264 :   ulong                       slots_in_epoch = get_slots_in_epoch( prev_epoch, epoch_schedule );
     642         264 :   double                      slots_per_year = fd_slot_params_at_slot( bank,
     643         264 :                                                                        fd_epoch_slot0( epoch_schedule, prev_epoch ) ).slots_per_year;
     644         264 :   return (double)slots_in_epoch / slots_per_year;
     645         264 : }
     646             : 
     647             : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
     648             : static void
     649             : calculate_previous_epoch_inflation_rewards( fd_bank_t const *                   bank,
     650             :                                             ulong                               prev_epoch_capitalization,
     651             :                                             ulong                               prev_epoch,
     652         264 :                                             fd_prev_epoch_inflation_rewards_t * rewards ) {
     653         264 :   double slot_in_year = slot_in_year_for_inflation( bank );
     654             : 
     655         264 :   rewards->validator_rate               = validator( &bank->f.inflation, slot_in_year );
     656         264 :   rewards->foundation_rate              = foundation( &bank->f.inflation, slot_in_year );
     657         264 :   rewards->prev_epoch_duration_in_years = epoch_duration_in_years( bank, prev_epoch );
     658         264 :   rewards->validator_rewards            = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
     659         264 :   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 ));
     660         264 : }
     661             : 
     662             : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
     663             : 
     664             :     https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
     665             :  */
     666             : static uint
     667             : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     668             :                                     ulong                       slot,
     669             :                                     ulong                       total_stake_accounts,
     670         276 :                                     ulong                       stake_account_stores_per_block ) {
     671             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
     672         276 :   if( epoch_schedule->warmup &&
     673         276 :       fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
     674           3 :     return 1UL;
     675           3 :   }
     676             : 
     677         273 :   FD_TEST( stake_account_stores_per_block );
     678         273 :   ulong num_chunks = total_stake_accounts / stake_account_stores_per_block + (total_stake_accounts % stake_account_stores_per_block != 0);
     679         273 :   num_chunks       = fd_ulong_max( num_chunks, 1UL );
     680         273 :   num_chunks       = fd_ulong_min( num_chunks,
     681         273 :                                    fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
     682         273 :   return (uint)num_chunks;
     683         273 : }
     684             : 
     685             : uint
     686             : fd_rewards_get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
     687             :                                                ulong                       slot,
     688             :                                                ulong                       total_stake_accounts,
     689          12 :                                                ulong                       stake_account_stores_per_block ) {
     690          12 :   return get_reward_distribution_num_blocks( epoch_schedule, slot, total_stake_accounts, stake_account_stores_per_block );
     691          12 : }
     692             : 
     693             : static void
     694             : read_stake_history( fd_accdb_t *         accdb,
     695             :                     fd_accdb_fork_id_t   fork_id,
     696             :                     uchar                data[ static FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ],
     697         267 :                     fd_stake_history_t * stake_history ) {
     698         267 :   fd_acc_t ro = fd_accdb_read_one( accdb, fork_id, fd_sysvar_stake_history_id.uc );
     699         267 :   if( FD_UNLIKELY( !ro.lamports ) ) FD_LOG_ERR(( "Unable to read stake history sysvar" ));
     700         267 :   ulong copy_sz = fd_ulong_min( ro.data_len, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
     701         267 :   fd_memcpy( data, ro.data, copy_sz );
     702         267 :   fd_accdb_unread_one( accdb, &ro );
     703         267 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_view( stake_history, data, copy_sz ) ) ) {
     704           0 :     FD_LOG_ERR(( "Unable to decode stake history sysvar" ));
     705           0 :   }
     706         267 : }
     707             : 
     708             : /* calculate_stake_points_and_credits() with some fast paths. */
     709             : static inline void
     710             : calculate_stake_points_fast( fd_epoch_credits_t *           epoch_credits,
     711             :                              fd_stake_history_t const *     stake_history,
     712             :                              fd_stake_delegation_t const *  stake,
     713             :                              ulong *                        new_rate_activation_epoch,
     714             :                              int                            use_fixed_point_stake_math,
     715             :                              ulong                          rewarded_epoch,
     716         327 :                              fd_calculated_stake_points_t * result ) {
     717         327 :   if( FD_UNLIKELY( !stake_epochs_are_normal( stake ) ) ) {
     718           0 :     FD_BASE58_ENCODE_32_BYTES( stake->stake_account.uc, stake_account_str );
     719           0 :     FD_BASE58_ENCODE_32_BYTES( stake->vote_account.uc,  vote_account_str  );
     720           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",
     721           0 :                   stake_account_str, vote_account_str, stake->stake, stake->lamports, stake->credits_observed, stake->activation_epoch, stake->deactivation_epoch ));
     722           0 :     calculate_stake_points_and_credits( epoch_credits, stake_history, stake, new_rate_activation_epoch, use_fixed_point_stake_math, result );
     723           0 :     return;
     724           0 :   }
     725             : 
     726         327 :   ulong cnt = epoch_credits->cnt;
     727         327 :   if( FD_LIKELY( epoch_credits->fast_path_ok && cnt ) ) {
     728         102 :     ulong base             = epoch_credits->base_credits;
     729         102 :     ulong credits_in_stake = stake->credits_observed;
     730         102 :     ulong credits_in_vote  = base+epoch_credits->credits_delta[ cnt-1UL ];
     731         102 :     if( FD_LIKELY( credits_in_vote>credits_in_stake ) ) {
     732          99 :       ulong initial_last = base+epoch_credits->prev_credits_delta[ cnt-1UL ];
     733          99 :       int fast = 0;
     734             : 
     735          99 :       if( FD_LIKELY( credits_in_stake>=initial_last ) ) {
     736             :         /* Single-term. */
     737          99 :         ulong target_epoch = epoch_credits->epoch[ cnt-1UL ];
     738          99 :         ulong effective_stake;
     739          99 :         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. */
     740             :           /* Single-term Case 3 or Case 1. */
     741           0 :           effective_stake = stake->state==FD_STAKE_DELEGATION_STATE_WARMED ? stake->stake : 0UL;
     742          99 :         } else {
     743             :           /* We could let this branch fall through to the slow path,
     744             :              whose loop will skip a whole bunch of epoch credits only to
     745             :              get to the final and only contributing term.  Computing it
     746             :              right here reduces about 800 instructions retired per such
     747             :              delegation. */
     748          99 :           effective_stake = fd_stakes_activating_and_deactivating( stake, target_epoch, stake_history, new_rate_activation_epoch, use_fixed_point_stake_math ).effective;
     749          99 :         }
     750          99 :         result->points.ud            = (uint128)effective_stake * (uint128)( credits_in_vote - credits_in_stake );
     751          99 :         result->new_credits_observed = credits_in_vote;
     752          99 :         fast = 1;
     753          99 :       } else {
     754             :         /* Multi-term. */
     755           0 :         ulong first_contributing_idx = 0UL;
     756           0 :         while( base+epoch_credits->credits_delta[ first_contributing_idx ]<=credits_in_stake ) first_contributing_idx++;
     757           0 :         FD_TEST( first_contributing_idx<cnt ); /* Guaranteed found because of the earlier credits_in_vote>credits_in_stake gate. */
     758             :         /* Multi-term Case 3.  effective=delegated at the earliest
     759             :            contributing term, and no deactivation at all, so the plateau
     760             :            simplifies the points calculation to a single closed form
     761             :            multiplication. */
     762           0 :         if( FD_LIKELY( is_warmed_plateau( stake, epoch_credits->epoch[ first_contributing_idx ], stake_history, new_rate_activation_epoch, use_fixed_point_stake_math ) ) ) {
     763           0 :           ulong start_credits          = fd_ulong_max( credits_in_stake, base+epoch_credits->prev_credits_delta[ 0UL ] );
     764           0 :           result->points.ud            = (uint128)stake->stake*(uint128)(credits_in_vote-start_credits);
     765           0 :           result->new_credits_observed = credits_in_vote;
     766           0 :           fast = 1;
     767           0 :         }
     768           0 :       }
     769             : 
     770          99 :       if( FD_LIKELY( fast ) ) {
     771          99 :         result->force_credits_update_with_skipped_reward = 0;
     772          99 :         return;
     773          99 :       }
     774          99 :     }
     775         102 :   }
     776             :   /* Potentially term-by-term slow path fallback for anything we can't
     777             :      conservatively prove to take the fast paths so far.  In this
     778             :      callee, early exit at the first fully deactivated term is the
     779             :      0-tail short circuit fast path for multi-term Cases 1 and 2. */
     780         228 :   calculate_stake_points_and_credits( epoch_credits, stake_history, stake, new_rate_activation_epoch, use_fixed_point_stake_math, result );
     781         228 : }
     782             : 
     783             : /* Calculates epoch reward points from stake/vote accounts.
     784             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L445 */
     785             : static uint128
     786             : calculate_reward_points_partitioned( fd_bank_t *                    bank,
     787             :                                      fd_accdb_t *                   accdb,
     788             :                                      fd_stake_delegations_t const * stake_delegations,
     789             :                                      fd_stake_history_t const *     stake_history,
     790             :                                      ulong                          rewarded_epoch,
     791         264 :                                      fd_runtime_stack_t *           runtime_stack ) {
     792             :   /* Calculate the points for each stake delegation */
     793         264 :   uint128 total_points = 0;
     794             : 
     795         264 :   fd_vote_rewards_t *     vote_ele     = runtime_stack->stakes.vote_ele;
     796         264 :   fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
     797         264 :   fd_epoch_credits_t *    epoch_credits_arr = fd_bank_epoch_credits( bank );
     798             : 
     799         264 :   fd_stake_delegations_iter_t iter_[1];
     800         264 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations, accdb, bank->accdb_fork_id, bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch );
     801         597 :        !fd_stake_delegations_iter_done( iter );
     802         333 :        fd_stake_delegations_iter_next( iter ) ) {
     803         333 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     804         333 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     805             : 
     806             :     /* Note that we don't check minimum delegation here, as there are
     807             :        no plans to activate stake_minimum_delegation_for_rewards.
     808             :        If this changes we need to skip stake accounts that are
     809             :        below the minimum delegation here. However we don't do this yet,
     810             :        to ensure that we audit the feature properly if this happens. */
     811             : 
     812         333 :     uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     813             : 
     814         333 :     if( FD_LIKELY( stake_delegation_idx<runtime_stack->max_stake_accounts ) ) {
     815         333 :       runtime_stack->stakes.stake_points_result[ stake_delegation_idx ].vote_idx = idx;
     816         333 :     }
     817             : 
     818         333 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
     819             : 
     820         324 :     fd_calculated_stake_points_t   stake_points_result_[1];
     821         324 :     fd_calculated_stake_points_t * stake_points_result;
     822         324 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->max_stake_accounts ) ) {
     823           0 :       stake_points_result = stake_points_result_;
     824         324 :     } else {
     825         324 :       stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
     826         324 :     }
     827             : 
     828         324 :     fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[ idx ];
     829             : 
     830         324 :     calculate_stake_points_fast( epoch_credits,
     831         324 :                                  stake_history,
     832         324 :                                  stake_delegation,
     833         324 :                                  &bank->f.warmup_cooldown_rate_epoch,
     834         324 :                                  FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
     835         324 :                                  rewarded_epoch,
     836         324 :                                  stake_points_result );
     837             : 
     838         324 :     total_points += stake_points_result->points.ud;
     839         324 :   }
     840             : 
     841         264 :   return total_points;
     842         264 : }
     843             : 
     844             : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L161-L173 */
     845             : static int
     846             : delegation_may_need_adjustment( fd_bank_t *                   bank,
     847             :                                 fd_stake_delegation_t const * stake_delegation,
     848             :                                 fd_stake_history_t const *    stake_history,
     849             :                                 ulong                         rewarded_epoch,
     850             :                                 ulong                         new_delegation_with_rewards,
     851             :                                 ulong                         lamports_with_rewards,
     852           0 :                                 ulong                         minimum_lamports ) {
     853           0 :   ulong new_delegation = fd_ulong_min(
     854           0 :     new_delegation_with_rewards,
     855           0 :     fd_ulong_sat_sub( lamports_with_rewards, minimum_lamports )
     856           0 :   );
     857             : 
     858           0 :   if( FD_LIKELY( new_delegation==stake_delegation->stake ) ) return 0;
     859             : 
     860             :   /* Delegations that are neither effective nor activating are left
     861             :      alone.  This is checked last because it can walk many stake history
     862             :      entries. */
     863           0 :   fd_stake_history_entry_t status = fd_stakes_activating_and_deactivating(
     864           0 :     stake_delegation,
     865           0 :     rewarded_epoch,
     866           0 :     stake_history,
     867           0 :     &bank->f.warmup_cooldown_rate_epoch,
     868           0 :     FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) );
     869             : 
     870           0 :   return !( status.effective==0UL && status.activating==0UL );
     871           0 : }
     872             : 
     873             : /* Calculates epoch rewards for stake/vote accounts.
     874             :    Returns vote rewards, stake rewards, and the sum of all stake rewards
     875             :    in lamports.
     876             : 
     877             :    In the future, the calculation will be cached in the snapshot, but
     878             :    for now we just re-calculate it (as Agave does).
     879             :    calculate_stake_vote_rewards is responsible for calculating
     880             :    stake account rewards based off of a combination of the
     881             :    stake delegation state as well as the vote account. If this
     882             :    calculation is done at the end of an epoch, we can just use the
     883             :    vote states at the end of the current epoch. However, because we
     884             :    are presumably booting up a node in the middle of rewards
     885             :    distribution, we need to make sure that we are using the vote
     886             :    states from the end of the previous epoch.
     887             : 
     888             :    https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
     889             : static void
     890             : calculate_stake_vote_rewards( fd_bank_t *                    bank,
     891             :                               fd_accdb_t *                   accdb,
     892             :                               fd_stake_delegations_t const * stake_delegations,
     893             :                               fd_capture_ctx_t *             capture_ctx FD_PARAM_UNUSED,
     894             :                               fd_stake_history_t const *     stake_history,
     895             :                               ulong                          rewarded_epoch,
     896             :                               ulong                          total_rewards,
     897             :                               uint128                        total_points,
     898             :                               fd_runtime_stack_t *           runtime_stack,
     899         267 :                               int                            is_recalculation ) {
     900             : 
     901         267 :   runtime_stack->stakes.stake_rewards_cnt = 0UL;
     902             : 
     903         267 :   fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
     904         267 :   fd_epoch_credits_t *          epoch_credits_arr = fd_bank_epoch_credits( bank );
     905             : 
     906         267 :   fd_stake_delegations_iter_t iter_[1];
     907         267 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations, accdb, bank->accdb_fork_id, bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch );
     908         603 :        !fd_stake_delegations_iter_done( iter );
     909         336 :        fd_stake_delegations_iter_next( iter ) ) {
     910         336 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
     911         336 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
     912             : 
     913             :     /* Note that we don't check minimum delegation here, as there are
     914             :        no plans to activate stake_minimum_delegation_for_rewards.
     915             :        If this changes we need to skip stake accounts that are
     916             :        below the minimum delegation here. However we don't do this yet,
     917             :        to ensure that we audit the feature properly if this happens. */
     918             : 
     919         336 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
     920         336 :     if( stake_delegation_idx>=runtime_stack->max_stake_accounts ) {
     921           0 :       calculated_stake_rewards = calculated_stake_rewards_;
     922         336 :     } else {
     923         336 :       calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
     924         336 :     }
     925         336 :     calculated_stake_rewards->success = 0;
     926             : 
     927         336 :     int cached = !is_recalculation && stake_delegation_idx<runtime_stack->max_stake_accounts;
     928         336 :     uint idx;
     929         336 :     if( FD_LIKELY( cached ) ) {
     930         333 :       idx = runtime_stack->stakes.stake_points_result[ stake_delegation_idx ].vote_idx;
     931         333 :     } else {
     932           3 :       fd_vote_rewards_t *     vote_ele     = runtime_stack->stakes.vote_ele;
     933           3 :       fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
     934           3 :       idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
     935           3 :     }
     936             : 
     937             :     /* Stake account may need to be adjusted to meet rent-exempt minimum
     938             :        balance requirements based on new rent and delegation parameters.
     939             :        https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L568-L608 */
     940         336 :     if( FD_UNLIKELY( idx==UINT_MAX ) ) {
     941           9 :       if( !FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) ) continue;
     942             : 
     943             :       /* If the stake account's resulting lamports would cause it to be
     944             :          below the rent exempt minimum balance, it needs to be queued
     945             :          for update (and thus affects the epoch reward partitions). */
     946           0 :       if( !delegation_may_need_adjustment(
     947           0 :             bank,
     948           0 :             stake_delegation,
     949           0 :             stake_history,
     950           0 :             rewarded_epoch,
     951           0 :             stake_delegation->stake,
     952           0 :             stake_delegation->lamports,
     953           0 :             fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) ) ) {
     954           0 :         continue;
     955           0 :       }
     956             : 
     957             :       /* Place an empty entry for this stake delegation idx so that
     958             :          the partitioning logic factors it in. */
     959           0 :       *calculated_stake_rewards = (fd_calculated_stake_rewards_t){
     960           0 :         .success              = 1,
     961           0 :         .staker_rewards       = 0,
     962           0 :         .voter_rewards        = 0,
     963           0 :         .new_credits_observed = stake_delegation->credits_observed
     964           0 :       };
     965           0 :       runtime_stack->stakes.stake_rewards_cnt++;
     966           0 :       continue;
     967           0 :     }
     968             : 
     969         327 :     fd_calculated_stake_points_t   stake_points_result_[1];
     970         327 :     fd_calculated_stake_points_t * stake_points_result;
     971         327 :     if( FD_LIKELY( cached ) ) {
     972         324 :       stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
     973         324 :     } else {
     974           3 :       fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[ idx ];
     975             : 
     976             :       /* We have not cached the stake points yet if we are recalculating
     977             :          stake rewards so we need to recalculate them.  ULONG_MAX
     978             :          disables the tag fast path. */
     979           3 :       calculate_stake_points_fast( epoch_credits,
     980           3 :                                    stake_history,
     981           3 :                                    stake_delegation,
     982           3 :                                    &bank->f.warmup_cooldown_rate_epoch,
     983           3 :                                    FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
     984           3 :                                    ULONG_MAX,
     985           3 :                                    stake_points_result_ );
     986           3 :       stake_points_result = stake_points_result_;
     987           3 :     }
     988             : 
     989             :     /* redeem_rewards is actually just responsible for calculating the
     990             :        vote and stake rewards for each stake account.  It does not do
     991             :        rewards redemption: it is a misnomer. */
     992         327 :     int err = redeem_rewards(
     993         327 :         stake_delegation,
     994         327 :         idx,
     995         327 :         rewarded_epoch,
     996         327 :         total_rewards,
     997         327 :         total_points,
     998         327 :         runtime_stack,
     999         327 :         stake_points_result,
    1000         327 :         calculated_stake_rewards );
    1001             : 
    1002         327 :     if( FD_UNLIKELY( err!=0 ) ) {
    1003             :       /* Even if there is an error computing rewards for the stake
    1004             :          account, there may be a required balance update for the stake
    1005             :          account if rent increased.
    1006             :          https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L132-L152 */
    1007           3 :       if( !FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) ) continue;
    1008             : 
    1009             :       /* staker rewards is 0 in the error case, so we can just use
    1010             :          the current stake and lamports in the function args. */
    1011           0 :       if( !delegation_may_need_adjustment(
    1012           0 :             bank,
    1013           0 :             stake_delegation,
    1014           0 :             stake_history,
    1015           0 :             rewarded_epoch,
    1016           0 :             stake_delegation->stake,
    1017           0 :             stake_delegation->lamports,
    1018           0 :             fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) ) ) {
    1019           0 :         continue;
    1020           0 :       }
    1021             : 
    1022           0 :       *calculated_stake_rewards = (fd_calculated_stake_rewards_t){
    1023           0 :         .success              = 1,
    1024           0 :         .staker_rewards       = 0,
    1025           0 :         .voter_rewards        = 0,
    1026           0 :         .new_credits_observed = stake_delegation->credits_observed
    1027           0 :       };
    1028         324 :     } else {
    1029         324 :       calculated_stake_rewards->success = 1;
    1030         324 :     }
    1031             : 
    1032         324 :     if( capture_ctx && capture_ctx->capture_solcap ) {
    1033           0 :       fd_capture_link_write_stake_reward_event( capture_ctx,
    1034           0 :                                                 bank->f.slot,
    1035           0 :                                                 stake_delegation->stake_account,
    1036           0 :                                                 stake_delegation->vote_account,
    1037           0 :                                                 runtime_stack->stakes.vote_ele[ idx ].commission,
    1038           0 :                                                 (long)calculated_stake_rewards->voter_rewards,
    1039           0 :                                                 (long)calculated_stake_rewards->staker_rewards,
    1040           0 :                                                 (long)calculated_stake_rewards->new_credits_observed );
    1041           0 :     }
    1042             : 
    1043         324 :     runtime_stack->stakes.vote_ele[ idx ].vote_rewards += calculated_stake_rewards->voter_rewards;
    1044         324 :     runtime_stack->stakes.stake_rewards_cnt++;
    1045         324 :   }
    1046         267 : }
    1047             : 
    1048             : /* setup_stake_partitions hashes every stake reward of the epoch into
    1049             :    its partition.  Only the rewards landing inside the fork's current
    1050             :    window are retained; see fd_stake_rewards.h.  The fork must already
    1051             :    have been initialized and its window positioned by the caller. */
    1052             : 
    1053             : static void
    1054             : setup_stake_partitions( fd_bank_t *                    bank,
    1055             :                         fd_accdb_t *                   accdb,
    1056             :                         fd_stake_history_t const *     stake_history,
    1057             :                         fd_stake_delegations_t const * stake_delegations,
    1058             :                         fd_runtime_stack_t *           runtime_stack,
    1059             :                         uchar                          fork_idx,
    1060             :                         ulong                          rewarded_epoch,
    1061             :                         ulong                          total_rewards,
    1062         267 :                         uint128                        total_points ) {
    1063             : 
    1064         267 :   fd_stake_rewards_t * stake_rewards     = fd_bank_stake_rewards_modify( bank );
    1065         267 :   fd_epoch_credits_t * epoch_credits_arr = fd_bank_epoch_credits( bank );
    1066             : 
    1067         267 :   fd_stake_delegations_iter_t iter_[1];
    1068         267 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations, accdb, bank->accdb_fork_id, bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch );
    1069         603 :        !fd_stake_delegations_iter_done( iter );
    1070         336 :        fd_stake_delegations_iter_next( iter ) ) {
    1071         336 :     fd_stake_delegation_t const * stake_delegation     = fd_stake_delegations_iter_ele( iter );
    1072         336 :     ulong                         stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
    1073             : 
    1074         336 :     fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
    1075         336 :     fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
    1076             : 
    1077         336 :     if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->max_stake_accounts ) ) {
    1078             : 
    1079           0 :       calculated_stake_rewards = calculated_stake_rewards_;
    1080             : 
    1081           0 :       fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
    1082           0 :       fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
    1083           0 :       uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
    1084           0 :       if( FD_UNLIKELY( idx==UINT_MAX ) ) {
    1085           0 :         if( !FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) ) continue;
    1086             : 
    1087             :         /* If the stake account's resulting lamports would cause it to be
    1088             :            below the rent exempt minimum balance, it needs to be queued
    1089             :            for update (and thus affects the epoch reward partitions). */
    1090           0 :         if( !delegation_may_need_adjustment(
    1091           0 :               bank,
    1092           0 :               stake_delegation,
    1093           0 :               stake_history,
    1094           0 :               rewarded_epoch,
    1095           0 :               stake_delegation->stake,
    1096           0 :               stake_delegation->lamports,
    1097           0 :               fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) ) ) {
    1098           0 :           continue;
    1099           0 :         }
    1100             : 
    1101           0 :         fd_stake_rewards_insert( stake_rewards, fork_idx, &stake_delegation->stake_account, 0UL, stake_delegation->credits_observed );
    1102           0 :         continue;
    1103           0 :       }
    1104             : 
    1105           0 :       fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[ idx ];
    1106             : 
    1107           0 :       fd_calculated_stake_points_t stake_points_result[1];
    1108           0 :       calculate_stake_points_fast( epoch_credits,
    1109           0 :                                    stake_history,
    1110           0 :                                    stake_delegation,
    1111           0 :                                    &bank->f.warmup_cooldown_rate_epoch,
    1112           0 :                                    FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
    1113           0 :                                    ULONG_MAX,
    1114           0 :                                    stake_points_result );
    1115             : 
    1116             :       /* redeem_rewards is actually just responsible for calculating the
    1117             :          vote and stake rewards for each stake account.  It does not do
    1118             :          rewards redemption: it is a misnomer. */
    1119           0 :       int err = redeem_rewards(
    1120           0 :           stake_delegation,
    1121           0 :           idx,
    1122           0 :           rewarded_epoch,
    1123           0 :           total_rewards,
    1124           0 :           total_points,
    1125           0 :           runtime_stack,
    1126           0 :           stake_points_result,
    1127           0 :           calculated_stake_rewards );
    1128             : 
    1129           0 :       if( FD_UNLIKELY( err!=0 ) ) {
    1130             :         /* Even if there is an error computing rewards for the stake
    1131             :            account, there may be a required balance update for the stake
    1132             :            account if rent increased.
    1133             :            https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/inflation_rewards/mod.rs#L132-L152 */
    1134           0 :         if( !FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) ) continue;
    1135             : 
    1136             :         /* staker rewards is 0 in the error case, so we can just use
    1137             :            the current stake and lamports in the function args. */
    1138           0 :         if( !delegation_may_need_adjustment(
    1139           0 :               bank,
    1140           0 :               stake_delegation,
    1141           0 :               stake_history,
    1142           0 :               rewarded_epoch,
    1143           0 :               stake_delegation->stake,
    1144           0 :               stake_delegation->lamports,
    1145           0 :               fd_rent_exempt_minimum_balance( &bank->f.rent, stake_delegation->acc_dlen ) ) ) {
    1146           0 :           continue;
    1147           0 :         }
    1148             : 
    1149           0 :         fd_stake_rewards_insert( stake_rewards, fork_idx, &stake_delegation->stake_account, 0UL, stake_delegation->credits_observed );
    1150           0 :         continue;
    1151           0 :       } else {
    1152           0 :         calculated_stake_rewards->success = 1;
    1153           0 :       }
    1154         336 :     } else {
    1155         336 :       calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
    1156         336 :     }
    1157             : 
    1158         336 :     if( FD_UNLIKELY( !calculated_stake_rewards->success ) ) continue;
    1159             : 
    1160         324 :     fd_stake_rewards_insert(
    1161         324 :       stake_rewards,
    1162         324 :       fork_idx,
    1163         324 :       &stake_delegation->stake_account,
    1164         324 :       calculated_stake_rewards->staker_rewards,
    1165         324 :       calculated_stake_rewards->new_credits_observed
    1166         324 :     );
    1167         324 :   }
    1168         267 : }
    1169             : 
    1170             : /* Calculate epoch reward and return vote and stake rewards.
    1171             : 
    1172             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
    1173             : static uint128
    1174             : calculate_validator_rewards( fd_bank_t *                    bank,
    1175             :                              fd_accdb_t *                   accdb,
    1176             :                              fd_runtime_stack_t *           runtime_stack,
    1177             :                              fd_stake_delegations_t const * stake_delegations,
    1178             :                              fd_capture_ctx_t *             capture_ctx,
    1179             :                              ulong                          rewarded_epoch,
    1180         264 :                              ulong *                        rewards_out ) {
    1181         264 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    1182         264 :   fd_stake_history_t stake_history[1];
    1183         264 :   read_stake_history( accdb, bank->accdb_fork_id, stake_history_data, stake_history );
    1184             : 
    1185             :   /* Calculate the epoch reward points from stake/vote accounts */
    1186         264 :   uint128 total_points = calculate_reward_points_partitioned(
    1187         264 :       bank,
    1188         264 :       accdb,
    1189         264 :       stake_delegations,
    1190         264 :       stake_history,
    1191         264 :       rewarded_epoch,
    1192         264 :       runtime_stack );
    1193             : 
    1194             :   /* If there are no points, then we set the rewards to 0. */
    1195         264 :   *rewards_out = total_points>0UL ? *rewards_out: 0UL;
    1196             : 
    1197         264 :   if( FD_UNLIKELY( capture_ctx && capture_ctx->capture_solcap ) ) {
    1198           0 :     ulong epoch = bank->f.epoch;
    1199           0 :     ulong slot  = bank->f.slot;
    1200           0 :     fd_capture_link_write_stake_rewards_begin( capture_ctx,
    1201           0 :                                                slot,
    1202           0 :                                                epoch,
    1203           0 :                                                epoch-1UL, /* FIXME: this is not strictly correct */
    1204           0 :                                                *rewards_out,
    1205           0 :                                                (ulong)total_points );
    1206           0 :   }
    1207             : 
    1208             :   /* Calculate the stake and vote rewards for each account. We want to
    1209             :      use the vote states from the end of the current_epoch. */
    1210         264 :   calculate_stake_vote_rewards(
    1211         264 :       bank,
    1212         264 :       accdb,
    1213         264 :       stake_delegations,
    1214         264 :       capture_ctx,
    1215         264 :       stake_history,
    1216         264 :       rewarded_epoch,
    1217         264 :       *rewards_out,
    1218         264 :       total_points,
    1219         264 :       runtime_stack,
    1220         264 :       0 );
    1221             : 
    1222         264 :   fd_hash_t const * parent_blockhash      = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
    1223         264 :   ulong             starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
    1224         264 :   uint              num_partitions        = get_reward_distribution_num_blocks( &bank->f.epoch_schedule,
    1225         264 :                                                                                 bank->f.slot,
    1226         264 :                                                                                 runtime_stack->stakes.stake_rewards_cnt,
    1227         264 :                                                                                 bank->f.slot_params.stake_account_stores_per_block );
    1228             : 
    1229         264 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1230         264 :   uchar                fork_idx      = fd_stake_rewards_init( stake_rewards,
    1231         264 :                                                               bank->f.epoch,
    1232         264 :                                                               parent_blockhash,
    1233         264 :                                                               starting_block_height,
    1234         264 :                                                               num_partitions,
    1235         264 :                                                               runtime_stack->stakes.stake_rewards_cnt );
    1236         264 :   if( FD_UNLIKELY( bank->stake_rewards_fork_id!=UCHAR_MAX ) ) {
    1237           3 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    1238           3 :   }
    1239         264 :   bank->stake_rewards_fork_id = fork_idx;
    1240             : 
    1241         264 :   setup_stake_partitions( bank,
    1242         264 :                           accdb,
    1243         264 :                           stake_history,
    1244         264 :                           stake_delegations,
    1245         264 :                           runtime_stack,
    1246         264 :                           fork_idx,
    1247         264 :                           rewarded_epoch,
    1248         264 :                           *rewards_out,
    1249         264 :                           total_points );
    1250             : 
    1251         264 :   return total_points;
    1252         264 : }
    1253             : 
    1254             : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
    1255             : 
    1256             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L277 */
    1257             : static void
    1258             : calculate_rewards_for_partitioning( fd_bank_t *                            bank,
    1259             :                                     fd_accdb_t *                           accdb,
    1260             :                                     fd_runtime_stack_t *                   runtime_stack,
    1261             :                                     fd_stake_delegations_t const *         stake_delegations,
    1262             :                                     fd_capture_ctx_t *                     capture_ctx,
    1263             :                                     ulong                                  prev_epoch,
    1264         264 :                                     fd_partitioned_rewards_calculation_t * result ) {
    1265         264 :   fd_prev_epoch_inflation_rewards_t rewards;
    1266             : 
    1267         264 :   calculate_previous_epoch_inflation_rewards( bank,
    1268         264 :                                               bank->f.capitalization,
    1269         264 :                                               prev_epoch,
    1270         264 :                                               &rewards );
    1271             : 
    1272         264 :   ulong total_rewards = rewards.validator_rewards;
    1273             : 
    1274         264 :   uint128 points = calculate_validator_rewards( bank,
    1275         264 :                                                 accdb,
    1276         264 :                                                 runtime_stack,
    1277         264 :                                                 stake_delegations,
    1278         264 :                                                 capture_ctx,
    1279         264 :                                                 prev_epoch,
    1280         264 :                                                 &total_rewards );
    1281             : 
    1282             :   /* The agave client does not partition the stake rewards until the
    1283             :      first distribution block.  We calculate the partitions during the
    1284             :      boundary. */
    1285         264 :   result->validator_points             = points;
    1286         264 :   result->validator_rewards            = total_rewards;
    1287         264 :   result->validator_rate               = rewards.validator_rate;
    1288         264 :   result->foundation_rate              = rewards.foundation_rate;
    1289         264 :   result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
    1290         264 :   result->capitalization               = bank->f.capitalization;
    1291         264 : }
    1292             : 
    1293             : /* Calculate rewards from previous epoch and distribute vote rewards
    1294             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L148 */
    1295             : static void
    1296             : calculate_rewards_and_distribute_vote_rewards( fd_bank_t *                    bank,
    1297             :                                                fd_accdb_t *                   accdb,
    1298             :                                                fd_runtime_stack_t *           runtime_stack,
    1299             :                                                fd_stake_delegations_t const * stake_delegations,
    1300             :                                                fd_capture_ctx_t *             capture_ctx,
    1301         264 :                                                ulong                          prev_epoch ) {
    1302             : 
    1303         264 :   fd_vote_rewards_t *     vote_ele_pool = runtime_stack->stakes.vote_ele;
    1304         264 :   fd_vote_rewards_map_t * vote_ele_map  = runtime_stack->stakes.vote_map;
    1305             : 
    1306             :   /* First we must compute the stake and vote rewards for the just
    1307             :      completed epoch.  We store the stake account rewards and vote
    1308             :      states rewards in the bank */
    1309             : 
    1310         264 :   fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
    1311         264 :   calculate_rewards_for_partitioning( bank,
    1312         264 :                                       accdb,
    1313         264 :                                       runtime_stack,
    1314         264 :                                       stake_delegations,
    1315         264 :                                       capture_ctx,
    1316         264 :                                       prev_epoch,
    1317         264 :                                       rewards_calc_result );
    1318             : 
    1319             : 
    1320             :   /* Distribute the commission rewards.  distributed_rewards includes
    1321             :      burned and incinerated amounts: the epoch rewards sysvar counts
    1322             :      them as distributed.
    1323             :      https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L186-L217 */
    1324         264 :   ulong distributed_rewards = 0UL;
    1325         264 :   if( FD_FEATURE_ACTIVE_BANK( bank, custom_commission_collector ) ) {
    1326             :     /* Aggregate rewards per collector before validation: several small
    1327             :        rewards may collectively make a new collector rent-exempt.
    1328             :        stake_accum is free for reuse as scratch here. */
    1329          54 :     fd_stake_accum_t *     collector_pool = runtime_stack->stakes.stake_accum;
    1330          54 :     fd_stake_accum_map_t * collector_map  = runtime_stack->stakes.stake_accum_map;
    1331          54 :     fd_stake_accum_map_reset( collector_map );
    1332          54 :     ulong collector_cnt = 0UL;
    1333             : 
    1334          54 :     for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
    1335         117 :          !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
    1336          63 :          iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
    1337          63 :       uint                idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
    1338          63 :       fd_vote_rewards_t * ele = &vote_ele_pool[idx];
    1339             : 
    1340          63 :       ulong rewards = ele->vote_rewards;
    1341          63 :       if( FD_UNLIKELY( !rewards ) ) continue;
    1342          54 :       distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards );
    1343             : 
    1344          54 :       fd_pubkey_t collector;
    1345          54 :       fd_rewards_inflation_collector( bank, &ele->pubkey, &collector );
    1346             : 
    1347          54 :       fd_stake_accum_t * collector_reward = fd_stake_accum_map_ele_query( collector_map, &collector, NULL, collector_pool );
    1348          54 :       if( FD_UNLIKELY( !collector_reward ) ) {
    1349          48 :         FD_TEST( collector_cnt<runtime_stack->max_vote_accounts );
    1350          48 :         collector_reward         = &collector_pool[ collector_cnt++ ];
    1351          48 :         collector_reward->pubkey = collector;
    1352          48 :         collector_reward->stake  = rewards;
    1353          48 :         fd_stake_accum_map_ele_insert( collector_map, collector_reward, collector_pool );
    1354          48 :       } else {
    1355           6 :         collector_reward->stake = fd_ulong_sat_add( collector_reward->stake, rewards );
    1356           6 :       }
    1357          54 :     }
    1358             : 
    1359          54 :     for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( collector_map, collector_pool );
    1360         102 :          !fd_stake_accum_map_iter_done( iter, collector_map, collector_pool );
    1361          54 :          iter = fd_stake_accum_map_iter_next( iter, collector_map, collector_pool ) ) {
    1362          48 :       fd_stake_accum_t * collector_reward = fd_stake_accum_map_iter_ele( iter, collector_map, collector_pool );
    1363             : 
    1364             :       /* A collector that is itself a self-collecting vote account is
    1365             :          paid only its own reward; rewards routed to it by other vote
    1366             :          accounts are burned (it is not system-owned).
    1367             :          https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L70-L124 */
    1368          48 :       fd_vote_rewards_t * self_ele = fd_vote_rewards_map_ele_query( vote_ele_map, &collector_reward->pubkey, NULL, vote_ele_pool );
    1369          48 :       int is_vote_account = 0;
    1370          48 :       if( self_ele && self_ele->vote_rewards ) {
    1371           6 :         fd_pubkey_t self_collector;
    1372           6 :         fd_rewards_inflation_collector( bank, &self_ele->pubkey, &self_collector );
    1373           6 :         is_vote_account = fd_pubkey_eq( &self_collector, &self_ele->pubkey );
    1374           6 :       }
    1375          48 :       ulong rewards = is_vote_account ? self_ele->vote_rewards : collector_reward->stake;
    1376             : 
    1377          48 :       fd_acc_t account = fd_accdb_read_one( accdb, bank->accdb_fork_id, collector_reward->pubkey.uc );
    1378          48 :       int      burn;
    1379          48 :       if( is_vote_account ) {
    1380             :         /* The vote account itself only needs the overflow check. */
    1381           6 :         ulong post_balance;
    1382           6 :         burn = __builtin_uaddl_overflow( account.lamports, rewards, &post_balance );
    1383          42 :       } else {
    1384          42 :         burn = fd_rewards_validate_commission_collector( bank, &collector_reward->pubkey, &account, rewards );
    1385          42 :       }
    1386          48 :       fd_accdb_unread_one( accdb, &account );
    1387             : 
    1388          48 :       if( FD_UNLIKELY( burn ) ) continue;
    1389             : 
    1390             :       /* Credit rewards to the collector (creating a new system account
    1391             :          if it does not exist). */
    1392          27 :       fd_accdb_svm_credit( bank, accdb, capture_ctx, &collector_reward->pubkey, rewards );
    1393          27 :     }
    1394         210 :   } else {
    1395         210 :     for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
    1396         468 :          !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
    1397         258 :          iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
    1398             : 
    1399         258 :       uint idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
    1400         258 :       fd_vote_rewards_t * ele = &vote_ele_pool[idx];
    1401             : 
    1402         258 :       ulong rewards = ele->vote_rewards;
    1403         258 :       if( rewards==0UL ) {
    1404         252 :         continue;
    1405         252 :       }
    1406             : 
    1407             :       /* Credit rewards to vote account (creating a new system account if
    1408             :          it does not exist) */
    1409           6 :       fd_accdb_svm_credit( bank, accdb, capture_ctx, &ele->pubkey, rewards );
    1410           6 :       distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards );
    1411           6 :     }
    1412         210 :   }
    1413             : 
    1414             :   /* Verify that we didn't pay any more than we expected to */
    1415         264 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1416         264 :   ulong total_stake_rewards = fd_stake_rewards_total_rewards( stake_rewards, bank->stake_rewards_fork_id );
    1417             : 
    1418         264 :   ulong total_rewards = fd_ulong_sat_add( distributed_rewards, total_stake_rewards );
    1419         264 :   if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
    1420           0 :     FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
    1421           0 :   }
    1422             : 
    1423         264 :   runtime_stack->stakes.distributed_rewards = distributed_rewards;
    1424         264 :   runtime_stack->stakes.total_rewards       = rewards_calc_result->validator_rewards;
    1425         264 :   runtime_stack->stakes.total_points.ud     = rewards_calc_result->validator_points;
    1426         264 : }
    1427             : 
    1428             : /* Note: modifies delegation in-place, adjusting it for rent-exempt
    1429             :    minimum balance requirements.
    1430             :    https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L55-L76 */
    1431             : static void
    1432             : adjust_delegation_for_rent( fd_delegation_t * delegation,
    1433             :                             ulong             rewarded_epoch,
    1434             :                             ulong             new_delegation_with_rewards,
    1435             :                             ulong             lamports_with_rewards,
    1436           3 :                             ulong             minimum_lamports ) {
    1437           3 :   ulong new_delegation = fd_ulong_min( new_delegation_with_rewards,
    1438           3 :                                        fd_ulong_sat_sub( lamports_with_rewards, minimum_lamports ) );
    1439             : 
    1440           3 :   if( new_delegation!=delegation->stake ) {
    1441           3 :     delegation->stake = new_delegation;
    1442           3 :     if( FD_UNLIKELY( new_delegation==0UL ) ) {
    1443           0 :       delegation->deactivation_epoch = rewarded_epoch;
    1444           0 :     }
    1445           3 :   }
    1446           3 : }
    1447             : 
    1448             : /* Distributes a single partitioned reward to a single stake account.  acc was
    1449             :    acquired by the caller as part of a batch and is released with it, so the
    1450             :    early returns below leave commit at 0, which fd_accdb_release skips. */
    1451             : 
    1452             : static int
    1453             : distribute_epoch_reward_to_stake_acc( fd_bank_t *         bank,
    1454             :                                       fd_capture_ctx_t *  capture_ctx,
    1455             :                                       ulong               reward_lamports,
    1456             :                                       ulong               new_credits_observed,
    1457             :                                       fd_acc_t *          acc,
    1458             :                                       fd_lthash_adder_t * adder_pre,
    1459             :                                       fd_lthash_value_t * sum_pre,
    1460             :                                       fd_lthash_adder_t * adder_post,
    1461         195 :                                       fd_lthash_value_t * sum_post ) {
    1462         195 :   if( FD_UNLIKELY( !acc->lamports ) ) {
    1463           0 :     return 1; /* account does not exist */
    1464           0 :   }
    1465             : 
    1466         195 :   fd_stake_state_t const * stake_state_orig = fd_stakes_get_state( acc );
    1467         195 :   if( FD_UNLIKELY( !stake_state_orig || stake_state_orig->stake_type!=FD_STAKE_STATE_STAKE ) ) {
    1468           0 :     return 1; /* not a valid stake account */
    1469           0 :   }
    1470             : 
    1471         195 :   fd_pubkey_t const * stake_pubkey   = fd_type_pun_const(acc->pubkey);
    1472         195 :   fd_stake_state_t    stake_state[1] = { *stake_state_orig };
    1473             : 
    1474         195 :   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 );
    1475             : 
    1476         195 :   FD_TEST( !__builtin_add_overflow( acc->lamports, reward_lamports, &acc->lamports ) );
    1477             : 
    1478         195 :   ulong old_credits_observed                = stake_state->stake.stake.credits_observed;
    1479         195 :   stake_state->stake.stake.credits_observed = new_credits_observed;
    1480         195 :   stake_state->stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->stake.stake.delegation.stake, reward_lamports );
    1481             : 
    1482             :   /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L259-L283 */
    1483         195 :   fd_stake_t * new_stake = &stake_state->stake.stake;
    1484         195 :   if( FD_FEATURE_ACTIVE_BANK( bank, relax_post_exec_min_balance_check ) ) {
    1485           3 :     ulong minimum_balance = fd_rent_exempt_minimum_balance( &bank->f.rent, acc->data_len );
    1486           3 :     adjust_delegation_for_rent(
    1487           3 :       &new_stake->delegation,
    1488           3 :       fd_ulong_sat_sub( bank->f.epoch, 1UL ),
    1489           3 :       new_stake->delegation.stake,
    1490           3 :       acc->lamports,
    1491           3 :       minimum_balance );
    1492           3 :   }
    1493             : 
    1494         195 :   fd_stake_delegations_t * stake_delegations_upd = fd_bank_stake_delegations_modify( bank );
    1495         195 :   fd_stake_delegations_fork_update( stake_delegations_upd,
    1496         195 :                                     bank->stake_delegations_fork_id,
    1497         195 :                                     stake_pubkey,
    1498         195 :                                     &stake_state->stake.stake.delegation.voter_pubkey,
    1499         195 :                                     stake_state->stake.stake.delegation.stake,
    1500         195 :                                     stake_state->stake.stake.delegation.activation_epoch,
    1501         195 :                                     stake_state->stake.stake.delegation.deactivation_epoch,
    1502         195 :                                     stake_state->stake.stake.credits_observed,
    1503         195 :                                     acc->lamports,
    1504         195 :                                     (uint)acc->data_len,
    1505         195 :                                     fd_stake_warmup_cooldown_rate( bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch ) );
    1506             : 
    1507         195 :   if( FD_UNLIKELY( capture_ctx && capture_ctx->capture_solcap ) ) {
    1508           0 :     fd_capture_link_write_stake_account_payout( capture_ctx,
    1509           0 :                                                 bank->f.slot,
    1510           0 :                                                 *stake_pubkey,
    1511           0 :                                                 bank->f.slot,
    1512           0 :                                                 acc->lamports,
    1513           0 :                                                 (long)reward_lamports,
    1514           0 :                                                 new_credits_observed,
    1515           0 :                                                 (long)( new_credits_observed - old_credits_observed ),
    1516           0 :                                                 stake_state->stake.stake.delegation.stake,
    1517           0 :                                                 (long)reward_lamports );
    1518           0 :   }
    1519             : 
    1520         195 :   FD_STORE( fd_stake_state_t, acc->data, *stake_state );
    1521         195 :   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 );
    1522         195 :   fd_hashes_capture_account( stake_pubkey->uc, acc->owner, acc->lamports, acc->executable, acc->data, acc->data_len, bank, capture_ctx );
    1523         195 :   acc->commit = 1;
    1524             : 
    1525         195 :   return 0;
    1526         195 : }
    1527             : 
    1528             : /* Accdb batch size for updating stake accounts */
    1529        5097 : #define STAKE_REWARD_ACC_BATCH_SZ (32UL)
    1530             : 
    1531             : /* Process reward credits for a partition of rewards.  Store the rewards
    1532             :    to AccountsDB, update reward history record and total capitalization
    1533             :    https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
    1534             : static void
    1535             : distribute_epoch_rewards_in_partition( fd_stake_rewards_t *      stake_rewards,
    1536             :                                        ulong                     partition_idx,
    1537             :                                        fd_bank_t *               bank,
    1538             :                                        fd_accdb_t *              accdb,
    1539         135 :                                        fd_capture_ctx_t *        capture_ctx ) {
    1540             : 
    1541         135 :   ulong lamports_distributed = 0UL;
    1542         135 :   ulong lamports_burned      = 0UL;
    1543             : 
    1544         135 :   fd_lthash_adder_t adder_pre[1], adder_post[1];
    1545         135 :   fd_lthash_adder_new( adder_pre  );
    1546         135 :   fd_lthash_adder_new( adder_post );
    1547         135 :   fd_lthash_value_t sum_pre[1], sum_post[1];
    1548         135 :   fd_lthash_zero( sum_pre  );
    1549         135 :   fd_lthash_zero( sum_post );
    1550             : 
    1551             :   /* Acquire and process stake accounts in batches of 32 */
    1552             : 
    1553         135 :   fd_pubkey_t   pubkeys         [ STAKE_REWARD_ACC_BATCH_SZ ];
    1554         135 :   uchar const * pubkey_ptrs     [ STAKE_REWARD_ACC_BATCH_SZ ];
    1555         135 :   int           writable        [ STAKE_REWARD_ACC_BATCH_SZ ];
    1556         135 :   fd_acc_t      accs            [ STAKE_REWARD_ACC_BATCH_SZ ];
    1557         135 :   ulong         reward_lamports [ STAKE_REWARD_ACC_BATCH_SZ ];
    1558         135 :   ulong         credits_observed[ STAKE_REWARD_ACC_BATCH_SZ ];
    1559             : 
    1560        4455 :   for( ulong i=0UL; i<STAKE_REWARD_ACC_BATCH_SZ; i++ ) {
    1561        4320 :     pubkey_ptrs[ i ] = pubkeys[ i ].uc;
    1562        4320 :     writable   [ i ] = 1;
    1563        4320 :   }
    1564             : 
    1565         135 :   fd_stake_rewards_iter_init( stake_rewards, bank->stake_rewards_fork_id, (ushort)partition_idx );
    1566         261 :   while( !fd_stake_rewards_iter_done( stake_rewards ) ) {
    1567             : 
    1568             :     /* Gather the next batch of rewards out of the partition. */
    1569         126 :     ulong batch_cnt = 0UL;
    1570         321 :     for( ; batch_cnt<STAKE_REWARD_ACC_BATCH_SZ && !fd_stake_rewards_iter_done( stake_rewards );
    1571         195 :          batch_cnt++, fd_stake_rewards_iter_next( stake_rewards, bank->stake_rewards_fork_id ) ) {
    1572         195 :       fd_stake_rewards_iter_ele( stake_rewards,
    1573         195 :                                  bank->stake_rewards_fork_id,
    1574         195 :                                  &pubkeys         [ batch_cnt ],
    1575         195 :                                  &reward_lamports [ batch_cnt ],
    1576         195 :                                  &credits_observed[ batch_cnt ] );
    1577         195 :     }
    1578             : 
    1579         126 :     fd_accdb_acquire( accdb, bank->accdb_fork_id, batch_cnt, pubkey_ptrs, writable, accs );
    1580             : 
    1581             :     /* Calculate and flush stake account updates */
    1582         321 :     for( ulong i=0UL; i<batch_cnt; i++ ) {
    1583         195 :       if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc( bank,
    1584         195 :                                                             capture_ctx,
    1585         195 :                                                             reward_lamports[ i ],
    1586         195 :                                                             credits_observed[ i ],
    1587         195 :                                                             &accs[ i ],
    1588         195 :                                                             adder_pre, sum_pre,
    1589         195 :                                                             adder_post, sum_post ) ) ) {
    1590         195 :         lamports_distributed += reward_lamports[ i ];
    1591         195 :       } else {
    1592           0 :         lamports_burned += reward_lamports[ i ];
    1593           0 :       }
    1594         195 :     }
    1595             : 
    1596         126 :     fd_accdb_release( accdb, batch_cnt, accs );
    1597         126 :   }
    1598             : 
    1599         135 :   fd_lthash_adder_flush( adder_pre,  sum_pre  );
    1600         135 :   fd_lthash_adder_flush( adder_post, sum_post );
    1601             : 
    1602         135 :   fd_lthash_value_t * bank_lthash = fd_bank_lthash_locking_modify( bank );
    1603         135 :   fd_lthash_sub( bank_lthash, sum_pre  );
    1604         135 :   fd_lthash_add( bank_lthash, sum_post );
    1605         135 :   fd_bank_lthash_end_locking_modify( bank );
    1606             : 
    1607             :   /* Update the epoch rewards sysvar with the amount distributed and burnt */
    1608         135 :   fd_sysvar_epoch_rewards_distribute( bank, accdb, capture_ctx, lamports_distributed + lamports_burned );
    1609             : 
    1610         135 :   FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
    1611             : 
    1612         135 :   bank->f.capitalization = bank->f.capitalization + lamports_distributed;
    1613         135 : }
    1614             : 
    1615             : static void
    1616             : recalculate_partitioned_rewards( fd_banks_t *         banks,
    1617             :                                  fd_bank_t *          bank,
    1618             :                                  fd_accdb_t *         accdb,
    1619             :                                  fd_runtime_stack_t * runtime_stack,
    1620             :                                  fd_capture_ctx_t *   capture_ctx,
    1621             :                                  int                  snapshot_boot,
    1622             :                                  uint                 win_lo );
    1623             : 
    1624             : /* Process reward distribution for the block if it is inside reward interval.
    1625             : 
    1626             :    https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L45-L136 */
    1627             : void
    1628             : fd_distribute_partitioned_epoch_rewards( fd_banks_t *         banks,
    1629             :                                          fd_bank_t *          bank,
    1630             :                                          fd_accdb_t *         accdb,
    1631             :                                          fd_runtime_stack_t * runtime_stack,
    1632        4479 :                                          fd_capture_ctx_t *   capture_ctx ) {
    1633             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L46-L48 */
    1634        4479 :   if( FD_LIKELY( bank->stake_rewards_fork_id==UCHAR_MAX ) ) return;
    1635             : 
    1636         399 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1637             : 
    1638         399 :   ulong block_height                       = bank->f.block_height;
    1639         399 :   ulong distribution_starting_block_height = fd_stake_rewards_starting_block_height( stake_rewards, bank->stake_rewards_fork_id );
    1640         399 :   ulong distribution_end_exclusive         = fd_stake_rewards_exclusive_ending_block_height( stake_rewards, bank->stake_rewards_fork_id );
    1641             : 
    1642             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L55-L58 */
    1643         399 :   if( FD_UNLIKELY( block_height<distribution_starting_block_height ) ) {
    1644         264 :     return;
    1645         264 :   }
    1646             : 
    1647             :   /* The logic in Agave for EpochRewardPhase::Calculation has no direct
    1648             :      equivalent in Firedancer, because reward calculation is done
    1649             :      eagerly at the epoch boundary, whereas for Agave it's done at the
    1650             :      first distribution block.
    1651             :      https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L60-L90 */
    1652             : 
    1653         135 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
    1654         135 :   ulong                       epoch          = bank->f.epoch;
    1655             : 
    1656         135 :   if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_schedule ) <= fd_stake_rewards_num_partitions( stake_rewards, bank->stake_rewards_fork_id ) ) ) {
    1657           0 :     FD_LOG_CRIT(( "Should not be distributing rewards" ));
    1658           0 :   }
    1659             : 
    1660             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L110-L114 */
    1661         135 :   if( FD_LIKELY( block_height>=distribution_starting_block_height && block_height<distribution_end_exclusive ) ) {
    1662         135 :     ulong partition_idx = block_height-distribution_starting_block_height;
    1663             : 
    1664             :     /* The rewards of this partition are only in memory if the window
    1665             :        covers it.  If they are not, re-derive the window that does. */
    1666         135 :     uchar fork_id = bank->stake_rewards_fork_id;
    1667         135 :     if( FD_UNLIKELY( partition_idx<(ulong)fd_stake_rewards_window_lo( stake_rewards, fork_id ) ||
    1668         135 :                      partition_idx>(ulong)fd_stake_rewards_window_hi( stake_rewards, fork_id ) ) ) {
    1669           0 :       FD_LOG_INFO(( "reward partition is not in the window, recalculating" ));
    1670           0 :       recalculate_partitioned_rewards( banks, bank, accdb, runtime_stack, capture_ctx, 0, (uint)partition_idx );
    1671           0 :       stake_rewards = fd_bank_stake_rewards_modify( bank );
    1672           0 :     }
    1673             : 
    1674         135 :     distribute_epoch_rewards_in_partition( stake_rewards, partition_idx, bank, accdb, capture_ctx );
    1675         135 :   }
    1676             : 
    1677             :   /* If we have finished distributing rewards, set the status to inactive
    1678             :      https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L116-L135 */
    1679         135 :   if( fd_ulong_sat_add( block_height, 1UL )>=distribution_end_exclusive ) {
    1680         129 :     fd_sysvar_epoch_rewards_set_inactive( bank, accdb, capture_ctx );
    1681         129 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    1682         129 :     bank->stake_rewards_fork_id = UCHAR_MAX;
    1683         129 :   }
    1684         135 : }
    1685             : 
    1686             : /* Partitioned epoch rewards entry-point.
    1687             : 
    1688             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L102
    1689             : */
    1690             : void
    1691             : fd_begin_partitioned_rewards( fd_bank_t *                    bank,
    1692             :                               fd_accdb_t *                   accdb,
    1693             :                               fd_runtime_stack_t *           runtime_stack,
    1694             :                               fd_capture_ctx_t *             capture_ctx,
    1695             :                               fd_stake_delegations_t const * stake_delegations,
    1696             :                               fd_hash_t const *              parent_blockhash,
    1697         264 :                               ulong                          parent_epoch ) {
    1698             : 
    1699         264 :   calculate_rewards_and_distribute_vote_rewards(
    1700         264 :       bank,
    1701         264 :       accdb,
    1702         264 :       runtime_stack,
    1703         264 :       stake_delegations,
    1704         264 :       capture_ctx,
    1705         264 :       parent_epoch );
    1706             : 
    1707             :   /* Once the rewards for vote accounts have been distributed and stake
    1708             :      account rewards have been calculated, we can now set our epoch
    1709             :      reward status to be active and we can initialize the epoch rewards
    1710             :      sysvar.  This sysvar is then deleted once all of the partitioned
    1711             :      stake rewards have been distributed.
    1712             : 
    1713             :      The Agave client calculates the partitions for each stake reward
    1714             :      when the first distribution block is reached.  The Firedancer
    1715             :      client differs here since we hash the partitions during the epoch
    1716             :      boundary. */
    1717             : 
    1718         264 :   ulong distribution_starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
    1719         264 :   uint  num_partitions                     = fd_stake_rewards_num_partitions( fd_bank_stake_rewards_query( bank ), bank->stake_rewards_fork_id );
    1720             : 
    1721         264 :   fd_sysvar_epoch_rewards_init(
    1722         264 :       bank,
    1723         264 :       accdb,
    1724         264 :       capture_ctx,
    1725         264 :       runtime_stack->stakes.distributed_rewards,
    1726         264 :       distribution_starting_block_height,
    1727         264 :       num_partitions,
    1728         264 :       runtime_stack->stakes.total_rewards,
    1729         264 :       runtime_stack->stakes.total_points.ud,
    1730         264 :       parent_blockhash );
    1731         264 : }
    1732             : 
    1733             : /*
    1734             :     Re-calculates partitioned stake rewards.
    1735             :     This updates the slot context's epoch reward status with the recalculated partitioned rewards.
    1736             : 
    1737             :     Everything the calculation needs survives in the epoch rewards
    1738             :     sysvar and in the bank, so the partitions can be rebuilt at any
    1739             :     point during the distribution interval.  The rebuilt partitions
    1740             :     always go into a freshly acquired fork, with its window starting at
    1741             :     win_lo, so that banks reading the fork this bank was using keep the
    1742             :     entries they computed for themselves.  snapshot_boot selects where
    1743             :     the vote commissions come from: a bank restored from a snapshot has
    1744             :     to resolve them, every other caller reuses the ones the epoch
    1745             :     boundary resolved.
    1746             : 
    1747             :     https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521 */
    1748             : static void
    1749             : recalculate_partitioned_rewards( fd_banks_t *              banks,
    1750             :                                  fd_bank_t *               bank,
    1751             :                                  fd_accdb_t *              accdb,
    1752             :                                  fd_runtime_stack_t *      runtime_stack,
    1753             :                                  fd_capture_ctx_t *        capture_ctx,
    1754             :                                  int                       snapshot_boot,
    1755           3 :                                  uint                      win_lo ) {
    1756             : 
    1757           3 :   fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
    1758           3 :   fd_vote_rewards_map_reset( vote_ele_map );
    1759             : 
    1760           3 :   ulong                epoch_credits_len = *fd_bank_epoch_credits_len( bank );
    1761           3 :   fd_epoch_credits_t * epoch_credits_arr = fd_bank_epoch_credits( bank );
    1762             : 
    1763           3 :   if( FD_LIKELY( !snapshot_boot ) ) {
    1764             : 
    1765             :     /* The rewards of this epoch were computed once already, at the epoch
    1766             :        boundary or at snapshot load, and only the window is moving.  The
    1767             :        commissions resolved back then are stored with the epoch credits
    1768             :        and must be reused: they come from a vote account snapshot two
    1769             :        epochs behind the rewarded epoch, which the top votes sets no
    1770             :        longer retain. */
    1771             : 
    1772           0 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    1773           0 :       fd_epoch_credits_t const * epoch_credits = &epoch_credits_arr[i];
    1774             : 
    1775           0 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
    1776           0 :       vote_ele->pubkey       = *(fd_pubkey_t const *)epoch_credits->pubkey;
    1777           0 :       vote_ele->vote_rewards = 0UL;
    1778           0 :       vote_ele->commission   = epoch_credits->commission;
    1779           0 :       fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
    1780           0 :     }
    1781             : 
    1782           3 :   } else {
    1783             : 
    1784             :     /* If the snapshot was loaded while partitioned epoch rewards is
    1785             :        active, then the vote rewards map must be populated with the state
    1786             :        of the vote accounts as of the end of the previous epoch boundary.
    1787             :        The epoch credits for these accounts are stored in the bank along
    1788             :        with the t-3 commission.  With this, it's possible to recalculate
    1789             :        the rewards for the previous epoch boundary.  We need the
    1790             :        commission from the end of the t-3 epoch if we are calculating
    1791             :        rewards for the transition from epoch t-1 to t since there needs to
    1792             :        be a 2 epoch commission gap for the delay_commission_updates
    1793             :        feature. */
    1794             : 
    1795           3 :     fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
    1796           3 :     ulong              fork_id     = bank->vote_stakes_fork_id;
    1797             : 
    1798           6 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    1799           3 :       fd_epoch_credits_t * epoch_credits = &epoch_credits_arr[i];
    1800           3 :       fd_pubkey_t const *  pubkey        = (fd_pubkey_t const *)epoch_credits->pubkey;
    1801             : 
    1802             :       /* Get the t-1 stake account information.  This is guaranteed to be
    1803             :          valid since the epoch credits are populated from the t-1 stakes
    1804             :          in the snapshot manifest. */
    1805           3 :       ushort commission_t_1 = 0;
    1806           3 :       FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, fork_id, pubkey, NULL, NULL, &commission_t_1 ) );
    1807             : 
    1808             :       /* Now get the t-2 information (if it exists).  This is not
    1809             :          guaranteed to be valid since it's possible for a vote account to
    1810             :          have been created in the last epoch. */
    1811           3 :       int    exists_t_2     = 0;
    1812           3 :       ushort commission_t_2 = 0;
    1813           3 :       exists_t_2 = fd_vote_stakes_query_t_2( vote_stakes, fork_id, pubkey, NULL, NULL, NULL, NULL, &commission_t_2, NULL );
    1814             : 
    1815           3 :       fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
    1816           3 :       vote_ele->pubkey       = *(fd_pubkey_t *)epoch_credits->pubkey;
    1817           3 :       vote_ele->vote_rewards = 0UL;
    1818           3 :       if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
    1819           0 :         vote_ele->commission = exists_t_2 ? commission_t_2 : commission_t_1;
    1820           3 :       } else {
    1821           3 :         vote_ele->commission = commission_t_1;
    1822           3 :       }
    1823           3 :       fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
    1824           3 :     }
    1825             : 
    1826             :     /* Copy in historical commission information if it exists. */
    1827           3 :     if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
    1828           0 :       ulong                     commission_t_3_len = *fd_bank_snapshot_commission_t_3_len( bank );
    1829           0 :       fd_stashed_commission_t * commission_t_3     = fd_bank_snapshot_commission_t_3( bank );
    1830           0 :       for( ulong i=0UL; i<commission_t_3_len; i++ ) {
    1831           0 :         fd_stashed_commission_t const * ele = &commission_t_3[i];
    1832           0 :         fd_vote_rewards_t * vote_ele = fd_vote_rewards_map_ele_query( vote_ele_map, (fd_pubkey_t *)ele->pubkey, NULL, runtime_stack->stakes.vote_ele );
    1833           0 :         if( FD_LIKELY( vote_ele ) ) vote_ele->commission = ele->commission;
    1834           0 :       }
    1835           0 :     }
    1836             : 
    1837             :     /* Publish the resolved commissions so that any later repositioning
    1838             :        of this fork's window reuses them. */
    1839           6 :     for( ulong i=0UL; i<epoch_credits_len; i++ ) {
    1840           3 :       epoch_credits_arr[i].commission = runtime_stack->stakes.vote_ele[i].commission;
    1841           3 :     }
    1842           3 :   }
    1843             : 
    1844           3 :   fd_sysvar_epoch_rewards_t epoch_rewards_sysvar[1];
    1845           3 :   if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read( accdb, bank->accdb_fork_id, epoch_rewards_sysvar ) ) ) {
    1846           0 :     FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
    1847           0 :     return;
    1848           0 :   }
    1849             : 
    1850           3 :   FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
    1851             : 
    1852           3 :   if( FD_UNLIKELY( !epoch_rewards_sysvar->active ) ) {
    1853           0 :     FD_LOG_DEBUG(( "epoch rewards is inactive" ));
    1854           0 :     return;
    1855           0 :   }
    1856             : 
    1857             :   /* If partitioned rewards are active, the rewarded epoch is always the immediately
    1858             :       preceding epoch.
    1859             : 
    1860             :       https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
    1861           3 :   FD_LOG_DEBUG(( "epoch rewards is active" ));
    1862             : 
    1863           3 :   ulong const epoch          = bank->f.epoch;
    1864           3 :   ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
    1865             : 
    1866           3 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    1867           3 :   fd_stake_history_t stake_history[1];
    1868           3 :   read_stake_history( accdb, bank->accdb_fork_id, stake_history_data, stake_history );
    1869             : 
    1870           3 :   fd_stake_delegations_t const * stake_delegations = fd_bank_stake_delegations_frontier_query( banks, bank );
    1871             : 
    1872           3 :   calculate_stake_vote_rewards(
    1873           3 :       bank,
    1874           3 :       accdb,
    1875           3 :       stake_delegations,
    1876           3 :       capture_ctx,
    1877           3 :       stake_history,
    1878           3 :       rewarded_epoch,
    1879           3 :       epoch_rewards_sysvar->total_rewards,
    1880           3 :       epoch_rewards_sysvar->total_points.ud,
    1881           3 :       runtime_stack,
    1882           3 :       1 );
    1883             : 
    1884             :   /* The recomputed rewards go into a fork of their own rather than over
    1885             :      the window the bank is holding: banks that branched off this one
    1886             :      share that window and are entitled to the entries they already have,
    1887             :      which were computed from their own delegations. */
    1888             : 
    1889           3 :   fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
    1890           3 :   uchar                fork_idx      = fd_stake_rewards_init( stake_rewards,
    1891           3 :                                                               bank->f.epoch,
    1892           3 :                                                               &epoch_rewards_sysvar->parent_blockhash,
    1893           3 :                                                               epoch_rewards_sysvar->distribution_starting_block_height,
    1894           3 :                                                               (uint)epoch_rewards_sysvar->num_partitions,
    1895           3 :                                                               runtime_stack->stakes.stake_rewards_cnt );
    1896           3 :   if( FD_LIKELY( win_lo ) ) {
    1897           0 :     fd_stake_rewards_window_advance( stake_rewards,
    1898           0 :                                      fork_idx,
    1899           0 :                                      &epoch_rewards_sysvar->parent_blockhash,
    1900           0 :                                      win_lo,
    1901           0 :                                      runtime_stack->stakes.stake_rewards_cnt );
    1902           0 :   }
    1903           3 :   if( FD_LIKELY( bank->stake_rewards_fork_id!=UCHAR_MAX ) ) {
    1904           0 :     fd_stake_rewards_release( stake_rewards, bank->stake_rewards_fork_id );
    1905           0 :   }
    1906           3 :   bank->stake_rewards_fork_id = fork_idx;
    1907             : 
    1908           3 :   setup_stake_partitions(
    1909           3 :       bank,
    1910           3 :       accdb,
    1911           3 :       stake_history,
    1912           3 :       stake_delegations,
    1913           3 :       runtime_stack,
    1914           3 :       fork_idx,
    1915           3 :       rewarded_epoch,
    1916           3 :       epoch_rewards_sysvar->total_rewards,
    1917           3 :       epoch_rewards_sysvar->total_points.ud );
    1918             : 
    1919           3 :   fd_bank_stake_delegations_end_frontier_query( banks, bank );
    1920           3 : }
    1921             : 
    1922             : void
    1923             : fd_rewards_recalculate_partitioned_rewards( fd_banks_t *         banks,
    1924             :                                             fd_bank_t *          bank,
    1925             :                                             fd_accdb_t *         accdb,
    1926             :                                             fd_runtime_stack_t * runtime_stack,
    1927           3 :                                             fd_capture_ctx_t *   capture_ctx ) {
    1928           3 :   recalculate_partitioned_rewards( banks, bank, accdb, runtime_stack, capture_ctx, 1, 0U );
    1929           3 : }

Generated by: LCOV version 1.14