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