Line data Source code
1 : #include "fd_rewards.h"
2 : #include <math.h>
3 :
4 : #include "../../ballet/siphash13/fd_siphash13.h"
5 : #include "../runtime/fd_executor_err.h"
6 : #include "../runtime/fd_system_ids.h"
7 : #include "../runtime/fd_runtime.h"
8 : #include "../runtime/context/fd_exec_slot_ctx.h"
9 : #include "../runtime/program/fd_program_util.h"
10 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
11 :
12 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/native_token.rs#L6 */
13 0 : #define LAMPORTS_PER_SOL (1000000000UL)
14 :
15 : /* Number of blocks for reward calculation and storing vote accounts.
16 : Distributing rewards to stake accounts begins AFTER this many blocks.
17 :
18 : https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L27 */
19 0 : #define REWARD_CALCULATION_NUM_BLOCKS (1UL)
20 :
21 : /* stake accounts to store in one block during partitioned reward interval. Target to store 64 rewards per entry/tick in a block. A block has a minimum of 64 entries/tick. This gives 4096 total rewards to store in one block. */
22 0 : #define STAKE_ACCOUNT_STORES_PER_BLOCK (4096UL)
23 :
24 : /* https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L219 */
25 0 : #define MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH (10UL)
26 :
27 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */
28 : static double
29 0 : total( fd_inflation_t const * inflation, double year ) {
30 0 : if ( FD_UNLIKELY( year == 0.0 ) ) {
31 0 : FD_LOG_ERR(( "inflation year 0" ));
32 0 : }
33 0 : double tapered = inflation->initial * pow( (1.0 - inflation->taper), year );
34 0 : return (tapered > inflation->terminal) ? tapered : inflation->terminal;
35 0 : }
36 :
37 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L102 */
38 : static double
39 0 : foundation( fd_inflation_t const * inflation, double year ) {
40 0 : return (year < inflation->foundation_term) ? inflation->foundation * total(inflation, year) : 0.0;
41 0 : }
42 :
43 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L97 */
44 : static double
45 0 : validator( fd_inflation_t const * inflation, double year) {
46 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/src/inflation.rs#L96-L99 */
47 0 : FD_LOG_DEBUG(("Validator Rate: %.16f %.16f %.16f %.16f %.16f", year, total( inflation, year ), foundation( inflation, year ), inflation->taper, inflation->initial));
48 0 : return total( inflation, year ) - foundation( inflation, year );
49 0 : }
50 :
51 : /* Calculates the starting slot for inflation from the activation slot. The activation slot is the earliest
52 : activation slot of the following features:
53 : - devnet_and_testnet
54 : - full_inflation_enable, if full_inflation_vote has been activated
55 :
56 : https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2095 */
57 : static FD_FN_CONST ulong
58 0 : get_inflation_start_slot( fd_exec_slot_ctx_t * slot_ctx ) {
59 0 : ulong devnet_and_testnet = FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, devnet_and_testnet ) ? fd_bank_features_query( slot_ctx->bank )->devnet_and_testnet : ULONG_MAX;
60 :
61 0 : ulong enable = ULONG_MAX;
62 0 : if( FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, full_inflation_vote ) &&
63 0 : FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, full_inflation_enable ) ) {
64 0 : enable = fd_bank_features_query( slot_ctx->bank )->full_inflation_enable;
65 0 : }
66 :
67 0 : ulong min_slot = fd_ulong_min( enable, devnet_and_testnet );
68 0 : if( min_slot == ULONG_MAX ) {
69 0 : if( FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, pico_inflation ) ) {
70 0 : min_slot = fd_bank_features_query( slot_ctx->bank )->pico_inflation;
71 0 : } else {
72 0 : min_slot = 0;
73 0 : }
74 0 : }
75 0 : return min_slot;
76 0 : }
77 :
78 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2110 */
79 : static ulong
80 : get_inflation_num_slots( fd_exec_slot_ctx_t * slot_ctx,
81 : fd_epoch_schedule_t const * epoch_schedule,
82 0 : ulong slot ) {
83 0 : ulong inflation_activation_slot = get_inflation_start_slot( slot_ctx );
84 0 : ulong inflation_start_slot = fd_epoch_slot0( epoch_schedule,
85 0 : fd_ulong_sat_sub( fd_slot_to_epoch( epoch_schedule,
86 0 : inflation_activation_slot, NULL ),
87 0 : 1UL ) );
88 :
89 0 : ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, NULL );
90 :
91 0 : return fd_epoch_slot0( epoch_schedule, epoch ) - inflation_start_slot;
92 0 : }
93 :
94 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2121 */
95 : static double
96 0 : slot_in_year_for_inflation( fd_exec_slot_ctx_t * slot_ctx ) {
97 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
98 :
99 0 : ulong num_slots = get_inflation_num_slots( slot_ctx, epoch_schedule, slot_ctx->slot );
100 0 : return (double)num_slots / (double)fd_bank_slots_per_year_get( slot_ctx->bank );
101 0 : }
102 :
103 : /* For a given stake and vote_state, calculate how many points were earned (credits * stake) and new value
104 : for credits_observed were the points paid
105 :
106 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L109 */
107 : static void
108 : calculate_stake_points_and_credits( fd_stake_history_t const * stake_history,
109 : fd_stake_t const * stake,
110 : fd_vote_state_versioned_t * vote_state_versioned,
111 : ulong * new_rate_activation_epoch,
112 0 : fd_calculated_stake_points_t * result ) {
113 :
114 0 : ulong credits_in_stake = stake->credits_observed;
115 :
116 0 : fd_vote_epoch_credits_t * epoch_credits;
117 0 : switch( vote_state_versioned->discriminant ) {
118 0 : case fd_vote_state_versioned_enum_current:
119 0 : epoch_credits = vote_state_versioned->inner.current.epoch_credits;
120 0 : break;
121 0 : case fd_vote_state_versioned_enum_v0_23_5:
122 0 : epoch_credits = vote_state_versioned->inner.v0_23_5.epoch_credits;
123 0 : break;
124 0 : case fd_vote_state_versioned_enum_v1_14_11:
125 0 : epoch_credits = vote_state_versioned->inner.v1_14_11.epoch_credits;
126 0 : break;
127 0 : default:
128 0 : FD_LOG_ERR(( "invalid vote account, should never happen" ));
129 0 : }
130 :
131 0 : ulong credits_in_vote = 0UL;
132 0 : if( FD_LIKELY( !deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) {
133 0 : credits_in_vote = deq_fd_vote_epoch_credits_t_peek_tail_const( epoch_credits )->credits;
134 0 : }
135 :
136 : /* If the Vote account has less credits observed than the Stake account,
137 : something is wrong and we need to force an update.
138 :
139 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L142 */
140 0 : if( FD_UNLIKELY( credits_in_vote < credits_in_stake ) ) {
141 0 : result->points = 0;
142 0 : result->new_credits_observed = credits_in_vote;
143 0 : result->force_credits_update_with_skipped_reward = 1;
144 0 : return;
145 0 : }
146 :
147 : /* If the Vote account has the same amount of credits observed as the Stake account,
148 : then the Vote account hasn't earnt any credits and so there is nothing to update.
149 :
150 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L148 */
151 0 : if( FD_UNLIKELY( credits_in_vote == credits_in_stake ) ) {
152 0 : result->points = 0;
153 0 : result->new_credits_observed = credits_in_vote;
154 0 : result->force_credits_update_with_skipped_reward = 0;
155 0 : return;
156 0 : }
157 :
158 : /* Calculate the points for each epoch credit */
159 0 : uint128 points = 0;
160 0 : ulong new_credits_observed = credits_in_stake;
161 0 : for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits );
162 0 : !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter );
163 0 : iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) {
164 :
165 0 : fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter );
166 0 : ulong final_epoch_credits = ele->credits;
167 0 : ulong initial_epoch_credits = ele->prev_credits;
168 0 : uint128 earned_credits = 0;
169 0 : if( FD_LIKELY( credits_in_stake < initial_epoch_credits ) ) {
170 0 : earned_credits = (uint128)(final_epoch_credits - initial_epoch_credits);
171 0 : } else if( FD_UNLIKELY( credits_in_stake < final_epoch_credits ) ) {
172 0 : earned_credits = (uint128)(final_epoch_credits - new_credits_observed);
173 0 : }
174 :
175 0 : new_credits_observed = fd_ulong_max( new_credits_observed, final_epoch_credits );
176 :
177 0 : ulong stake_amount = fd_stake_activating_and_deactivating( &stake->delegation, ele->epoch, stake_history, new_rate_activation_epoch ).effective;
178 :
179 0 : points += (uint128)stake_amount * earned_credits;
180 0 : }
181 :
182 0 : result->points = points;
183 0 : result->new_credits_observed = new_credits_observed;
184 0 : result->force_credits_update_with_skipped_reward = 0;
185 0 : }
186 :
187 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L127 */
188 : static int
189 : calculate_stake_rewards( fd_stake_history_t const * stake_history,
190 : fd_stake_t const * stake,
191 : fd_vote_state_versioned_t * vote_state_versioned,
192 : ulong rewarded_epoch,
193 : fd_point_value_t * point_value,
194 : ulong * new_rate_activation_epoch,
195 0 : fd_calculated_stake_rewards_t * result ) {
196 0 : fd_calculated_stake_points_t stake_points_result = {0};
197 0 : calculate_stake_points_and_credits( stake_history, stake, vote_state_versioned, new_rate_activation_epoch, &stake_points_result);
198 :
199 : // Drive credits_observed forward unconditionally when rewards are disabled
200 : // or when this is the stake's activation epoch
201 0 : if( ( point_value->rewards==0UL ) ||
202 0 : ( stake->delegation.activation_epoch==rewarded_epoch ) ) {
203 0 : stake_points_result.force_credits_update_with_skipped_reward |= 1;
204 0 : }
205 :
206 0 : if( stake_points_result.force_credits_update_with_skipped_reward ) {
207 0 : result->staker_rewards = 0;
208 0 : result->voter_rewards = 0;
209 0 : result->new_credits_observed = stake_points_result.new_credits_observed;
210 0 : return 0;
211 0 : }
212 0 : if( stake_points_result.points == 0 || point_value->points == 0 ) {
213 0 : return 1;
214 0 : }
215 :
216 : /* FIXME: need to error out if the conversion from uint128 to u64 fails, also use 128 checked mul and div */
217 0 : ulong rewards = (ulong)(stake_points_result.points * (uint128)(point_value->rewards) / (uint128) point_value->points);
218 0 : if( rewards == 0 ) {
219 0 : return 1;
220 0 : }
221 :
222 0 : fd_commission_split_t split_result;
223 0 : fd_vote_commission_split( vote_state_versioned, rewards, &split_result );
224 0 : if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) {
225 0 : return 1;
226 0 : }
227 :
228 0 : result->staker_rewards = split_result.staker_portion;
229 0 : result->voter_rewards = split_result.voter_portion;
230 0 : result->new_credits_observed = stake_points_result.new_credits_observed;
231 0 : return 0;
232 0 : }
233 :
234 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */
235 : static int
236 : redeem_rewards( fd_stake_history_t const * stake_history,
237 : fd_stake_t const * stake,
238 : fd_vote_state_versioned_t * vote_state_versioned,
239 : ulong rewarded_epoch,
240 : fd_point_value_t * point_value,
241 : ulong * new_rate_activation_epoch,
242 0 : fd_calculated_stake_rewards_t * calculated_stake_rewards) {
243 :
244 0 : int rc = calculate_stake_rewards( stake_history, stake, vote_state_versioned, rewarded_epoch, point_value, new_rate_activation_epoch, calculated_stake_rewards );
245 0 : if( FD_UNLIKELY( rc!=0 ) ) {
246 0 : return rc;
247 0 : }
248 :
249 0 : return FD_EXECUTOR_INSTR_SUCCESS;
250 0 : }
251 :
252 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/points.rs#L70 */
253 : static int
254 : calculate_points( fd_stake_t const * stake,
255 : fd_vote_state_versioned_t * vote_state_versioned,
256 : fd_stake_history_t const * stake_history,
257 : ulong * new_rate_activation_epoch,
258 0 : uint128 * result ) {
259 0 : fd_calculated_stake_points_t stake_point_result;
260 0 : calculate_stake_points_and_credits( stake_history, stake, vote_state_versioned, new_rate_activation_epoch, &stake_point_result );
261 0 : *result = stake_point_result.points;
262 :
263 0 : return FD_EXECUTOR_INSTR_SUCCESS;
264 0 : }
265 :
266 : /* Returns the length of the given epoch in slots
267 :
268 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/sdk/program/src/epoch_schedule.rs#L103 */
269 : static ulong
270 : get_slots_in_epoch( ulong epoch,
271 0 : fd_epoch_schedule_t const * epoch_schedule ) {
272 0 : return (epoch < epoch_schedule->first_normal_epoch) ?
273 0 : 1UL << fd_ulong_sat_add(epoch, FD_EPOCH_LEN_MIN_TRAILING_ZERO) :
274 0 : epoch_schedule->slots_per_epoch;
275 0 : }
276 :
277 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank.rs#L2082 */
278 : static double
279 : epoch_duration_in_years( fd_exec_slot_ctx_t * slot_ctx,
280 0 : ulong prev_epoch ) {
281 0 : ulong slots_in_epoch = get_slots_in_epoch( prev_epoch, fd_bank_epoch_schedule_query( slot_ctx->bank ) );
282 0 : return (double)slots_in_epoch / (double)fd_bank_slots_per_year_get( slot_ctx->bank );
283 0 : }
284 :
285 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank.rs#L2128 */
286 : static void
287 : calculate_previous_epoch_inflation_rewards( fd_exec_slot_ctx_t * slot_ctx,
288 : ulong prev_epoch_capitalization,
289 : ulong prev_epoch,
290 0 : fd_prev_epoch_inflation_rewards_t * rewards ) {
291 0 : double slot_in_year = slot_in_year_for_inflation( slot_ctx );
292 :
293 0 : rewards->validator_rate = validator( fd_bank_inflation_query( slot_ctx->bank ), slot_in_year );
294 0 : rewards->foundation_rate = foundation( fd_bank_inflation_query( slot_ctx->bank ), slot_in_year );
295 0 : rewards->prev_epoch_duration_in_years = epoch_duration_in_years( slot_ctx, prev_epoch );
296 0 : rewards->validator_rewards = (ulong)(rewards->validator_rate * (double)prev_epoch_capitalization * rewards->prev_epoch_duration_in_years);
297 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 ));
298 0 : }
299 :
300 : /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/lib.rs#L29 */
301 : static ulong
302 0 : get_minimum_stake_delegation( fd_exec_slot_ctx_t * slot_ctx ) {
303 0 : if( !FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, stake_minimum_delegation_for_rewards ) ) {
304 0 : return 0UL;
305 0 : }
306 :
307 0 : if( FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, stake_raise_minimum_delegation_to_1_sol ) ) {
308 0 : return LAMPORTS_PER_SOL;
309 0 : }
310 :
311 0 : return 1;
312 0 : }
313 :
314 : static void
315 : calculate_points_range( fd_epoch_info_pair_t const * stake_infos,
316 : fd_calculate_points_task_args_t * task_args,
317 : ulong start_idx,
318 0 : ulong end_idx ) {
319 :
320 0 : fd_stake_history_t const * stake_history = task_args->stake_history;
321 0 : ulong * new_warmup_cooldown_rate_epoch = task_args->new_warmup_cooldown_rate_epoch;
322 0 : ulong minimum_stake_delegation = task_args->minimum_stake_delegation;
323 :
324 0 : uint128 total_points = 0;
325 0 : for( ulong i=start_idx; i<end_idx; i++ ) {
326 0 : fd_epoch_info_pair_t const * stake_info = stake_infos + i;
327 0 : fd_stake_t const * stake = &stake_info->stake;
328 :
329 0 : if( FD_UNLIKELY( stake->delegation.stake<minimum_stake_delegation ) ) {
330 0 : continue;
331 0 : }
332 :
333 : /* Check that the vote account is present in our cache */
334 0 : fd_vote_info_pair_t_mapnode_t query_key;
335 0 : query_key.elem.account = stake->delegation.voter_pubkey;
336 0 : fd_vote_info_pair_t_mapnode_t * vote_state_info = fd_vote_info_pair_t_map_find( task_args->vote_states_pool, task_args->vote_states_root, &query_key );
337 0 : if( FD_UNLIKELY( vote_state_info==NULL ) ) {
338 0 : FD_LOG_DEBUG(( "vote account missing from cache" ));
339 0 : continue;
340 0 : }
341 :
342 0 : uint128 account_points;
343 0 : int err = calculate_points( stake, &vote_state_info->elem.state, stake_history, new_warmup_cooldown_rate_epoch, &account_points );
344 0 : if( FD_UNLIKELY( err ) ) {
345 0 : FD_LOG_DEBUG(( "failed to calculate points" ));
346 0 : continue;
347 0 : }
348 :
349 0 : total_points += account_points;
350 0 : }
351 :
352 0 : FD_ATOMIC_FETCH_AND_ADD( task_args->total_points, total_points );
353 :
354 :
355 0 : }
356 :
357 : static void
358 : calculate_points_tpool_task( void *tpool,
359 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
360 : void *args,
361 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
362 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
363 : ulong m0, ulong m1,
364 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
365 0 : fd_epoch_info_pair_t const * stake_infos = ((fd_epoch_info_pair_t const *)tpool);
366 0 : fd_calculate_points_task_args_t * task_args = (fd_calculate_points_task_args_t *)args;
367 :
368 0 : calculate_points_range( stake_infos, task_args, m0, m1 );
369 0 : }
370 :
371 : /* Calculates epoch reward points from stake/vote accounts.
372 :
373 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L472 */
374 : static void
375 : calculate_reward_points_partitioned( fd_exec_slot_ctx_t * slot_ctx,
376 : fd_stake_history_t const * stake_history,
377 : ulong rewards,
378 : fd_point_value_t * result,
379 : fd_tpool_t * tpool,
380 : fd_epoch_info_t * temp_info,
381 0 : fd_spad_t * runtime_spad ) {
382 :
383 0 : uint128 points = 0;
384 0 : ulong minimum_stake_delegation = get_minimum_stake_delegation( slot_ctx );
385 :
386 : /* Calculate the points for each stake delegation */
387 0 : int _err[1];
388 0 : ulong new_warmup_cooldown_rate_epoch_val = 0UL;
389 0 : ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_val;
390 0 : int is_some = fd_new_warmup_cooldown_rate_epoch( slot_ctx->slot,
391 0 : slot_ctx->funk,
392 0 : slot_ctx->funk_txn,
393 0 : runtime_spad,
394 0 : fd_bank_features_query( slot_ctx->bank ),
395 0 : new_warmup_cooldown_rate_epoch,
396 0 : _err );
397 0 : if( FD_UNLIKELY( !is_some ) ) {
398 0 : new_warmup_cooldown_rate_epoch = NULL;
399 0 : }
400 :
401 0 : fd_calculate_points_task_args_t task_args = {
402 0 : .stake_history = stake_history,
403 0 : .new_warmup_cooldown_rate_epoch = new_warmup_cooldown_rate_epoch,
404 0 : .minimum_stake_delegation = minimum_stake_delegation,
405 0 : .vote_states_pool = temp_info->vote_states_pool,
406 0 : .vote_states_root = temp_info->vote_states_root,
407 0 : .total_points = &points,
408 0 : };
409 :
410 0 : if( !!tpool ) {
411 0 : fd_tpool_exec_all_batch( tpool, 0UL, fd_tpool_worker_cnt( tpool ), calculate_points_tpool_task,
412 0 : temp_info->stake_infos, &task_args, NULL,
413 0 : 1UL, 0UL, temp_info->stake_infos_len );
414 0 : } else {
415 0 : calculate_points_range( temp_info->stake_infos, &task_args, 0UL, temp_info->stake_infos_len );
416 0 : }
417 :
418 0 : if( points > 0 ) {
419 0 : result->points = points;
420 0 : result->rewards = rewards;
421 0 : }
422 0 : }
423 :
424 : static void
425 : calculate_stake_vote_rewards_account( fd_epoch_info_t const * temp_info,
426 : fd_calculate_stake_vote_rewards_task_args_t const * task_args,
427 : ulong start_idx,
428 0 : ulong end_idx ) {
429 :
430 0 : fd_epoch_info_pair_t const * stake_infos = temp_info->stake_infos;
431 0 : fd_exec_slot_ctx_t * slot_ctx = task_args->slot_ctx;
432 0 : fd_stake_history_t const * stake_history = task_args->stake_history;
433 0 : ulong rewarded_epoch = task_args->rewarded_epoch;
434 0 : ulong * new_warmup_cooldown_rate_epoch = task_args->new_warmup_cooldown_rate_epoch;
435 0 : fd_point_value_t * point_value = task_args->point_value;
436 0 : fd_calculate_stake_vote_rewards_result_t * result = task_args->result; // written to
437 0 : fd_spad_t * spad = task_args->exec_spads[ fd_tile_idx() ];
438 :
439 0 : FD_SPAD_FRAME_BEGIN( spad ) {
440 :
441 0 : ulong minimum_stake_delegation = get_minimum_stake_delegation( slot_ctx );
442 0 : ulong total_stake_rewards = 0UL;
443 0 : ulong dlist_additional_cnt = 0UL;
444 :
445 : /* Build a local vote reward map */
446 0 : fd_vote_reward_t_mapnode_t * vote_reward_map_pool = fd_vote_reward_t_map_join( fd_vote_reward_t_map_new( fd_spad_alloc( spad,
447 0 : fd_vote_reward_t_map_align(),
448 0 : fd_vote_reward_t_map_footprint( end_idx-start_idx )),
449 0 : end_idx-start_idx ) );
450 0 : fd_vote_reward_t_mapnode_t * vote_reward_map_root = NULL;
451 :
452 0 : for( ulong i=start_idx; i<end_idx; i++ ) {
453 0 : fd_epoch_info_pair_t const * stake_info = stake_infos + i;
454 0 : fd_pubkey_t const * stake_acc = &stake_info->account;
455 0 : fd_stake_t const * stake = &stake_info->stake;
456 :
457 0 : if( FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, stake_minimum_delegation_for_rewards ) ) {
458 0 : if( stake->delegation.stake<minimum_stake_delegation ) {
459 0 : continue;
460 0 : }
461 0 : }
462 :
463 0 : fd_pubkey_t const * voter_acc = &stake->delegation.voter_pubkey;
464 0 : fd_vote_info_pair_t_mapnode_t key;
465 0 : key.elem.account = *voter_acc;
466 0 : fd_vote_info_pair_t_mapnode_t * vote_state_entry = fd_vote_info_pair_t_map_find( temp_info->vote_states_pool,
467 0 : temp_info->vote_states_root,
468 0 : &key );
469 0 : if( FD_UNLIKELY( vote_state_entry==NULL ) ) {
470 0 : continue;
471 0 : }
472 :
473 0 : fd_vote_state_versioned_t * vote_state = &vote_state_entry->elem.state;
474 :
475 : /* Note, this doesn't actually redeem any rewards.. this is a misnomer. */
476 0 : fd_calculated_stake_rewards_t calculated_stake_rewards[1] = {0};
477 0 : int err = redeem_rewards( stake_history,
478 0 : stake,
479 0 : vote_state,
480 0 : rewarded_epoch,
481 0 : point_value,
482 0 : new_warmup_cooldown_rate_epoch,
483 0 : calculated_stake_rewards );
484 0 : if( FD_UNLIKELY( err!=0 ) ) {
485 0 : FD_LOG_DEBUG(( "redeem_rewards failed for %s with error %d", FD_BASE58_ENC_32_ALLOCA( stake_acc->key ), err ));
486 0 : continue;
487 0 : }
488 :
489 : /* Fetch the comission for the vote account */
490 0 : uchar commission = 0;
491 0 : switch( vote_state->discriminant ) {
492 0 : case fd_vote_state_versioned_enum_current:
493 0 : commission = vote_state->inner.current.commission;
494 0 : break;
495 0 : case fd_vote_state_versioned_enum_v0_23_5:
496 0 : commission = vote_state->inner.v0_23_5.commission;
497 0 : break;
498 0 : case fd_vote_state_versioned_enum_v1_14_11:
499 0 : commission = vote_state->inner.v1_14_11.commission;
500 0 : break;
501 0 : default:
502 0 : FD_LOG_DEBUG(( "unsupported vote account" ));
503 0 : continue;
504 0 : }
505 :
506 : // Find and update the vote reward node in the local map
507 0 : fd_vote_reward_t_mapnode_t vote_map_key[1];
508 0 : vote_map_key->elem.pubkey = *voter_acc;
509 0 : fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_find( result->vote_reward_map_pool, result->vote_reward_map_root, vote_map_key );
510 0 : if( FD_UNLIKELY( vote_reward_node==NULL ) ) {
511 0 : FD_LOG_WARNING(( "vote account is missing from the vote rewards pool" ));
512 0 : continue;
513 0 : }
514 :
515 0 : vote_reward_node = fd_vote_reward_t_map_find( vote_reward_map_pool, vote_reward_map_root, vote_map_key );
516 :
517 0 : if( vote_reward_node==NULL ) {
518 0 : vote_reward_node = fd_vote_reward_t_map_acquire( vote_reward_map_pool );
519 0 : vote_reward_node->elem.pubkey = *voter_acc;
520 0 : vote_reward_node->elem.commission = commission;
521 0 : vote_reward_node->elem.vote_rewards = calculated_stake_rewards->voter_rewards;
522 0 : vote_reward_node->elem.needs_store = 1;
523 0 : fd_vote_reward_t_map_insert( vote_reward_map_pool, &vote_reward_map_root, vote_reward_node );
524 0 : } else {
525 0 : vote_reward_node->elem.vote_rewards += calculated_stake_rewards->voter_rewards;
526 0 : }
527 :
528 : /* Add the stake reward to list of all stake rewards. The update is thread-safe because each index in the dlist
529 : is only ever accessed / written to once among all threads. */
530 0 : fd_stake_reward_t * stake_reward = fd_stake_reward_calculation_pool_ele( result->stake_reward_calculation.pool, i );
531 0 : if( FD_UNLIKELY( stake_reward==NULL ) ) {
532 0 : FD_LOG_WARNING(( "could not find stake reward node in pool" ));
533 0 : continue;
534 0 : }
535 :
536 0 : fd_memcpy( stake_reward->stake_pubkey.uc, stake_acc, sizeof(fd_pubkey_t) );
537 0 : stake_reward->lamports = calculated_stake_rewards->staker_rewards;
538 0 : stake_reward->credits_observed = calculated_stake_rewards->new_credits_observed;
539 0 : stake_reward->valid = 1;
540 :
541 : /* Update the total stake rewards */
542 0 : total_stake_rewards += calculated_stake_rewards->staker_rewards;
543 0 : dlist_additional_cnt++;
544 0 : }
545 :
546 : /* Merge vote rewards with result after */
547 0 : for( fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_minimum( vote_reward_map_pool, vote_reward_map_root );
548 0 : vote_reward_node;
549 0 : vote_reward_node = fd_vote_reward_t_map_successor( vote_reward_map_pool, vote_reward_node ) ) {
550 :
551 0 : fd_vote_reward_t_mapnode_t * result_reward_node = fd_vote_reward_t_map_find( result->vote_reward_map_pool, result->vote_reward_map_root, vote_reward_node );
552 0 : FD_ATOMIC_CAS( &result_reward_node->elem.commission, 0, vote_reward_node->elem.commission );
553 0 : FD_ATOMIC_FETCH_AND_ADD( &result_reward_node->elem.vote_rewards, vote_reward_node->elem.vote_rewards );
554 0 : FD_ATOMIC_CAS( &result_reward_node->elem.needs_store, 0, 1 );
555 0 : }
556 :
557 0 : FD_ATOMIC_FETCH_AND_ADD( &result->stake_reward_calculation.total_stake_rewards_lamports, total_stake_rewards );
558 0 : FD_ATOMIC_FETCH_AND_ADD( &result->stake_reward_calculation.stake_rewards_len, dlist_additional_cnt );
559 :
560 0 : } FD_SPAD_FRAME_END;
561 :
562 :
563 0 : }
564 :
565 : /* Calculate the partitioned stake rewards for a single stake/vote account pair, updates result with these. */
566 : static void
567 : calculate_stake_vote_rewards_account_tpool_task( void *tpool,
568 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
569 : void *args,
570 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
571 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
572 : ulong m0, ulong m1,
573 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
574 :
575 0 : fd_epoch_info_t const * temp_info = ((fd_epoch_info_t const *)tpool);
576 0 : fd_calculate_stake_vote_rewards_task_args_t const * task_args = (fd_calculate_stake_vote_rewards_task_args_t const *)args;
577 0 : calculate_stake_vote_rewards_account( temp_info, task_args, m0, m1 );
578 0 : }
579 :
580 : /* Calculates epoch rewards for stake/vote accounts.
581 : Returns vote rewards, stake rewards, and the sum of all stake rewards in lamports.
582 :
583 : This uses a pool to allocate the stake rewards, which means that we can use dlists to
584 : distribute these into partitions of variable size without copying them or over-allocating
585 : the partitions.
586 : - We use a single dlist to put all the stake rewards during the calculation phase.
587 : - We then distribute these into partitions (whose size cannot be known in advance), where each
588 : partition is a separate dlist.
589 : - The dlist elements are all backed by the same pool, and allocated once.
590 : This approach optimizes memory usage and reduces copying.
591 :
592 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L334 */
593 : static void
594 : calculate_stake_vote_rewards( fd_exec_slot_ctx_t * slot_ctx,
595 : fd_stake_history_t const * stake_history,
596 : ulong rewarded_epoch,
597 : fd_point_value_t * point_value,
598 : fd_calculate_stake_vote_rewards_result_t * result,
599 : fd_epoch_info_t * temp_info,
600 : fd_tpool_t * tpool,
601 : fd_spad_t * * exec_spads,
602 : ulong exec_spad_cnt,
603 0 : fd_spad_t * runtime_spad ) {
604 :
605 0 : int _err[1];
606 0 : ulong new_warmup_cooldown_rate_epoch_val = 0UL;
607 0 : ulong * new_warmup_cooldown_rate_epoch = &new_warmup_cooldown_rate_epoch_val;
608 0 : int is_some = fd_new_warmup_cooldown_rate_epoch( slot_ctx->slot,
609 0 : slot_ctx->funk,
610 0 : slot_ctx->funk_txn,
611 0 : runtime_spad,
612 0 : fd_bank_features_query( slot_ctx->bank ),
613 0 : new_warmup_cooldown_rate_epoch,
614 0 : _err );
615 0 : if( FD_UNLIKELY( !is_some ) ) {
616 0 : new_warmup_cooldown_rate_epoch = NULL;
617 0 : }
618 :
619 0 : ulong rewards_max_count = temp_info->stake_infos_len;
620 :
621 : /* Create the stake rewards pool and dlist. The pool will be destoyed after the stake rewards have been distributed. */
622 0 : result->stake_reward_calculation.pool = fd_stake_reward_calculation_pool_join( fd_stake_reward_calculation_pool_new( fd_spad_alloc( runtime_spad,
623 0 : fd_stake_reward_calculation_pool_align(),
624 0 : fd_stake_reward_calculation_pool_footprint( rewards_max_count ) ),
625 0 : rewards_max_count ) );
626 0 : result->stake_reward_calculation.stake_rewards = fd_spad_alloc( runtime_spad,
627 0 : fd_stake_reward_calculation_dlist_align(),
628 0 : fd_stake_reward_calculation_dlist_footprint() );
629 :
630 0 : fd_stake_reward_calculation_dlist_new( result->stake_reward_calculation.stake_rewards );
631 0 : result->stake_reward_calculation.stake_rewards_len = 0UL;
632 :
633 : /* Create the vote rewards map. This will be destroyed after the vote rewards have been distributed. */
634 0 : ulong vote_account_cnt = fd_vote_info_pair_t_map_size( temp_info->vote_states_pool, temp_info->vote_states_root );
635 0 : result->vote_reward_map_pool = fd_vote_reward_t_map_join( fd_vote_reward_t_map_new( fd_spad_alloc( runtime_spad,
636 0 : fd_vote_reward_t_map_align(),
637 0 : fd_vote_reward_t_map_footprint( vote_account_cnt )),
638 0 : vote_account_cnt ) );
639 0 : result->vote_reward_map_root = NULL;
640 :
641 : /* Pre-fill the vote pubkeys in the vote rewards map pool */
642 0 : for( fd_vote_info_pair_t_mapnode_t * vote_info = fd_vote_info_pair_t_map_minimum( temp_info->vote_states_pool, temp_info->vote_states_root );
643 0 : vote_info;
644 0 : vote_info = fd_vote_info_pair_t_map_successor( temp_info->vote_states_pool, vote_info ) ) {
645 :
646 0 : fd_pubkey_t const * voter_pubkey = &vote_info->elem.account;
647 0 : fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_acquire( result->vote_reward_map_pool );
648 :
649 0 : vote_reward_node->elem.pubkey = *voter_pubkey;
650 0 : vote_reward_node->elem.vote_rewards = 0UL;
651 0 : vote_reward_node->elem.needs_store = 0;
652 :
653 0 : fd_vote_reward_t_map_insert( result->vote_reward_map_pool, &result->vote_reward_map_root, vote_reward_node );
654 0 : }
655 :
656 : /* Pre-allocate the dlist stake reward elements */
657 0 : for( ulong i=0UL; i<temp_info->stake_infos_len; i++ ) {
658 0 : fd_stake_reward_t * stake_reward = fd_stake_reward_calculation_pool_ele_acquire( result->stake_reward_calculation.pool );
659 0 : if( FD_UNLIKELY( stake_reward==NULL ) ) {
660 0 : FD_LOG_ERR(( "insufficient space allocated for stake reward calculation pool" ));
661 0 : return;
662 0 : }
663 0 : stake_reward->valid = 0;
664 0 : fd_stake_reward_calculation_dlist_ele_push_tail( result->stake_reward_calculation.stake_rewards, stake_reward, result->stake_reward_calculation.pool );
665 0 : }
666 :
667 0 : fd_calculate_stake_vote_rewards_task_args_t task_args = {
668 0 : .slot_ctx = slot_ctx,
669 0 : .stake_history = stake_history,
670 0 : .rewarded_epoch = rewarded_epoch,
671 0 : .new_warmup_cooldown_rate_epoch = new_warmup_cooldown_rate_epoch,
672 0 : .point_value = point_value,
673 0 : .result = result,
674 0 : .exec_spads = exec_spads,
675 0 : .exec_spad_cnt = exec_spad_cnt,
676 0 : };
677 :
678 : /* Loop over all the delegations
679 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L367 */
680 0 : if( !!tpool ) {
681 0 : fd_tpool_exec_all_batch( tpool, 0UL, fd_tpool_worker_cnt( tpool ), calculate_stake_vote_rewards_account_tpool_task,
682 0 : temp_info, &task_args,
683 0 : NULL, 1UL, 0UL, temp_info->stake_infos_len );
684 0 : } else {
685 0 : calculate_stake_vote_rewards_account( temp_info, &task_args, 0UL, temp_info->stake_infos_len );
686 0 : }
687 0 : }
688 :
689 : /* Calculate epoch reward and return vote and stake rewards.
690 :
691 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L273 */
692 : static void
693 : calculate_validator_rewards( fd_exec_slot_ctx_t * slot_ctx,
694 : ulong rewarded_epoch,
695 : ulong rewards,
696 : fd_calculate_validator_rewards_result_t * result,
697 : fd_epoch_info_t * temp_info,
698 : fd_tpool_t * tpool,
699 : fd_spad_t * * exec_spads,
700 : ulong exec_spad_cnt,
701 0 : fd_spad_t * runtime_spad ) {
702 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L2759-L2786 */
703 0 : fd_stake_history_t const * stake_history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
704 0 : if( FD_UNLIKELY( !stake_history ) ) {
705 0 : FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
706 0 : }
707 :
708 : /* Calculate the epoch reward points from stake/vote accounts */
709 0 : calculate_reward_points_partitioned( slot_ctx,
710 0 : stake_history,
711 0 : rewards,
712 0 : &result->point_value,
713 0 : tpool,
714 0 : temp_info,
715 0 : runtime_spad );
716 :
717 : /* Calculate the stake and vote rewards for each account */
718 0 : calculate_stake_vote_rewards( slot_ctx,
719 0 : stake_history,
720 0 : rewarded_epoch,
721 0 : &result->point_value,
722 0 : &result->calculate_stake_vote_rewards_result,
723 0 : temp_info,
724 0 : tpool,
725 0 : exec_spads,
726 0 : exec_spad_cnt,
727 0 : runtime_spad );
728 0 : }
729 :
730 : /* Calculate the number of blocks required to distribute rewards to all stake accounts.
731 :
732 : https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/mod.rs#L214
733 : */
734 : static ulong
735 : get_reward_distribution_num_blocks( fd_epoch_schedule_t const * epoch_schedule,
736 : ulong slot,
737 0 : ulong total_stake_accounts ) {
738 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1250-L1267 */
739 0 : if( epoch_schedule->warmup &&
740 0 : fd_slot_to_epoch( epoch_schedule, slot, NULL ) < epoch_schedule->first_normal_epoch ) {
741 0 : return 1UL;
742 0 : }
743 :
744 0 : ulong num_chunks = total_stake_accounts / (ulong)STAKE_ACCOUNT_STORES_PER_BLOCK + (total_stake_accounts % STAKE_ACCOUNT_STORES_PER_BLOCK != 0);
745 0 : num_chunks = fd_ulong_max( num_chunks, 1UL );
746 0 : num_chunks = fd_ulong_min( num_chunks,
747 0 : fd_ulong_max( epoch_schedule->slots_per_epoch / (ulong)MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH, 1UL ) );
748 0 : return num_chunks;
749 0 : }
750 :
751 : static void
752 : hash_rewards_into_partitions( fd_stake_reward_calculation_t * stake_reward_calculation,
753 : fd_hash_t const * parent_blockhash,
754 : ulong num_partitions,
755 : fd_stake_reward_calculation_partitioned_t * result,
756 0 : fd_spad_t * runtime_spad ) {
757 :
758 : /* Initialize a dlist for every partition.
759 : These will all use the same pool - we do not re-allocate the stake rewards, only move them into partitions. */
760 0 : result->partitioned_stake_rewards.pool = stake_reward_calculation->pool;
761 0 : result->partitioned_stake_rewards.partitions_len = num_partitions;
762 0 : result->partitioned_stake_rewards.partitions = fd_spad_alloc( runtime_spad,
763 0 : fd_partitioned_stake_rewards_dlist_align(),
764 0 : fd_partitioned_stake_rewards_dlist_footprint() * num_partitions );
765 :
766 : /* Ownership of these dlist's and the pool gets transferred to stake_rewards_by_partition, which then gets transferred to epoch_reward_status.
767 : These are eventually cleaned up when epoch_reward_status_inactive is called. */
768 0 : for( ulong i = 0; i < num_partitions; ++i ) {
769 0 : fd_partitioned_stake_rewards_dlist_new( &result->partitioned_stake_rewards.partitions[ i ] );
770 0 : }
771 :
772 : /* Iterate over all the stake rewards, moving references to them into the appropiate partitions.
773 : IMPORTANT: after this, we cannot use the original stake rewards dlist anymore. */
774 0 : fd_stake_reward_calculation_dlist_iter_t next_iter;
775 0 : for( fd_stake_reward_calculation_dlist_iter_t iter = fd_stake_reward_calculation_dlist_iter_fwd_init( stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
776 0 : !fd_stake_reward_calculation_dlist_iter_done( iter, stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
777 0 : iter = next_iter ) {
778 0 : fd_stake_reward_t * stake_reward = fd_stake_reward_calculation_dlist_iter_ele( iter, stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
779 : /* Cache the next iter here, as we will overwrite the DLIST_NEXT value further down in the loop iteration. */
780 0 : next_iter = fd_stake_reward_calculation_dlist_iter_fwd_next( iter, stake_reward_calculation->stake_rewards, stake_reward_calculation->pool );
781 :
782 0 : if( FD_UNLIKELY( !stake_reward->valid ) ) {
783 0 : continue;
784 0 : }
785 :
786 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/epoch_rewards_hasher.rs#L43C31-L61 */
787 0 : fd_siphash13_t _sip[1] = {0};
788 0 : fd_siphash13_t * hasher = fd_siphash13_init( _sip, 0UL, 0UL );
789 :
790 0 : hasher = fd_siphash13_append( hasher, parent_blockhash->hash, sizeof(fd_hash_t) );
791 0 : fd_siphash13_append( hasher, (const uchar *) stake_reward->stake_pubkey.key, sizeof(fd_pubkey_t) );
792 :
793 0 : ulong hash64 = fd_siphash13_fini( hasher );
794 : /* hash_to_partition */
795 : /* FIXME: should be saturating add */
796 0 : ulong partition_index = (ulong)((uint128) num_partitions *
797 0 : (uint128) hash64 /
798 0 : ((uint128)ULONG_MAX + 1));
799 :
800 : /* Move the stake reward to the partition's dlist */
801 0 : fd_partitioned_stake_rewards_dlist_t * partition = &result->partitioned_stake_rewards.partitions[ partition_index ];
802 0 : fd_partitioned_stake_rewards_dlist_ele_push_tail( partition, stake_reward, stake_reward_calculation->pool );
803 0 : result->partitioned_stake_rewards.partitions_lengths[ partition_index ]++;
804 0 : }
805 0 : }
806 :
807 : /* Calculate rewards from previous epoch to prepare for partitioned distribution.
808 :
809 : https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L214 */
810 : static void
811 : calculate_rewards_for_partitioning( fd_exec_slot_ctx_t * slot_ctx,
812 : ulong prev_epoch,
813 : const fd_hash_t * parent_blockhash,
814 : fd_partitioned_rewards_calculation_t * result,
815 : fd_epoch_info_t * temp_info,
816 : fd_tpool_t * tpool,
817 : fd_spad_t * * exec_spads,
818 : ulong exec_spad_cnt,
819 0 : fd_spad_t * runtime_spad ) {
820 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L227 */
821 0 : fd_prev_epoch_inflation_rewards_t rewards;
822 :
823 0 : calculate_previous_epoch_inflation_rewards( slot_ctx,
824 0 : fd_bank_capitalization_get( slot_ctx->bank ),
825 0 : prev_epoch,
826 0 : &rewards );
827 :
828 0 : fd_calculate_validator_rewards_result_t validator_result[1] = {0};
829 0 : calculate_validator_rewards( slot_ctx,
830 0 : prev_epoch,
831 0 : rewards.validator_rewards,
832 0 : validator_result,
833 0 : temp_info,
834 0 : tpool,
835 0 : exec_spads,
836 0 : exec_spad_cnt,
837 0 : runtime_spad );
838 :
839 0 : fd_stake_reward_calculation_t * stake_reward_calculation = &validator_result->calculate_stake_vote_rewards_result.stake_reward_calculation;
840 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
841 0 : ulong num_partitions = get_reward_distribution_num_blocks( epoch_schedule,
842 0 : slot_ctx->slot,
843 0 : stake_reward_calculation->stake_rewards_len );
844 0 : hash_rewards_into_partitions( stake_reward_calculation,
845 0 : parent_blockhash,
846 0 : num_partitions,
847 0 : &result->stake_rewards_by_partition,
848 0 : runtime_spad );
849 :
850 0 : result->stake_rewards_by_partition.total_stake_rewards_lamports =
851 0 : validator_result->calculate_stake_vote_rewards_result.stake_reward_calculation.total_stake_rewards_lamports;
852 :
853 0 : result->vote_reward_map_pool = validator_result->calculate_stake_vote_rewards_result.vote_reward_map_pool;
854 0 : result->vote_reward_map_root = validator_result->calculate_stake_vote_rewards_result.vote_reward_map_root;
855 0 : result->validator_rewards = rewards.validator_rewards;
856 0 : result->validator_rate = rewards.validator_rate;
857 0 : result->foundation_rate = rewards.foundation_rate;
858 0 : result->prev_epoch_duration_in_years = rewards.prev_epoch_duration_in_years;
859 0 : result->capitalization = fd_bank_capitalization_get( slot_ctx->bank );
860 0 : result->point_value = validator_result->point_value;
861 0 : }
862 :
863 : /* Calculate rewards from previous epoch and distribute vote rewards
864 :
865 : https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L97 */
866 : static void
867 : calculate_rewards_and_distribute_vote_rewards( fd_exec_slot_ctx_t * slot_ctx,
868 : ulong prev_epoch,
869 : fd_hash_t const * parent_blockhash,
870 : fd_calculate_rewards_and_distribute_vote_rewards_result_t * result,
871 : fd_epoch_info_t * temp_info,
872 : fd_tpool_t * tpool,
873 : fd_spad_t * * exec_spads,
874 : ulong exec_spad_cnt,
875 0 : fd_spad_t * runtime_spad ) {
876 :
877 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L2406-L2492 */
878 0 : fd_partitioned_rewards_calculation_t rewards_calc_result[1] = {0};
879 0 : calculate_rewards_for_partitioning( slot_ctx,
880 0 : prev_epoch,
881 0 : parent_blockhash,
882 0 : rewards_calc_result,
883 0 : temp_info,
884 0 : tpool,
885 0 : exec_spads,
886 0 : exec_spad_cnt,
887 0 : runtime_spad );
888 :
889 : /* Iterate over all the vote reward nodes */
890 0 : for( fd_vote_reward_t_mapnode_t * vote_reward_node = fd_vote_reward_t_map_minimum( rewards_calc_result->vote_reward_map_pool, rewards_calc_result->vote_reward_map_root);
891 0 : vote_reward_node;
892 0 : vote_reward_node = fd_vote_reward_t_map_successor( rewards_calc_result->vote_reward_map_pool, vote_reward_node ) ) {
893 :
894 0 : if( FD_UNLIKELY( !vote_reward_node->elem.needs_store ) ) {
895 0 : continue;
896 0 : }
897 :
898 0 : fd_pubkey_t const * vote_pubkey = &vote_reward_node->elem.pubkey;
899 0 : FD_TXN_ACCOUNT_DECL( vote_rec );
900 :
901 0 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_mutable( vote_rec,
902 0 : vote_pubkey,
903 0 : slot_ctx->funk,
904 0 : slot_ctx->funk_txn,
905 0 : 1,
906 0 : 0UL ) != FD_ACC_MGR_SUCCESS ) ) {
907 0 : FD_LOG_ERR(( "Unable to modify vote account" ));
908 0 : }
909 :
910 0 : vote_rec->vt->set_slot( vote_rec, slot_ctx->slot );
911 :
912 0 : if( FD_UNLIKELY( vote_rec->vt->checked_add_lamports( vote_rec, vote_reward_node->elem.vote_rewards ) ) ) {
913 0 : FD_LOG_ERR(( "Adding lamports to vote account would cause overflow" ));
914 0 : }
915 :
916 0 : fd_txn_account_mutable_fini( vote_rec, slot_ctx->funk, slot_ctx->funk_txn );
917 :
918 0 : result->distributed_rewards = fd_ulong_sat_add( result->distributed_rewards, vote_reward_node->elem.vote_rewards );
919 0 : }
920 :
921 : /* There is no need to free the vote reward map since it was spad*/
922 :
923 : /* Verify that we didn't pay any more than we expected to */
924 0 : result->total_rewards = fd_ulong_sat_add( result->distributed_rewards, rewards_calc_result->stake_rewards_by_partition.total_stake_rewards_lamports );
925 0 : if( FD_UNLIKELY( rewards_calc_result->validator_rewards < result->total_rewards ) ) {
926 0 : FD_LOG_ERR(( "Unexpected rewards calculation result" ));
927 0 : }
928 :
929 0 : fd_bank_capitalization_set( slot_ctx->bank, fd_bank_capitalization_get( slot_ctx->bank ) + result->distributed_rewards );
930 :
931 : /* Cheap because this doesn't copy all the rewards, just pointers to the dlist */
932 0 : result->stake_rewards_by_partition = rewards_calc_result->stake_rewards_by_partition;
933 0 : result->point_value = rewards_calc_result->point_value;
934 0 : }
935 :
936 : /* Distributes a single partitioned reward to a single stake account */
937 : static int
938 : distribute_epoch_reward_to_stake_acc( fd_exec_slot_ctx_t * slot_ctx,
939 : fd_pubkey_t * stake_pubkey,
940 : ulong reward_lamports,
941 0 : ulong new_credits_observed ) {
942 0 : FD_TXN_ACCOUNT_DECL( stake_acc_rec );
943 0 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_mutable( stake_acc_rec,
944 0 : stake_pubkey,
945 0 : slot_ctx->funk,
946 0 : slot_ctx->funk_txn,
947 0 : 0,
948 0 : 0UL ) != FD_ACC_MGR_SUCCESS ) ) {
949 0 : FD_LOG_ERR(( "Unable to modify stake account" ));
950 0 : }
951 :
952 0 : stake_acc_rec->vt->set_slot( stake_acc_rec, slot_ctx->slot );
953 :
954 0 : fd_stake_state_v2_t stake_state[1] = {0};
955 0 : if( fd_stake_get_state( stake_acc_rec, stake_state ) != 0 ) {
956 0 : FD_LOG_DEBUG(( "failed to read stake state for %s", FD_BASE58_ENC_32_ALLOCA( stake_pubkey ) ));
957 0 : return 1;
958 0 : }
959 :
960 0 : if ( !fd_stake_state_v2_is_stake( stake_state ) ) {
961 0 : FD_LOG_DEBUG(( "non-stake stake account, this should never happen" ));
962 0 : return 1;
963 0 : }
964 :
965 0 : if( stake_acc_rec->vt->checked_add_lamports( stake_acc_rec, reward_lamports ) ) {
966 0 : FD_LOG_DEBUG(( "failed to add lamports to stake account" ));
967 0 : return 1;
968 0 : }
969 :
970 0 : stake_state->inner.stake.stake.credits_observed = new_credits_observed;
971 0 : stake_state->inner.stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->inner.stake.stake.delegation.stake,
972 0 : reward_lamports );
973 :
974 0 : if( FD_UNLIKELY( write_stake_state( stake_acc_rec, stake_state ) != 0 ) ) {
975 0 : FD_LOG_ERR(( "write_stake_state failed" ));
976 0 : }
977 :
978 0 : fd_txn_account_mutable_fini( stake_acc_rec, slot_ctx->funk, slot_ctx->funk_txn );
979 :
980 0 : return 0;
981 0 : }
982 :
983 : /* Sets the epoch reward status to inactive, and destroys any allocated state associated with the active state. */
984 : static void
985 0 : set_epoch_reward_status_inactive( fd_exec_slot_ctx_t * slot_ctx ) {
986 0 : fd_epoch_reward_status_global_t * epoch_reward_status = fd_bank_epoch_reward_status_locking_modify( slot_ctx->bank );
987 0 : if( epoch_reward_status->discriminant == fd_epoch_reward_status_enum_Active ) {
988 0 : FD_LOG_NOTICE(( "Done partitioning rewards for current epoch" ));
989 0 : }
990 0 : epoch_reward_status->discriminant = fd_epoch_reward_status_enum_Inactive;
991 0 : fd_bank_epoch_reward_status_end_locking_modify( slot_ctx->bank );
992 0 : }
993 :
994 : /* Sets the epoch reward status to active.
995 :
996 : Takes ownership of the given stake_rewards_by_partition data structure,
997 : which will be destroyed when set_epoch_reward_status_inactive is called. */
998 : static void
999 : set_epoch_reward_status_active( fd_exec_slot_ctx_t * slot_ctx,
1000 : ulong distribution_starting_block_height,
1001 0 : fd_partitioned_stake_rewards_t * partitioned_rewards ) {
1002 :
1003 0 : FD_LOG_NOTICE(( "Setting epoch reward status as active" ));
1004 :
1005 0 : fd_epoch_reward_status_global_t * epoch_reward_status = fd_bank_epoch_reward_status_locking_modify( slot_ctx->bank );
1006 0 : epoch_reward_status->discriminant = fd_epoch_reward_status_enum_Active;
1007 0 : epoch_reward_status->inner.Active.distribution_starting_block_height = distribution_starting_block_height;
1008 :
1009 0 : epoch_reward_status->inner.Active.partitioned_stake_rewards.partitions_len = partitioned_rewards->partitions_len;
1010 0 : fd_memcpy( epoch_reward_status->inner.Active.partitioned_stake_rewards.partitions_lengths,
1011 0 : partitioned_rewards->partitions_lengths,
1012 0 : sizeof(ulong[4096]) );
1013 :
1014 0 : ulong pool_max = fd_stake_reward_calculation_pool_max( partitioned_rewards->pool );
1015 0 : ulong pool_footprint = fd_stake_reward_calculation_pool_footprint( pool_max );
1016 :
1017 : /* Copy in the pool */
1018 0 : uchar * pool_mem = (uchar *)fd_ulong_align_up( (ulong)epoch_reward_status + sizeof(fd_epoch_reward_status_global_t),
1019 0 : fd_stake_reward_calculation_pool_align() );
1020 0 : fd_memcpy( pool_mem, fd_stake_reward_calculation_pool_leave( partitioned_rewards->pool ), pool_footprint );
1021 0 : epoch_reward_status->inner.Active.partitioned_stake_rewards.pool_offset = (ulong)pool_mem - (ulong)&epoch_reward_status->inner.Active.partitioned_stake_rewards;
1022 :
1023 : /* Copy in the partitions */
1024 0 : uchar * partitions_mem = (uchar *)fd_ulong_align_up( (ulong)pool_mem + pool_footprint, fd_partitioned_stake_rewards_dlist_align() );
1025 0 : ulong partitions_footprint = fd_partitioned_stake_rewards_dlist_footprint() * partitioned_rewards->partitions_len;
1026 0 : fd_memcpy( partitions_mem, fd_partitioned_stake_rewards_dlist_leave( partitioned_rewards->partitions ), partitions_footprint );
1027 0 : epoch_reward_status->inner.Active.partitioned_stake_rewards.partitions_offset = (ulong)partitions_mem - (ulong)&epoch_reward_status->inner.Active.partitioned_stake_rewards;
1028 :
1029 0 : fd_bank_epoch_reward_status_end_locking_modify( slot_ctx->bank );
1030 0 : }
1031 :
1032 : /* Process reward credits for a partition of rewards.
1033 : Store the rewards to AccountsDB, update reward history record and total capitalization
1034 :
1035 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L88 */
1036 : static void
1037 : distribute_epoch_rewards_in_partition( fd_partitioned_stake_rewards_dlist_t * partition,
1038 : fd_stake_reward_t * pool,
1039 : fd_exec_slot_ctx_t * slot_ctx,
1040 0 : fd_spad_t * runtime_spad ) {
1041 :
1042 0 : ulong lamports_distributed = 0UL;
1043 0 : ulong lamports_burned = 0UL;
1044 :
1045 0 : for( fd_partitioned_stake_rewards_dlist_iter_t iter = fd_partitioned_stake_rewards_dlist_iter_fwd_init( partition, pool );
1046 0 : !fd_partitioned_stake_rewards_dlist_iter_done( iter, partition, pool );
1047 0 : iter = fd_partitioned_stake_rewards_dlist_iter_fwd_next( iter, partition, pool ) ) {
1048 0 : fd_stake_reward_t * stake_reward = fd_partitioned_stake_rewards_dlist_iter_ele( iter, partition, pool );
1049 :
1050 0 : if( distribute_epoch_reward_to_stake_acc( slot_ctx,
1051 0 : &stake_reward->stake_pubkey,
1052 0 : stake_reward->lamports,
1053 0 : stake_reward->credits_observed ) == 0 ) {
1054 0 : lamports_distributed += stake_reward->lamports;
1055 0 : } else {
1056 0 : lamports_burned += stake_reward->lamports;
1057 0 : }
1058 0 : }
1059 :
1060 : /* Update the epoch rewards sysvar with the amount distributed and burnt */
1061 0 : fd_sysvar_epoch_rewards_distribute( slot_ctx,
1062 0 : lamports_distributed + lamports_burned,
1063 0 : runtime_spad );
1064 :
1065 0 : FD_LOG_DEBUG(( "lamports burned: %lu, lamports distributed: %lu", lamports_burned, lamports_distributed ));
1066 :
1067 0 : fd_bank_capitalization_set( slot_ctx->bank, fd_bank_capitalization_get( slot_ctx->bank ) + lamports_distributed );
1068 0 : }
1069 :
1070 : /* Process reward distribution for the block if it is inside reward interval.
1071 :
1072 : https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/runtime/src/bank/partitioned_epoch_rewards/distribution.rs#L42 */
1073 : void
1074 : fd_distribute_partitioned_epoch_rewards( fd_exec_slot_ctx_t * slot_ctx,
1075 : fd_tpool_t * tpool,
1076 : fd_spad_t * * exec_spads,
1077 : ulong exec_spad_cnt,
1078 0 : fd_spad_t * runtime_spad ) {
1079 :
1080 0 : (void)tpool;
1081 0 : (void)exec_spads;
1082 0 : (void)exec_spad_cnt;
1083 :
1084 0 : fd_epoch_reward_status_global_t const * epoch_reward_status = fd_bank_epoch_reward_status_locking_query( slot_ctx->bank );
1085 :
1086 0 : if( epoch_reward_status->discriminant == fd_epoch_reward_status_enum_Inactive ) {
1087 0 : fd_bank_epoch_reward_status_end_locking_query( slot_ctx->bank );
1088 0 : return;
1089 0 : }
1090 :
1091 0 : fd_start_block_height_and_rewards_global_t const * status = &epoch_reward_status->inner.Active;
1092 :
1093 0 : fd_partitioned_stake_rewards_dlist_t * partitions =
1094 0 : (fd_partitioned_stake_rewards_dlist_t *)((uchar *)&status->partitioned_stake_rewards + status->partitioned_stake_rewards.partitions_offset);
1095 :
1096 0 : fd_stake_reward_t * pool = fd_stake_reward_calculation_pool_join( (uchar *)&status->partitioned_stake_rewards + status->partitioned_stake_rewards.pool_offset );
1097 0 : if( FD_UNLIKELY( !pool ) ) {
1098 0 : FD_LOG_CRIT(( "Failed to join pool" ));
1099 0 : }
1100 :
1101 0 : ulong height = fd_bank_block_height_get( slot_ctx->bank );
1102 0 : ulong distribution_starting_block_height = status->distribution_starting_block_height;
1103 0 : ulong distribution_end_exclusive = distribution_starting_block_height + status->partitioned_stake_rewards.partitions_len;
1104 :
1105 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
1106 0 : ulong epoch = fd_slot_to_epoch( epoch_schedule, slot_ctx->slot, NULL );
1107 :
1108 0 : if( FD_UNLIKELY( get_slots_in_epoch( epoch, epoch_schedule ) <= status->partitioned_stake_rewards.partitions_len ) ) {
1109 0 : FD_LOG_ERR(( "Should not be distributing rewards" ));
1110 0 : }
1111 :
1112 0 : if( (height>=distribution_starting_block_height) && (height < distribution_end_exclusive) ) {
1113 0 : ulong partition_index = height - distribution_starting_block_height;
1114 0 : distribute_epoch_rewards_in_partition( &partitions[ partition_index ],
1115 0 : pool,
1116 0 : slot_ctx,
1117 0 : runtime_spad );
1118 0 : }
1119 :
1120 0 : fd_bank_epoch_reward_status_end_locking_query( slot_ctx->bank );
1121 :
1122 : /* If we have finished distributing rewards, set the status to inactive */
1123 0 : if( fd_ulong_sat_add( height, 1UL ) >= distribution_end_exclusive ) {
1124 0 : set_epoch_reward_status_inactive( slot_ctx );
1125 0 : fd_sysvar_epoch_rewards_set_inactive( slot_ctx, runtime_spad );
1126 0 : }
1127 0 : }
1128 :
1129 : /* Partitioned epoch rewards entry-point.
1130 :
1131 : https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L41
1132 : */
1133 : void
1134 : fd_begin_partitioned_rewards( fd_exec_slot_ctx_t * slot_ctx,
1135 : fd_hash_t const * parent_blockhash,
1136 : ulong parent_epoch,
1137 : fd_epoch_info_t * temp_info,
1138 : fd_tpool_t * tpool,
1139 : fd_spad_t * * exec_spads,
1140 : ulong exec_spad_cnt,
1141 0 : fd_spad_t * runtime_spad ) {
1142 :
1143 : /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L55 */
1144 0 : fd_calculate_rewards_and_distribute_vote_rewards_result_t rewards_result[1] = {0};
1145 0 : calculate_rewards_and_distribute_vote_rewards( slot_ctx,
1146 0 : parent_epoch,
1147 0 : parent_blockhash,
1148 0 : rewards_result,
1149 0 : temp_info,
1150 0 : tpool,
1151 0 : exec_spads,
1152 0 : exec_spad_cnt,
1153 0 : runtime_spad );
1154 :
1155 : /* https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L62 */
1156 0 : ulong distribution_starting_block_height = fd_bank_block_height_get( slot_ctx->bank ) + REWARD_CALCULATION_NUM_BLOCKS;
1157 :
1158 : /* Set the epoch reward status to be active */
1159 0 : set_epoch_reward_status_active( slot_ctx,
1160 0 : distribution_starting_block_height,
1161 0 : &rewards_result->stake_rewards_by_partition.partitioned_stake_rewards );
1162 :
1163 : /* Initialize the epoch rewards sysvar
1164 : https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L78 */
1165 0 : fd_sysvar_epoch_rewards_init( slot_ctx,
1166 0 : rewards_result->distributed_rewards,
1167 0 : distribution_starting_block_height,
1168 0 : rewards_result->stake_rewards_by_partition.partitioned_stake_rewards.partitions_len,
1169 0 : rewards_result->point_value,
1170 0 : parent_blockhash );
1171 0 : }
1172 :
1173 : /*
1174 : Re-calculates partitioned stake rewards.
1175 : This updates the slot context's epoch reward status with the recalculated partitioned rewards.
1176 :
1177 : https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L521 */
1178 : void
1179 : fd_rewards_recalculate_partitioned_rewards( fd_exec_slot_ctx_t * slot_ctx,
1180 : fd_tpool_t * tpool,
1181 : fd_spad_t * * exec_spads,
1182 : ulong exec_spad_cnt,
1183 0 : fd_spad_t * runtime_spad ) {
1184 0 : fd_sysvar_epoch_rewards_t * epoch_rewards = fd_sysvar_epoch_rewards_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
1185 0 : if( FD_UNLIKELY( epoch_rewards == NULL ) ) {
1186 0 : FD_LOG_NOTICE(( "Failed to read or decode epoch rewards sysvar - may not have been created yet" ));
1187 0 : set_epoch_reward_status_inactive( slot_ctx );
1188 0 : return;
1189 0 : }
1190 :
1191 0 : FD_LOG_NOTICE(( "recalculating partitioned rewards" ));
1192 :
1193 0 : if( FD_UNLIKELY( epoch_rewards->active ) ) {
1194 :
1195 : /* If epoch rewards are active, we must calculate the rewards partitions
1196 : so we can start distributing. For the same reason as described in
1197 : fd_runtime_process_new_epoch, we must push on a spad frame at this
1198 : point. */
1199 0 : fd_spad_push( runtime_spad );
1200 :
1201 : /* If partitioned rewards are active, the rewarded epoch is always the immediately
1202 : preceeding epoch.
1203 :
1204 : https://github.com/anza-xyz/agave/blob/2316fea4c0852e59c071f72d72db020017ffd7d0/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L566 */
1205 0 : FD_LOG_NOTICE(( "epoch rewards is active" ));
1206 :
1207 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
1208 0 : ulong epoch = fd_slot_to_epoch( epoch_schedule, slot_ctx->slot, NULL );
1209 0 : ulong rewarded_epoch = fd_ulong_sat_sub( epoch, 1UL );
1210 :
1211 0 : int _err[1] = {0};
1212 0 : ulong * new_warmup_cooldown_rate_epoch = fd_spad_alloc( runtime_spad, alignof(ulong), sizeof(ulong) );
1213 0 : int is_some = fd_new_warmup_cooldown_rate_epoch( slot_ctx->slot,
1214 0 : slot_ctx->funk,
1215 0 : slot_ctx->funk_txn,
1216 0 : runtime_spad,
1217 0 : fd_bank_features_query( slot_ctx->bank ),
1218 0 : new_warmup_cooldown_rate_epoch,
1219 0 : _err );
1220 0 : if( FD_UNLIKELY( !is_some ) ) {
1221 0 : new_warmup_cooldown_rate_epoch = NULL;
1222 0 : }
1223 :
1224 0 : fd_stake_history_t const * stake_history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
1225 0 : if( FD_UNLIKELY( !stake_history ) ) {
1226 0 : FD_LOG_ERR(( "Unable to read and decode stake history sysvar" ));
1227 0 : }
1228 :
1229 0 : fd_point_value_t point_value = { .points = epoch_rewards->total_points,
1230 0 : .rewards = epoch_rewards->total_rewards };
1231 :
1232 : /* Populate vote and stake state info from vote and stakes cache for the stake vote rewards calculation */
1233 0 : fd_stakes_global_t const * stakes = fd_bank_stakes_locking_query( slot_ctx->bank );
1234 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
1235 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
1236 :
1237 0 : fd_epoch_info_t epoch_info = {0};
1238 0 : fd_epoch_info_new( &epoch_info );
1239 :
1240 0 : ulong stake_delegation_sz = fd_delegation_pair_t_map_size( stake_delegations_pool, stake_delegations_root );
1241 0 : epoch_info.stake_infos_len = 0UL;
1242 0 : epoch_info.stake_infos = fd_spad_alloc( runtime_spad, FD_EPOCH_INFO_PAIR_ALIGN, sizeof(fd_epoch_info_pair_t)*stake_delegation_sz );
1243 :
1244 0 : fd_stake_history_entry_t _accumulator = {
1245 0 : .effective = 0UL,
1246 0 : .activating = 0UL,
1247 0 : .deactivating = 0UL
1248 0 : };
1249 :
1250 0 : fd_accumulate_stake_infos( slot_ctx,
1251 0 : stakes,
1252 0 : stake_history,
1253 0 : new_warmup_cooldown_rate_epoch,
1254 0 : &_accumulator,
1255 0 : &epoch_info,
1256 0 : tpool,
1257 0 : exec_spads,
1258 0 : exec_spad_cnt,
1259 0 : runtime_spad );
1260 :
1261 0 : fd_bank_stakes_end_locking_query( slot_ctx->bank );
1262 :
1263 : /* NOTE: this is just a workaround for now to correctly populate epoch_info. */
1264 0 : fd_populate_vote_accounts( slot_ctx,
1265 0 : stake_history,
1266 0 : new_warmup_cooldown_rate_epoch,
1267 0 : &epoch_info,
1268 0 : tpool,
1269 0 : exec_spads,
1270 0 : exec_spad_cnt,
1271 0 : runtime_spad );
1272 : /* In future, the calculation will be cached in the snapshot, but for now we just re-calculate it
1273 : (as Agave does). */
1274 0 : fd_calculate_stake_vote_rewards_result_t calculate_stake_vote_rewards_result[1];
1275 0 : calculate_stake_vote_rewards( slot_ctx,
1276 0 : stake_history,
1277 0 : rewarded_epoch,
1278 0 : &point_value,
1279 0 : calculate_stake_vote_rewards_result,
1280 0 : &epoch_info,
1281 0 : tpool,
1282 0 : exec_spads,
1283 0 : exec_spad_cnt,
1284 0 : runtime_spad );
1285 :
1286 : /* The vote reward map isn't actually used in this code path and will only
1287 : be freed after rewards have been distributed. */
1288 :
1289 :
1290 : /* Use the epoch rewards sysvar parent_blockhash and num_partitions.
1291 : https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L579 */
1292 0 : fd_stake_reward_calculation_partitioned_t stake_rewards_by_partition[1];
1293 0 : hash_rewards_into_partitions( &calculate_stake_vote_rewards_result->stake_reward_calculation,
1294 0 : &epoch_rewards->parent_blockhash,
1295 0 : epoch_rewards->num_partitions,
1296 0 : stake_rewards_by_partition,
1297 0 : runtime_spad );
1298 :
1299 : /* Update the epoch reward status with the newly re-calculated partitions. */
1300 0 : set_epoch_reward_status_active( slot_ctx,
1301 0 : epoch_rewards->distribution_starting_block_height,
1302 0 : &stake_rewards_by_partition->partitioned_stake_rewards );
1303 0 : } else {
1304 0 : set_epoch_reward_status_inactive( slot_ctx );
1305 0 : }
1306 0 : }
|