Line data Source code
1 : #include "fd_rewards.h"
2 : #include <math.h>
3 :
4 : #include "../runtime/fd_acc_mgr.h"
5 : #include "../runtime/fd_executor_err.h"
6 : #include "../runtime/program/fd_vote_program.h"
7 : #include "../runtime/sysvar/fd_sysvar_epoch_rewards.h"
8 : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
9 : #include "../stakes/fd_stakes.h"
10 : #include "../runtime/program/fd_stake_program.h"
11 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
12 : #include "../runtime/context/fd_capture_ctx.h"
13 : #include "../runtime/fd_runtime_stack.h"
14 : #include "../runtime/fd_runtime.h"
15 : #include "fd_epoch_rewards.h"
16 :
17 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
18 : static double
19 0 : total( fd_inflation_t const * inflation, double year ) {
20 0 : if ( FD_UNLIKELY( year == 0.0 ) ) {
21 0 : FD_LOG_ERR(( "inflation year 0" ));
22 0 : }
23 0 : double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
24 0 : return (tapered > inflation->terminal) ? tapered : inflation->terminal;
25 0 : }
26 :
27 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
28 : static double
29 0 : foundation( fd_inflation_t const * inflation, double year ) {
30 0 : return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
31 0 : }
32 :
33 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
34 : static double
35 0 : validator( fd_inflation_t const * inflation, double year) {
36 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
37 0 : FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
38 0 : return total( inflation, year ) - foundation( inflation, year );
39 0 : }
40 :
41 : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
42 : activation slot of the following features:
43 : - devnet_and_testnet
44 : - full_inflation_enable, if full_inflation_vote has been activated
45 :
46 : https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
47 : static FD_FN_CONST ulong
48 0 : get_inflation_start_slot( fd_bank_t const * bank ) {
49 0 : ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( bank, devnet_and_testnet )
50 0 : ? fd_bank_features_query( bank )->devnet_and_testnet
51 0 : : ULONG_MAX;
52 :
53 0 : ulong enable = ULONG_MAX;
54 0 : if( FD_FEATURE_ACTIVE_BANK( bank, full_inflation_vote ) &&
55 0 : FD_FEATURE_ACTIVE_BANK( bank, full_inflation_enable ) ) {
56 0 : enable = fd_bank_features_query( bank )->full_inflation_enable;
57 0 : }
58 :
59 0 : ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
60 0 : if( min_slot == ULONG_MAX ) {
61 0 : if( FD_FEATURE_ACTIVE_BANK( bank, pico_inflation ) ) {
62 0 : min_slot = fd_bank_features_query( bank )->pico_inflation;
63 0 : } else {
64 0 : min_slot = 0;
65 0 : }
66 0 : }
67 0 : return min_slot;
68 0 : }
69 :
70 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2110 */
71 : static ulong
72 : get_inflation_num_slots( fd_bank_t const * bank,
73 : fd_epoch_schedule_t const * epoch_schedule,
74 0 : ulong slot ) {
75 0 : ulong inflation_activation_slot = get_inflation_start_slot( bank );
76 0 : ulong inflation_start_slot = fd_epoch_slot0( epoch_schedule,
77 0 : fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule,
78 0 : inflation_activation_slot, NULL ),
79 0 : 1UL ) );
80 :
81 0 : ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, NULL );
82 :
83 0 : return fd_epoch_slot0( epoch_schedule, epoch ) - inflation_start_slot;
84 0 : }
85 :
86 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2121 */
87 : static double
88 0 : slot_in_year_for_inflation( fd_bank_t const * bank ) {
89 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
90 0 : ulong num_slots = get_inflation_num_slots( bank, epoch_schedule, fd_bank_slot_get( bank ) );
91 0 : return (double)num_slots / (double)fd_bank_slots_per_year_get( bank );
92 0 : }
93 :
94 : static void
95 : get_vote_credits( uchar const * account_data,
96 : ulong account_data_len,
97 : uchar * buf,
98 0 : fd_vote_epoch_credits_t * * credits ) {
99 :
100 0 : fd_bincode_decode_ctx_t ctx = {
101 0 : .data = account_data,
102 0 : .dataend = account_data + account_data_len,
103 0 : };
104 :
105 0 : fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( buf, &ctx );
106 0 : if( FD_UNLIKELY( vsv==NULL ) ) {
107 0 : FD_LOG_CRIT(( "unable to decode vote state versioned" ));
108 0 : }
109 :
110 0 : switch( vsv->discriminant ) {
111 0 : case fd_vote_state_versioned_enum_v0_23_5:
112 0 : *credits = vsv->inner.v0_23_5.epoch_credits;
113 0 : break;
114 0 : case fd_vote_state_versioned_enum_v1_14_11:
115 0 : *credits = vsv->inner.v1_14_11.epoch_credits;
116 0 : break;
117 0 : case fd_vote_state_versioned_enum_current:
118 0 : *credits = vsv->inner.current.epoch_credits;
119 0 : break;
120 0 : default:
121 0 : __builtin_unreachable();
122 0 : }
123 0 : }
124 :
125 : static void
126 : calculate_stake_points_and_credits_recalculation( fd_stake_history_t const * stake_history,
127 : fd_stake_delegation_t const * stake,
128 : ulong * new_rate_activation_epoch,
129 : fd_vote_state_credits_t * recalc_vote_state_credits,
130 0 : fd_calculated_stake_points_t * result ) {
131 0 : ulong credits_in_stake = stake->credits_observed;
132 0 : ulong credits_in_vote = recalc_vote_state_credits->credits[ recalc_vote_state_credits->credits_cnt-1UL ];
133 :
134 : /* If the Vote account has less credits observed than the Stake account,
135 : something is wrong and we need to force an update.
136 :
137 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
138 0 : if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
139 0 : result->points = 0;
140 0 : result->new_credits_observed = credits_in_vote;
141 0 : result->force_credits_update_with_skipped_reward = 1;
142 0 : return;
143 0 : }
144 :
145 : /* If the Vote account has the same amount of credits observed as the Stake account,
146 : then the Vote account hasn't earnt any credits and so there is nothing to update.
147 :
148 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
149 0 : if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
150 0 : result->points = 0;
151 0 : result->new_credits_observed = credits_in_vote;
152 0 : result->force_credits_update_with_skipped_reward = 0;
153 0 : return;
154 0 : }
155 :
156 : /* Calculate the points for each epoch credit */
157 0 : uint128 points = 0;
158 0 : ulong new_credits_observed = credits_in_stake;
159 0 : for( ulong i=0UL; i<recalc_vote_state_credits->credits_cnt; i++ ) {
160 :
161 0 : ulong final_epoch_credits = recalc_vote_state_credits->credits[ i ];
162 0 : ulong initial_epoch_credits = recalc_vote_state_credits->prev_credits[ i ];
163 0 : uint128 earned_credits = 0;
164 0 : if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
165 0 : earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
166 0 : } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
167 0 : earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
168 0 : }
169 :
170 0 : new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
171 :
172 0 : fd_delegation_t delegation = {
173 0 : .voter_pubkey = stake->vote_account,
174 0 : .stake = stake->stake,
175 0 : .activation_epoch = stake->activation_epoch,
176 0 : .deactivation_epoch = stake->deactivation_epoch,
177 0 : .warmup_cooldown_rate = stake->warmup_cooldown_rate,
178 0 : };
179 :
180 0 : ulong stake_amount = fd_stake_activating_and_deactivating(
181 0 : &delegation,
182 0 : recalc_vote_state_credits->epoch[ i ],
183 0 : stake_history,
184 0 : new_rate_activation_epoch ).effective;
185 :
186 0 : points += (uint128)stake_amount * earned_credits;
187 :
188 0 : }
189 :
190 0 : result->points = points;
191 0 : result->new_credits_observed = new_credits_observed;
192 0 : result->force_credits_update_with_skipped_reward = 0;
193 0 : }
194 :
195 : /* For a given stake and vote_state, calculate how many points were earned (credits * stake) and new value
196 : for credits_observed were the points paid
197 :
198 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
199 : static void
200 : calculate_stake_points_and_credits( fd_funk_t * funk,
201 : fd_funk_txn_xid_t const * xid,
202 : fd_stake_history_t const * stake_history,
203 : fd_stake_delegation_t const * stake,
204 : fd_vote_state_ele_t const * vote_state,
205 : ulong * new_rate_activation_epoch,
206 0 : fd_calculated_stake_points_t * result ) {
207 :
208 :
209 0 : fd_txn_account_t vote_rec[1];
210 0 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( vote_rec,
211 0 : &vote_state->vote_account,
212 0 : funk,
213 0 : xid )!=FD_ACC_MGR_SUCCESS ) ) {
214 0 : FD_LOG_ERR(( "Unable to read vote account" ));
215 0 : }
216 :
217 0 : fd_vote_epoch_credits_t * epoch_credits = NULL;
218 :
219 0 : uchar __attribute__((aligned(FD_VOTE_STATE_VERSIONED_ALIGN))) buf[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
220 :
221 0 : get_vote_credits( fd_txn_account_get_data( vote_rec ), fd_txn_account_get_data_len( vote_rec ), buf, &epoch_credits );
222 :
223 0 : ulong credits_in_stake = stake->credits_observed;
224 0 : ulong credits_cnt = deq_fd_vote_epoch_credits_t_cnt( epoch_credits );
225 0 : ulong credits_in_vote = credits_cnt > 0UL ? deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits : 0UL;
226 :
227 : /* If the Vote account has less credits observed than the Stake account,
228 : something is wrong and we need to force an update.
229 :
230 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
231 0 : if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
232 0 : result->points = 0;
233 0 : result->new_credits_observed = credits_in_vote;
234 0 : result->force_credits_update_with_skipped_reward = 1;
235 0 : return;
236 0 : }
237 :
238 : /* If the Vote account has the same amount of credits observed as the Stake account,
239 : then the Vote account hasn't earnt any credits and so there is nothing to update.
240 :
241 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
242 0 : if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
243 0 : result->points = 0;
244 0 : result->new_credits_observed = credits_in_vote;
245 0 : result->force_credits_update_with_skipped_reward = 0;
246 0 : return;
247 0 : }
248 :
249 : /* Calculate the points for each epoch credit */
250 0 : uint128 points = 0;
251 0 : ulong new_credits_observed = credits_in_stake;
252 0 : for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits );
253 0 : !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter );
254 0 : iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) {
255 :
256 0 : fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter );
257 :
258 0 : ulong final_epoch_credits = ele->credits;
259 0 : ulong initial_epoch_credits = ele->prev_credits;
260 0 : uint128 earned_credits = 0;
261 0 : if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
262 0 : earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
263 0 : } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
264 0 : earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
265 0 : }
266 :
267 0 : new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
268 :
269 0 : fd_delegation_t delegation = {
270 0 : .voter_pubkey = stake->vote_account,
271 0 : .stake = stake->stake,
272 0 : .activation_epoch = stake->activation_epoch,
273 0 : .deactivation_epoch = stake->deactivation_epoch,
274 0 : .warmup_cooldown_rate = stake->warmup_cooldown_rate,
275 0 : };
276 :
277 0 : ulong stake_amount = fd_stake_activating_and_deactivating(
278 0 : &delegation,
279 0 : ele->epoch,
280 0 : stake_history,
281 0 : new_rate_activation_epoch ).effective;
282 :
283 0 : points += (uint128)stake_amount * earned_credits;
284 :
285 0 : }
286 :
287 0 : result->points = points;
288 0 : result->new_credits_observed = new_credits_observed;
289 0 : result->force_credits_update_with_skipped_reward = 0;
290 0 : }
291 :
292 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
293 : static int
294 : redeem_rewards( fd_funk_t * funk,
295 : fd_funk_txn_xid_t const * xid,
296 : fd_stake_history_t const * stake_history,
297 : fd_stake_delegation_t const * stake,
298 : fd_vote_state_ele_t const * vote_state,
299 : ulong rewarded_epoch,
300 : ulong total_rewards,
301 : uint128 total_points,
302 : ulong * new_rate_activation_epoch,
303 : fd_vote_state_credits_t * recalc_vote_state_credits,
304 0 : fd_calculated_stake_rewards_t * result ) {
305 :
306 : /* The firedancer implementation of redeem_rewards inlines a lot of
307 : the helper functions that the Agave implementation uses.
308 : In Agave: redeem_rewards calls redeem_stake_rewards which calls
309 : calculate_stake_rewards. */
310 :
311 0 : fd_calculated_stake_points_t stake_points_result = {0};
312 0 : if( FD_LIKELY( !recalc_vote_state_credits ) ) {
313 0 : calculate_stake_points_and_credits(
314 0 : funk,
315 0 : xid,
316 0 : stake_history,
317 0 : stake,
318 0 : vote_state,
319 0 : new_rate_activation_epoch,
320 0 : &stake_points_result );
321 0 : } else {
322 0 : calculate_stake_points_and_credits_recalculation(
323 0 : stake_history,
324 0 : stake,
325 0 : new_rate_activation_epoch,
326 0 : recalc_vote_state_credits,
327 0 : &stake_points_result );
328 0 : }
329 :
330 : // Drive credits_observed forward unconditionally when rewards are disabled
331 : // or when this is the stake's activation epoch
332 0 : if( total_rewards==0UL || stake->activation_epoch==rewarded_epoch ) {
333 0 : stake_points_result.force_credits_update_with_skipped_reward = 1;
334 0 : }
335 :
336 0 : if( stake_points_result.force_credits_update_with_skipped_reward ) {
337 0 : result->staker_rewards = 0;
338 0 : result->voter_rewards = 0;
339 0 : result->new_credits_observed = stake_points_result.new_credits_observed;
340 0 : return 0;
341 0 : }
342 0 : if( stake_points_result.points==0 || total_points==0 ) {
343 0 : return 1;
344 0 : }
345 :
346 : /* FIXME: need to error out if the conversion from uint128 to u64 fails, also use 128 checked mul and div */
347 0 : ulong rewards = (ulong)(stake_points_result.points * (uint128)(total_rewards) / (uint128) total_points);
348 0 : if( rewards == 0 ) {
349 0 : return 1;
350 0 : }
351 :
352 0 : fd_commission_split_t split_result;
353 0 : fd_vote_commission_split( vote_state->commission, rewards, &split_result );
354 0 : if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
355 0 : return 1;
356 0 : }
357 :
358 0 : result->staker_rewards = split_result.staker_portion;
359 0 : result->voter_rewards = split_result.voter_portion;
360 0 : result->new_credits_observed = stake_points_result.new_credits_observed;
361 0 : return 0;
362 0 : }
363 :
364 : /* Returns the length of the given epoch in slots
365 :
366 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
367 : static ulong
368 : get_slots_in_epoch( ulong epoch,
369 0 : fd_epoch_schedule_t const * epoch_schedule ) {
370 0 : return epoch < epoch_schedule->first_normal_epoch ?
371 0 : 1UL << fd_ulong_sat_add( epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO ) :
372 0 : epoch_schedule->slots_per_epoch;
373 0 : }
374 :
375 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
376 : static double
377 : epoch_duration_in_years( fd_bank_t const * bank,
378 0 : ulong prev_epoch ) {
379 0 : ulong slots_in_epoch = get_slots_in_epoch( prev_epoch, fd_bank_epoch_schedule_query( bank ) );
380 0 : return (double)slots_in_epoch / (double)fd_bank_slots_per_year_get( bank );
381 0 : }
382 :
383 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
384 : static void
385 : calculate_previous_epoch_inflation_rewards( fd_bank_t const * bank,
386 : ulong prev_epoch_capitalization,
387 : ulong prev_epoch,
388 0 : fd_prev_epoch_inflation_rewards_t * rewards ) {
389 0 : double slot_in_year = slot_in_year_for_inflation( bank );
390 :
391 0 : rewards->validator_rate = validator( fd_bank_inflation_query( bank ), slot_in_year );
392 0 : rewards->foundation_rate = foundation( fd_bank_inflation_query( bank ), slot_in_year );
393 0 : rewards->prev_epoch_duration_in_years = epoch_duration_in_years( bank, prev_epoch );
394 0 : rewards->validator_rewards = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
395 0 : 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 ));
396 0 : }
397 :
398 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/lib.rs#L29 */
399 : static ulong
400 0 : get_minimum_stake_delegation( fd_bank_t * bank ) {
401 0 : if( !FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
402 0 : return 0UL;
403 0 : }
404 :
405 0 : if( FD_FEATURE_ACTIVE_BANK( bank, stake_raise_minimum_delegation_to_1_sol ) ) {
406 0 : return LAMPORTS_PER_SOL;
407 0 : }
408 :
409 0 : return 1;
410 0 : }
411 :
412 : /* Calculates epoch reward points from stake/vote accounts.
413 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L445 */
414 : static uint128
415 : calculate_reward_points_partitioned( fd_bank_t * bank,
416 : fd_funk_t * funk,
417 : fd_funk_txn_xid_t const * xid,
418 : fd_stake_delegations_t const * stake_delegations,
419 0 : fd_stake_history_t const * stake_history ) {
420 0 : ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
421 :
422 : /* Calculate the points for each stake delegation */
423 0 : int _err[1];
424 0 : ulong new_warmup_cooldown_rate_epoch_val = 0UL;
425 0 : ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_val;
426 0 : int is_some = fd_new_warmup_cooldown_rate_epoch(
427 0 : fd_bank_epoch_schedule_query( bank ),
428 0 : fd_bank_features_query( bank ),
429 0 : fd_bank_slot_get( bank ),
430 0 : new_warmup_cooldown_rate_epoch,
431 0 : _err );
432 0 : if( FD_UNLIKELY( !is_some ) ) {
433 0 : new_warmup_cooldown_rate_epoch = NULL;
434 0 : }
435 :
436 0 : uint128 total_points = 0;
437 :
438 0 : fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( bank );
439 :
440 0 : fd_stake_delegations_iter_t iter_[1];
441 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
442 0 : !fd_stake_delegations_iter_done( iter );
443 0 : fd_stake_delegations_iter_next( iter ) ) {
444 0 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
445 :
446 0 : if( FD_UNLIKELY( stake_delegation->stake<minimum_stake_delegation ) ) {
447 0 : continue;
448 0 : }
449 :
450 0 : fd_vote_state_ele_t * vote_state_ele = fd_vote_states_query( vote_states, &stake_delegation->vote_account );
451 0 : if( FD_UNLIKELY( !vote_state_ele ) ) {
452 0 : continue;
453 0 : }
454 :
455 0 : fd_calculated_stake_points_t stake_point_result;
456 0 : calculate_stake_points_and_credits( funk,
457 0 : xid,
458 0 : stake_history,
459 0 : stake_delegation,
460 0 : vote_state_ele,
461 0 : new_warmup_cooldown_rate_epoch,
462 0 : &stake_point_result );
463 0 : total_points += stake_point_result.points;
464 0 : }
465 :
466 0 : fd_bank_vote_states_end_locking_query( bank );
467 :
468 0 : return total_points;
469 0 : }
470 :
471 : /* Calculates epoch rewards for stake/vote accounts.
472 : Returns vote rewards, stake rewards, and the sum of all stake rewards
473 : in lamports.
474 :
475 : In the future, the calculation will be cached in the snapshot, but
476 : for now we just re-calculate it (as Agave does).
477 : calculate_stake_vote_rewards is responsible for calculating
478 : stake account rewards based off of a combination of the
479 : stake delegation state as well as the vote account. If this
480 : calculation is done at the end of an epoch, we can just use the
481 : vote states at the end of the current epoch. However, because we
482 : are presumably booting up a node in the middle of rewards
483 : distribution, we need to make sure that we are using the vote
484 : states from the end of the previous epoch.
485 :
486 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L323 */
487 : static void
488 : calculate_stake_vote_rewards( fd_bank_t * bank,
489 : fd_funk_t * funk,
490 : fd_funk_txn_xid_t const * xid,
491 : fd_stake_delegations_t const * stake_delegations,
492 : fd_capture_ctx_t * capture_ctx,
493 : fd_stake_history_t const * stake_history,
494 : ulong rewarded_epoch,
495 : ulong total_rewards,
496 : uint128 total_points,
497 0 : fd_runtime_stack_t * runtime_stack ) {
498 :
499 0 : int _err[1];
500 0 : ulong new_warmup_cooldown_rate_epoch_val = 0UL;
501 0 : ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_val;
502 0 : int is_some = fd_new_warmup_cooldown_rate_epoch(
503 0 : fd_bank_epoch_schedule_query( bank ),
504 0 : fd_bank_features_query( bank ),
505 0 : fd_bank_slot_get( bank ),
506 0 : new_warmup_cooldown_rate_epoch,
507 0 : _err );
508 0 : if( FD_UNLIKELY( !is_some ) ) {
509 0 : new_warmup_cooldown_rate_epoch = NULL;
510 0 : }
511 :
512 :
513 0 : ulong minimum_stake_delegation = get_minimum_stake_delegation( bank );
514 0 : ulong stake_delegation_cnt = fd_stake_delegations_cnt( stake_delegations );
515 :
516 0 : fd_vote_states_t * vote_states = !!runtime_stack->stakes.prev_vote_credits_used ? fd_bank_vote_states_prev_locking_modify( bank ) : fd_bank_vote_states_locking_modify( bank );
517 :
518 0 : fd_epoch_rewards_t * epoch_rewards = fd_epoch_rewards_join( fd_epoch_rewards_new( fd_bank_epoch_rewards_locking_modify( bank ), stake_delegation_cnt ) );
519 :
520 : /* Reset the vote rewards for each vote account. */
521 0 : fd_memset( runtime_stack->stakes.vote_rewards, 0UL, sizeof(runtime_stack->stakes.vote_rewards) );
522 :
523 0 : fd_stake_delegations_iter_t iter_[1];
524 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
525 0 : !fd_stake_delegations_iter_done( iter );
526 0 : fd_stake_delegations_iter_next( iter ) ) {
527 0 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
528 :
529 0 : if( FD_FEATURE_ACTIVE_BANK( bank, stake_minimum_delegation_for_rewards ) ) {
530 0 : if( stake_delegation->stake<minimum_stake_delegation ) {
531 0 : continue;
532 0 : }
533 0 : }
534 :
535 0 : fd_pubkey_t const * voter_acc = &stake_delegation->vote_account;
536 0 : fd_vote_state_ele_t * vote_state_ele = fd_vote_states_query( vote_states, voter_acc );
537 0 : if( FD_UNLIKELY( !vote_state_ele ) ) {
538 0 : continue;
539 0 : }
540 :
541 0 : fd_vote_state_credits_t * realc_credit = !!runtime_stack->stakes.prev_vote_credits_used ?
542 0 : &runtime_stack->stakes.vote_credits[ vote_state_ele->idx ] : NULL;
543 :
544 : /* redeem_rewards is actually just responisble for calculating the
545 : vote and stake rewards for each stake account. It does not do
546 : rewards redemption: it is a misnomer. */
547 0 : fd_calculated_stake_rewards_t calculated_stake_rewards[1] = {0};
548 0 : int err = redeem_rewards(
549 0 : funk,
550 0 : xid,
551 0 : stake_history,
552 0 : stake_delegation,
553 0 : vote_state_ele,
554 0 : rewarded_epoch,
555 0 : total_rewards,
556 0 : total_points,
557 0 : new_warmup_cooldown_rate_epoch,
558 0 : realc_credit,
559 0 : calculated_stake_rewards );
560 :
561 0 : if( FD_UNLIKELY( err!=0 ) ) {
562 0 : FD_LOG_DEBUG(( "redeem_rewards failed for %s with error %d", FD_BASE58_ENC_32_ALLOCA( &stake_delegation->stake_account ), err ));
563 0 : continue;
564 0 : }
565 :
566 0 : if( capture_ctx ) {
567 0 : fd_solcap_write_stake_reward_event( capture_ctx->capture,
568 0 : &stake_delegation->stake_account,
569 0 : voter_acc,
570 0 : vote_state_ele->commission,
571 0 : (long)calculated_stake_rewards->voter_rewards,
572 0 : (long)calculated_stake_rewards->staker_rewards,
573 0 : (long)calculated_stake_rewards->new_credits_observed );
574 0 : }
575 :
576 0 : runtime_stack->stakes.vote_rewards[ vote_state_ele->idx ] += calculated_stake_rewards->voter_rewards;
577 :
578 0 : fd_epoch_rewards_insert( epoch_rewards, &stake_delegation->stake_account, calculated_stake_rewards->new_credits_observed, calculated_stake_rewards->staker_rewards );
579 0 : }
580 :
581 0 : fd_bank_epoch_rewards_end_locking_modify( bank );
582 :
583 0 : !!runtime_stack->stakes.prev_vote_credits_used ? fd_bank_vote_states_prev_end_locking_modify( bank ) : fd_bank_vote_states_end_locking_modify( bank );
584 0 : }
585 :
586 : /* Calculate epoch reward and return vote and stake rewards.
587 :
588 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
589 : static uint128
590 : calculate_validator_rewards( fd_bank_t * bank,
591 : fd_funk_t * funk,
592 : fd_funk_txn_xid_t const * xid,
593 : fd_runtime_stack_t * runtime_stack,
594 : fd_stake_delegations_t const * stake_delegations,
595 : fd_capture_ctx_t * capture_ctx,
596 : ulong rewarded_epoch,
597 0 : ulong * rewards_out ) {
598 :
599 0 : fd_stake_history_t stake_history[1];
600 0 : if( FD_UNLIKELY( !fd_sysvar_stake_history_read( funk, xid, stake_history ) ) ) {
601 0 : FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
602 0 : }
603 :
604 : /* Calculate the epoch reward points from stake/vote accounts */
605 0 : uint128 points = calculate_reward_points_partitioned(
606 0 : bank,
607 0 : funk,
608 0 : xid,
609 0 : stake_delegations,
610 0 : stake_history );
611 :
612 : /* If there are no points, then we set the rewards to 0. */
613 0 : *rewards_out = points>0UL ? *rewards_out: 0UL;
614 :
615 0 : if( capture_ctx ) {
616 0 : ulong const epoch = fd_bank_epoch_get( bank );
617 0 : fd_solcap_writer_stake_rewards_begin( capture_ctx->capture,
618 0 : epoch,
619 0 : epoch-1UL, /* FIXME: this is not strictly correct */
620 0 : *rewards_out,
621 0 : points );
622 0 : }
623 :
624 : /* Calculate the stake and vote rewards for each account. We want to
625 : use the vote states from the end of the current_epoch. */
626 0 : calculate_stake_vote_rewards(
627 0 : bank,
628 0 : funk,
629 0 : xid,
630 0 : stake_delegations,
631 0 : capture_ctx,
632 0 : stake_history,
633 0 : rewarded_epoch,
634 0 : *rewards_out,
635 0 : points,
636 0 : runtime_stack );
637 :
638 0 : return points;
639 0 : }
640 :
641 : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
642 :
643 : https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
644 : */
645 : static ulong
646 : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
647 : ulong slot,
648 0 : ulong total_stake_accounts ) {
649 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
650 0 : if( epoch_schedule->warmup &&
651 0 : fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
652 0 : return 1UL;
653 0 : }
654 :
655 0 : ulong num_chunks = total_stake_accounts / (ulong)STAKE_ACCOUNT_STORES_PER_BLOCK + (total_stake_accounts % STAKE_ACCOUNT_STORES_PER_BLOCK != 0);
656 0 : num_chunks = fd_ulong_max( num_chunks, 1UL );
657 0 : num_chunks = fd_ulong_min( num_chunks,
658 0 : fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
659 0 : return num_chunks;
660 0 : }
661 :
662 : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
663 :
664 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L277 */
665 : static void
666 : calculate_rewards_for_partitioning( fd_bank_t * bank,
667 : fd_funk_t * funk,
668 : fd_funk_txn_xid_t const * xid,
669 : fd_runtime_stack_t * runtime_stack,
670 : fd_stake_delegations_t const * stake_delegations,
671 : fd_capture_ctx_t * capture_ctx,
672 : ulong prev_epoch,
673 0 : fd_partitioned_rewards_calculation_t * result ) {
674 0 : fd_prev_epoch_inflation_rewards_t rewards;
675 :
676 0 : calculate_previous_epoch_inflation_rewards( bank,
677 0 : fd_bank_capitalization_get( bank ),
678 0 : prev_epoch,
679 0 : &rewards );
680 :
681 0 : ulong total_rewards = rewards.validator_rewards;
682 :
683 0 : uint128 points = calculate_validator_rewards( bank,
684 0 : funk,
685 0 : xid,
686 0 : runtime_stack,
687 0 : stake_delegations,
688 0 : capture_ctx,
689 0 : prev_epoch,
690 0 : &total_rewards );
691 :
692 : /* The agave client does not partition the stake rewards until the
693 : first distribution block. We calculate the partitions during the
694 : boundary. */
695 0 : result->validator_points = points;
696 0 : result->validator_rewards = total_rewards;
697 0 : result->validator_rate = rewards.validator_rate;
698 0 : result->foundation_rate = rewards.foundation_rate;
699 0 : result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
700 0 : result->capitalization = fd_bank_capitalization_get( bank );
701 0 : }
702 :
703 : /* Calculate rewards from previous epoch and distribute vote rewards
704 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L148 */
705 : static void
706 : calculate_rewards_and_distribute_vote_rewards( fd_bank_t * bank,
707 : fd_accdb_user_t * accdb,
708 : fd_funk_txn_xid_t const * xid,
709 : fd_runtime_stack_t * runtime_stack,
710 : fd_stake_delegations_t const * stake_delegations,
711 : fd_capture_ctx_t * capture_ctx,
712 0 : ulong prev_epoch ) {
713 :
714 : /* First we must compute the stake and vote rewards for the just
715 : completed epoch. We store the stake account rewards and vote
716 : states rewards in the bank */
717 :
718 0 : fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
719 0 : calculate_rewards_for_partitioning( bank,
720 0 : accdb->funk,
721 0 : xid,
722 0 : runtime_stack,
723 0 : stake_delegations,
724 0 : capture_ctx,
725 0 : prev_epoch,
726 0 : rewards_calc_result );
727 :
728 0 : fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( bank );
729 :
730 : /* Iterate over all the vote reward nodes and distribute the rewards
731 : to the vote accounts. After each reward has been paid out,
732 : calcualte the lthash for each vote account. */
733 0 : ulong distributed_rewards = 0UL;
734 0 : fd_vote_states_iter_t iter_[1];
735 0 : for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( iter_, vote_states );
736 0 : !fd_vote_states_iter_done( iter );
737 0 : fd_vote_states_iter_next( iter ) ) {
738 0 : fd_vote_state_ele_t * vote_state = fd_vote_states_iter_ele( iter );
739 :
740 0 : ulong rewards = runtime_stack->stakes.vote_rewards[ vote_state->idx ];
741 0 : if( rewards==0UL ) {
742 0 : continue;
743 0 : }
744 :
745 0 : fd_pubkey_t const * vote_pubkey = &vote_state->vote_account;
746 0 : fd_txn_account_t vote_rec[1];
747 0 : fd_funk_rec_prepare_t prepare = {0};
748 :
749 0 : if( FD_UNLIKELY( !fd_txn_account_init_from_funk_mutable(
750 0 : vote_rec,
751 0 : vote_pubkey,
752 0 : accdb,
753 0 : xid,
754 0 : 1,
755 0 : 0UL,
756 0 : &prepare
757 0 : ) ) ) {
758 0 : FD_LOG_ERR(( "Unable to modify vote account" ));
759 0 : }
760 :
761 0 : fd_lthash_value_t prev_hash[1];
762 0 : fd_hashes_account_lthash(
763 0 : vote_pubkey,
764 0 : fd_txn_account_get_meta( vote_rec ),
765 0 : fd_txn_account_get_data( vote_rec ),
766 0 : prev_hash );
767 :
768 0 : fd_txn_account_set_slot( vote_rec, fd_bank_slot_get( bank ) );
769 :
770 0 : if( FD_UNLIKELY( fd_txn_account_checked_add_lamports( vote_rec, rewards ) ) ) {
771 0 : FD_LOG_ERR(( "Adding lamports to vote account would cause overflow" ));
772 0 : }
773 :
774 0 : fd_hashes_update_lthash( vote_rec, prev_hash,bank, capture_ctx );
775 0 : fd_txn_account_mutable_fini( vote_rec, accdb, &prepare );
776 :
777 0 : distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards );
778 :
779 0 : if( capture_ctx ) {
780 0 : fd_solcap_write_vote_account_payout( capture_ctx->capture,
781 0 : vote_pubkey,
782 0 : fd_bank_slot_get( bank ),
783 0 : fd_txn_account_get_lamports( vote_rec ),
784 0 : (long)rewards );
785 0 : }
786 0 : }
787 :
788 0 : fd_bank_vote_states_end_locking_query( bank );
789 :
790 : /* Verify that we didn't pay any more than we expected to */
791 0 : fd_epoch_rewards_t * epoch_rewards = fd_bank_epoch_rewards_locking_modify( bank );
792 :
793 0 : ulong total_rewards = fd_ulong_sat_add( distributed_rewards, epoch_rewards->total_stake_rewards );
794 0 : if( FD_UNLIKELY( rewards_calc_result->validator_rewards<total_rewards ) ) {
795 0 : FD_LOG_CRIT(( "Unexpected rewards calculation result" ));
796 0 : }
797 :
798 0 : fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + distributed_rewards );
799 :
800 0 : epoch_rewards->distributed_rewards = distributed_rewards;
801 0 : epoch_rewards->total_rewards = rewards_calc_result->validator_rewards;
802 0 : epoch_rewards->total_points = rewards_calc_result->validator_points;
803 0 : fd_bank_epoch_rewards_end_locking_modify( bank );
804 0 : }
805 :
806 : /* Distributes a single partitioned reward to a single stake account */
807 : static int
808 : distribute_epoch_reward_to_stake_acc( fd_bank_t * bank,
809 : fd_accdb_user_t * accdb,
810 : fd_funk_txn_xid_t const * xid,
811 : fd_capture_ctx_t * capture_ctx,
812 : fd_pubkey_t * stake_pubkey,
813 : ulong reward_lamports,
814 0 : ulong new_credits_observed ) {
815 0 : fd_txn_account_t stake_acc_rec[1];
816 0 : fd_funk_rec_prepare_t prepare = {0};
817 0 : if( FD_UNLIKELY( !fd_txn_account_init_from_funk_mutable( stake_acc_rec,
818 0 : stake_pubkey,
819 0 : accdb,
820 0 : xid,
821 0 : 0,
822 0 : 0UL,
823 0 : &prepare ) ) ) {
824 0 : FD_LOG_ERR(( "Unable to modify stake account" ));
825 0 : }
826 :
827 0 : fd_lthash_value_t prev_hash[1];
828 0 : fd_hashes_account_lthash(
829 0 : stake_pubkey,
830 0 : fd_txn_account_get_meta( stake_acc_rec ),
831 0 : fd_txn_account_get_data( stake_acc_rec ),
832 0 : prev_hash );
833 :
834 0 : fd_txn_account_set_slot( stake_acc_rec, fd_bank_slot_get( bank ) );
835 :
836 0 : fd_stake_state_v2_t stake_state[1] = {0};
837 0 : if( fd_stake_get_state( stake_acc_rec, stake_state ) != 0 ) {
838 0 : FD_LOG_DEBUG(( "failed to read stake state for %s", FD_BASE58_ENC_32_ALLOCA( stake_pubkey ) ));
839 0 : return 1;
840 0 : }
841 :
842 0 : if ( !fd_stake_state_v2_is_stake( stake_state ) ) {
843 0 : FD_LOG_DEBUG(( "non-stake stake account, this should never happen" ));
844 0 : return 1;
845 0 : }
846 :
847 0 : if( fd_txn_account_checked_add_lamports( stake_acc_rec, reward_lamports ) ) {
848 0 : FD_LOG_DEBUG(( "failed to add lamports to stake account" ));
849 0 : return 1;
850 0 : }
851 :
852 0 : ulong old_credits_observed = stake_state->inner.stake.stake.credits_observed;
853 0 : stake_state->inner.stake.stake.credits_observed = new_credits_observed;
854 0 : stake_state->inner.stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->inner.stake.stake.delegation.stake,
855 0 : reward_lamports );
856 :
857 : /* The stake account has just been updated, so we need to update the
858 : stake delegations stored in the bank. */
859 0 : fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_delta_locking_modify( bank );
860 0 : fd_stake_delegations_update(
861 0 : stake_delegations,
862 0 : stake_pubkey,
863 0 : &stake_state->inner.stake.stake.delegation.voter_pubkey,
864 0 : stake_state->inner.stake.stake.delegation.stake,
865 0 : stake_state->inner.stake.stake.delegation.activation_epoch,
866 0 : stake_state->inner.stake.stake.delegation.deactivation_epoch,
867 0 : stake_state->inner.stake.stake.credits_observed,
868 0 : stake_state->inner.stake.stake.delegation.warmup_cooldown_rate );
869 0 : fd_bank_stake_delegations_delta_end_locking_modify( bank );
870 :
871 0 : if( capture_ctx ) {
872 0 : fd_solcap_write_stake_account_payout( capture_ctx->capture,
873 0 : stake_pubkey,
874 0 : fd_bank_slot_get( bank ),
875 0 : fd_txn_account_get_lamports( stake_acc_rec ),
876 0 : (long)reward_lamports,
877 0 : new_credits_observed,
878 0 : (long)( new_credits_observed-old_credits_observed ),
879 0 : stake_state->inner.stake.stake.delegation.stake,
880 0 : (long)reward_lamports );
881 0 : }
882 :
883 0 : if( FD_UNLIKELY( write_stake_state( stake_acc_rec, stake_state ) != 0 ) ) {
884 0 : FD_LOG_ERR(( "write_stake_state failed" ));
885 0 : }
886 :
887 0 : fd_hashes_update_lthash( stake_acc_rec, prev_hash, bank, capture_ctx );
888 0 : fd_txn_account_mutable_fini( stake_acc_rec, accdb, &prepare );
889 :
890 0 : return 0;
891 0 : }
892 :
893 : /* Process reward credits for a partition of rewards. Store the rewards
894 : to AccountsDB, update reward history record and total capitalization
895 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
896 : static void
897 : distribute_epoch_rewards_in_partition( fd_epoch_rewards_t const * epoch_rewards,
898 : ulong partition_idx,
899 : fd_bank_t * bank,
900 : fd_accdb_user_t * accdb,
901 : fd_funk_txn_xid_t const * xid,
902 0 : fd_capture_ctx_t * capture_ctx ) {
903 :
904 0 : ulong lamports_distributed = 0UL;
905 0 : ulong lamports_burned = 0UL;
906 :
907 0 : fd_epoch_rewards_iter_t iter_[1];
908 0 : for( fd_epoch_rewards_iter_t * iter = fd_epoch_rewards_iter_init( iter_, epoch_rewards, partition_idx );
909 0 : !fd_epoch_rewards_iter_done( iter );
910 0 : fd_epoch_rewards_iter_next( iter ) ) {
911 0 : fd_epoch_stake_reward_t * stake_reward = fd_epoch_rewards_iter_ele( iter );
912 :
913 0 : if( FD_LIKELY( !distribute_epoch_reward_to_stake_acc( bank,
914 0 : accdb,
915 0 : xid,
916 0 : capture_ctx,
917 0 : &stake_reward->stake_pubkey,
918 0 : stake_reward->lamports,
919 0 : stake_reward->credits_observed ) ) ) {
920 0 : lamports_distributed += stake_reward->lamports;
921 0 : } else {
922 0 : lamports_burned += stake_reward->lamports;
923 0 : }
924 0 : }
925 :
926 : /* Update the epoch rewards sysvar with the amount distributed and burnt */
927 0 : fd_sysvar_epoch_rewards_distribute( bank, accdb, xid, capture_ctx, lamports_distributed + lamports_burned );
928 :
929 0 : FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
930 :
931 0 : fd_bank_capitalization_set( bank, fd_bank_capitalization_get( bank ) + lamports_distributed );
932 0 : }
933 :
934 : /* Process reward distribution for the block if it is inside reward interval.
935 :
936 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L42 */
937 : void
938 : fd_distribute_partitioned_epoch_rewards( fd_bank_t * bank,
939 : fd_accdb_user_t * accdb,
940 : fd_funk_txn_xid_t const * xid,
941 0 : fd_capture_ctx_t * capture_ctx ) {
942 :
943 0 : fd_epoch_rewards_t const * epoch_rewards = fd_bank_epoch_rewards_locking_query( bank );
944 0 : if( FD_LIKELY( !epoch_rewards ) ) {
945 0 : fd_bank_epoch_rewards_end_locking_query( bank );
946 0 : return;
947 0 : }
948 :
949 0 : ulong block_height = fd_bank_block_height_get( bank );
950 0 : ulong distribution_starting_block_height = epoch_rewards->starting_block_height;
951 0 : ulong distribution_end_exclusive = fd_epoch_rewards_get_exclusive_ending_block_height( epoch_rewards );
952 :
953 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
954 0 : ulong epoch = fd_bank_epoch_get( bank );
955 :
956 0 : if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_schedule ) <= epoch_rewards->num_partitions ) ) {
957 0 : FD_LOG_CRIT(( "Should not be distributing rewards" ));
958 0 : }
959 :
960 0 : if( FD_UNLIKELY( block_height>=distribution_starting_block_height && block_height<distribution_end_exclusive ) ) {
961 :
962 0 : ulong partition_idx = block_height-distribution_starting_block_height;
963 0 : distribute_epoch_rewards_in_partition( epoch_rewards, partition_idx, bank, accdb, xid, capture_ctx );
964 :
965 : /* If we have finished distributing rewards, set the status to inactive */
966 0 : if( fd_ulong_sat_add( block_height, 1UL )>=distribution_end_exclusive ) {
967 0 : fd_sysvar_epoch_rewards_set_inactive( bank, accdb, xid, capture_ctx );
968 0 : }
969 0 : }
970 :
971 0 : fd_bank_epoch_rewards_end_locking_query( bank );
972 :
973 0 : }
974 :
975 : /* Partitioned epoch rewards entry-point.
976 :
977 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L102
978 : */
979 : void
980 : fd_begin_partitioned_rewards( fd_bank_t * bank,
981 : fd_accdb_user_t * accdb,
982 : fd_funk_txn_xid_t const * xid,
983 : fd_runtime_stack_t * runtime_stack,
984 : fd_capture_ctx_t * capture_ctx,
985 : fd_stake_delegations_t const * stake_delegations,
986 : fd_hash_t const * parent_blockhash,
987 0 : ulong parent_epoch ) {
988 :
989 0 : calculate_rewards_and_distribute_vote_rewards(
990 0 : bank,
991 0 : accdb,
992 0 : xid,
993 0 : runtime_stack,
994 0 : stake_delegations,
995 0 : capture_ctx,
996 0 : parent_epoch );
997 :
998 : /* Once the rewards for vote accounts have been distributed and stake
999 : account rewards have been calculated, we can now set our epoch
1000 : reward status to be active and we can initialize the epoch rewards
1001 : sysvar. This sysvar is then deleted once all of the partitioned
1002 : stake rewards have been distributed.
1003 :
1004 : The Agave client calculates the partitions for each stake reward
1005 : when the first distribution block is reached. The Firedancer
1006 : client differs here since we hash the partitions during the epoch
1007 : boundary. */
1008 :
1009 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
1010 0 : fd_epoch_rewards_t * epoch_rewards = fd_epoch_rewards_join( fd_bank_epoch_rewards_locking_modify( bank ) );
1011 :
1012 0 : ulong num_partitions = get_reward_distribution_num_blocks(
1013 0 : epoch_schedule,
1014 0 : fd_bank_slot_get( bank ),
1015 0 : epoch_rewards->stake_rewards_cnt );
1016 :
1017 0 : fd_epoch_rewards_hash_into_partitions( epoch_rewards, parent_blockhash, num_partitions );
1018 :
1019 0 : ulong distribution_starting_block_height = fd_bank_block_height_get( bank ) + REWARD_CALCULATION_NUM_BLOCKS;
1020 :
1021 0 : epoch_rewards->starting_block_height = distribution_starting_block_height;
1022 :
1023 0 : fd_sysvar_epoch_rewards_init(
1024 0 : bank,
1025 0 : accdb,
1026 0 : xid,
1027 0 : capture_ctx,
1028 0 : epoch_rewards->distributed_rewards,
1029 0 : distribution_starting_block_height,
1030 0 : epoch_rewards->num_partitions,
1031 0 : epoch_rewards->total_rewards,
1032 0 : epoch_rewards->total_points,
1033 0 : parent_blockhash );
1034 0 : fd_bank_epoch_rewards_end_locking_modify( bank );
1035 0 : }
1036 :
1037 : /*
1038 : Re-calculates partitioned stake rewards.
1039 : This updates the slot context's epoch reward status with the recalculated partitioned rewards.
1040 :
1041 : https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521 */
1042 : void
1043 : fd_rewards_recalculate_partitioned_rewards( fd_banks_t * banks,
1044 : fd_bank_t * bank,
1045 : fd_funk_t * funk,
1046 : fd_funk_txn_xid_t const * xid,
1047 : fd_runtime_stack_t * runtime_stack,
1048 0 : fd_capture_ctx_t * capture_ctx ) {
1049 :
1050 0 : fd_sysvar_epoch_rewards_t epoch_rewards_sysvar[1];
1051 0 : if( FD_UNLIKELY( !fd_sysvar_epoch_rewards_read( funk, xid, epoch_rewards_sysvar ) ) ) {
1052 0 : FD_LOG_DEBUG(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
1053 0 : return;
1054 0 : }
1055 :
1056 0 : FD_LOG_DEBUG(( "recalculating partitioned rewards" ));
1057 :
1058 0 : if( FD_UNLIKELY( !epoch_rewards_sysvar->active ) ) {
1059 0 : FD_LOG_DEBUG(( "epoch rewards is inactive" ));
1060 0 : return;
1061 0 : }
1062 :
1063 : /* If partitioned rewards are active, the rewarded epoch is always the immediately
1064 : preceeding epoch.
1065 :
1066 : https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
1067 0 : FD_LOG_DEBUG(( "epoch rewards is active" ));
1068 0 : runtime_stack->stakes.prev_vote_credits_used = 1;
1069 :
1070 0 : ulong const slot = fd_bank_slot_get( bank );
1071 0 : ulong const epoch = fd_bank_epoch_get( bank );
1072 0 : ulong const rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
1073 :
1074 0 : int _err[1] = {0};
1075 0 : ulong new_warmup_cooldown_rate_epoch_;
1076 0 : ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_;
1077 0 : int is_some = fd_new_warmup_cooldown_rate_epoch(
1078 0 : fd_bank_epoch_schedule_query( bank ),
1079 0 : fd_bank_features_query( bank ),
1080 0 : slot,
1081 0 : new_warmup_cooldown_rate_epoch,
1082 0 : _err );
1083 0 : if( FD_UNLIKELY( !is_some ) ) {
1084 0 : new_warmup_cooldown_rate_epoch = NULL;
1085 0 : }
1086 :
1087 0 : fd_stake_history_t stake_history[1];
1088 0 : if( FD_UNLIKELY( !fd_sysvar_stake_history_read( funk, xid, stake_history ) ) ) {
1089 0 : FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
1090 0 : }
1091 :
1092 0 : fd_stake_delegations_t const * stake_delegations = fd_bank_stake_delegations_frontier_query( banks, bank );
1093 0 : if( FD_UNLIKELY( !stake_delegations ) ) {
1094 0 : FD_LOG_CRIT(( "stake_delegations is NULL" ));
1095 0 : }
1096 :
1097 : /* Make sure is_recalculation is ==1 since we are booting up in the
1098 : middle of rewards distribution (so we should use the epoch
1099 : stakes for the end of epoch E-1 since we are still distributing
1100 : rewards for the previous epoch). */
1101 0 : calculate_stake_vote_rewards(
1102 0 : bank,
1103 0 : funk,
1104 0 : xid,
1105 0 : stake_delegations,
1106 0 : capture_ctx,
1107 0 : stake_history,
1108 0 : rewarded_epoch,
1109 0 : epoch_rewards_sysvar->total_rewards,
1110 0 : epoch_rewards_sysvar->total_points,
1111 0 : runtime_stack );
1112 :
1113 0 : fd_epoch_rewards_t * epoch_rewards = fd_epoch_rewards_join( fd_bank_epoch_rewards_locking_modify( bank ) );
1114 0 : fd_epoch_rewards_hash_into_partitions( epoch_rewards, &epoch_rewards_sysvar->parent_blockhash, epoch_rewards_sysvar->num_partitions );
1115 :
1116 : /* Update the epoch reward status with the newly re-calculated partitions. */
1117 0 : epoch_rewards->starting_block_height = epoch_rewards_sysvar->distribution_starting_block_height;
1118 0 : fd_bank_epoch_rewards_end_locking_modify( bank );
1119 :
1120 0 : runtime_stack->stakes.prev_vote_credits_used = 0;
1121 0 : }
|