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