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