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