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