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