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 6000036 : #define FD_BASIS_POINTS_PER_UNIT (10000UL)
21 8993637 : #define FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS (2500UL)
22 3006417 : #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/state.rs#L694-L778
39 : static effective_activating_t
40 : stake_and_activating( fd_delegation_t const * self,
41 : ulong target_epoch,
42 : fd_stake_history_t const * history,
43 1056 : ulong * new_rate_activation_epoch ) {
44 1056 : ulong delegated_stake = self->stake;
45 :
46 1056 : fd_stake_history_entry_t const * cluster_stake_at_activation_epoch;
47 1056 : if( self->activation_epoch==ULONG_MAX ) {
48 660 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
49 660 : } else if( self->activation_epoch==self->deactivation_epoch ) {
50 168 : return ( effective_activating_t ){ .effective = 0, .activating = 0 };
51 228 : } else if( target_epoch==self->activation_epoch ) {
52 45 : return ( effective_activating_t ){ .effective = 0, .activating = delegated_stake };
53 183 : } else if( target_epoch<self->activation_epoch ) {
54 0 : return ( effective_activating_t ){ .effective = 0, .activating = 0 };
55 183 : } else if( history &&
56 183 : ( cluster_stake_at_activation_epoch = fd_sysvar_stake_history_query( history, self->activation_epoch ) ) ) {
57 3 : ulong prev_epoch = self->activation_epoch;
58 3 : fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_activation_epoch;
59 :
60 3 : ulong current_epoch;
61 3 : ulong current_effective_stake = 0;
62 3 : for( ;; ) {
63 3 : current_epoch = prev_epoch + 1;
64 3 : if( FD_LIKELY( prev_cluster_stake->activating==0 ) ) {
65 3 : break;
66 3 : }
67 :
68 0 : ulong remaining_activating_stake = delegated_stake - current_effective_stake;
69 0 : ulong newly_effective_stake = fd_ulong_max( fd_stake_calculate_change_allowance_float( current_epoch, remaining_activating_stake, prev_cluster_stake->activating, prev_cluster_stake->effective, new_rate_activation_epoch ), 1 );
70 :
71 0 : current_effective_stake += newly_effective_stake;
72 0 : if( FD_LIKELY( current_effective_stake>=delegated_stake ) ) {
73 0 : current_effective_stake = delegated_stake;
74 0 : break;
75 0 : }
76 :
77 0 : if( FD_LIKELY( current_epoch>=target_epoch ||
78 0 : current_epoch>=self->deactivation_epoch ) ) {
79 0 : break;
80 0 : }
81 :
82 0 : fd_stake_history_entry_t const * current_cluster_stake = fd_sysvar_stake_history_query( history, current_epoch );
83 0 : if( FD_LIKELY( current_cluster_stake ) ) {
84 0 : prev_epoch = current_epoch;
85 0 : prev_cluster_stake = current_cluster_stake;
86 0 : } else {
87 0 : break;
88 0 : }
89 0 : }
90 3 : return ( effective_activating_t ){ .effective = current_effective_stake,
91 3 : .activating = delegated_stake - current_effective_stake };
92 180 : } else {
93 180 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
94 180 : }
95 1056 : }
96 :
97 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L606-L690 */
98 : fd_stake_history_entry_t
99 : stake_activating_and_deactivating( fd_delegation_t const * self,
100 : ulong target_epoch,
101 : fd_stake_history_t const * stake_history,
102 1056 : ulong * new_rate_activation_epoch ) {
103 :
104 1056 : effective_activating_t effective_activating = stake_and_activating( self, target_epoch, stake_history, new_rate_activation_epoch );
105 :
106 1056 : ulong effective_stake = effective_activating.effective;
107 1056 : ulong activating_stake = effective_activating.activating;
108 :
109 1056 : fd_stake_history_entry_t const * cluster_stake_at_deactivation_epoch = NULL;
110 :
111 1056 : if( target_epoch<self->deactivation_epoch ) {
112 792 : if( activating_stake==0 ) {
113 744 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .deactivating = 0, .activating = 0 };
114 744 : } else {
115 48 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .deactivating = 0, .activating = activating_stake };
116 48 : }
117 792 : } else if( target_epoch==self->deactivation_epoch ) {
118 81 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .deactivating = effective_stake, .activating = 0 };
119 183 : } else if( stake_history &&
120 183 : ( cluster_stake_at_deactivation_epoch = fd_sysvar_stake_history_query( stake_history, self->deactivation_epoch ) ) ) {
121 3 : ulong prev_epoch = self->deactivation_epoch;
122 3 : fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_deactivation_epoch;
123 :
124 3 : ulong current_epoch;
125 3 : ulong current_effective_stake = effective_stake;
126 3 : for( ;; ) {
127 3 : current_epoch = prev_epoch + 1;
128 3 : if( prev_cluster_stake->deactivating==0 ) break;
129 :
130 0 : ulong newly_not_effective_stake = fd_ulong_max( fd_stake_calculate_change_allowance_float( current_epoch, current_effective_stake, prev_cluster_stake->deactivating, prev_cluster_stake->effective, new_rate_activation_epoch ), 1 );
131 :
132 0 : current_effective_stake = fd_ulong_sat_sub( current_effective_stake, newly_not_effective_stake );
133 0 : if( current_effective_stake==0 ) break;
134 :
135 0 : if( current_epoch>=target_epoch ) break;
136 :
137 0 : fd_stake_history_entry_t const * current_cluster_stake = NULL;
138 0 : if( ( current_cluster_stake = fd_sysvar_stake_history_query(stake_history, current_epoch ) ) ) {
139 0 : prev_epoch = current_epoch;
140 0 : prev_cluster_stake = current_cluster_stake;
141 0 : } else {
142 0 : break;
143 0 : }
144 0 : }
145 3 : return ( fd_stake_history_entry_t ){ .effective = current_effective_stake,
146 3 : .deactivating = current_effective_stake,
147 3 : .activating = 0 };
148 180 : } else {
149 180 : return ( fd_stake_history_entry_t ){ .effective = 0, .activating = 0, .deactivating = 0 };
150 180 : }
151 1056 : }
152 :
153 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L7-L14 */
154 : static ulong
155 12000054 : fd_stake_warmup_cooldown_rate_bps( ulong current_epoch, ulong * opt_rate_change_activation_epoch ) {
156 12000054 : if( opt_rate_change_activation_epoch && current_epoch>=*opt_rate_change_activation_epoch ) {
157 3006417 : return FD_TOWER_WARMUP_COOLDOWN_RATE_BPS;
158 8993637 : } else {
159 8993637 : return FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS;
160 8993637 : }
161 12000054 : }
162 :
163 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L54-L93 */
164 : static ulong
165 : calculate_stake_change_allowance( ulong epoch,
166 : ulong account_portion,
167 : ulong cluster_portion,
168 : ulong cluster_effective,
169 6000054 : ulong * opt_rate_change_activation_epoch ) {
170 6000054 : if( account_portion==0UL || cluster_portion==0UL || cluster_effective==0UL ) {
171 18 : return 0UL;
172 18 : }
173 :
174 6000036 : ulong rate_bps = fd_stake_warmup_cooldown_rate_bps( epoch, opt_rate_change_activation_epoch );
175 :
176 6000036 : uint128 numerator = fd_uint128_sat_mul( fd_uint128_sat_mul( (uint128)account_portion, (uint128)cluster_effective ),
177 6000036 : (uint128)rate_bps );
178 6000036 : uint128 denominator = fd_uint128_sat_mul( (uint128)cluster_portion, (uint128)FD_BASIS_POINTS_PER_UNIT );
179 :
180 : /* denominator is never zero due to guard above */
181 6000036 : uint128 delta = numerator / denominator;
182 6000036 : uint128 cap = (uint128)account_portion;
183 6000036 : return (ulong)( delta<cap ? delta : cap );
184 6000054 : }
185 :
186 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L16-L33 */
187 : ulong
188 : fd_stake_calculate_activation_allowance( ulong current_epoch,
189 : ulong account_activating_stake,
190 : fd_stake_history_entry_t const * prev_epoch_cluster_state,
191 6000030 : ulong * opt_rate_change_activation_epoch ) {
192 6000030 : return calculate_stake_change_allowance( current_epoch,
193 6000030 : account_activating_stake,
194 6000030 : prev_epoch_cluster_state->activating,
195 6000030 : prev_epoch_cluster_state->effective,
196 6000030 : opt_rate_change_activation_epoch );
197 6000030 : }
198 :
199 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L35-L52 */
200 : static ulong
201 : fd_stake_calculate_deactivation_allowance( ulong current_epoch,
202 : ulong account_deactivating_stake,
203 : fd_stake_history_entry_t const * prev_epoch_cluster_state,
204 24 : ulong * opt_rate_change_activation_epoch ) {
205 24 : return calculate_stake_change_allowance( current_epoch,
206 24 : account_deactivating_stake,
207 24 : prev_epoch_cluster_state->deactivating,
208 24 : prev_epoch_cluster_state->effective,
209 24 : opt_rate_change_activation_epoch );
210 24 : }
211 :
212 : /* Fixed-point version of stake_and_activating.
213 : Mirrors exactly the logic in the on-chain stake program:
214 : https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L881-L971 */
215 : static effective_activating_t
216 : stake_and_activating_v2( fd_delegation_t const * self,
217 : ulong target_epoch,
218 : fd_stake_history_t const * history,
219 213 : ulong * new_rate_activation_epoch ) {
220 213 : ulong delegated_stake = self->stake;
221 :
222 213 : fd_stake_history_entry_t const * prev_cluster_stake = NULL;
223 :
224 213 : if( self->activation_epoch==ULONG_MAX ) {
225 42 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
226 171 : } else if( self->activation_epoch==self->deactivation_epoch ) {
227 141 : return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
228 141 : } else if( target_epoch==self->activation_epoch ) {
229 3 : return ( effective_activating_t ){ .effective = 0UL, .activating = delegated_stake };
230 27 : } else if( target_epoch<self->activation_epoch ) {
231 27 : return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
232 27 : } else if( history &&
233 0 : ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->activation_epoch ) ) ) {
234 :
235 0 : ulong prev_epoch = self->activation_epoch;
236 :
237 0 : ulong current_epoch;
238 0 : ulong activated_stake_amount = 0UL;
239 0 : for(;;) {
240 0 : current_epoch = prev_epoch + 1UL;
241 :
242 : /* If there is no activating stake at prev epoch, we should have
243 : been fully effective at this moment */
244 0 : if( FD_LIKELY( prev_cluster_stake->activating==0UL ) ) break;
245 :
246 : /* Calculate how much of this account's remaining stake becomes
247 : effective in current_epoch. */
248 0 : ulong remaining_activating_stake = delegated_stake - activated_stake_amount;
249 0 : ulong newly_effective_stake = fd_stake_calculate_activation_allowance( current_epoch,
250 0 : remaining_activating_stake,
251 0 : prev_cluster_stake,
252 0 : new_rate_activation_epoch );
253 :
254 : /* Add the newly effective stake, clamping the per-epoch increase
255 : to at least 1 lamport so warmup always makes progress */
256 0 : activated_stake_amount += fd_ulong_max( newly_effective_stake, 1UL );
257 :
258 : /* Stop if we've fully warmed up this account's stake. */
259 0 : if( FD_LIKELY( activated_stake_amount>=delegated_stake ) ) {
260 0 : activated_stake_amount = delegated_stake;
261 0 : break;
262 0 : }
263 :
264 : /* Stop when we've reached the time bound for this query */
265 0 : if( FD_LIKELY( current_epoch>=target_epoch || current_epoch>=self->deactivation_epoch ) ) break;
266 :
267 : /* Advance to the next epoch if we have history,
268 : otherwise we can't model further warmup */
269 0 : fd_stake_history_entry_t const * current_cluster_stake =
270 0 : fd_sysvar_stake_history_query( history, current_epoch );
271 0 : if( FD_LIKELY( current_cluster_stake ) ) {
272 0 : prev_epoch = current_epoch;
273 0 : prev_cluster_stake = current_cluster_stake;
274 0 : } else {
275 0 : break;
276 0 : }
277 0 : }
278 :
279 0 : return ( effective_activating_t ){ .effective = activated_stake_amount,
280 0 : .activating = delegated_stake - activated_stake_amount };
281 0 : } else {
282 0 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
283 0 : }
284 213 : }
285 :
286 : /* Fixed-point version of stake_activating_and_deactivating.
287 : Mirrors exactly the logic in the on-chain stake program:
288 : https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L790-L879 */
289 : static fd_stake_history_entry_t
290 : stake_activating_and_deactivating_v2( fd_delegation_t const * self,
291 : ulong target_epoch,
292 : fd_stake_history_t const * history,
293 213 : ulong * new_rate_activation_epoch ) {
294 213 : effective_activating_t effective_activating =
295 213 : stake_and_activating_v2( self, target_epoch, history, new_rate_activation_epoch );
296 213 : ulong effective_stake = effective_activating.effective;
297 213 : ulong activating_stake = effective_activating.activating;
298 :
299 213 : fd_stake_history_entry_t const * prev_cluster_stake = NULL;
300 :
301 213 : if( target_epoch<self->deactivation_epoch ) {
302 186 : if( activating_stake==0UL ) {
303 183 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = 0UL };
304 183 : } else {
305 3 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = activating_stake, .deactivating = 0UL };
306 3 : }
307 186 : } else if( target_epoch==self->deactivation_epoch ) {
308 9 : return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = effective_stake };
309 18 : } else if( history &&
310 18 : ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->deactivation_epoch ) ) ) {
311 0 : ulong prev_epoch = self->deactivation_epoch;
312 :
313 : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L830-L871 */
314 0 : ulong current_epoch;
315 0 : ulong remaining_deactivating_stake = effective_stake;
316 0 : for(;;) {
317 0 : current_epoch = prev_epoch + 1UL;
318 :
319 : /* If there is no deactivating stake at prev epoch, we should
320 : have been fully undelegated at this moment */
321 0 : if( FD_LIKELY( prev_cluster_stake->deactivating==0UL ) ) break;
322 :
323 : /* Compute how much of this account's stake cools down in
324 : current_epoch */
325 0 : ulong newly_deactivated_stake = fd_stake_calculate_deactivation_allowance( current_epoch,
326 0 : remaining_deactivating_stake,
327 0 : prev_cluster_stake,
328 0 : new_rate_activation_epoch );
329 :
330 : /* Subtract the newly deactivated stake, clamping the per-epoch
331 : decrease to at least 1 lamport so cooldown always makes
332 : progress */
333 0 : remaining_deactivating_stake =
334 0 : fd_ulong_sat_sub( remaining_deactivating_stake, fd_ulong_max( newly_deactivated_stake, 1UL ) );
335 :
336 : /* Stop if we've fully cooled down this account */
337 0 : if( remaining_deactivating_stake==0UL ) break;
338 :
339 : /* Stop when we've reached the time bound for this query */
340 0 : if( current_epoch>=target_epoch ) break;
341 :
342 : /* Advance to the next epoch if we have history,
343 : otherwise we can't model further cooldown */
344 0 : fd_stake_history_entry_t const * current_cluster_stake =
345 0 : fd_sysvar_stake_history_query( history, current_epoch );
346 0 : if( FD_LIKELY( current_cluster_stake ) ) {
347 0 : prev_epoch = current_epoch;
348 0 : prev_cluster_stake = current_cluster_stake;
349 0 : } else {
350 0 : break;
351 0 : }
352 0 : }
353 :
354 0 : return ( fd_stake_history_entry_t ){ .effective = remaining_deactivating_stake,
355 0 : .activating = 0UL,
356 0 : .deactivating = remaining_deactivating_stake };
357 18 : } else {
358 18 : return ( fd_stake_history_entry_t ){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
359 18 : }
360 213 : }
361 :
362 : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/stake_delegation.rs#L27-L41 */
363 : fd_stake_history_entry_t
364 : fd_delegation_activation_status( fd_delegation_t const * self,
365 : ulong target_epoch,
366 : fd_stake_history_t const * stake_history,
367 : ulong * new_rate_activation_epoch,
368 1269 : int use_fixed_point_stake_math ) {
369 1269 : if( use_fixed_point_stake_math ) {
370 213 : return stake_activating_and_deactivating_v2( self, target_epoch, stake_history, new_rate_activation_epoch );
371 1056 : } else {
372 1056 : return stake_activating_and_deactivating( self, target_epoch, stake_history, new_rate_activation_epoch );
373 1056 : }
374 1269 : }
375 :
376 : int
377 : fd_delegation_is_inactive( fd_delegation_t const * delegation,
378 : ulong target_epoch,
379 : fd_stake_history_t const * stake_history,
380 : ulong * new_rate_activation_epoch,
381 120 : int use_fixed_point_stake_math ) {
382 120 : fd_stake_history_entry_t status = fd_delegation_activation_status(
383 120 : delegation,
384 120 : target_epoch,
385 120 : stake_history,
386 120 : new_rate_activation_epoch,
387 120 : use_fixed_point_stake_math );
388 120 : return !status.effective && !status.activating;
389 120 : }
390 :
391 : static fd_delegation_t
392 1209 : fd_delegation_from_stake_delegation( fd_stake_delegation_t const * delegation ) {
393 1209 : return (fd_delegation_t) {
394 1209 : .voter_pubkey = delegation->vote_account,
395 1209 : .stake = delegation->stake,
396 1209 : .deactivation_epoch = delegation->deactivation_epoch==USHORT_MAX ? ULONG_MAX : delegation->deactivation_epoch,
397 1209 : .activation_epoch = delegation->activation_epoch==USHORT_MAX ? ULONG_MAX : delegation->activation_epoch,
398 1209 : .warmup_cooldown_rate = fd_stake_delegations_warmup_cooldown_rate_to_double( delegation->warmup_cooldown_rate ),
399 1209 : };
400 1209 : }
401 :
402 : /**********************************************************************/
403 : /* Public API */
404 : /**********************************************************************/
405 :
406 : fd_stake_state_t const *
407 : fd_stake_state_view( uchar const * data,
408 327 : ulong data_sz ) {
409 327 : if( FD_UNLIKELY( data_sz<4UL ) ) return NULL;
410 327 : uint stake_type = FD_LOAD( uint, data );
411 327 : switch( stake_type ) {
412 0 : case FD_STAKE_STATE_UNINITIALIZED:
413 0 : break;
414 3 : case FD_STAKE_STATE_INITIALIZED:
415 3 : if( FD_UNLIKELY( data_sz<124 ) ) return NULL;
416 3 : break;
417 324 : case FD_STAKE_STATE_STAKE:
418 324 : if( FD_UNLIKELY( data_sz<197 ) ) return NULL;
419 324 : break;
420 324 : case FD_STAKE_STATE_REWARDS_POOL:
421 0 : break;
422 0 : default:
423 0 : return NULL;
424 327 : }
425 327 : return fd_type_pun_const( data );
426 327 : }
427 :
428 : fd_stake_state_t const *
429 210 : fd_stakes_get_state( fd_acc_t const * acc ) {
430 210 : if( FD_UNLIKELY( memcmp( acc->owner, &fd_solana_stake_program_id, 32UL ) ) ) return NULL;
431 171 : if( FD_UNLIKELY( acc->lamports==0UL ) ) return NULL;
432 171 : return fd_stake_state_view( acc->data, acc->data_len );
433 171 : }
434 :
435 : fd_stake_history_entry_t
436 : fd_stake_delegation_activation_status( fd_stake_delegation_t const * stake_delegation,
437 : ulong target_epoch,
438 : fd_stake_history_t const * stake_history,
439 : ulong * new_rate_activation_epoch,
440 1146 : int use_fixed_point_stake_math ) {
441 1146 : fd_delegation_t delegation = fd_delegation_from_stake_delegation( stake_delegation );
442 1146 : return fd_delegation_activation_status( &delegation, target_epoch, stake_history, new_rate_activation_epoch, use_fixed_point_stake_math );
443 1146 : }
444 :
445 : int
446 : fd_stake_delegation_is_inactive( fd_stake_delegation_t const * delegation,
447 : ulong target_epoch,
448 : fd_stake_history_t const * stake_history,
449 : ulong * new_rate_activation_epoch,
450 63 : int use_fixed_point_stake_math ) {
451 63 : fd_delegation_t raw_delegation = fd_delegation_from_stake_delegation( delegation );
452 63 : return fd_delegation_is_inactive( &raw_delegation,
453 63 : target_epoch,
454 63 : stake_history,
455 63 : new_rate_activation_epoch,
456 63 : use_fixed_point_stake_math );
457 63 : }
458 :
459 : ulong
460 : fd_stake_weights_by_node( fd_vote_stakes_t const * vote_stakes,
461 : ulong fork_id,
462 : int iter_kind,
463 282 : fd_vote_stake_weight_t * weights ) {
464 :
465 : /* We don't care if an account is invalid, we just want to get the
466 : stake weights: they are calculated from an older snapshot of
467 : vote account stakes. */
468 282 : ulong weights_cnt = 0;
469 282 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
470 282 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
471 633 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
472 351 : fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
473 351 : fd_pubkey_t pubkey;
474 351 : ulong stake;
475 351 : fd_pubkey_t node_account;
476 351 : uchar bls_key[ FD_BLS_PUBKEY_COMPRESSED_SZ ];
477 351 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, &pubkey, &node_account, &stake,
478 351 : NULL, NULL, NULL, NULL, NULL, bls_key, NULL );
479 :
480 351 : FD_TEST( weights_cnt<MAX_STAKE_WEIGHTS );
481 351 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
482 351 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account, sizeof(fd_pubkey_t) );
483 351 : fd_memcpy( weights[ weights_cnt ].bls_key, bls_key, sizeof(weights[ weights_cnt ].bls_key) );
484 351 : weights[ weights_cnt ].stake = stake;
485 351 : weights_cnt++;
486 351 : }
487 :
488 282 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
489 :
490 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/leader-schedule/src/lib.rs#L80-L83
491 : We do not deduplicate the weights here, unlike Agave, as it is
492 : guaranteed there will be no duplicate stake entries for a given fork
493 : in the stakes map. */
494 :
495 282 : return weights_cnt;
496 282 : }
497 :
498 : static void
499 : get_vote_credits( uchar const * account_data,
500 : ulong account_data_len,
501 : ushort commission,
502 339 : fd_epoch_credits_t * epoch_credits ) {
503 :
504 339 : ulong cnt = 0UL;
505 339 : fd_vote_epoch_credits_t const * vote_epoch_credits = fd_vote_account_epoch_credits( account_data, account_data_len, &cnt );
506 339 : FD_TEST( vote_epoch_credits );
507 339 : FD_TEST( cnt<=FD_EPOCH_CREDITS_MAX );
508 339 : epoch_credits->commission = commission;
509 :
510 339 : ulong n = 0UL;
511 339 : ulong base = 0UL;
512 453 : for( ulong i=0UL; i<cnt; i++ ) {
513 114 : fd_vote_epoch_credits_t const * ele = &vote_epoch_credits[ i ];
514 :
515 114 : if( fd_vote_epoch_credits_is_alpenglow_marker( ele ) ) continue;
516 114 : if( !n ) base = ele->prev_credits;
517 :
518 114 : FD_TEST( ele->epoch<=USHORT_MAX ); /* Epoch should fit. */
519 114 : FD_TEST( ele->prev_credits>=base ); /* Prevent unsigned underflow. */
520 114 : FD_TEST( ele->credits>=base ); /* Prevent unsigned underflow. */
521 114 : FD_TEST( ele->prev_credits<=ele->credits ); /* Vote credits should be monotonic. */
522 :
523 114 : epoch_credits->epoch[ n ] = (ushort)ele->epoch;
524 114 : epoch_credits->credits_delta[ n ] = ele->credits - base;
525 114 : epoch_credits->prev_credits_delta[ n ] = ele->prev_credits - base;
526 114 : n++;
527 114 : }
528 :
529 339 : epoch_credits->cnt = (uchar)n;
530 339 : epoch_credits->base_credits = base;
531 339 : epoch_credits->fast_path_ok = fd_epoch_credits_fast_path_ok( epoch_credits );
532 339 : }
533 :
534 : void
535 : fd_refresh_vote_accounts( fd_bank_t * bank,
536 : fd_accdb_t * accdb,
537 : fd_runtime_stack_t * runtime_stack,
538 : fd_stake_delegations_t const * stake_delegations,
539 : fd_stake_history_t const * history,
540 : ulong rewarded_epoch,
541 282 : ulong * new_rate_activation_epoch ) {
542 282 : fd_bank_epoch_credits_new_fork( bank );
543 :
544 282 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
545 282 : ulong fork_id = bank->vote_stakes_fork_id;
546 :
547 282 : fd_stake_accum_map_reset( runtime_stack->stakes.stake_accum_map );
548 282 : ulong epoch = bank->f.epoch;
549 282 : ulong total_stake = 0UL;
550 282 : ulong total_activating = 0UL;
551 282 : ulong total_deactivating = 0UL;
552 282 : ulong staked_accounts = 0UL;
553 282 : int use_fixed_point_stake_math = FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 );
554 282 : int alpenglow_enabled = FD_FEATURE_ACTIVE_BANK( bank, alpenglow );
555 282 : int accumulate_reward_stakes = alpenglow_enabled && rewarded_epoch!=ULONG_MAX;
556 :
557 282 : fd_stake_accum_t * stake_accum_pool = runtime_stack->stakes.stake_accum;
558 282 : fd_stake_accum_map_t * stake_accum_map = runtime_stack->stakes.stake_accum_map;
559 :
560 : /* Accumulate stakes across all delegations for all vote accounts. */
561 282 : fd_stake_delegations_iter_t iter_[1];
562 282 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations, accdb, bank->accdb_fork_id, epoch, new_rate_activation_epoch );
563 642 : !fd_stake_delegations_iter_done( iter );
564 360 : fd_stake_delegations_iter_next( iter ) ) {
565 :
566 360 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
567 :
568 360 : fd_stake_history_entry_t new_acc;
569 360 : uchar st = stake_delegation->state;
570 360 : if( FD_LIKELY( st==FD_STAKE_DELEGATION_STATE_WARMED ) ) {
571 45 : new_acc = (fd_stake_history_entry_t){ .effective = stake_delegation->stake, .activating = 0UL, .deactivating = 0UL };
572 315 : } else if( st==FD_STAKE_DELEGATION_STATE_COOLED ) {
573 0 : new_acc = (fd_stake_history_entry_t){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
574 315 : } else {
575 315 : new_acc = fd_stake_delegation_activation_status( stake_delegation, epoch, history, new_rate_activation_epoch, use_fixed_point_stake_math );
576 315 : }
577 :
578 360 : ulong reward_stake = 0UL;
579 360 : if( FD_UNLIKELY( accumulate_reward_stakes ) ) {
580 12 : if( FD_LIKELY( st==FD_STAKE_DELEGATION_STATE_WARMED ) ) {
581 0 : reward_stake = stake_delegation->stake;
582 12 : } else if( st!=FD_STAKE_DELEGATION_STATE_COOLED ) {
583 12 : reward_stake = fd_stake_delegation_activation_status( stake_delegation, rewarded_epoch, history, new_rate_activation_epoch, use_fixed_point_stake_math ).effective;
584 12 : }
585 12 : }
586 :
587 360 : total_stake += new_acc.effective;
588 360 : total_activating += new_acc.activating;
589 360 : total_deactivating += new_acc.deactivating;
590 360 : if( FD_UNLIKELY( !new_acc.effective && !reward_stake ) ) continue;
591 :
592 354 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_ele_query( stake_accum_map, &stake_delegation->vote_account, NULL, stake_accum_pool );
593 354 : if( FD_UNLIKELY( !stake_accum ) ) {
594 354 : if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_staked_vote_accounts ) ) {
595 0 : FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
596 0 : }
597 354 : stake_accum = &runtime_stack->stakes.stake_accum[ staked_accounts ];
598 354 : stake_accum->pubkey = stake_delegation->vote_account;
599 354 : stake_accum->stake = new_acc.effective;
600 354 : stake_accum->reward_stake = reward_stake;
601 354 : fd_stake_accum_map_ele_insert( stake_accum_map, stake_accum, stake_accum_pool );
602 354 : staked_accounts++;
603 354 : } else {
604 0 : stake_accum->stake += new_acc.effective;
605 0 : FD_TEST( !__builtin_uaddl_overflow( stake_accum->reward_stake, reward_stake, &stake_accum->reward_stake ) );
606 0 : }
607 354 : }
608 :
609 : /* Only update total_*_stake at the epoch boundary. These values
610 : are snapshots of the stake totals for the current epoch. */
611 282 : bank->f.total_activating_stake = total_activating;
612 282 : bank->f.total_deactivating_stake = total_deactivating;
613 282 : bank->f.total_effective_stake = total_stake;
614 :
615 : /* Iterate over the valid delegated vote accounts and insert them into
616 : the top votes set for the t-1 epoch. */
617 :
618 : /* Rotate the SIMD-0232 collector override fork for the new epoch. */
619 282 : fd_collector_overrides_t * overrides = fd_bank_collector_overrides( bank );
620 282 : ushort co_child = fd_collector_overrides_new_child( overrides );
621 282 : fd_collector_overrides_inherit( overrides, bank->collector_overrides_fork_id, co_child, fd_ulong_sat_sub( bank->f.epoch, 1UL ) );
622 282 : bank->collector_overrides_fork_id = co_child;
623 :
624 282 : ulong top_votes_eligible = 0UL;
625 282 : for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( stake_accum_map, stake_accum_pool );
626 636 : !fd_stake_accum_map_iter_done( iter, stake_accum_map, stake_accum_pool );
627 354 : iter = fd_stake_accum_map_iter_next( iter, stake_accum_map, stake_accum_pool ) ) {
628 354 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_iter_ele( iter, stake_accum_map, stake_accum_pool );
629 :
630 354 : fd_pubkey_t node_account_t_1 = {0};
631 354 : ulong stake_t_1 = stake_accum->stake;
632 354 : ushort commission_t_1 = 0;
633 354 : uchar bls_key_t_1[ FD_BLS_PUBKEY_COMPRESSED_SZ ];
634 :
635 354 : if( FD_UNLIKELY( !stake_t_1 ) ) continue;
636 :
637 354 : fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, stake_accum->pubkey.uc );
638 : /* Agave's VAT filter also checks lamports against the VoteStateV4
639 : rent-exempt minimum, plus one epoch's VAT burn once alpenglow is
640 : active. */
641 354 : if( FD_UNLIKELY( !acc.lamports ) ) {
642 0 : fd_accdb_unread_one( accdb, &acc );
643 0 : continue;
644 0 : }
645 :
646 354 : ulong vote_account_lamports = acc.lamports;
647 354 : ulong vat_to_burn_per_epoch = alpenglow_enabled ? fd_slot_params_at_slot( bank, bank->f.slot ).vat_to_burn_per_epoch : 0UL;
648 354 : ulong minimum_vote_account_balance = fd_rent_exempt_minimum_balance( &bank->f.rent, FD_VOTE_STATE_V4_SZ ) + vat_to_burn_per_epoch;
649 354 : if( FD_UNLIKELY( vote_account_lamports < minimum_vote_account_balance ) ) {
650 6 : fd_accdb_unread_one( accdb, &acc );
651 6 : continue;
652 6 : }
653 348 : if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( acc.owner, acc.data, acc.data_len ) ||
654 348 : !fd_vote_account_is_v4_with_bls_pubkey( acc.data, acc.data_len ) ) ) {
655 9 : fd_accdb_unread_one( accdb, &acc );
656 9 : continue;
657 9 : }
658 :
659 339 : 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 ) );
660 339 : FD_TEST( !fd_vote_account_node_pubkey( acc.data, acc.data_len, &node_account_t_1 ) );
661 339 : if( FD_LIKELY( fd_vote_account_is_v4_with_bls_pubkey( acc.data, acc.data_len ) ) ) {
662 339 : FD_TEST( !fd_vote_account_bls_pubkey( acc.data, acc.data_len, bls_key_t_1 ) );
663 339 : }
664 :
665 339 : 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 );
666 339 : top_votes_eligible++;
667 339 : fd_accdb_unread_one( accdb, &acc );
668 339 : }
669 :
670 : /* Capture SIMD-0232 collector overrides for the admitted t-1 set.
671 : Only admitted vote accounts can be scheduled as leaders or earn
672 : inflation rewards, so collectors of accounts outside the set are
673 : never consulted. Capturing after selection bounds the override
674 : store by the admitted set size. */
675 282 : {
676 282 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) co_iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
677 282 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, co_iter_mem );
678 621 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter );
679 339 : fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter ) ) {
680 339 : fd_pubkey_t vote_pubkey;
681 339 : fd_pubkey_t node_pubkey;
682 339 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter,
683 339 : &vote_pubkey, &node_pubkey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
684 :
685 339 : fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, vote_pubkey.uc );
686 339 : fd_pubkey_t inflation_collector;
687 339 : fd_pubkey_t block_collector;
688 339 : FD_TEST( !fd_vote_account_collectors( acc.data, acc.data_len, &vote_pubkey, &node_pubkey, &inflation_collector, &block_collector ) );
689 339 : int has_inflation = !fd_pubkey_eq( &inflation_collector, &vote_pubkey );
690 339 : int has_block = !fd_pubkey_eq( &block_collector, &node_pubkey );
691 339 : if( FD_UNLIKELY( has_inflation | has_block ) ) {
692 54 : fd_collector_overrides_upsert( overrides, co_child, bank->f.epoch, &vote_pubkey,
693 54 : has_inflation, &inflation_collector,
694 54 : has_block, &block_collector );
695 54 : }
696 339 : fd_accdb_unread_one( accdb, &acc );
697 339 : }
698 282 : }
699 :
700 : /* Seed status for the t-2 top votes set for clock calculation. */
701 282 : fd_vote_stakes_refresh( vote_stakes, fork_id, accdb, bank->accdb_fork_id );
702 :
703 : /* Populate the vote rewards map with the final set of filtered vote
704 : accounts. */
705 282 : fd_vote_rewards_map_t * vote_reward_map = runtime_stack->stakes.vote_map;
706 282 : fd_vote_rewards_map_reset( vote_reward_map );
707 282 : ulong vote_reward_cnt = 0UL;
708 :
709 : /* Populate the vote rewards map with the final set of filtered vote
710 : accounts for the t-1 epoch. */
711 282 : bank->f.total_epoch_stake = 0UL;
712 282 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) t_1_iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
713 282 : 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 );
714 621 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter );
715 339 : fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter ) ) {
716 339 : fd_pubkey_t pubkey;
717 339 : fd_pubkey_t node_account;
718 339 : ulong stake;
719 339 : ushort commission_t_1 = 0;
720 339 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter, &pubkey, &node_account, &stake,
721 339 : NULL, NULL, &commission_t_1, NULL, NULL, NULL, NULL );
722 :
723 339 : ushort commission_t_3 = 0;
724 339 : int exists_t_3 = fd_vote_stakes_query_t_3( vote_stakes, fork_id, &pubkey, NULL, NULL, &commission_t_3 );
725 :
726 339 : ushort commission_t_2 = 0;
727 339 : int exists_t_2 = fd_vote_stakes_query_t_2( vote_stakes, fork_id, &pubkey, NULL, NULL, NULL, NULL, &commission_t_2, NULL );
728 :
729 339 : fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
730 339 : vote_ele->pubkey = pubkey;
731 339 : vote_ele->vote_rewards = 0UL;
732 339 : if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
733 72 : vote_ele->commission = exists_t_3 ? commission_t_3 : (exists_t_2 ? commission_t_2 : commission_t_1);
734 267 : } else {
735 267 : vote_ele->commission = commission_t_1;
736 267 : }
737 :
738 339 : fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, pubkey.uc );
739 339 : FD_TEST( acc.lamports );
740 :
741 339 : if( FD_UNLIKELY( vote_reward_cnt>=FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ) ) {
742 0 : FD_LOG_ERR(( "invariant violation: vote_reward_cnt >= epoch credits max" ));
743 0 : }
744 339 : fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[ vote_reward_cnt ];
745 339 : fd_memcpy( epoch_credits->pubkey, &pubkey, sizeof(fd_pubkey_t) );
746 339 : get_vote_credits( acc.data, acc.data_len, vote_ele->commission, epoch_credits );
747 339 : fd_accdb_unread_one( accdb, &acc );
748 :
749 339 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) {
750 0 : fd_event_runtime_vote_account_emit( bank, pubkey.uc, node_account.uc, stake,
751 0 : commission_t_1, exists_t_2, commission_t_2, exists_t_3, commission_t_3,
752 0 : vote_ele->commission, epoch_credits );
753 0 : }
754 :
755 339 : fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
756 339 : vote_reward_cnt++;
757 339 : bank->f.total_epoch_stake += stake;
758 339 : }
759 282 : *fd_bank_epoch_credits_len( bank ) = vote_reward_cnt;
760 282 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_votes( staked_accounts, top_votes_eligible );
761 282 : }
762 :
763 : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank.rs#L2644-L2695 */
764 : static void
765 : fd_stakes_burn_vat( fd_bank_t * bank,
766 : fd_accdb_t * accdb,
767 282 : fd_capture_ctx_t * capture_ctx ) {
768 282 : if( !FD_FEATURE_ACTIVE_BANK( bank, alpenglow ) ) return;
769 :
770 9 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
771 9 : ulong fork_id = bank->vote_stakes_fork_id;
772 9 : ulong burn_per_epoch = fd_slot_params_at_slot( bank, bank->f.slot ).vat_to_burn_per_epoch;
773 9 : ulong total_vat = 0UL;
774 :
775 9 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
776 9 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter_mem );
777 15 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter );
778 9 : fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter ) ) {
779 6 : fd_pubkey_t vote_pubkey;
780 6 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter,
781 6 : &vote_pubkey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
782 :
783 6 : fd_accdb_svm_update_t update[1];
784 6 : fd_acc_t acc = fd_accdb_svm_open_rw( bank, accdb, update, &vote_pubkey, 0 );
785 6 : FD_TEST( acc.lamports>=burn_per_epoch );
786 6 : total_vat += burn_per_epoch;
787 6 : acc.lamports -= burn_per_epoch;
788 6 : update->skip_event_diff = 1;
789 6 : fd_accdb_svm_close_rw( bank, accdb, capture_ctx, &acc, update );
790 6 : }
791 :
792 9 : fd_accdb_svm_credit( bank, accdb, capture_ctx, &fd_sysvar_incinerator_id, total_vat, 0 );
793 :
794 9 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_vat_burn( burn_per_epoch );
795 9 : }
796 :
797 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L280 */
798 : void
799 : fd_stakes_activate_epoch( fd_bank_t * bank,
800 : fd_runtime_stack_t * runtime_stack,
801 : fd_accdb_t * accdb,
802 : fd_capture_ctx_t * capture_ctx,
803 : fd_stake_delegations_t * stake_delegations,
804 282 : ulong * new_rate_activation_epoch ) {
805 : /* We can update our stake history sysvar based on the bank stake values.
806 : Afterward, we can refresh the stake values for the vote accounts. */
807 :
808 282 : fd_stake_history_entry_t elem = {
809 282 : .epoch = bank->f.epoch,
810 282 : .effective = stake_delegations->effective_stake,
811 282 : .activating = stake_delegations->activating_stake,
812 282 : .deactivating = stake_delegations->deactivating_stake,
813 282 : };
814 :
815 : /* Agave recomputes each stake history entry from scratch every epoch
816 : boundary, whereas Firedancer keeps running totals. Therefore,
817 : at the boundary where upgrade_bpf_stake_program_to_v5_1 is
818 : activated, we need to recompute the stake history entry for the
819 : epoch that has just ended, so that all the delegations for this
820 : entry are summed using the new fixed point arithmetic. We only
821 : need to do this once, at the feature activation epoch boundary.
822 :
823 : https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/stakes.rs#L444-L477
824 :
825 : The same recomputation needs to be done as soon as fallback stake
826 : accounts are enabled. */
827 282 : int fallback = fd_stake_delegations_pubkey_fallback( stake_delegations );
828 282 : if( FD_UNLIKELY( fallback || FD_FEATURE_JUST_ACTIVATED_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) ) ) {
829 0 : fd_stake_history_t history[1];
830 0 : if( FD_UNLIKELY( !fd_sysvar_cache_stake_history_view( &bank->f.sysvar_cache, history ) ) ) {
831 0 : FD_LOG_CRIT(( "invariant violation: StakeHistory sysvar missing or invalid" ));
832 0 : }
833 0 : ulong effective = 0UL;
834 0 : ulong activating = 0UL;
835 0 : ulong deactivating = 0UL;
836 :
837 0 : int use_fixed_point_stake_math = FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 );
838 :
839 0 : fd_stake_delegations_iter_t iter_[1];
840 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations, accdb, bank->accdb_fork_id, bank->f.epoch, new_rate_activation_epoch );
841 0 : !fd_stake_delegations_iter_done( iter );
842 0 : fd_stake_delegations_iter_next( iter ) ) {
843 0 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
844 0 : fd_stake_history_entry_t acc = fd_stake_delegation_activation_status(
845 0 : stake_delegation, bank->f.epoch, history, new_rate_activation_epoch, use_fixed_point_stake_math );
846 0 : effective += acc.effective;
847 0 : activating += acc.activating;
848 0 : deactivating += acc.deactivating;
849 0 : }
850 :
851 0 : elem.effective = effective;
852 0 : elem.activating = activating;
853 0 : elem.deactivating = deactivating;
854 0 : }
855 :
856 282 : fd_sysvar_stake_history_update( bank, accdb, capture_ctx, &elem );
857 282 : if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_stake_history( &elem );
858 :
859 : /* Snapshot the stake history sysvar into a local buffer and release
860 : the accdb bracket before calling fd_refresh_vote_accounts, which
861 : performs its own accdb acquires. fd_sysvar_stake_history_view
862 : aliases the source bytes, so the bracket cannot be held open across
863 : an inner acquire. */
864 282 : uchar stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
865 282 : fd_stake_history_t stake_history[1];
866 282 : {
867 282 : fd_acc_t ro = fd_accdb_read_one( accdb, bank->accdb_fork_id, fd_sysvar_stake_history_id.uc );
868 282 : if( FD_UNLIKELY( !ro.lamports ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing" ));
869 282 : ulong copy_sz = fd_ulong_min( ro.data_len, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
870 282 : fd_memcpy( stake_history_data, ro.data, copy_sz );
871 282 : fd_accdb_unread_one( accdb, &ro );
872 282 : if( FD_UNLIKELY( !fd_sysvar_stake_history_view( stake_history, stake_history_data, copy_sz ) ) ) {
873 0 : FD_LOG_HEXDUMP_ERR(( "Invalid StakeHistory sysvar", stake_history_data, copy_sz ));
874 0 : }
875 282 : }
876 :
877 282 : if( FD_UNLIKELY( !fd_sysvar_stake_history_is_contiguous( stake_history ) ) ) {
878 0 : fd_stake_delegations_invalidate_warmed( stake_delegations );
879 0 : }
880 :
881 : /* Now increment the epoch and recompute the stakes for the vote
882 : accounts for the new epoch value. The rewarded epoch trails the
883 : current epoch by 1 except for the first epoch (genesis) case. */
884 :
885 282 : ulong rewarded_epoch = bank->f.epoch;
886 282 : bank->f.epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.slot, NULL );
887 :
888 282 : fd_refresh_vote_accounts( bank,
889 282 : accdb,
890 282 : runtime_stack,
891 282 : stake_delegations,
892 282 : stake_history,
893 282 : rewarded_epoch,
894 282 : new_rate_activation_epoch );
895 282 : fd_stakes_burn_vat( bank, accdb, capture_ctx );
896 282 : }
897 :
898 :
899 : void
900 : fd_stakes_update_stake_delegation( fd_pubkey_t const * pubkey,
901 : fd_acc_t const * acc,
902 : fd_bank_t * bank,
903 69 : fd_txn_in_t const * txn_in ) {
904 :
905 69 : fd_stake_state_t const * stake_state = fd_stakes_get_state( acc );
906 69 : fd_stake_state_t const * prior_stake_state = NULL;
907 69 : if( FD_LIKELY( acc->prior_lamports && !memcmp( acc->prior_owner, &fd_solana_stake_program_id, 32UL ) ) ) {
908 39 : prior_stake_state = fd_stake_state_view( acc->prior_data, acc->prior_data_len );
909 39 : }
910 :
911 69 : int current_has_delegation = stake_state && stake_state->stake_type==FD_STAKE_STATE_STAKE;
912 69 : int prior_has_delegation = prior_stake_state && prior_stake_state->stake_type==FD_STAKE_STATE_STAKE;
913 :
914 : /* If the current stake state isn't a delegation and it was a
915 : delegation in the previous stake state, insert a tombstone into the
916 : stake delegation's fork. */
917 69 : if( FD_UNLIKELY( !current_has_delegation ) ) {
918 39 : if( FD_LIKELY( !prior_has_delegation ) ) return; /* nothing to remove from */
919 6 : fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
920 6 : fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, pubkey );
921 6 : if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, NULL );
922 6 : return;
923 39 : }
924 :
925 : /* Agave replaces the cached version of the account whenever the
926 : account changes. */
927 :
928 30 : int account_changed = acc->prior_lamports !=acc->lamports ||
929 30 : acc->prior_executable!=acc->executable ||
930 30 : acc->prior_data_len !=acc->data_len ||
931 30 : memcmp( acc->prior_owner, acc->owner, sizeof(fd_pubkey_t) );
932 30 : if( FD_LIKELY( !account_changed && acc->data_len ) ) {
933 3 : account_changed = !!memcmp( acc->prior_data, acc->data, acc->data_len );
934 3 : }
935 30 : if( FD_LIKELY( prior_has_delegation && !account_changed ) ) return;
936 :
937 30 : fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
938 30 : if( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) ) {
939 18 : fd_stake_history_t stake_history_[1];
940 18 : fd_stake_history_t const * stake_history = fd_sysvar_cache_stake_history_view( &bank->f.sysvar_cache, stake_history_ );
941 18 : int use_fp_stake_math = FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 );
942 18 : fd_delegation_t const * delegation = &stake_state->stake.stake.delegation;
943 18 : if( FD_UNLIKELY( fd_delegation_is_inactive( delegation, bank->f.epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch, use_fp_stake_math ) &&
944 18 : fd_delegation_is_inactive( delegation, fd_ulong_sat_sub( bank->f.epoch, 1UL ), stake_history, &bank->f.warmup_cooldown_rate_epoch, use_fp_stake_math ) ) ) {
945 6 : fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, pubkey );
946 6 : if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, NULL );
947 6 : return;
948 6 : }
949 18 : }
950 :
951 24 : ulong new_stake = stake_state->stake.stake.delegation.stake;
952 24 : fd_stake_delegations_fork_update( stake_delegations, bank->stake_delegations_fork_id, pubkey,
953 24 : &stake_state->stake.stake.delegation.voter_pubkey,
954 24 : new_stake,
955 24 : stake_state->stake.stake.delegation.activation_epoch,
956 24 : stake_state->stake.stake.delegation.deactivation_epoch,
957 24 : stake_state->stake.stake.credits_observed,
958 24 : acc->lamports,
959 24 : (uint)acc->data_len,
960 24 : fd_stake_warmup_cooldown_rate( bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch ) );
961 24 : if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, stake_state );
962 24 : }
|