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