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