Line data Source code
1 : #include "fd_rewards.h"
2 : #include "fd_stake_rewards.h"
3 : #include <math.h>
4 :
5 : #include "../runtime/sysvar/fd_sysvar_epoch_rewards.h"
6 : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
7 : #include "../stakes/fd_stakes.h"
8 : #include "../runtime/program/vote/fd_vote_codec.h"
9 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
10 : #include "../runtime/fd_system_ids.h"
11 : #include "../capture/fd_capture_ctx.h"
12 : #include "../runtime/fd_runtime_stack.h"
13 : #include "../runtime/fd_accdb_svm.h"
14 : #include "../runtime/fd_hashes.h"
15 : #include "../accdb/fd_accdb_sync.h"
16 :
17 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
18 : static double
19 339 : total( fd_inflation_t const * inflation, double year ) {
20 339 : double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
21 339 : return (tapered > inflation->terminal) ? tapered : inflation->terminal;
22 339 : }
23 :
24 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
25 : static double
26 387 : foundation( fd_inflation_t const * inflation, double year ) {
27 387 : return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
28 387 : }
29 :
30 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
31 : static double
32 129 : validator( fd_inflation_t const * inflation, double year) {
33 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
34 129 : FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
35 129 : return total( inflation, year ) - foundation( inflation, year );
36 129 : }
37 :
38 : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
39 : activation slot of the following features:
40 : - devnet_and_testnet
41 : - full_inflation_enable, if full_inflation_vote has been activated
42 :
43 : https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
44 : static FD_FN_CONST ulong
45 129 : get_inflation_start_slot( fd_bank_t const * bank ) {
46 129 : ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
47 129 : ? bank->f.features.devnet_and_testnet
48 129 : : ULONG_MAX;
49 :
50 129 : ulong enable = bank->f.features.full_inflation_enable;
51 :
52 129 : ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
53 129 : if( min_slot == ULONG_MAX ) {
54 129 : if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
55 6 : min_slot = bank->f.features.pico_inflation;
56 123 : } else {
57 123 : min_slot = 0;
58 123 : }
59 129 : }
60 129 : return min_slot;
61 129 : }
62 :
63 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2110 */
64 : static ulong
65 : get_inflation_num_slots( fd_bank_t const * bank,
66 : fd_epoch_schedule_t const * epoch_schedule,
67 129 : ulong slot ) {
68 129 : ulong inflation_activation_slot = get_inflation_start_slot( bank );
69 129 : ulong inflation_start_slot = fd_epoch_slot0( epoch_schedule,
70 129 : fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule,
71 129 : inflation_activation_slot, NULL ),
72 129 : 1UL ) );
73 :
74 129 : ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, NULL );
75 :
76 129 : return fd_epoch_slot0( epoch_schedule, epoch ) - inflation_start_slot;
77 129 : }
78 :
79 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2121 */
80 : static double
81 129 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
82 129 : fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
83 129 : ulong num_slots = get_inflation_num_slots( bank, epoch_schedule, bank->f.slot );
84 129 : return (double)num_slots / (double)bank->f.slots_per_year;
85 129 : }
86 :
87 : /* For a given stake and vote_state, calculate how many points were earned (credits * stake) and new value
88 : for credits_observed were the points paid
89 :
90 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
91 : static void
92 : calculate_stake_points_and_credits( fd_epoch_credits_t * epoch_credits,
93 : fd_stake_history_t const * stake_history,
94 : fd_stake_delegation_t const * stake,
95 : ulong * new_rate_activation_epoch,
96 135 : fd_calculated_stake_points_t * result ) {
97 :
98 135 : ulong credits_in_stake = stake->credits_observed;
99 135 : ulong credits_cnt = epoch_credits->cnt;
100 135 : ulong base = epoch_credits->base_credits;
101 135 : ulong credits_in_vote = credits_cnt > 0UL ? base + epoch_credits->credits_delta[ credits_cnt - 1UL ] : 0UL;
102 :
103 :
104 : /* If the Vote account has less credits observed than the Stake account,
105 : something is wrong and we need to force an update.
106 :
107 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
108 135 : if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
109 3 : result->points.ud = 0;
110 3 : result->new_credits_observed = credits_in_vote;
111 3 : result->force_credits_update_with_skipped_reward = 1;
112 3 : return;
113 3 : }
114 :
115 : /* If the Vote account has the same amount of credits observed as the Stake account,
116 : then the Vote account hasn't earnt any credits and so there is nothing to update.
117 :
118 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
119 132 : if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
120 102 : result->points.ud = 0;
121 102 : result->new_credits_observed = credits_in_vote;
122 102 : result->force_credits_update_with_skipped_reward = 0;
123 102 : return;
124 102 : }
125 :
126 : /* Calculate the points for each epoch credit */
127 30 : uint128 points = 0;
128 30 : ulong new_credits_observed = credits_in_stake;
129 60 : for( ulong i=0UL; i<epoch_credits->cnt; i++ ) {
130 :
131 30 : ulong final_epoch_credits = base + epoch_credits->credits_delta[ i ];
132 30 : ulong initial_epoch_credits = base + epoch_credits->prev_credits_delta[ i ];
133 :
134 : /* Vote account credits can only increase or stay the same, so
135 : initial_epoch_credits <= final_epoch_credits always holds. */
136 30 : FD_TEST( initial_epoch_credits<=final_epoch_credits );
137 :
138 : /* If final_epoch_credits <= credits_in_stake, then:
139 : initial_epoch_credits <= final_epoch_credits <= credits_in_stake
140 :
141 : * earned_credits = 0 since both conditions are false.
142 : * new_credits_observed stays the same since it is already set
143 : to credits_in_stake and final_epoch_credits <= credits_in_stake
144 :
145 : Since earned_credits = 0 and new_credits_observed stays the same,
146 : points computation can be skipped. */
147 30 : if( FD_LIKELY( final_epoch_credits<=credits_in_stake ) ) continue;
148 :
149 30 : uint128 earned_credits = 0;
150 30 : if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
151 0 : earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
152 30 : } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
153 30 : earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
154 30 : }
155 :
156 30 : new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
157 :
158 30 : ulong stake_amount = fd_stakes_activating_and_deactivating(
159 30 : stake,
160 30 : epoch_credits->epoch[ i ],
161 30 : stake_history,
162 30 : new_rate_activation_epoch ).effective;
163 :
164 30 : points += (uint128)stake_amount * earned_credits;
165 30 : }
166 :
167 30 : result->points.ud = points;
168 30 : result->new_credits_observed = new_credits_observed;
169 30 : result->force_credits_update_with_skipped_reward = 0;
170 30 : }
171 :
172 : /// returns commission split as (voter_portion, staker_portion, was_split) tuple
173 : ///
174 : /// if commission calculation is 100% one way or other, indicate with false for was_split
175 :
176 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L543
177 : void
178 : fd_vote_commission_split( uchar commission,
179 : ulong on,
180 48 : fd_commission_split_t * result ) {
181 48 : uint commission_split = fd_uint_min( (uint)commission, 100 );
182 48 : result->is_split = (commission_split != 0 && commission_split != 100);
183 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L545
184 48 : if( commission_split==0U ) {
185 18 : result->voter_portion = 0;
186 18 : result->staker_portion = on;
187 18 : return;
188 18 : }
189 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L546
190 30 : if( commission_split==100U ) {
191 9 : result->voter_portion = on;
192 9 : result->staker_portion = 0;
193 9 : return;
194 9 : }
195 : /* Note: order of operations may matter for int division. That's why I didn't make the
196 : * optimization of getting out the common calculations */
197 :
198 : // ... This is copied from the solana comments...
199 : //
200 : // Calculate mine and theirs independently and symmetrically instead
201 : // of using the remainder of the other to treat them strictly
202 : // equally. This is also to cancel the rewarding if either of the
203 : // parties should receive only fractional lamports, resulting in not
204 : // being rewarded at all. Thus, note that we intentionally discard
205 : // any residual fractional lamports.
206 :
207 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L548
208 21 : result->voter_portion =
209 21 : (ulong)((uint128)on * (uint128)commission_split / (uint128)100);
210 21 : result->staker_portion =
211 21 : (ulong)((uint128)on * (uint128)( 100 - commission_split ) / (uint128)100);
212 21 : }
213 :
214 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
215 : static int
216 : redeem_rewards( fd_stake_delegation_t const * stake,
217 : ulong vote_state_idx,
218 : ulong rewarded_epoch,
219 : ulong total_rewards,
220 : uint128 total_points,
221 : fd_runtime_stack_t * runtime_stack,
222 : fd_calculated_stake_points_t * stake_points_result,
223 135 : fd_calculated_stake_rewards_t * result ) {
224 :
225 : /* The firedancer implementation of redeem_rewards inlines a lot of
226 : the helper functions that the Agave implementation uses.
227 : In Agave: redeem_rewards calls redeem_stake_rewards which calls
228 : calculate_stake_rewards. */
229 :
230 : // Drive credits_observed forward unconditionally when rewards are disabled
231 : // or when this is the stake's activation epoch
232 135 : if( total_rewards==0UL || stake->activation_epoch==rewarded_epoch ) {
233 111 : stake_points_result->force_credits_update_with_skipped_reward = 1;
234 111 : }
235 :
236 135 : if( stake_points_result->force_credits_update_with_skipped_reward ) {
237 111 : result->staker_rewards = 0;
238 111 : result->voter_rewards = 0;
239 111 : result->new_credits_observed = stake_points_result->new_credits_observed;
240 111 : return 0;
241 111 : }
242 24 : if( stake_points_result->points.ud==0 || total_points==0 ) {
243 0 : return 1;
244 0 : }
245 :
246 24 : uint128 rewards_u128;
247 24 : if( FD_UNLIKELY( __builtin_mul_overflow( stake_points_result->points.ud, (uint128)(total_rewards), &rewards_u128 ) ) ) {
248 0 : FD_LOG_ERR(( "Rewards intermediate calculation should fit within u128" ));
249 0 : }
250 :
251 24 : FD_TEST( total_points );
252 24 : rewards_u128 /= (uint128) total_points;
253 :
254 24 : if( FD_UNLIKELY( rewards_u128>(uint128)ULONG_MAX ) ) {
255 0 : FD_LOG_ERR(( "Rewards should fit within u64" ));
256 0 : }
257 :
258 24 : ulong rewards = (ulong)rewards_u128;
259 24 : if( rewards == 0 ) {
260 0 : return 1;
261 0 : }
262 :
263 24 : fd_commission_split_t split_result;
264 24 : fd_vote_commission_split( runtime_stack->stakes.vote_ele[ vote_state_idx ].commission, rewards, &split_result );
265 24 : if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
266 3 : return 1;
267 3 : }
268 :
269 21 : result->staker_rewards = split_result.staker_portion;
270 21 : result->voter_rewards = split_result.voter_portion;
271 21 : result->new_credits_observed = stake_points_result->new_credits_observed;
272 21 : return 0;
273 24 : }
274 :
275 : /* Returns the length of the given epoch in slots
276 :
277 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
278 : static ulong
279 : get_slots_in_epoch( ulong epoch,
280 192 : fd_epoch_schedule_t const * epoch_schedule ) {
281 192 : return epoch < epoch_schedule->first_normal_epoch ?
282 0 : 1UL << fd_ulong_sat_add( epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO ) :
283 192 : epoch_schedule->slots_per_epoch;
284 192 : }
285 :
286 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
287 : static double
288 : epoch_duration_in_years( fd_bank_t const * bank,
289 129 : ulong prev_epoch ) {
290 129 : ulong slots_in_epoch = get_slots_in_epoch( prev_epoch, &bank->f.epoch_schedule );
291 129 : return (double)slots_in_epoch / (double)bank->f.slots_per_year;
292 129 : }
293 :
294 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
295 : static void
296 : calculate_previous_epoch_inflation_rewards( fd_bank_t const * bank,
297 : ulong prev_epoch_capitalization,
298 : ulong prev_epoch,
299 129 : fd_prev_epoch_inflation_rewards_t * rewards ) {
300 129 : double slot_in_year = slot_in_year_for_inflation( bank );
301 :
302 129 : rewards->validator_rate = validator( &bank->f.inflation, slot_in_year );
303 129 : rewards->foundation_rate = foundation( &bank->f.inflation, slot_in_year );
304 129 : rewards->prev_epoch_duration_in_years = epoch_duration_in_years( bank, prev_epoch );
305 129 : rewards->validator_rewards = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
306 129 : 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 ));
307 129 : }
308 :
309 : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
310 :
311 : https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
312 : */
313 : static uint
314 : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
315 : ulong slot,
316 141 : ulong total_stake_accounts ) {
317 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
318 141 : if( epoch_schedule->warmup &&
319 141 : fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
320 3 : return 1UL;
321 3 : }
322 :
323 138 : ulong num_chunks = total_stake_accounts / (ulong)STAKE_ACCOUNT_STORES_PER_BLOCK + (total_stake_accounts % STAKE_ACCOUNT_STORES_PER_BLOCK != 0);
324 138 : num_chunks = fd_ulong_max( num_chunks, 1UL );
325 138 : num_chunks = fd_ulong_min( num_chunks,
326 138 : fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
327 138 : return (uint)num_chunks;
328 141 : }
329 :
330 : uint
331 : fd_rewards_get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
332 : ulong slot,
333 12 : ulong total_stake_accounts ) {
334 12 : return get_reward_distribution_num_blocks( epoch_schedule, slot, total_stake_accounts );
335 12 : }
336 :
337 : /* Calculates epoch reward points from stake/vote accounts.
338 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L445 */
339 : static uint128
340 : calculate_reward_points_partitioned( fd_bank_t * bank,
341 : fd_stake_delegations_t const * stake_delegations,
342 : fd_stake_history_t const * stake_history,
343 129 : fd_runtime_stack_t * runtime_stack ) {
344 : /* Calculate the points for each stake delegation */
345 129 : uint128 total_points = 0;
346 :
347 129 : fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
348 129 : fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
349 :
350 129 : fd_stake_delegations_iter_t iter_[1];
351 129 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
352 264 : !fd_stake_delegations_iter_done( iter );
353 135 : fd_stake_delegations_iter_next( iter ) ) {
354 135 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
355 135 : ulong stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
356 :
357 : /* Note that we don't check minimum delegation here, as there are
358 : no plans to activate stake_minimum_delegation_for_rewards.
359 : If this changes we need to skip stake accounts that are
360 : below the minimum delegation here. However we don't do this yet,
361 : to ensure that we audit the feature properly if this happens. */
362 :
363 135 : uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
364 135 : if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
365 :
366 135 : fd_calculated_stake_points_t stake_points_result_[1];
367 135 : fd_calculated_stake_points_t * stake_points_result;
368 135 : if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) ) {
369 0 : stake_points_result = stake_points_result_;
370 135 : } else {
371 135 : stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
372 135 : }
373 :
374 135 : fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[ idx ];
375 :
376 135 : calculate_stake_points_and_credits( epoch_credits,
377 135 : stake_history,
378 135 : stake_delegation,
379 135 : &bank->f.warmup_cooldown_rate_epoch,
380 135 : stake_points_result );
381 :
382 135 : total_points += stake_points_result->points.ud;
383 135 : }
384 :
385 129 : return total_points;
386 129 : }
387 :
388 : /* Calculates epoch rewards for stake/vote accounts.
389 : Returns vote rewards, stake rewards, and the sum of all stake rewards
390 : in lamports.
391 :
392 : In the future, the calculation will be cached in the snapshot, but
393 : for now we just re-calculate it (as Agave does).
394 : calculate_stake_vote_rewards is responsible for calculating
395 : stake account rewards based off of a combination of the
396 : stake delegation state as well as the vote account. If this
397 : calculation is done at the end of an epoch, we can just use the
398 : vote states at the end of the current epoch. However, because we
399 : are presumably booting up a node in the middle of rewards
400 : distribution, we need to make sure that we are using the vote
401 : states from the end of the previous epoch.
402 :
403 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
404 : static void
405 : calculate_stake_vote_rewards( fd_bank_t * bank,
406 : fd_stake_delegations_t const * stake_delegations,
407 : fd_capture_ctx_t * capture_ctx FD_PARAM_UNUSED,
408 : fd_stake_history_t const * stake_history,
409 : ulong rewarded_epoch,
410 : ulong total_rewards,
411 : uint128 total_points,
412 : fd_runtime_stack_t * runtime_stack,
413 129 : int is_recalculation ) {
414 :
415 129 : runtime_stack->stakes.stake_rewards_cnt = 0UL;
416 :
417 129 : fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
418 :
419 129 : fd_stake_delegations_iter_t iter_[1];
420 129 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
421 264 : !fd_stake_delegations_iter_done( iter );
422 135 : fd_stake_delegations_iter_next( iter ) ) {
423 135 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
424 135 : ulong stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
425 :
426 : /* Note that we don't check minimum delegation here, as there are
427 : no plans to activate stake_minimum_delegation_for_rewards.
428 : If this changes we need to skip stake accounts that are
429 : below the minimum delegation here. However we don't do this yet,
430 : to ensure that we audit the feature properly if this happens. */
431 :
432 135 : fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
433 135 : if( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) {
434 0 : calculated_stake_rewards = calculated_stake_rewards_;
435 135 : } else {
436 135 : calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
437 135 : }
438 135 : calculated_stake_rewards->success = 0;
439 :
440 135 : fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
441 135 : fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
442 135 : uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
443 135 : if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
444 :
445 135 : fd_calculated_stake_points_t stake_points_result_[1];
446 135 : fd_calculated_stake_points_t * stake_points_result;
447 135 : if( is_recalculation || FD_UNLIKELY( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) ) {
448 0 : fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[ idx ];
449 :
450 : /* We have not cached the stake points yet if we are recalculating
451 : stake rewards so we need to recalculate them. */
452 0 : calculate_stake_points_and_credits(
453 0 : epoch_credits,
454 0 : stake_history,
455 0 : stake_delegation,
456 0 : &bank->f.warmup_cooldown_rate_epoch,
457 0 : stake_points_result_ );
458 0 : stake_points_result = stake_points_result_;
459 135 : } else {
460 135 : stake_points_result = &runtime_stack->stakes.stake_points_result[ stake_delegation_idx ];
461 135 : }
462 :
463 : /* redeem_rewards is actually just responsible for calculating the
464 : vote and stake rewards for each stake account. It does not do
465 : rewards redemption: it is a misnomer. */
466 135 : int err = redeem_rewards(
467 135 : stake_delegation,
468 135 : idx,
469 135 : rewarded_epoch,
470 135 : total_rewards,
471 135 : total_points,
472 135 : runtime_stack,
473 135 : stake_points_result,
474 135 : calculated_stake_rewards );
475 :
476 135 : if( FD_UNLIKELY( err!=0 ) ) {
477 3 : continue;
478 3 : }
479 :
480 132 : calculated_stake_rewards->success = 1;
481 :
482 132 : if( capture_ctx && capture_ctx->capture_solcap ) {
483 0 : fd_capture_link_write_stake_reward_event( capture_ctx,
484 0 : bank->f.slot,
485 0 : stake_delegation->stake_account,
486 0 : stake_delegation->vote_account,
487 0 : runtime_stack->stakes.vote_ele[ idx ].commission,
488 0 : (long)calculated_stake_rewards->voter_rewards,
489 0 : (long)calculated_stake_rewards->staker_rewards,
490 0 : (long)calculated_stake_rewards->new_credits_observed );
491 0 : }
492 :
493 132 : runtime_stack->stakes.vote_ele[ idx ].vote_rewards += calculated_stake_rewards->voter_rewards;
494 132 : runtime_stack->stakes.stake_rewards_cnt++;
495 132 : }
496 129 : }
497 :
498 : static void
499 : setup_stake_partitions( fd_bank_t * bank,
500 : fd_stake_history_t const * stake_history,
501 : fd_stake_delegations_t const * stake_delegations,
502 : fd_runtime_stack_t * runtime_stack,
503 : fd_hash_t const * parent_blockhash,
504 : ulong starting_block_height,
505 : uint num_partitions,
506 : ulong rewarded_epoch,
507 : ulong total_rewards,
508 129 : uint128 total_points ) {
509 :
510 129 : fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
511 129 : uchar fork_idx = fd_stake_rewards_init( stake_rewards, bank->f.epoch, parent_blockhash, starting_block_height, (uint)num_partitions );
512 129 : bank->stake_rewards_fork_id = fork_idx;
513 :
514 129 : fd_stake_delegations_iter_t iter_[1];
515 129 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
516 264 : !fd_stake_delegations_iter_done( iter );
517 135 : fd_stake_delegations_iter_next( iter ) ) {
518 135 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
519 135 : ulong stake_delegation_idx = fd_stake_delegations_iter_idx( iter );
520 :
521 135 : fd_calculated_stake_rewards_t calculated_stake_rewards_[1];
522 135 : fd_calculated_stake_rewards_t * calculated_stake_rewards = NULL;
523 :
524 135 : if( FD_UNLIKELY( stake_delegation_idx>=runtime_stack->expected_stake_accounts ) ) {
525 :
526 0 : calculated_stake_rewards = calculated_stake_rewards_;
527 :
528 0 : fd_vote_rewards_t * vote_ele = runtime_stack->stakes.vote_ele;
529 0 : fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
530 0 : uint idx = (uint)fd_vote_rewards_map_idx_query( vote_ele_map, &stake_delegation->vote_account, UINT_MAX, vote_ele );
531 0 : if( FD_UNLIKELY( idx==UINT_MAX ) ) continue;
532 :
533 0 : fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[ idx ];
534 :
535 0 : fd_calculated_stake_points_t stake_points_result[1];
536 0 : calculate_stake_points_and_credits(
537 0 : epoch_credits,
538 0 : stake_history,
539 0 : stake_delegation,
540 0 : &bank->f.warmup_cooldown_rate_epoch,
541 0 : stake_points_result );
542 :
543 : /* redeem_rewards is actually just responsible for calculating the
544 : vote and stake rewards for each stake account. It does not do
545 : rewards redemption: it is a misnomer. */
546 0 : int err = redeem_rewards(
547 0 : stake_delegation,
548 0 : idx,
549 0 : rewarded_epoch,
550 0 : total_rewards,
551 0 : total_points,
552 0 : runtime_stack,
553 0 : stake_points_result,
554 0 : calculated_stake_rewards );
555 0 : calculated_stake_rewards->success = err==0;
556 135 : } else {
557 135 : calculated_stake_rewards = &runtime_stack->stakes.stake_rewards_result[ stake_delegation_idx ];
558 135 : }
559 :
560 135 : if( FD_UNLIKELY( !calculated_stake_rewards->success ) ) continue;
561 :
562 132 : fd_stake_rewards_insert(
563 132 : stake_rewards,
564 132 : fork_idx,
565 132 : &stake_delegation->stake_account,
566 132 : calculated_stake_rewards->staker_rewards,
567 132 : calculated_stake_rewards->new_credits_observed
568 132 : );
569 132 : }
570 129 : }
571 :
572 : /* Calculate epoch reward and return vote and stake rewards.
573 :
574 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
575 : static uint128
576 : calculate_validator_rewards( fd_bank_t * bank,
577 : fd_accdb_user_t * accdb,
578 : fd_funk_txn_xid_t const * xid,
579 : fd_runtime_stack_t * runtime_stack,
580 : fd_stake_delegations_t const * stake_delegations,
581 : fd_capture_ctx_t * capture_ctx,
582 : ulong rewarded_epoch,
583 129 : ulong * rewards_out ) {
584 :
585 129 : fd_accdb_ro_t sh_ro[1];
586 129 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, sh_ro, xid, &fd_sysvar_stake_history_id ) ) ) {
587 0 : FD_LOG_ERR(( "Unable to read stake history sysvar" ));
588 0 : }
589 129 : fd_stake_history_t stake_history[1];
590 129 : if( FD_UNLIKELY( !fd_sysvar_stake_history_view( stake_history, fd_accdb_ref_data_const( sh_ro ), fd_accdb_ref_data_sz( sh_ro ) ) ) ) {
591 0 : FD_LOG_ERR(( "Unable to decode stake history sysvar" ));
592 0 : }
593 :
594 : /* Calculate the epoch reward points from stake/vote accounts */
595 129 : uint128 total_points = calculate_reward_points_partitioned(
596 129 : bank,
597 129 : stake_delegations,
598 129 : stake_history,
599 129 : runtime_stack );
600 :
601 : /* If there are no points, then we set the rewards to 0. */
602 129 : *rewards_out = total_points>0UL ? *rewards_out: 0UL;
603 :
604 129 : if( capture_ctx && capture_ctx->capture_solcap ) {
605 0 : ulong epoch = bank->f.epoch;
606 0 : ulong slot = bank->f.slot;
607 0 : fd_capture_link_write_stake_rewards_begin( capture_ctx,
608 0 : slot,
609 0 : epoch,
610 0 : epoch-1UL, /* FIXME: this is not strictly correct */
611 0 : *rewards_out,
612 0 : (ulong)total_points );
613 0 : }
614 :
615 : /* Calculate the stake and vote rewards for each account. We want to
616 : use the vote states from the end of the current_epoch. */
617 129 : calculate_stake_vote_rewards(
618 129 : bank,
619 129 : stake_delegations,
620 129 : capture_ctx,
621 129 : stake_history,
622 129 : rewarded_epoch,
623 129 : *rewards_out,
624 129 : total_points,
625 129 : runtime_stack,
626 129 : 0 );
627 :
628 129 : fd_hash_t const * parent_blockhash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
629 129 : ulong starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
630 129 : uint num_partitions = get_reward_distribution_num_blocks( &bank->f.epoch_schedule,
631 129 : bank->f.slot,
632 129 : runtime_stack->stakes.stake_rewards_cnt );
633 :
634 129 : setup_stake_partitions(
635 129 : bank,
636 129 : stake_history,
637 129 : stake_delegations,
638 129 : runtime_stack,
639 129 : parent_blockhash,
640 129 : starting_block_height,
641 129 : num_partitions,
642 129 : rewarded_epoch,
643 129 : *rewards_out,
644 129 : total_points );
645 :
646 129 : fd_accdb_close_ro( accdb, sh_ro );
647 129 : return total_points;
648 129 : }
649 :
650 : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
651 :
652 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L277 */
653 : static void
654 : calculate_rewards_for_partitioning( fd_bank_t * bank,
655 : fd_accdb_user_t * accdb,
656 : fd_funk_txn_xid_t const * xid,
657 : fd_runtime_stack_t * runtime_stack,
658 : fd_stake_delegations_t const * stake_delegations,
659 : fd_capture_ctx_t * capture_ctx,
660 : ulong prev_epoch,
661 129 : fd_partitioned_rewards_calculation_t * result ) {
662 129 : fd_prev_epoch_inflation_rewards_t rewards;
663 :
664 129 : calculate_previous_epoch_inflation_rewards( bank,
665 129 : bank->f.capitalization,
666 129 : prev_epoch,
667 129 : &rewards );
668 :
669 129 : ulong total_rewards = rewards.validator_rewards;
670 :
671 129 : uint128 points = calculate_validator_rewards( bank,
672 129 : accdb,
673 129 : xid,
674 129 : runtime_stack,
675 129 : stake_delegations,
676 129 : capture_ctx,
677 129 : prev_epoch,
678 129 : &total_rewards );
679 :
680 : /* The agave client does not partition the stake rewards until the
681 : first distribution block. We calculate the partitions during the
682 : boundary. */
683 129 : result->validator_points = points;
684 129 : result->validator_rewards = total_rewards;
685 129 : result->validator_rate = rewards.validator_rate;
686 129 : result->foundation_rate = rewards.foundation_rate;
687 129 : result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
688 129 : result->capitalization = bank->f.capitalization;
689 129 : }
690 :
691 : /* Calculate rewards from previous epoch and distribute vote rewards
692 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L148 */
693 : static void
694 : calculate_rewards_and_distribute_vote_rewards( fd_bank_t * bank,
695 : fd_accdb_user_t * accdb,
696 : fd_funk_txn_xid_t const * xid,
697 : fd_runtime_stack_t * runtime_stack,
698 : fd_stake_delegations_t const * stake_delegations,
699 : fd_capture_ctx_t * capture_ctx,
700 129 : ulong prev_epoch ) {
701 :
702 129 : fd_vote_rewards_t * vote_ele_pool = runtime_stack->stakes.vote_ele;
703 129 : fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
704 :
705 : /* First we must compute the stake and vote rewards for the just
706 : completed epoch. We store the stake account rewards and vote
707 : states rewards in the bank */
708 :
709 129 : fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
710 129 : calculate_rewards_for_partitioning( bank,
711 129 : accdb,
712 129 : xid,
713 129 : runtime_stack,
714 129 : stake_delegations,
715 129 : capture_ctx,
716 129 : prev_epoch,
717 129 : rewards_calc_result );
718 :
719 :
720 : /* Iterate over all the vote reward nodes and distribute the rewards
721 : to the vote accounts. After each reward has been paid out,
722 : calcualte the lthash for each vote account. */
723 129 : ulong distributed_rewards = 0UL;
724 129 : for( fd_vote_rewards_map_iter_t iter = fd_vote_rewards_map_iter_init( vote_ele_map, vote_ele_pool );
725 264 : !fd_vote_rewards_map_iter_done( iter, vote_ele_map, vote_ele_pool );
726 135 : iter = fd_vote_rewards_map_iter_next( iter, vote_ele_map, vote_ele_pool ) ) {
727 :
728 135 : uint idx = (uint)fd_vote_rewards_map_iter_idx( iter, vote_ele_map, vote_ele_pool );
729 135 : fd_vote_rewards_t * ele = &vote_ele_pool[idx];
730 :
731 135 : ulong rewards = runtime_stack->stakes.vote_ele[ idx ].vote_rewards;
732 135 : if( rewards==0UL ) {
733 129 : continue;
734 129 : }
735 :
736 : /* Credit rewards to vote account (creating a new system account if
737 : it does not exist) */
738 6 : fd_pubkey_t const * vote_pubkey = &ele->pubkey;
739 6 : fd_accdb_svm_credit( accdb, bank, xid, capture_ctx, vote_pubkey, rewards );
740 6 : distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards );
741 6 : }
742 :
743 : /* Verify that we didn't pay any more than we expected to */
744 129 : fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
745 129 : ulong total_stake_rewards = fd_stake_rewards_total_rewards( stake_rewards, bank->stake_rewards_fork_id );
746 :
747 129 : ulong total_rewards = fd_ulong_sat_add( distributed_rewards, total_stake_rewards );
748 129 : if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
749 0 : FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
750 0 : }
751 :
752 129 : runtime_stack->stakes.distributed_rewards = distributed_rewards;
753 129 : runtime_stack->stakes.total_rewards = rewards_calc_result->validator_rewards;
754 129 : runtime_stack->stakes.total_points.ud = rewards_calc_result->validator_points;
755 129 : }
756 :
757 : /* Distributes a single partitioned reward to a single stake account */
758 : static int
759 : distribute_epoch_reward_to_stake_acc( fd_bank_t * bank,
760 : fd_accdb_user_t * accdb,
761 : fd_funk_txn_xid_t const * xid,
762 : fd_capture_ctx_t * capture_ctx,
763 : fd_pubkey_t * stake_pubkey,
764 : ulong reward_lamports,
765 69 : ulong new_credits_observed ) {
766 :
767 69 : fd_accdb_rw_t rw[1];
768 69 : if( FD_UNLIKELY( !fd_accdb_open_rw( accdb, rw, xid, stake_pubkey, 0UL, 0 ) ) ) {
769 0 : return 1; /* account does not exist */
770 0 : }
771 :
772 69 : fd_stake_state_t const * stake_state_orig = fd_stakes_get_state( rw->meta );
773 69 : if( !stake_state_orig || stake_state_orig->stake_type != FD_STAKE_STATE_STAKE ) {
774 0 : fd_accdb_close_rw( accdb, rw );
775 0 : return 1; /* not a valid stake account */
776 0 : }
777 69 : fd_stake_state_t stake_state[1] = { *stake_state_orig };
778 :
779 69 : fd_lthash_value_t prev_hash[1];
780 69 : fd_hashes_account_lthash( stake_pubkey, rw->meta, fd_accdb_ref_data_const( rw->ro ), prev_hash );
781 :
782 : /* Credit rewards to stake account */
783 69 : ulong acc_lamports = fd_accdb_ref_lamports( rw->ro );
784 69 : if( FD_UNLIKELY( __builtin_uaddl_overflow( acc_lamports, reward_lamports, &acc_lamports ) ) ) {
785 0 : FD_BASE58_ENCODE_32_BYTES( stake_pubkey->key, addr_b58 );
786 0 : FD_LOG_EMERG(( "integer overflow while crediting %lu stake reward lamports to %s (previous balance %lu)",
787 0 : reward_lamports, addr_b58, fd_accdb_ref_lamports( rw->ro ) ));
788 0 : }
789 69 : fd_accdb_ref_lamports_set( rw, acc_lamports );
790 :
791 69 : ulong old_credits_observed = stake_state->stake.stake.credits_observed;
792 69 : stake_state->stake.stake.credits_observed = new_credits_observed;
793 69 : stake_state->stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->stake.stake.delegation.stake, reward_lamports );
794 :
795 69 : fd_stake_delegations_t * stake_delegations_upd = fd_bank_stake_delegations_modify( bank );
796 69 : fd_stake_delegations_fork_update( stake_delegations_upd,
797 69 : bank->stake_delegations_fork_id,
798 69 : stake_pubkey,
799 69 : &stake_state->stake.stake.delegation.voter_pubkey,
800 69 : stake_state->stake.stake.delegation.stake,
801 69 : stake_state->stake.stake.delegation.activation_epoch,
802 69 : stake_state->stake.stake.delegation.deactivation_epoch,
803 69 : stake_state->stake.stake.credits_observed,
804 69 : fd_stake_warmup_cooldown_rate( bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch ) );
805 :
806 69 : if( capture_ctx && capture_ctx->capture_solcap ) {
807 0 : fd_capture_link_write_stake_account_payout( capture_ctx,
808 0 : bank->f.slot,
809 0 : *stake_pubkey,
810 0 : bank->f.slot,
811 0 : acc_lamports,
812 0 : (long)reward_lamports,
813 0 : new_credits_observed,
814 0 : (long)( new_credits_observed - old_credits_observed ),
815 0 : stake_state->stake.stake.delegation.stake,
816 0 : (long)reward_lamports );
817 0 : }
818 :
819 69 : FD_STORE( fd_stake_state_t, fd_accdb_ref_data( rw ), *stake_state );
820 69 : fd_hashes_update_lthash( stake_pubkey, rw->meta, prev_hash, bank, capture_ctx );
821 69 : fd_accdb_close_rw( accdb, rw );
822 :
823 69 : return 0;
824 69 : }
825 :
826 : /* Process reward credits for a partition of rewards. Store the rewards
827 : to AccountsDB, update reward history record and total capitalization
828 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
829 : static void
830 : distribute_epoch_rewards_in_partition( fd_stake_rewards_t * stake_rewards,
831 : ulong partition_idx,
832 : fd_bank_t * bank,
833 : fd_accdb_user_t * accdb,
834 : fd_funk_txn_xid_t const * xid,
835 63 : fd_capture_ctx_t * capture_ctx ) {
836 :
837 63 : ulong lamports_distributed = 0UL;
838 63 : ulong lamports_burned = 0UL;
839 :
840 63 : for( fd_stake_rewards_iter_init( stake_rewards, bank->stake_rewards_fork_id, (ushort)partition_idx );
841 132 : !fd_stake_rewards_iter_done( stake_rewards );
842 69 : fd_stake_rewards_iter_next( stake_rewards, bank->stake_rewards_fork_id ) ) {
843 69 : fd_pubkey_t pubkey;
844 69 : ulong lamports;
845 69 : ulong credits_observed;
846 69 : fd_stake_rewards_iter_ele( stake_rewards, bank->stake_rewards_fork_id, &pubkey, &lamports, &credits_observed );
847 :
848 69 : if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc( bank,
849 69 : accdb,
850 69 : xid,
851 69 : capture_ctx,
852 69 : &pubkey,
853 69 : lamports,
854 69 : credits_observed ) ) ) {
855 69 : lamports_distributed += lamports;
856 69 : } else {
857 0 : lamports_burned += lamports;
858 0 : }
859 69 : }
860 :
861 : /* Update the epoch rewards sysvar with the amount distributed and burnt */
862 63 : fd_sysvar_epoch_rewards_distribute( bank, accdb, xid, capture_ctx, lamports_distributed + lamports_burned );
863 :
864 63 : FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
865 :
866 63 : bank->f.capitalization = bank->f.capitalization + lamports_distributed;
867 63 : }
868 :
869 : /* Process reward distribution for the block if it is inside reward interval.
870 :
871 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L45-L136 */
872 : void
873 : fd_distribute_partitioned_epoch_rewards( fd_bank_t * bank,
874 : fd_accdb_user_t * accdb,
875 : fd_funk_txn_xid_t const * xid,
876 3549 : fd_capture_ctx_t * capture_ctx ) {
877 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L46-L48 */
878 3549 : if( FD_LIKELY( bank->stake_rewards_fork_id==UCHAR_MAX ) ) return;
879 :
880 192 : fd_stake_rewards_t * stake_rewards = fd_bank_stake_rewards_modify( bank );
881 :
882 192 : ulong block_height = bank->f.block_height;
883 192 : ulong distribution_starting_block_height = fd_stake_rewards_starting_block_height( stake_rewards, bank->stake_rewards_fork_id );
884 192 : ulong distribution_end_exclusive = fd_stake_rewards_exclusive_ending_block_height( stake_rewards, bank->stake_rewards_fork_id );
885 :
886 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L55-L58 */
887 192 : if( FD_UNLIKELY( block_height<distribution_starting_block_height ) ) {
888 129 : return;
889 129 : }
890 :
891 : /* The logic in Agave for EpochRewardPhase::Calculation has no direct
892 : equivalent in Firedancer, because reward calculation is done
893 : eagerly at the epoch boundary, whereas for Agave it's done at the
894 : first distribution block.
895 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L60-L90 */
896 :
897 63 : fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
898 63 : ulong epoch = bank->f.epoch;
899 :
900 63 : if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_schedule ) <= fd_stake_rewards_num_partitions( stake_rewards, bank->stake_rewards_fork_id ) ) ) {
901 0 : FD_LOG_CRIT(( "Should not be distributing rewards" ));
902 0 : }
903 :
904 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L110-L114 */
905 63 : if( FD_LIKELY( block_height>=distribution_starting_block_height && block_height<distribution_end_exclusive ) ) {
906 63 : ulong partition_idx = block_height-distribution_starting_block_height;
907 63 : distribute_epoch_rewards_in_partition( stake_rewards, partition_idx, bank, accdb, xid, capture_ctx );
908 63 : }
909 :
910 : /* If we have finished distributing rewards, set the status to inactive
911 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L116-L135 */
912 63 : if( fd_ulong_sat_add( block_height, 1UL )>=distribution_end_exclusive ) {
913 57 : fd_sysvar_epoch_rewards_set_inactive( bank, accdb, xid, capture_ctx );
914 57 : bank->stake_rewards_fork_id = UCHAR_MAX;
915 57 : }
916 63 : }
917 :
918 : /* Partitioned epoch rewards entry-point.
919 :
920 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L102
921 : */
922 : void
923 : fd_begin_partitioned_rewards( fd_bank_t * bank,
924 : fd_accdb_user_t * accdb,
925 : fd_funk_txn_xid_t const * xid,
926 : fd_runtime_stack_t * runtime_stack,
927 : fd_capture_ctx_t * capture_ctx,
928 : fd_stake_delegations_t const * stake_delegations,
929 : fd_hash_t const * parent_blockhash,
930 129 : ulong parent_epoch ) {
931 :
932 129 : calculate_rewards_and_distribute_vote_rewards(
933 129 : bank,
934 129 : accdb,
935 129 : xid,
936 129 : runtime_stack,
937 129 : stake_delegations,
938 129 : capture_ctx,
939 129 : parent_epoch );
940 :
941 : /* Once the rewards for vote accounts have been distributed and stake
942 : account rewards have been calculated, we can now set our epoch
943 : reward status to be active and we can initialize the epoch rewards
944 : sysvar. This sysvar is then deleted once all of the partitioned
945 : stake rewards have been distributed.
946 :
947 : The Agave client calculates the partitions for each stake reward
948 : when the first distribution block is reached. The Firedancer
949 : client differs here since we hash the partitions during the epoch
950 : boundary. */
951 :
952 129 : ulong distribution_starting_block_height = bank->f.block_height + REWARD_CALCULATION_NUM_BLOCKS;
953 129 : uint num_partitions = fd_stake_rewards_num_partitions( fd_bank_stake_rewards_query( bank ), bank->stake_rewards_fork_id );
954 :
955 129 : fd_sysvar_epoch_rewards_init(
956 129 : bank,
957 129 : accdb,
958 129 : xid,
959 129 : capture_ctx,
960 129 : runtime_stack->stakes.distributed_rewards,
961 129 : distribution_starting_block_height,
962 129 : num_partitions,
963 129 : runtime_stack->stakes.total_rewards,
964 129 : runtime_stack->stakes.total_points.ud,
965 129 : parent_blockhash );
966 129 : }
967 :
968 : /*
969 : Re-calculates partitioned stake rewards.
970 : This updates the slot context's epoch reward status with the recalculated partitioned rewards.
971 :
972 : https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521 */
973 : void
974 : fd_rewards_recalculate_partitioned_rewards( fd_banks_t * banks,
975 : fd_bank_t * bank,
976 : fd_accdb_user_t * accdb,
977 : fd_funk_txn_xid_t const * xid,
978 : fd_runtime_stack_t * runtime_stack,
979 0 : fd_capture_ctx_t * capture_ctx ) {
980 :
981 : /* If the snapshot was loaded while partitioned epoch rewards is
982 : active, then the vote rewards map must be populated with the state
983 : of the vote accounts as of the end of the previous epoch boundary.
984 : The epoch credits for these accounts are stored in the bank along
985 : with the t-3 commission. With this, it's possible to recalculate
986 : the rewards for the previous epoch boundary. We need the
987 : commission from the end of the t-3 epoch if we are calculating
988 : rewards for the transition from epoch t-1 to t since there needs to
989 : be a 2 epoch commission gap for the delay_commission_updates
990 : feature. */
991 :
992 0 : fd_vote_rewards_map_t * vote_ele_map = runtime_stack->stakes.vote_map;
993 :
994 0 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
995 0 : ushort vs_fork_idx = bank->vote_stakes_fork_id;
996 :
997 0 : ulong epoch_credits_len = *fd_bank_epoch_credits_len( bank );
998 0 : for( ulong i=0UL; i<epoch_credits_len; i++ ) {
999 0 : fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[i];
1000 0 : ulong stake_t_1;
1001 0 : uchar commission_t_1;
1002 0 : ulong stake_t_2;
1003 0 : uchar commission_t_2;
1004 0 : uchar exists_t_1 = 0;
1005 0 : uchar exists_t_2 = 0;
1006 0 : fd_vote_stakes_query( vote_stakes, vs_fork_idx, (fd_pubkey_t *)epoch_credits->pubkey, &stake_t_1, &stake_t_2, NULL, NULL, &commission_t_1, &commission_t_2, &exists_t_1, &exists_t_2 );
1007 :
1008 0 : fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
1009 0 : vote_ele->pubkey = *(fd_pubkey_t *)epoch_credits->pubkey;
1010 0 : vote_ele->vote_rewards = 0UL;
1011 0 : if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
1012 0 : vote_ele->commission = exists_t_2 ? commission_t_2 : commission_t_1;
1013 0 : } else {
1014 0 : vote_ele->commission = commission_t_1;
1015 0 : }
1016 0 : fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
1017 0 : }
1018 :
1019 0 : if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
1020 0 : ulong commission_t_3_len = *fd_bank_snapshot_commission_t_3_len( bank );
1021 0 : fd_stashed_commission_t * commission_t_3 = fd_bank_snapshot_commission_t_3( bank );
1022 0 : for( ulong i=0UL; i<commission_t_3_len; i++ ) {
1023 0 : fd_stashed_commission_t const * ele = &commission_t_3[i];
1024 0 : fd_vote_rewards_t * vote_ele = fd_vote_rewards_map_ele_query( vote_ele_map, (fd_pubkey_t *)ele->pubkey, NULL, runtime_stack->stakes.vote_ele );
1025 0 : if( FD_LIKELY( vote_ele ) ) vote_ele->commission = ele->commission;
1026 0 : }
1027 0 : }
1028 :
1029 0 : fd_sysvar_epoch_rewards_t epoch_rewards_sysvar[1];
1030 0 : if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read( accdb, xid, epoch_rewards_sysvar ) ) ) {
1031 0 : FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
1032 0 : return;
1033 0 : }
1034 :
1035 0 : FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
1036 :
1037 0 : if( FD_UNLIKELY( !epoch_rewards_sysvar->active ) ) {
1038 0 : FD_LOG_DEBUG(( "epoch rewards is inactive" ));
1039 0 : return;
1040 0 : }
1041 :
1042 : /* If partitioned rewards are active, the rewarded epoch is always the immediately
1043 : preceeding epoch.
1044 :
1045 : https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
1046 0 : FD_LOG_DEBUG(( "epoch rewards is active" ));
1047 :
1048 0 : ulong const epoch = bank->f.epoch;
1049 0 : ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
1050 :
1051 0 : fd_accdb_ro_t sh_ro[1];
1052 0 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, sh_ro, xid, &fd_sysvar_stake_history_id ) ) ) {
1053 0 : FD_LOG_ERR(( "Unable to read stake history sysvar" ));
1054 0 : }
1055 0 : fd_stake_history_t stake_history[1];
1056 0 : if( FD_UNLIKELY( !fd_sysvar_stake_history_view( stake_history, fd_accdb_ref_data_const( sh_ro ), fd_accdb_ref_data_sz( sh_ro ) ) ) ) {
1057 0 : FD_LOG_ERR(( "Unable to decode stake history sysvar" ));
1058 0 : }
1059 :
1060 0 : fd_stake_delegations_t const * stake_delegations = fd_bank_stake_delegations_frontier_query( banks, bank );
1061 0 : if( FD_UNLIKELY( !stake_delegations ) ) {
1062 0 : FD_LOG_CRIT(( "stake_delegations is NULL" ));
1063 0 : }
1064 :
1065 0 : calculate_stake_vote_rewards(
1066 0 : bank,
1067 0 : stake_delegations,
1068 0 : capture_ctx,
1069 0 : stake_history,
1070 0 : rewarded_epoch,
1071 0 : epoch_rewards_sysvar->total_rewards,
1072 0 : epoch_rewards_sysvar->total_points.ud,
1073 0 : runtime_stack,
1074 0 : 1 );
1075 :
1076 0 : setup_stake_partitions(
1077 0 : bank,
1078 0 : stake_history,
1079 0 : stake_delegations,
1080 0 : runtime_stack,
1081 0 : &epoch_rewards_sysvar->parent_blockhash,
1082 0 : epoch_rewards_sysvar->distribution_starting_block_height,
1083 0 : (uint)epoch_rewards_sysvar->num_partitions,
1084 0 : rewarded_epoch,
1085 0 : epoch_rewards_sysvar->total_rewards,
1086 0 : epoch_rewards_sysvar->total_points.ud );
1087 :
1088 0 : fd_accdb_close_ro( accdb, sh_ro );
1089 0 : fd_bank_stake_delegations_end_frontier_query( banks, bank );
1090 0 : }
|