Line data Source code
1 : #include "fd_stakes.h"
2 : #include "fd_stake_weight_sort.h"
3 : #include "../events/fd_event_runtime.h"
4 : #include "../runtime/program/vote/fd_vote_state_versioned.h"
5 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
6 : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
7 : #include "../runtime/program/fd_vote_program.h"
8 : #include "../runtime/fd_runtime_stack_tmpl.h"
9 : #include "../runtime/fd_system_ids.h"
10 : #include "../runtime/fd_accdb_svm.h"
11 : #include "../../util/bits/fd_sat.h"
12 :
13 : /**********************************************************************/
14 : /* Constants */
15 : /**********************************************************************/
16 :
17 : #define DEFAULT_SLASH_PENALTY ( 12 )
18 :
19 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L3-L5 */
20 7843 : #define FD_BASIS_POINTS_PER_UNIT (10000UL)
21 0 : #define FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS (2500UL)
22 7843 : #define FD_TOWER_WARMUP_COOLDOWN_RATE_BPS (900UL)
23 :
24 : /**********************************************************************/
25 : /* Types */
26 : /**********************************************************************/
27 :
28 : struct effective_activating {
29 : ulong effective;
30 : ulong activating;
31 : };
32 : typedef struct effective_activating effective_activating_t;
33 :
34 : /**********************************************************************/
35 : /* Static helpers */
36 : /**********************************************************************/
37 :
38 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L7-L14 */
39 : static ulong
40 7843 : fd_stake_warmup_cooldown_rate_bps( ulong current_epoch, ulong * opt_rate_change_activation_epoch ) {
41 7843 : if( opt_rate_change_activation_epoch && current_epoch>=*opt_rate_change_activation_epoch ) {
42 7843 : return FD_TOWER_WARMUP_COOLDOWN_RATE_BPS;
43 7843 : } else {
44 0 : return FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS;
45 0 : }
46 7843 : }
47 :
48 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L54-L93 */
49 : static ulong
50 : calculate_stake_change_allowance( ulong epoch,
51 : ulong account_portion,
52 : ulong cluster_portion,
53 : ulong cluster_effective,
54 7846 : ulong * opt_rate_change_activation_epoch ) {
55 7846 : if( account_portion==0UL || cluster_portion==0UL || cluster_effective==0UL ) {
56 3 : return 0UL;
57 3 : }
58 :
59 7843 : ulong rate_bps = fd_stake_warmup_cooldown_rate_bps( epoch, opt_rate_change_activation_epoch );
60 :
61 7843 : uint128 numerator = fd_uint128_sat_mul( fd_uint128_sat_mul( (uint128)account_portion, (uint128)cluster_effective ),
62 7843 : (uint128)rate_bps );
63 7843 : uint128 denominator = fd_uint128_sat_mul( (uint128)cluster_portion, (uint128)FD_BASIS_POINTS_PER_UNIT );
64 :
65 : /* denominator is never zero due to guard above */
66 7843 : uint128 delta = numerator / denominator;
67 7843 : uint128 cap = (uint128)account_portion;
68 7843 : return (ulong)( delta<cap ? delta : cap );
69 7846 : }
70 :
71 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L16-L33 */
72 : ulong
73 : fd_stake_calculate_activation_allowance( ulong current_epoch,
74 : ulong account_activating_stake,
75 : fd_stake_history_entry_t const * prev_epoch_cluster_state,
76 6805 : ulong * opt_rate_change_activation_epoch ) {
77 6805 : return calculate_stake_change_allowance( current_epoch,
78 6805 : account_activating_stake,
79 6805 : prev_epoch_cluster_state->activating,
80 6805 : prev_epoch_cluster_state->effective,
81 6805 : opt_rate_change_activation_epoch );
82 6805 : }
83 :
84 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L35-L52 */
85 : static ulong
86 : fd_stake_calculate_deactivation_allowance( ulong current_epoch,
87 : ulong account_deactivating_stake,
88 : fd_stake_history_entry_t const * prev_epoch_cluster_state,
89 1041 : ulong * opt_rate_change_activation_epoch ) {
90 1041 : return calculate_stake_change_allowance( current_epoch,
91 1041 : account_deactivating_stake,
92 1041 : prev_epoch_cluster_state->deactivating,
93 1041 : prev_epoch_cluster_state->effective,
94 1041 : opt_rate_change_activation_epoch );
95 1041 : }
96 :
97 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L881-L971 */
98 : static effective_activating_t
99 : stake_and_activating( fd_delegation_t const * self,
100 : ulong target_epoch,
101 : fd_stake_history_t const * history,
102 1886 : ulong * new_rate_activation_epoch ) {
103 1886 : ulong delegated_stake = self->stake;
104 :
105 1886 : fd_stake_history_entry_t const * prev_cluster_stake = NULL;
106 :
107 1886 : if( self->activation_epoch==ULONG_MAX ) {
108 309 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
109 1577 : } else if( self->activation_epoch==self->deactivation_epoch ) {
110 0 : return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
111 1577 : } else if( target_epoch==self->activation_epoch ) {
112 24 : return ( effective_activating_t ){ .effective = 0UL, .activating = delegated_stake };
113 1553 : } else if( target_epoch<self->activation_epoch ) {
114 164 : return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
115 1389 : } else if( history &&
116 1389 : ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->activation_epoch ) ) ) {
117 :
118 324 : ulong prev_epoch = self->activation_epoch;
119 :
120 324 : ulong current_epoch;
121 324 : ulong activated_stake_amount = 0UL;
122 6805 : for(;;) {
123 6805 : current_epoch = prev_epoch + 1UL;
124 :
125 : /* If there is no activating stake at prev epoch, we should have
126 : been fully effective at this moment */
127 6805 : if( FD_LIKELY( prev_cluster_stake->activating==0UL ) ) break;
128 :
129 : /* Calculate how much of this account's remaining stake becomes
130 : effective in current_epoch. */
131 6805 : ulong remaining_activating_stake = delegated_stake - activated_stake_amount;
132 6805 : ulong newly_effective_stake = fd_stake_calculate_activation_allowance( current_epoch,
133 6805 : remaining_activating_stake,
134 6805 : prev_cluster_stake,
135 6805 : new_rate_activation_epoch );
136 :
137 : /* Add the newly effective stake, clamping the per-epoch increase
138 : to at least 1 lamport so warmup always makes progress */
139 6805 : activated_stake_amount += fd_ulong_max( newly_effective_stake, 1UL );
140 :
141 : /* Stop if we've fully warmed up this account's stake. */
142 6805 : if( FD_LIKELY( activated_stake_amount>=delegated_stake ) ) {
143 247 : activated_stake_amount = delegated_stake;
144 247 : break;
145 247 : }
146 :
147 : /* Stop when we've reached the time bound for this query */
148 6558 : if( FD_LIKELY( current_epoch>=target_epoch || current_epoch>=self->deactivation_epoch ) ) break;
149 :
150 : /* Advance to the next epoch if we have history,
151 : otherwise we can't model further warmup */
152 6490 : fd_stake_history_entry_t const * current_cluster_stake =
153 6490 : fd_sysvar_stake_history_query( history, current_epoch );
154 6490 : if( FD_LIKELY( current_cluster_stake ) ) {
155 6481 : prev_epoch = current_epoch;
156 6481 : prev_cluster_stake = current_cluster_stake;
157 6481 : } else {
158 9 : break;
159 9 : }
160 6490 : }
161 :
162 324 : return ( effective_activating_t ){ .effective = activated_stake_amount,
163 324 : .activating = delegated_stake - activated_stake_amount };
164 1065 : } else {
165 1065 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
166 1065 : }
167 1886 : }
168 :
169 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L790-L879 */
170 : static fd_stake_history_entry_t
171 : stake_activating_and_deactivating( fd_delegation_t const * self,
172 : ulong target_epoch,
173 : fd_stake_history_t const * history,
174 1886 : ulong * new_rate_activation_epoch ) {
175 1886 : effective_activating_t effective_activating =
176 1886 : stake_and_activating( self, target_epoch, history, new_rate_activation_epoch );
177 1886 : ulong effective_stake = effective_activating.effective;
178 1886 : ulong activating_stake = effective_activating.activating;
179 :
180 1886 : fd_stake_history_entry_t const * prev_cluster_stake = NULL;
181 :
182 1886 : if( target_epoch<self->deactivation_epoch ) {
183 1766 : if( activating_stake==0UL ) {
184 1685 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = 0UL };
185 1685 : } else {
186 81 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = activating_stake, .deactivating = 0UL };
187 81 : }
188 1766 : } else if( target_epoch==self->deactivation_epoch ) {
189 4 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = effective_stake };
190 116 : } else if( history &&
191 116 : ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->deactivation_epoch ) ) ) {
192 84 : ulong prev_epoch = self->deactivation_epoch;
193 :
194 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L830-L871 */
195 84 : ulong current_epoch;
196 84 : ulong remaining_deactivating_stake = effective_stake;
197 1041 : for(;;) {
198 1041 : current_epoch = prev_epoch + 1UL;
199 :
200 : /* If there is no deactivating stake at prev epoch, we should
201 : have been fully undelegated at this moment */
202 1041 : if( FD_LIKELY( prev_cluster_stake->deactivating==0UL ) ) break;
203 :
204 : /* Compute how much of this account's stake cools down in
205 : current_epoch */
206 1041 : ulong newly_deactivated_stake = fd_stake_calculate_deactivation_allowance( current_epoch,
207 1041 : remaining_deactivating_stake,
208 1041 : prev_cluster_stake,
209 1041 : new_rate_activation_epoch );
210 :
211 : /* Subtract the newly deactivated stake, clamping the per-epoch
212 : decrease to at least 1 lamport so cooldown always makes
213 : progress */
214 1041 : remaining_deactivating_stake =
215 1041 : fd_ulong_sat_sub( remaining_deactivating_stake, fd_ulong_max( newly_deactivated_stake, 1UL ) );
216 :
217 : /* Stop if we've fully cooled down this account */
218 1041 : if( remaining_deactivating_stake==0UL ) break;
219 :
220 : /* Stop when we've reached the time bound for this query */
221 988 : if( current_epoch>=target_epoch ) break;
222 :
223 : /* Advance to the next epoch if we have history,
224 : otherwise we can't model further cooldown */
225 961 : fd_stake_history_entry_t const * current_cluster_stake =
226 961 : fd_sysvar_stake_history_query( history, current_epoch );
227 961 : if( FD_LIKELY( current_cluster_stake ) ) {
228 957 : prev_epoch = current_epoch;
229 957 : prev_cluster_stake = current_cluster_stake;
230 957 : } else {
231 4 : break;
232 4 : }
233 961 : }
234 :
235 84 : return ( fd_stake_history_entry_t ){ .effective = remaining_deactivating_stake,
236 84 : .activating = 0UL,
237 84 : .deactivating = remaining_deactivating_stake };
238 84 : } else {
239 32 : return ( fd_stake_history_entry_t ){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
240 32 : }
241 1886 : }
242 :
243 : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/stake_delegation.rs#L27-L41 */
244 : fd_stake_history_entry_t
245 : fd_delegation_activation_status( fd_delegation_t const * self,
246 : ulong target_epoch,
247 : fd_stake_history_t const * stake_history,
248 1886 : ulong * new_rate_activation_epoch ) {
249 1886 : return stake_activating_and_deactivating( self, target_epoch, stake_history, new_rate_activation_epoch );
250 1886 : }
251 :
252 : int
253 : fd_delegation_is_inactive( fd_delegation_t const * delegation,
254 : ulong target_epoch,
255 : fd_stake_history_t const * stake_history,
256 0 : ulong * new_rate_activation_epoch ) {
257 0 : fd_stake_history_entry_t status = fd_delegation_activation_status(
258 0 : delegation,
259 0 : target_epoch,
260 0 : stake_history,
261 0 : new_rate_activation_epoch );
262 0 : return !status.effective && !status.activating;
263 0 : }
264 :
265 : static fd_delegation_t
266 1886 : fd_delegation_from_stake_delegation( fd_stake_delegation_t const * delegation ) {
267 1886 : return (fd_delegation_t) {
268 1886 : .voter_pubkey = delegation->vote_account,
269 1886 : .stake = delegation->stake,
270 1886 : .deactivation_epoch = delegation->deactivation_epoch==USHORT_MAX ? ULONG_MAX : delegation->deactivation_epoch,
271 1886 : .activation_epoch = delegation->activation_epoch==USHORT_MAX ? ULONG_MAX : delegation->activation_epoch,
272 1886 : };
273 1886 : }
274 :
275 : /**********************************************************************/
276 : /* Public API */
277 : /**********************************************************************/
278 :
279 : fd_stake_state_t const *
280 : fd_stake_state_view( uchar const * data,
281 1158 : ulong data_sz ) {
282 1158 : if( FD_UNLIKELY( data_sz<4UL ) ) return NULL;
283 1158 : uint stake_type = FD_LOAD( uint, data );
284 1158 : switch( stake_type ) {
285 4 : case FD_STAKE_STATE_UNINITIALIZED:
286 4 : break;
287 2 : case FD_STAKE_STATE_INITIALIZED:
288 2 : if( FD_UNLIKELY( data_sz<124 ) ) return NULL;
289 2 : break;
290 1152 : case FD_STAKE_STATE_STAKE:
291 1152 : if( FD_UNLIKELY( data_sz<197 ) ) return NULL;
292 1151 : break;
293 1151 : case FD_STAKE_STATE_REWARDS_POOL:
294 0 : break;
295 0 : default:
296 0 : return NULL;
297 1158 : }
298 1157 : return fd_type_pun_const( data );
299 1158 : }
300 :
301 : fd_stake_state_t const *
302 435 : fd_stakes_get_state( fd_acc_t const * acc ) {
303 435 : if( FD_UNLIKELY( memcmp( acc->owner, &fd_solana_stake_program_id, 32UL ) ) ) return NULL;
304 136 : if( FD_UNLIKELY( acc->lamports==0UL ) ) return NULL;
305 136 : return fd_stake_state_view( acc->data, acc->data_len );
306 136 : }
307 :
308 : fd_stake_history_entry_t
309 : fd_stake_delegation_activation_status( fd_stake_delegation_t const * stake_delegation,
310 : ulong target_epoch,
311 : fd_stake_history_t const * stake_history,
312 1886 : ulong * new_rate_activation_epoch ) {
313 1886 : fd_delegation_t delegation = fd_delegation_from_stake_delegation( stake_delegation );
314 1886 : return fd_delegation_activation_status( &delegation, target_epoch, stake_history, new_rate_activation_epoch );
315 1886 : }
316 :
317 : int
318 : fd_stake_delegation_is_inactive( fd_stake_delegation_t const * delegation,
319 : ulong target_epoch,
320 : fd_stake_history_t const * stake_history,
321 0 : ulong * new_rate_activation_epoch ) {
322 0 : fd_delegation_t raw_delegation = fd_delegation_from_stake_delegation( delegation );
323 0 : return fd_delegation_is_inactive( &raw_delegation,
324 0 : target_epoch,
325 0 : stake_history,
326 0 : new_rate_activation_epoch );
327 0 : }
328 :
329 : ulong
330 : fd_stake_weights_by_node( fd_vote_stakes_t * vote_stakes,
331 : ushort fork_id,
332 : int iter_kind,
333 639 : fd_vote_stake_weight_t * weights ) {
334 :
335 : /* We don't care if an account is invalid, we just want to get the
336 : stake weights: they are calculated from an older snapshot of
337 : vote account stakes. */
338 639 : ulong weights_cnt = 0;
339 639 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
340 639 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
341 1794 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
342 1155 : fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
343 1155 : fd_vote_stakes_ele_t ele[1];
344 1155 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, ele );
345 :
346 1155 : FD_TEST( weights_cnt<MAX_STAKE_WEIGHTS );
347 1155 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &ele->pubkey, sizeof(fd_pubkey_t) );
348 1155 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &ele->node_account, sizeof(fd_pubkey_t) );
349 1155 : fd_memcpy( weights[ weights_cnt ].bls_key, ele->bls_key, sizeof(weights[ weights_cnt ].bls_key) );
350 1155 : weights[ weights_cnt ].location = ele->location;
351 1155 : weights[ weights_cnt ].stake = ele->stake;
352 1155 : weights[ weights_cnt ].rank = ele->rank;
353 1155 : weights_cnt++;
354 1155 : }
355 :
356 639 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
357 :
358 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/leader-schedule/src/lib.rs#L80-L83
359 : We do not deduplicate the weights here, unlike Agave, as it is
360 : guaranteed there will be no duplicate stake entries for a given fork
361 : in the stakes map. */
362 :
363 639 : return weights_cnt;
364 639 : }
365 :
366 : static void
367 : get_vote_credits( uchar const * account_data,
368 : ulong account_data_len,
369 : ushort commission,
370 101 : fd_epoch_credits_t * epoch_credits ) {
371 :
372 101 : ulong cnt = 0UL;
373 101 : fd_vote_epoch_credits_t const * vote_epoch_credits = fd_vote_account_epoch_credits( account_data, account_data_len, &cnt );
374 101 : FD_TEST( vote_epoch_credits );
375 101 : FD_TEST( cnt<=FD_EPOCH_CREDITS_MAX );
376 101 : epoch_credits->commission = commission;
377 :
378 101 : ulong n = 0UL;
379 101 : ulong base = 0UL;
380 2093 : for( ulong i=0UL; i<cnt; i++ ) {
381 1992 : fd_vote_epoch_credits_t const * ele = &vote_epoch_credits[ i ];
382 :
383 1992 : if( fd_vote_epoch_credits_is_alpenglow_marker( ele ) ) continue;
384 1992 : if( !n ) base = ele->prev_credits;
385 :
386 1992 : FD_TEST( ele->epoch<=USHORT_MAX ); /* Epoch should fit. */
387 1992 : FD_TEST( ele->prev_credits>=base ); /* Prevent unsigned underflow. */
388 1992 : FD_TEST( ele->credits>=base ); /* Prevent unsigned underflow. */
389 1992 : FD_TEST( ele->prev_credits<=ele->credits ); /* Vote credits should be monotonic. */
390 :
391 1992 : epoch_credits->epoch[ n ] = (ushort)ele->epoch;
392 1992 : epoch_credits->credits_delta[ n ] = ele->credits - base;
393 1992 : epoch_credits->prev_credits_delta[ n ] = ele->prev_credits - base;
394 1992 : n++;
395 1992 : }
396 :
397 101 : epoch_credits->cnt = (uchar)n;
398 101 : epoch_credits->base_credits = base;
399 101 : }
400 :
401 : int
402 : fd_stakes_vote_account_is_admissible( fd_bank_t const * bank,
403 : ulong lamports,
404 : uchar const * owner,
405 : uchar const * data,
406 106 : ulong data_len ) {
407 : /* The VAT filter also checks lamports against the rent-exempt
408 : minimum for the account's size, plus one epoch's VAT burn. */
409 106 : ulong vat_to_burn_per_epoch = fd_slot_params_at_slot( bank, bank->f.slot ).vat_to_burn_per_epoch;
410 106 : ulong minimum_balance = fd_rent_exempt_minimum_balance( &bank->f.rent, data_len ) + vat_to_burn_per_epoch;
411 106 : return lamports>=minimum_balance &&
412 106 : fd_vsv_is_correct_size_owner_and_init( owner, data, data_len ) &&
413 106 : fd_vote_account_has_bls_pubkey( data, data_len ) &&
414 106 : !( FD_FEATURE_ACTIVE_BANK( bank, enforce_validator_location_registration ) &&
415 101 : !fd_vote_account_is_valid_v5( data, data_len ) );
416 106 : }
417 :
418 : void
419 : fd_refresh_vote_accounts( fd_bank_t * bank,
420 : fd_accdb_t * accdb,
421 : fd_runtime_stack_t * runtime_stack,
422 : fd_stake_delegations_t const * stake_delegations,
423 : fd_stake_history_t const * history,
424 : ulong rewarded_epoch,
425 28 : ulong * new_rate_activation_epoch ) {
426 28 : bank->epoch_credits_fork_id = fd_epoch_credits_store_new_fork( fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id );
427 :
428 28 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
429 28 : ushort fork_id = bank->vote_stakes_fork_id;
430 :
431 28 : fd_stake_accum_map_reset( runtime_stack->stakes.stake_accum_map );
432 28 : ulong epoch = bank->f.epoch;
433 28 : ulong total_stake = 0UL;
434 28 : ulong total_activating = 0UL;
435 28 : ulong total_deactivating = 0UL;
436 28 : ulong staked_accounts = 0UL;
437 28 : int block_revenue_sharing = FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing );
438 28 : int accumulate_reward_stakes = rewarded_epoch!=ULONG_MAX;
439 :
440 28 : fd_stake_accum_t * stake_accum_pool = runtime_stack->stakes.stake_accum;
441 28 : fd_stake_accum_map_t * stake_accum_map = runtime_stack->stakes.stake_accum_map;
442 :
443 : /* Accumulate stakes across all delegations for all vote accounts. */
444 28 : fd_stake_delegations_iter_t iter_[1];
445 28 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
446 184 : !fd_stake_delegations_iter_done( iter );
447 156 : fd_stake_delegations_iter_next( iter ) ) {
448 :
449 156 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
450 :
451 156 : fd_stake_history_entry_t new_acc;
452 156 : uchar st = stake_delegation->state;
453 156 : if( FD_LIKELY( st==FD_STAKE_DELEGATION_STATE_WARMED ) ) {
454 117 : new_acc = (fd_stake_history_entry_t){ .effective = stake_delegation->stake, .activating = 0UL, .deactivating = 0UL };
455 117 : } else if( st==FD_STAKE_DELEGATION_STATE_COOLED ) {
456 8 : new_acc = (fd_stake_history_entry_t){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
457 31 : } else {
458 31 : new_acc = fd_stake_delegation_activation_status( stake_delegation, epoch, history, new_rate_activation_epoch );
459 31 : }
460 :
461 156 : ulong reward_stake = 0UL;
462 156 : if( FD_UNLIKELY( accumulate_reward_stakes ) ) {
463 156 : if( FD_LIKELY( st==FD_STAKE_DELEGATION_STATE_WARMED ) ) {
464 117 : reward_stake = stake_delegation->stake;
465 117 : } else if( st!=FD_STAKE_DELEGATION_STATE_COOLED ) {
466 31 : reward_stake = fd_stake_delegation_activation_status( stake_delegation, rewarded_epoch, history, new_rate_activation_epoch ).effective;
467 31 : }
468 156 : }
469 :
470 156 : total_stake += new_acc.effective;
471 156 : total_activating += new_acc.activating;
472 156 : total_deactivating += new_acc.deactivating;
473 156 : if( FD_UNLIKELY( !new_acc.effective && !reward_stake ) ) continue;
474 :
475 117 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_ele_query( stake_accum_map, &stake_delegation->vote_account, NULL, stake_accum_pool );
476 117 : if( FD_UNLIKELY( !stake_accum ) ) {
477 115 : if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_staked_vote_accounts ) ) {
478 0 : FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
479 0 : }
480 115 : stake_accum = &runtime_stack->stakes.stake_accum[ staked_accounts ];
481 115 : stake_accum->pubkey = stake_delegation->vote_account;
482 115 : stake_accum->stake = new_acc.effective;
483 115 : stake_accum->reward_stake = reward_stake;
484 115 : fd_stake_accum_map_ele_insert( stake_accum_map, stake_accum, stake_accum_pool );
485 115 : staked_accounts++;
486 115 : } else {
487 2 : stake_accum->stake += new_acc.effective;
488 2 : FD_TEST( !__builtin_uaddl_overflow( stake_accum->reward_stake, reward_stake, &stake_accum->reward_stake ) );
489 2 : }
490 117 : }
491 :
492 : /* Only update total_*_stake at the epoch boundary. These values
493 : are snapshots of the stake totals for the current epoch. */
494 28 : bank->f.total_activating_stake = total_activating;
495 28 : bank->f.total_deactivating_stake = total_deactivating;
496 28 : bank->f.total_effective_stake = total_stake;
497 :
498 : /* Iterate over the valid delegated vote accounts and insert them into
499 : the top votes set for the t-1 epoch. */
500 :
501 28 : ulong top_votes_eligible = 0UL;
502 28 : for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( stake_accum_map, stake_accum_pool );
503 143 : !fd_stake_accum_map_iter_done( iter, stake_accum_map, stake_accum_pool );
504 115 : iter = fd_stake_accum_map_iter_next( iter, stake_accum_map, stake_accum_pool ) ) {
505 115 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_iter_ele( iter, stake_accum_map, stake_accum_pool );
506 :
507 115 : fd_pubkey_t node_account_t_1 = {0};
508 115 : ulong stake_t_1 = stake_accum->stake;
509 115 : ushort commission_t_1 = 0;
510 115 : uchar bls_key_t_1[ FD_BLS_PUBKEY_COMPRESSED_SZ ];
511 :
512 115 : if( FD_UNLIKELY( !stake_t_1 ) ) continue;
513 :
514 : /* Stake can be delegated to any address, so reject accounts that
515 : can't be vote accounts before reading their data. */
516 111 : ulong data_len = fd_accdb_data_len( accdb, bank->accdb_fork_id, stake_accum->pubkey.uc );
517 111 : if( FD_UNLIKELY( data_len!=FD_VOTE_STATE_V2_SZ && data_len!=FD_VOTE_STATE_V3_SZ &&
518 111 : data_len!=FD_VOTE_STATE_V4_SZ && data_len!=FD_VOTE_STATE_V5_SZ ) ) continue;
519 :
520 106 : fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, stake_accum->pubkey.uc );
521 106 : if( FD_UNLIKELY( !acc.lamports ) ) {
522 0 : fd_accdb_unread_one( accdb, &acc );
523 0 : continue;
524 0 : }
525 :
526 : /* SIMD-0123: delegator rewards do not count towards the balance
527 : https://github.com/anza-xyz/agave/blob/v4.4/vote/src/vote_account.rs#L225-L234 */
528 106 : ulong available_balance = acc.lamports;
529 106 : if( block_revenue_sharing ) {
530 0 : ulong pending_delegator_rewards = 0UL;
531 0 : if( FD_LIKELY( !fd_vote_account_pending_delegator_rewards( acc.data, acc.data_len, &pending_delegator_rewards ) ) ) {
532 0 : available_balance = fd_ulong_sat_sub( acc.lamports, pending_delegator_rewards );
533 0 : }
534 0 : }
535 106 : if( FD_UNLIKELY( !fd_stakes_vote_account_is_admissible( bank, available_balance, acc.owner, acc.data, acc.data_len ) ) ) {
536 5 : fd_accdb_unread_one( accdb, &acc );
537 5 : continue;
538 5 : }
539 :
540 101 : FD_TEST( !fd_vote_account_commission_bps( acc.data, acc.data_len, FD_FEATURE_ACTIVE_BANK( bank, commission_rate_in_basis_points ), &commission_t_1 ) );
541 101 : FD_TEST( !fd_vote_account_node_pubkey( acc.data, acc.data_len, &node_account_t_1 ) );
542 101 : if( FD_LIKELY( fd_vote_account_has_bls_pubkey( acc.data, acc.data_len ) ) ) {
543 101 : FD_TEST( !fd_vote_account_bls_pubkey( acc.data, acc.data_len, bls_key_t_1 ) );
544 101 : }
545 :
546 : /* SIMD-0123 fields and SIMD-0232 collectors from the same account
547 : image as the inflation commission. */
548 101 : ushort block_revenue_commission_bps;
549 101 : ulong pending_delegator_rewards;
550 101 : fd_pubkey_t inflation_collector;
551 101 : fd_pubkey_t block_collector;
552 101 : FD_TEST( !fd_vote_account_block_revenue_commission_bps( acc.data, acc.data_len, &block_revenue_commission_bps ) );
553 101 : FD_TEST( !fd_vote_account_pending_delegator_rewards( acc.data, acc.data_len, &pending_delegator_rewards ) );
554 101 : FD_TEST( !fd_vote_account_collectors( acc.data, acc.data_len, &stake_accum->pubkey, &node_account_t_1, &inflation_collector, &block_collector ) );
555 :
556 101 : fd_validator_location_t location = fd_vote_account_location( acc.data, acc.data_len );
557 101 : fd_vote_stakes_insert( vote_stakes, fork_id, &stake_accum->pubkey, &node_account_t_1, stake_t_1, commission_t_1, bls_key_t_1,
558 101 : block_revenue_commission_bps, pending_delegator_rewards, &inflation_collector, &block_collector, &location );
559 101 : top_votes_eligible++;
560 101 : fd_accdb_unread_one( accdb, &acc );
561 101 : }
562 :
563 : /* The t-1 set is fixed from here on: rank it for Alpenglow. */
564 28 : fd_vote_stakes_finalize( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1 );
565 :
566 : /* Seed validity for the t-2 top votes set. */
567 28 : fd_vote_stakes_refresh( vote_stakes, fork_id, accdb, bank->accdb_fork_id );
568 :
569 : /* Populate the vote rewards map with the final set of filtered vote
570 : accounts. */
571 28 : fd_vote_rewards_map_t * vote_reward_map = runtime_stack->stakes.vote_map;
572 28 : fd_vote_rewards_map_reset( vote_reward_map );
573 28 : ulong vote_reward_cnt = 0UL;
574 :
575 28 : fd_epoch_credits_view_t epoch_credits_view[1];
576 28 : FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
577 :
578 : /* Populate the vote rewards map with the final set of filtered vote
579 : accounts for the t-1 epoch. */
580 28 : bank->f.total_epoch_stake = 0UL;
581 28 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) t_1_iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
582 28 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, t_1_iter_mem );
583 129 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter );
584 101 : fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter ) ) {
585 101 : fd_vote_stakes_ele_t ele[1];
586 101 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter, ele );
587 :
588 101 : fd_vote_stakes_ele_t ele_t_3[1];
589 101 : fd_vote_stakes_ele_t ele_t_2[1];
590 101 : int exists_t_3 = fd_vote_stakes_query_t_3( vote_stakes, fork_id, &ele->pubkey, ele_t_3 );
591 101 : int exists_t_2 = fd_vote_stakes_query_t_2( vote_stakes, fork_id, &ele->pubkey, ele_t_2 );
592 101 : ushort commission_t_3 = exists_t_3 ? ele_t_3->commission : 0;
593 101 : ushort commission_t_2 = exists_t_2 ? ele_t_2->commission : 0;
594 :
595 101 : fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
596 101 : vote_ele->pubkey = ele->pubkey;
597 101 : vote_ele->vote_rewards = 0UL;
598 101 : if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
599 101 : vote_ele->commission = exists_t_3 ? commission_t_3 : (exists_t_2 ? commission_t_2 : ele->commission);
600 101 : } else {
601 0 : vote_ele->commission = ele->commission;
602 0 : }
603 :
604 101 : fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, ele->pubkey.uc );
605 101 : FD_TEST( acc.lamports );
606 :
607 101 : if( FD_UNLIKELY( vote_reward_cnt>=FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ) ) {
608 0 : FD_LOG_ERR(( "invariant violation: vote_reward_cnt >= epoch credits max" ));
609 0 : }
610 101 : fd_epoch_credits_t * epoch_credits = &epoch_credits_view->credits[ vote_reward_cnt ];
611 101 : fd_memcpy( epoch_credits->pubkey, &ele->pubkey, sizeof(fd_pubkey_t) );
612 101 : get_vote_credits( acc.data, acc.data_len, vote_ele->commission, epoch_credits );
613 101 : fd_accdb_unread_one( accdb, &acc );
614 :
615 101 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) {
616 0 : fd_event_runtime_vote_account_emit( bank, ele->pubkey.uc, ele->node_account.uc, ele->stake,
617 0 : ele->commission, exists_t_2, commission_t_2, exists_t_3, commission_t_3,
618 0 : vote_ele->commission, epoch_credits );
619 0 : }
620 :
621 101 : fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
622 101 : vote_reward_cnt++;
623 101 : bank->f.total_epoch_stake += ele->stake;
624 101 : }
625 28 : epoch_credits_view->len = vote_reward_cnt;
626 28 : fd_epoch_credits_view_fini( epoch_credits_view );
627 28 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_votes( staked_accounts, top_votes_eligible );
628 28 : }
629 :
630 : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank.rs#L2644-L2695 */
631 : static void
632 : fd_stakes_burn_vat( fd_bank_t * bank,
633 : fd_accdb_t * accdb,
634 28 : fd_capture_ctx_t * capture_ctx ) {
635 28 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
636 28 : ushort fork_id = bank->vote_stakes_fork_id;
637 28 : ulong burn_per_epoch = fd_slot_params_at_slot( bank, bank->f.slot ).vat_to_burn_per_epoch;
638 28 : ulong total_vat = 0UL;
639 :
640 28 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
641 28 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter_mem );
642 129 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter );
643 101 : fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter ) ) {
644 101 : fd_vote_stakes_ele_t ele[1];
645 101 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter, ele );
646 :
647 101 : fd_accdb_svm_update_t update[1];
648 101 : fd_acc_t acc = fd_accdb_svm_open_rw( bank, accdb, update, &ele->pubkey, 0 );
649 101 : FD_TEST( acc.lamports>=burn_per_epoch );
650 101 : total_vat += burn_per_epoch;
651 101 : acc.lamports -= burn_per_epoch;
652 101 : update->skip_event_diff = 1;
653 101 : fd_accdb_svm_close_rw( bank, accdb, capture_ctx, &acc, update );
654 101 : }
655 :
656 28 : fd_accdb_svm_credit( bank, accdb, capture_ctx, &fd_sysvar_incinerator_id, total_vat, 0 );
657 :
658 28 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_vat_burn( burn_per_epoch );
659 28 : }
660 :
661 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L280 */
662 : void
663 : fd_stakes_activate_epoch( fd_bank_t * bank,
664 : fd_runtime_stack_t * runtime_stack,
665 : fd_accdb_t * accdb,
666 : fd_capture_ctx_t * capture_ctx,
667 : fd_stake_delegations_t * stake_delegations,
668 28 : ulong * new_rate_activation_epoch ) {
669 : /* We can update our stake history sysvar based on the bank stake values.
670 : Afterward, we can refresh the stake values for the vote accounts. */
671 :
672 28 : fd_stake_history_entry_t elem = fd_stake_delegations_totals( stake_delegations );
673 28 : elem.epoch = bank->f.epoch;
674 :
675 28 : fd_sysvar_stake_history_update( bank, accdb, capture_ctx, &elem );
676 28 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_stake_history( &elem );
677 :
678 28 : uchar stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
679 28 : fd_stake_history_t stake_history[1];
680 28 : if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, bank->accdb_fork_id, stake_history_data, stake_history ) ) ) {
681 0 : FD_LOG_ERR(( "StakeHistory sysvar missing or invalid" ));
682 0 : }
683 :
684 : /* Now increment the epoch and recompute the stakes for the vote
685 : accounts for the new epoch value. The rewarded epoch trails the
686 : current epoch by 1 except for the first epoch (genesis) case. */
687 :
688 28 : ulong rewarded_epoch = bank->f.epoch;
689 28 : bank->f.epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.slot, NULL );
690 :
691 28 : fd_refresh_vote_accounts( bank,
692 28 : accdb,
693 28 : runtime_stack,
694 28 : stake_delegations,
695 28 : stake_history,
696 28 : rewarded_epoch,
697 28 : new_rate_activation_epoch );
698 28 : fd_stakes_burn_vat( bank, accdb, capture_ctx );
699 28 : }
700 :
701 :
702 : void
703 : fd_stakes_update_stake_delegation( fd_pubkey_t const * pubkey,
704 : fd_acc_t const * acc,
705 : fd_bank_t * bank,
706 : fd_stake_history_t const * stake_history,
707 300 : fd_txn_in_t const * txn_in ) {
708 :
709 300 : fd_stake_state_t const * stake_state = fd_stakes_get_state( acc );
710 300 : fd_stake_state_t const * prior_stake_state = NULL;
711 300 : if( FD_LIKELY( acc->prior_lamports && !memcmp( acc->prior_owner, &fd_solana_stake_program_id, 32UL ) ) ) {
712 0 : prior_stake_state = fd_stake_state_view( acc->prior_data, acc->prior_data_len );
713 0 : }
714 :
715 300 : int current_has_delegation = stake_state && stake_state->stake_type==FD_STAKE_STATE_STAKE;
716 300 : int prior_has_delegation = prior_stake_state && prior_stake_state->stake_type==FD_STAKE_STATE_STAKE;
717 :
718 : /* If the current stake state isn't a delegation and it was a
719 : delegation in the previous stake state, insert a tombstone into the
720 : stake delegation's fork. */
721 300 : if( FD_UNLIKELY( !current_has_delegation ) ) {
722 300 : if( FD_LIKELY( !prior_has_delegation ) ) return; /* nothing to remove from */
723 0 : fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
724 0 : fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, 0UL, pubkey, 0 );
725 0 : if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, NULL );
726 0 : return;
727 300 : }
728 :
729 : /* Agave replaces the cached version of the account whenever the
730 : account changes. */
731 :
732 0 : int account_changed = acc->prior_lamports !=acc->lamports ||
733 0 : acc->prior_executable!=acc->executable ||
734 0 : acc->prior_data_len !=acc->data_len ||
735 0 : memcmp( acc->prior_owner, acc->owner, sizeof(fd_pubkey_t) );
736 0 : if( FD_LIKELY( !account_changed && acc->data_len ) ) {
737 0 : account_changed = !!memcmp( acc->prior_data, acc->data, acc->data_len );
738 0 : }
739 0 : if( FD_LIKELY( prior_has_delegation && !account_changed ) ) return;
740 :
741 0 : fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
742 0 : if( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) ) {
743 0 : fd_delegation_t const * delegation = &stake_state->stake.stake.delegation;
744 0 : if( FD_UNLIKELY( fd_delegation_is_inactive( delegation, bank->f.epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch ) &&
745 0 : fd_delegation_is_inactive( delegation, fd_ulong_sat_sub( bank->f.epoch, 1UL ), stake_history, &bank->f.warmup_cooldown_rate_epoch ) ) ) {
746 0 : fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, 0UL, pubkey, 0 );
747 0 : if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, NULL );
748 0 : return;
749 0 : }
750 0 : }
751 :
752 0 : ulong new_stake = stake_state->stake.stake.delegation.stake;
753 0 : fd_stake_delegations_fork_update( stake_delegations, bank->stake_delegations_fork_id, 0UL, pubkey,
754 0 : &stake_state->stake.stake.delegation.voter_pubkey,
755 0 : new_stake,
756 0 : stake_state->stake.stake.delegation.activation_epoch,
757 0 : stake_state->stake.stake.delegation.deactivation_epoch,
758 0 : stake_state->stake.stake.credits_observed,
759 0 : acc->lamports,
760 0 : (uint)acc->data_len );
761 0 : if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, stake_state );
762 0 : }
|