Line data Source code
1 : #include <limits.h>
2 :
3 : #include "fd_stakes.h"
4 : #include "../runtime/fd_bank.h"
5 : #include "../runtime/program/vote/fd_vote_state_versioned.h"
6 : #include "../runtime/program/vote/fd_vote_codec.h"
7 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
8 : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
9 : #include "../runtime/program/fd_vote_program.h"
10 : #include "../runtime/fd_runtime_stack.h"
11 : #include "../runtime/fd_system_ids.h"
12 : #include "fd_stake_delegations.h"
13 : #include "../accdb/fd_accdb_sync.h"
14 : #include "../../util/bits/fd_sat.h"
15 : #include "fd_stake_types.h"
16 : #include "fd_top_votes.h"
17 :
18 : /**********************************************************************/
19 : /* Constants */
20 : /**********************************************************************/
21 :
22 : #define DEFAULT_SLASH_PENALTY ( 12 )
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 : static inline double
39 0 : warmup_cooldown_rate( ulong current_epoch, ulong * new_rate_activation_epoch ) {
40 0 : return fd_stake_delegations_warmup_cooldown_rate_to_double(
41 0 : fd_stake_warmup_cooldown_rate( current_epoch, new_rate_activation_epoch ) );
42 0 : }
43 :
44 : // https://github.com/anza-xyz/agave/blob/c8685ce0e1bb9b26014f1024de2cd2b8c308cbde/sdk/program/src/stake/state.rs#L728
45 : static effective_activating_t
46 : stake_and_activating( fd_delegation_t const * self,
47 : ulong target_epoch,
48 : fd_stake_history_t const * history,
49 597 : ulong * new_rate_activation_epoch ) {
50 597 : ulong delegated_stake = self->stake;
51 :
52 597 : fd_stake_history_entry_t const * cluster_stake_at_activation_epoch;
53 597 : if( self->activation_epoch==ULONG_MAX ) {
54 234 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
55 363 : } else if( self->activation_epoch==self->deactivation_epoch ) {
56 282 : return ( effective_activating_t ){ .effective = 0, .activating = 0 };
57 282 : } else if( target_epoch==self->activation_epoch ) {
58 6 : return ( effective_activating_t ){ .effective = 0, .activating = delegated_stake };
59 75 : } else if( target_epoch<self->activation_epoch ) {
60 18 : return ( effective_activating_t ){ .effective = 0, .activating = 0 };
61 57 : } else if( history &&
62 57 : ( cluster_stake_at_activation_epoch = fd_sysvar_stake_history_query(
63 48 : history, self->activation_epoch ) ) ) {
64 3 : ulong prev_epoch = self->activation_epoch;
65 3 : fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_activation_epoch;
66 :
67 3 : ulong current_epoch;
68 3 : ulong current_effective_stake = 0;
69 3 : for( ;; ) {
70 3 : current_epoch = prev_epoch + 1;
71 3 : if( FD_LIKELY( prev_cluster_stake->activating==0 ) ) {
72 3 : break;
73 3 : }
74 :
75 0 : ulong remaining_activating_stake = delegated_stake - current_effective_stake;
76 0 : double weight = (double)remaining_activating_stake / (double)prev_cluster_stake->activating;
77 0 : double warmup_cooldown_rate_ =
78 0 : warmup_cooldown_rate( current_epoch, new_rate_activation_epoch );
79 :
80 0 : double newly_effective_cluster_stake =
81 0 : (double)prev_cluster_stake->effective * warmup_cooldown_rate_;
82 0 : ulong newly_effective_stake =
83 0 : fd_ulong_max( fd_rust_cast_double_to_ulong( weight * newly_effective_cluster_stake ), 1 );
84 :
85 0 : current_effective_stake += newly_effective_stake;
86 0 : if( FD_LIKELY( current_effective_stake>=delegated_stake ) ) {
87 0 : current_effective_stake = delegated_stake;
88 0 : break;
89 0 : }
90 :
91 0 : if( FD_LIKELY( current_epoch>=target_epoch ||
92 0 : current_epoch>=self->deactivation_epoch ) ) {
93 0 : break;
94 0 : }
95 :
96 0 : fd_stake_history_entry_t const * current_cluster_stake =
97 0 : fd_sysvar_stake_history_query( history, current_epoch );
98 0 : if( FD_LIKELY( current_cluster_stake ) ) {
99 0 : prev_epoch = current_epoch;
100 0 : prev_cluster_stake = current_cluster_stake;
101 0 : } else {
102 0 : break;
103 0 : }
104 0 : }
105 3 : return ( effective_activating_t ){ .effective = current_effective_stake,
106 3 : .activating = delegated_stake - current_effective_stake };
107 54 : } else {
108 54 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
109 54 : }
110 597 : }
111 :
112 : // https://github.com/anza-xyz/agave/blob/c8685ce0e1bb9b26014f1024de2cd2b8c308cbde/sdk/program/src/stake/state.rs#L641
113 : fd_stake_history_entry_t
114 : stake_activating_and_deactivating( fd_delegation_t const * self,
115 : ulong target_epoch,
116 : fd_stake_history_t const * stake_history,
117 597 : ulong * new_rate_activation_epoch ) {
118 :
119 597 : effective_activating_t effective_activating =
120 597 : stake_and_activating( self, target_epoch, stake_history, new_rate_activation_epoch );
121 :
122 597 : ulong effective_stake = effective_activating.effective;
123 597 : ulong activating_stake = effective_activating.activating;
124 :
125 597 : fd_stake_history_entry_t const * cluster_stake_at_deactivation_epoch = NULL;
126 :
127 597 : if( target_epoch<self->deactivation_epoch ) {
128 375 : if( activating_stake==0 ) {
129 366 : return ( fd_stake_history_entry_t ){
130 366 : .effective = effective_stake, .deactivating = 0, .activating = 0 };
131 366 : } else {
132 9 : return ( fd_stake_history_entry_t ){
133 9 : .effective = effective_stake, .deactivating = 0, .activating = activating_stake };
134 9 : }
135 375 : } else if( target_epoch==self->deactivation_epoch ) {
136 0 : return ( fd_stake_history_entry_t ){
137 0 : .effective = effective_stake, .deactivating = effective_stake, .activating = 0 };
138 222 : } else if( stake_history &&
139 222 : ( cluster_stake_at_deactivation_epoch = fd_sysvar_stake_history_query( stake_history, self->deactivation_epoch ) ) ) {
140 0 : ulong prev_epoch = self->deactivation_epoch;
141 0 : fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_deactivation_epoch;
142 :
143 0 : ulong current_epoch;
144 0 : ulong current_effective_stake = effective_stake;
145 0 : for( ;; ) {
146 0 : current_epoch = prev_epoch + 1;
147 0 : if( prev_cluster_stake->deactivating==0 ) break;
148 :
149 0 : double weight = (double)current_effective_stake / (double)prev_cluster_stake->deactivating;
150 0 : double warmup_cooldown_rate_ =
151 0 : warmup_cooldown_rate( current_epoch, new_rate_activation_epoch );
152 :
153 0 : double newly_not_effective_cluster_stake =
154 0 : (double)prev_cluster_stake->effective * warmup_cooldown_rate_;
155 0 : ulong newly_not_effective_stake =
156 0 : fd_ulong_max( fd_rust_cast_double_to_ulong( weight * newly_not_effective_cluster_stake ), 1 );
157 :
158 0 : current_effective_stake =
159 0 : fd_ulong_sat_sub( current_effective_stake, newly_not_effective_stake );
160 0 : if( current_effective_stake==0 ) break;
161 :
162 0 : if( current_epoch>=target_epoch ) break;
163 :
164 0 : fd_stake_history_entry_t const * current_cluster_stake = NULL;
165 0 : if( ( current_cluster_stake = fd_sysvar_stake_history_query(stake_history, current_epoch ) ) ) {
166 0 : prev_epoch = current_epoch;
167 0 : prev_cluster_stake = current_cluster_stake;
168 0 : } else {
169 0 : break;
170 0 : }
171 0 : }
172 0 : return ( fd_stake_history_entry_t ){ .effective = current_effective_stake,
173 0 : .deactivating = current_effective_stake,
174 0 : .activating = 0 };
175 222 : } else {
176 222 : return ( fd_stake_history_entry_t ){ .effective = 0, .activating = 0, .deactivating = 0 };
177 222 : }
178 597 : }
179 :
180 : /**********************************************************************/
181 : /* Public API */
182 : /**********************************************************************/
183 :
184 : fd_stake_state_t const *
185 : fd_stake_state_view( uchar const * data,
186 138 : ulong data_sz ) {
187 138 : if( FD_UNLIKELY( data_sz<4UL ) ) return NULL;
188 138 : uint stake_type = FD_LOAD( uint, data );
189 138 : switch( stake_type ) {
190 0 : case FD_STAKE_STATE_UNINITIALIZED:
191 0 : break;
192 0 : case FD_STAKE_STATE_INITIALIZED:
193 0 : if( FD_UNLIKELY( data_sz<124 ) ) return NULL;
194 0 : break;
195 138 : case FD_STAKE_STATE_STAKE:
196 138 : if( FD_UNLIKELY( data_sz<197 ) ) return NULL;
197 138 : break;
198 138 : case FD_STAKE_STATE_REWARDS_POOL:
199 0 : break;
200 0 : default:
201 0 : return NULL;
202 138 : }
203 138 : return fd_type_pun_const( data );
204 138 : }
205 :
206 : fd_stake_state_t const *
207 72 : fd_stakes_get_state( fd_account_meta_t const * meta ) {
208 72 : if( FD_UNLIKELY( 0!=memcmp( meta->owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) ) {
209 3 : return NULL;
210 3 : }
211 69 : if( FD_UNLIKELY( meta->lamports==0UL ) ) return NULL;
212 69 : return fd_stake_state_view( fd_account_data( meta ), meta->dlen );
213 69 : }
214 :
215 : fd_stake_history_entry_t
216 : fd_stakes_activating_and_deactivating( fd_stake_delegation_t const * stake_delegation,
217 : ulong target_epoch,
218 : fd_stake_history_t const * stake_history,
219 597 : ulong * new_rate_activation_epoch ) {
220 597 : fd_delegation_t delegation = {
221 597 : .voter_pubkey = stake_delegation->vote_account,
222 597 : .stake = stake_delegation->stake,
223 597 : .deactivation_epoch = stake_delegation->deactivation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->deactivation_epoch,
224 597 : .activation_epoch = stake_delegation->activation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->activation_epoch,
225 597 : .warmup_cooldown_rate = fd_stake_delegations_warmup_cooldown_rate_to_double( stake_delegation->warmup_cooldown_rate ),
226 597 : };
227 :
228 597 : return stake_activating_and_deactivating(
229 597 : &delegation, target_epoch, stake_history, new_rate_activation_epoch );
230 597 : }
231 :
232 : ulong
233 : fd_stake_weights_by_node( fd_top_votes_t const * top_votes_t_2,
234 : fd_vote_stakes_t * vote_stakes,
235 : ushort fork_idx,
236 : fd_vote_stake_weight_t * weights,
237 129 : int vat_enabled ) {
238 129 : ulong weights_cnt = 0;
239 129 : if( vat_enabled ) {
240 0 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
241 0 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_2, iter_mem );
242 0 : !fd_top_votes_iter_done( top_votes_t_2, iter );
243 0 : fd_top_votes_iter_next( top_votes_t_2, iter ) ) {
244 0 : fd_pubkey_t pubkey;
245 0 : ulong stake_t_2;
246 0 : fd_pubkey_t node_account_t_2;
247 0 : fd_top_votes_iter_ele( top_votes_t_2, iter, &pubkey, &node_account_t_2, &stake_t_2, NULL, NULL, NULL );
248 :
249 0 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
250 0 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_2, sizeof(fd_pubkey_t) );
251 0 : weights[ weights_cnt ].stake = stake_t_2;
252 0 : weights_cnt++;
253 0 : }
254 129 : } else {
255 129 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
256 129 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_fork_iter_init( vote_stakes, fork_idx, iter_mem );
257 264 : !fd_vote_stakes_fork_iter_done( vote_stakes, fork_idx, iter );
258 135 : fd_vote_stakes_fork_iter_next( vote_stakes, fork_idx, iter ) ) {
259 135 : fd_pubkey_t pubkey;
260 135 : ulong stake_t_2;
261 135 : fd_pubkey_t node_account_t_2;
262 135 : fd_vote_stakes_fork_iter_ele( vote_stakes, fork_idx, iter, &pubkey, NULL, &stake_t_2, NULL, &node_account_t_2, NULL, NULL );
263 135 : if( FD_UNLIKELY( !stake_t_2 ) ) continue;
264 :
265 135 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
266 135 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_2, sizeof(fd_pubkey_t) );
267 135 : weights[ weights_cnt ].stake = stake_t_2;
268 135 : weights_cnt++;
269 135 : }
270 129 : fd_vote_stakes_fork_iter_fini( vote_stakes );
271 129 : }
272 :
273 129 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
274 :
275 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/leader-schedule/src/lib.rs#L80-L83
276 : We do not deduplicate the weights here, unlike Agave, as it is
277 : guaranteed there will be no duplicate stake entries for a given fork
278 : in the stakes map. */
279 :
280 129 : return weights_cnt;
281 129 : }
282 :
283 : ulong
284 : fd_stake_weights_by_node_next( fd_top_votes_t const * top_votes_t_1,
285 : fd_vote_stakes_t * vote_stakes,
286 : ushort fork_idx,
287 : fd_vote_stake_weight_t * weights,
288 0 : int vat_enabled ) {
289 :
290 0 : ulong weights_cnt = 0;
291 0 : if( vat_enabled ) {
292 0 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
293 0 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_1, iter_mem );
294 0 : !fd_top_votes_iter_done( top_votes_t_1, iter );
295 0 : fd_top_votes_iter_next( top_votes_t_1, iter ) ) {
296 0 : fd_pubkey_t pubkey;
297 0 : ulong stake_t_1;
298 0 : fd_pubkey_t node_account_t_1;
299 0 : fd_top_votes_iter_ele( top_votes_t_1, iter, &pubkey, &node_account_t_1, &stake_t_1, NULL, NULL, NULL );
300 :
301 0 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
302 0 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_1, sizeof(fd_pubkey_t) );
303 0 : weights[ weights_cnt ].stake = stake_t_1;
304 0 : weights_cnt++;
305 0 : }
306 0 : } else {
307 0 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
308 0 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_fork_iter_init( vote_stakes, fork_idx, iter_mem );
309 0 : !fd_vote_stakes_fork_iter_done( vote_stakes, fork_idx, iter );
310 0 : fd_vote_stakes_fork_iter_next( vote_stakes, fork_idx, iter ) ) {
311 :
312 0 : fd_pubkey_t pubkey;
313 0 : ulong stake_t_1;
314 0 : fd_pubkey_t node_account_t_1;
315 0 : fd_vote_stakes_fork_iter_ele( vote_stakes, fork_idx, iter, &pubkey, &stake_t_1, NULL, &node_account_t_1, NULL, NULL, NULL );
316 0 : if( FD_UNLIKELY( !stake_t_1 ) ) continue;
317 :
318 0 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
319 0 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_1, sizeof(fd_pubkey_t) );
320 0 : weights[ weights_cnt ].stake = stake_t_1;
321 0 : weights_cnt++;
322 0 : }
323 0 : fd_vote_stakes_fork_iter_fini( vote_stakes );
324 0 : }
325 :
326 0 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
327 :
328 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/leader-schedule/src/lib.rs#L80-L83
329 : We do not deduplicate the weights here, unlike Agave, as it is
330 : guaranteed there will be no duplicate stake entries for a given fork
331 : in the stakes map. */
332 :
333 0 : return weights_cnt;
334 0 : }
335 :
336 : static void
337 : get_vote_credits( uchar const * account_data,
338 : ulong account_data_len,
339 135 : fd_epoch_credits_t * epoch_credits ) {
340 :
341 135 : fd_vote_epoch_credits_t const * vote_epoch_credits = fd_vote_account_epoch_credits( account_data, account_data_len, &epoch_credits->cnt );
342 135 : FD_TEST( vote_epoch_credits );
343 :
344 135 : ulong base = epoch_credits->cnt ? vote_epoch_credits[0].prev_credits : 0UL;
345 168 : for( ulong i=0UL; i<epoch_credits->cnt; i++ ) {
346 33 : fd_vote_epoch_credits_t const * ele = &vote_epoch_credits[ i ];
347 33 : epoch_credits->epoch[ i ] = (ushort)ele->epoch;
348 33 : epoch_credits->credits_delta[ i ] = (uint)( ele->credits - base );
349 33 : epoch_credits->prev_credits_delta[ i ] = (uint)( ele->prev_credits - base );
350 33 : }
351 :
352 135 : epoch_credits->base_credits = base;
353 135 : }
354 :
355 : static void
356 : fd_refresh_vote_accounts_vat( fd_bank_t * bank,
357 : fd_accdb_user_t * accdb,
358 : fd_funk_txn_xid_t const * xid,
359 : fd_runtime_stack_t * runtime_stack,
360 : fd_stake_delegations_t const * stake_delegations,
361 : fd_stake_history_t const * history,
362 0 : ulong * new_rate_activation_epoch ) {
363 :
364 0 : fd_top_votes_t * top_votes_t_1 = fd_bank_top_votes_t_1_modify( bank );
365 0 : fd_top_votes_t * top_votes_t_2 = fd_bank_top_votes_t_2_modify( bank );
366 :
367 0 : uchar __attribute__((aligned(FD_TOP_VOTES_ALIGN))) top_votes_t_3_mem[ FD_TOP_VOTES_MAX_FOOTPRINT ];
368 :
369 : /* Copy over the old t-2 top votes into a temporary t-3 buffer. Copy
370 : over the old t-1 top votes to the t-2 top votes. Reset the
371 : existing t-1 top votes to prepare it for insertion. Handle the
372 : transition to the next epoch. */
373 0 : fd_memcpy( top_votes_t_3_mem, top_votes_t_2, FD_TOP_VOTES_MAX_FOOTPRINT );
374 0 : fd_memcpy( top_votes_t_2, top_votes_t_1, FD_TOP_VOTES_MAX_FOOTPRINT );
375 0 : fd_top_votes_init( top_votes_t_1 );
376 0 : fd_top_votes_t * top_votes_t_3 = fd_type_pun( top_votes_t_3_mem );
377 :
378 0 : fd_stake_accum_map_reset( runtime_stack->stakes.stake_accum_map );
379 0 : ulong epoch = bank->f.epoch;
380 0 : ulong total_stake = 0UL;
381 0 : ulong total_activating = 0UL;
382 0 : ulong total_deactivating = 0UL;
383 0 : ulong staked_accounts = 0UL;
384 :
385 0 : fd_stake_accum_t * stake_accum_pool = runtime_stack->stakes.stake_accum;
386 0 : fd_stake_accum_map_t * stake_accum_map = runtime_stack->stakes.stake_accum_map;
387 :
388 : /* Accumulate stakes across all delegations for all vote accounts. */
389 0 : fd_stake_delegations_iter_t iter_[1];
390 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
391 0 : !fd_stake_delegations_iter_done( iter );
392 0 : fd_stake_delegations_iter_next( iter ) ) {
393 :
394 0 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
395 :
396 0 : fd_stake_history_entry_t new_entry = fd_stakes_activating_and_deactivating(
397 0 : stake_delegation,
398 0 : epoch,
399 0 : history,
400 0 : new_rate_activation_epoch );
401 0 : total_stake += new_entry.effective;
402 0 : total_activating += new_entry.activating;
403 0 : total_deactivating += new_entry.deactivating;
404 :
405 0 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_ele_query( stake_accum_map, &stake_delegation->vote_account, NULL, stake_accum_pool );
406 0 : if( FD_UNLIKELY( !stake_accum ) ) {
407 0 : if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_vote_accounts ) ) {
408 0 : FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
409 0 : }
410 0 : stake_accum = &runtime_stack->stakes.stake_accum[ staked_accounts ];
411 0 : stake_accum->pubkey = stake_delegation->vote_account;
412 0 : stake_accum->stake = new_entry.effective;
413 0 : fd_stake_accum_map_ele_insert( stake_accum_map, stake_accum, stake_accum_pool );
414 0 : staked_accounts++;
415 0 : } else {
416 0 : stake_accum->stake += new_entry.effective;
417 0 : }
418 0 : }
419 :
420 : /* Only update total_*_stake at the epoch boundary. These values
421 : are snapshots of the stake totals for the current epoch. */
422 0 : bank->f.total_activating_stake = total_activating;
423 0 : bank->f.total_deactivating_stake = total_deactivating;
424 0 : bank->f.total_effective_stake = total_stake;
425 :
426 : /* Iterate over the valid delegated vote accounts and insert them into
427 : the top votes set for the t-1 epoch. */
428 :
429 0 : for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( stake_accum_map, stake_accum_pool );
430 0 : !fd_stake_accum_map_iter_done( iter, stake_accum_map, stake_accum_pool );
431 0 : iter = fd_stake_accum_map_iter_next( iter, stake_accum_map, stake_accum_pool ) ) {
432 0 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_iter_ele( iter, stake_accum_map, stake_accum_pool );
433 :
434 0 : fd_pubkey_t node_account_t_1 = {0};
435 0 : ulong stake_t_1 = stake_accum->stake;
436 0 : uchar commission_t_1 = 0;
437 :
438 0 : fd_accdb_ro_t vote_ro[1];
439 : /* Agave's VAT filter also checks lamports against the VoteStateV4
440 : rent-exempt minimum. */
441 0 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, vote_ro, xid, &stake_accum->pubkey ) ) ) {
442 0 : continue;
443 0 : }
444 0 : ulong vote_account_lamports = vote_ro->meta->lamports;
445 0 : ulong vote_account_rent_exempt_minimum = fd_rent_exempt_minimum_balance( &bank->f.rent, FD_VOTE_STATE_V4_SZ );
446 0 : if( FD_UNLIKELY( vote_account_lamports < vote_account_rent_exempt_minimum ) ) {
447 0 : fd_accdb_close_ro( accdb, vote_ro );
448 0 : continue;
449 0 : }
450 0 : if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( vote_ro->meta ) ||
451 0 : !fd_vote_account_is_v4_with_bls_pubkey( fd_account_data( vote_ro->meta ), vote_ro->meta->dlen ) ) ) {
452 0 : fd_accdb_close_ro( accdb, vote_ro );
453 0 : continue;
454 0 : }
455 :
456 0 : FD_TEST( !fd_vote_account_commission( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), &commission_t_1 ) );
457 0 : FD_TEST( !fd_vote_account_node_pubkey( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), &node_account_t_1 ) );
458 :
459 0 : fd_top_votes_insert( top_votes_t_1, &stake_accum->pubkey, &node_account_t_1, stake_t_1, commission_t_1 );
460 0 : fd_accdb_close_ro( accdb, vote_ro );
461 0 : }
462 :
463 : /* Seed status for the t-2 top votes set for clock calculation. */
464 0 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) top_votes_iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
465 0 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_2, top_votes_iter_mem );
466 0 : !fd_top_votes_iter_done( top_votes_t_2, iter );
467 0 : fd_top_votes_iter_next( top_votes_t_2, iter ) ) {
468 0 : fd_pubkey_t pubkey;
469 0 : uchar commission_t_2;
470 0 : fd_top_votes_iter_ele( top_votes_t_2, iter, &pubkey, NULL, NULL, &commission_t_2, NULL, NULL );
471 :
472 0 : fd_accdb_ro_t vote_ro[1];
473 0 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, vote_ro, xid, &pubkey ) ) ) {
474 0 : fd_top_votes_invalidate( top_votes_t_2, &pubkey );
475 0 : continue;
476 0 : }
477 0 : if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( vote_ro->meta ) ) ) {
478 0 : fd_top_votes_invalidate( top_votes_t_2, &pubkey );
479 0 : fd_accdb_close_ro( accdb, vote_ro );
480 0 : continue;
481 0 : }
482 :
483 0 : fd_vote_block_timestamp_t last_vote;
484 0 : FD_TEST( !fd_vote_account_last_timestamp( fd_account_data( vote_ro->meta ), vote_ro->meta->dlen, &last_vote ) );
485 0 : fd_top_votes_update( top_votes_t_2, &pubkey, last_vote.slot, last_vote.timestamp );
486 0 : fd_accdb_close_ro( accdb, vote_ro );
487 0 : }
488 :
489 : /* Populate the vote rewards map with the final set of filtered vote
490 : accounts. */
491 0 : fd_vote_rewards_map_t * vote_reward_map = runtime_stack->stakes.vote_map;
492 0 : fd_vote_rewards_map_reset( vote_reward_map );
493 0 : ulong vote_reward_cnt = 0UL;
494 :
495 : /* If VAT feature has just been activated, we want to reference the
496 : t-2/t-3 commissions from the vote stakes and not the top votes. */
497 0 : ulong vat_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.features.validator_admission_ticket, NULL );
498 0 : int vat_in_t_2 = bank->f.epoch>vat_epoch;
499 0 : int vat_in_t_3 = fd_ulong_sat_sub(bank->f.epoch, 1UL )>vat_epoch;
500 :
501 0 : ushort parent_idx = bank->vote_stakes_fork_id;
502 0 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
503 :
504 : /* Populate the vote rewards map with the final set of filtered vote
505 : accounts for the t-1 epoch. */
506 0 : bank->f.total_epoch_stake = 0UL;
507 0 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_1, top_votes_iter_mem );
508 0 : !fd_top_votes_iter_done( top_votes_t_1, iter );
509 0 : fd_top_votes_iter_next( top_votes_t_1, iter ) ) {
510 0 : fd_pubkey_t pubkey;
511 0 : ulong stake;
512 0 : uchar commission_t_1 = 0;
513 0 : fd_top_votes_iter_ele( top_votes_t_1, iter, &pubkey, NULL, &stake, &commission_t_1, NULL, NULL );
514 :
515 0 : int exists_t_3 = 0;
516 0 : uchar commission_t_3 = 0;
517 0 : if( FD_LIKELY( vat_in_t_3 ) ) {
518 0 : exists_t_3 = fd_top_votes_query( top_votes_t_3, &pubkey, NULL, NULL, NULL, NULL, &commission_t_3 );
519 0 : } else {
520 0 : exists_t_3 = fd_vote_stakes_query_t_2( vote_stakes, parent_idx, &pubkey, NULL, NULL, &commission_t_3 );
521 0 : }
522 :
523 0 : int exists_t_2 = 0;
524 0 : uchar commission_t_2 = 0;
525 0 : if( FD_LIKELY( vat_in_t_2 ) ) {
526 0 : exists_t_2 = fd_top_votes_query( top_votes_t_2, &pubkey, NULL, NULL, NULL, NULL, &commission_t_2 );
527 0 : } else {
528 0 : exists_t_2 = fd_vote_stakes_query_t_1( vote_stakes, parent_idx, &pubkey, NULL, NULL, &commission_t_2 );
529 0 : }
530 :
531 0 : fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
532 0 : vote_ele->pubkey = pubkey;
533 0 : vote_ele->vote_rewards = 0UL;
534 0 : if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
535 0 : vote_ele->commission = exists_t_3 ? commission_t_3 : (exists_t_2 ? commission_t_2 : commission_t_1);
536 0 : } else {
537 0 : vote_ele->commission = commission_t_1;
538 0 : }
539 :
540 0 : fd_accdb_ro_t vote_ro[1];
541 0 : FD_TEST( fd_accdb_open_ro( accdb, vote_ro, xid, &pubkey ) );
542 0 : fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[ vote_reward_cnt ];
543 0 : get_vote_credits( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), epoch_credits );
544 0 : fd_accdb_close_ro( accdb, vote_ro );
545 :
546 0 : fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
547 0 : vote_reward_cnt++;
548 0 : bank->f.total_epoch_stake += stake;
549 0 : }
550 0 : *fd_bank_epoch_credits_len( bank ) = vote_reward_cnt;
551 0 : }
552 :
553 : static void
554 : fd_refresh_vote_accounts_no_vat( fd_bank_t * bank,
555 : fd_accdb_user_t * accdb,
556 : fd_funk_txn_xid_t const * xid,
557 : fd_runtime_stack_t * runtime_stack,
558 : fd_stake_delegations_t const * stake_delegations,
559 : fd_stake_history_t const * history,
560 129 : ulong * new_rate_activation_epoch ) {
561 129 : fd_vote_rewards_map_t * vote_reward_map = runtime_stack->stakes.vote_map;
562 129 : fd_vote_rewards_map_reset( vote_reward_map );
563 129 : ulong vote_reward_cnt = 0UL;
564 :
565 : /* First accumulate stakes across all delegations for all vote
566 : accounts. At this point, don't care if they are valid accounts or
567 : if they will be inserted into the top votes set. */
568 :
569 129 : fd_stake_accum_t * stake_accum_pool = runtime_stack->stakes.stake_accum;
570 129 : fd_stake_accum_map_t * stake_accum_map = runtime_stack->stakes.stake_accum_map;
571 :
572 129 : ushort parent_idx = bank->vote_stakes_fork_id;
573 :
574 129 : fd_stake_accum_map_reset( runtime_stack->stakes.stake_accum_map );
575 129 : ulong epoch = bank->f.epoch;
576 129 : ulong total_stake = 0UL;
577 129 : ulong total_activating = 0UL;
578 129 : ulong total_deactivating = 0UL;
579 129 : ulong staked_accounts = 0UL;
580 :
581 : /* Seed stake_accum_map with all vote accounts from the parent fork
582 : with zero stake. The delegation loop below will update the stake
583 : for any account that has active delegations. This needs to be done
584 : because zero-staked, active vote accounts have their historical
585 : commission tracked for payouts. */
586 :
587 129 : fd_vote_stakes_t * vs = fd_bank_vote_stakes( bank );
588 129 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem_vs[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
589 129 : for( fd_vote_stakes_iter_t * vs_iter = fd_vote_stakes_fork_iter_init( vs, parent_idx, iter_mem_vs );
590 264 : !fd_vote_stakes_fork_iter_done( vs, parent_idx, vs_iter );
591 135 : fd_vote_stakes_fork_iter_next( vs, parent_idx, vs_iter ) ) {
592 135 : fd_pubkey_t vs_pubkey;
593 135 : fd_vote_stakes_fork_iter_ele( vs, parent_idx, vs_iter, &vs_pubkey, NULL, NULL, NULL, NULL, NULL, NULL );
594 135 : if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_vote_accounts ) ) {
595 0 : FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
596 0 : }
597 135 : fd_stake_accum_t * sa = &runtime_stack->stakes.stake_accum[ staked_accounts ];
598 135 : if( !fd_stake_accum_map_ele_query( stake_accum_map, &vs_pubkey, NULL, stake_accum_pool ) ) {
599 135 : sa->pubkey = vs_pubkey;
600 135 : sa->stake = 0UL;
601 135 : fd_stake_accum_map_ele_insert( stake_accum_map, sa, stake_accum_pool );
602 135 : staked_accounts++;
603 135 : }
604 135 : }
605 129 : fd_vote_stakes_fork_iter_fini( vs );
606 :
607 : /* Add any pubkeys visible via new_votes that were not already seeded
608 : from the parent vote_stakes set. Some iterator entries may be
609 : tombstones: that is harmless here because a pubkey absent from both
610 : the current accdb view and the parent vote_stakes view is filtered
611 : out before insertion into the child vote_stakes. New vote accounts
612 : with zero staked must be tracked for historical commission
613 : lookups. */
614 :
615 129 : fd_new_votes_t * new_votes = fd_bank_new_votes( bank );
616 129 : ushort fork_indices[ FD_RUNTIME_MAX_FORK_CNT ];
617 129 : ulong forks_cnt = fd_banks_new_votes_fork_indices( bank, fork_indices );
618 :
619 129 : uchar __attribute__((aligned(FD_NEW_VOTES_ITER_ALIGN))) iter_mem[ FD_NEW_VOTES_ITER_FOOTPRINT ];
620 129 : fd_new_votes_iter_t * iter = fd_new_votes_iter_init( new_votes, fork_indices, forks_cnt, iter_mem );
621 129 : for( ; !fd_new_votes_iter_done( iter ); fd_new_votes_iter_next( iter ) ) {
622 0 : int is_tombstone = 0;
623 0 : fd_pubkey_t const * pubkey = fd_new_votes_iter_ele( iter, &is_tombstone );
624 0 : if( FD_UNLIKELY( is_tombstone ) ) continue;
625 :
626 0 : if( FD_LIKELY( !fd_stake_accum_map_ele_query( stake_accum_map, pubkey, NULL, stake_accum_pool ) ) ) {
627 0 : fd_stake_accum_t * sa = &runtime_stack->stakes.stake_accum[ staked_accounts ];
628 0 : sa->pubkey = *pubkey;
629 0 : sa->stake = 0UL;
630 0 : fd_stake_accum_map_ele_insert( stake_accum_map, sa, stake_accum_pool );
631 0 : staked_accounts++;
632 0 : }
633 0 : }
634 129 : fd_new_votes_iter_fini( iter );
635 :
636 : /* Now accumulate vote stakes for all stake delegations. */
637 :
638 129 : fd_stake_delegations_iter_t iter_[1];
639 129 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
640 264 : !fd_stake_delegations_iter_done( iter );
641 135 : fd_stake_delegations_iter_next( iter ) ) {
642 :
643 135 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
644 :
645 135 : fd_stake_history_entry_t new_entry = fd_stakes_activating_and_deactivating(
646 135 : stake_delegation,
647 135 : epoch,
648 135 : history,
649 135 : new_rate_activation_epoch );
650 135 : total_stake += new_entry.effective;
651 135 : total_activating += new_entry.activating;
652 135 : total_deactivating += new_entry.deactivating;
653 :
654 135 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_ele_query( stake_accum_map, &stake_delegation->vote_account, NULL, stake_accum_pool );
655 135 : if( FD_UNLIKELY( !stake_accum ) ) {
656 0 : if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_vote_accounts ) ) {
657 0 : FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
658 0 : }
659 0 : stake_accum = &runtime_stack->stakes.stake_accum[ staked_accounts ];
660 0 : stake_accum->pubkey = stake_delegation->vote_account;
661 0 : stake_accum->stake = new_entry.effective;
662 0 : fd_stake_accum_map_ele_insert( stake_accum_map, stake_accum, stake_accum_pool );
663 0 : staked_accounts++;
664 135 : } else {
665 135 : stake_accum->stake += new_entry.effective;
666 135 : }
667 135 : }
668 :
669 : /* Only update total_*_stake at the epoch boundary. These values
670 : are snapshots of the stake totals for the current epoch. */
671 129 : bank->f.total_activating_stake = total_activating;
672 129 : bank->f.total_deactivating_stake = total_deactivating;
673 129 : bank->f.total_effective_stake = total_stake;
674 :
675 : /* Copy the top votes set for the t-1 epoch into the t-2 epoch now
676 : that the epoch boundary is being crossed. Reset the existing t-1
677 : top votes set to prepare it for insertion. Refresh the states of
678 : the t-2 top votes set: figure out if the account still exists and
679 : what the last vote timestamp and slot are. */
680 :
681 129 : fd_top_votes_t * top_votes_t_1 = fd_bank_top_votes_t_1_modify( bank );
682 129 : fd_top_votes_t * top_votes_t_2 = fd_bank_top_votes_t_2_modify( bank );
683 129 : fd_memcpy( top_votes_t_2, top_votes_t_1, FD_TOP_VOTES_MAX_FOOTPRINT );
684 129 : fd_top_votes_init( top_votes_t_1 );
685 :
686 129 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) top_votes_iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
687 129 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_2, top_votes_iter_mem );
688 261 : !fd_top_votes_iter_done( top_votes_t_2, iter );
689 132 : fd_top_votes_iter_next( top_votes_t_2, iter ) ) {
690 132 : fd_pubkey_t pubkey;
691 132 : uchar commission_t_2;
692 132 : fd_top_votes_iter_ele( top_votes_t_2, iter, &pubkey, NULL, NULL, &commission_t_2, NULL, NULL );
693 :
694 132 : fd_accdb_ro_t vote_ro[1];
695 132 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, vote_ro, xid, &pubkey ) ) ) {
696 0 : fd_top_votes_invalidate( top_votes_t_2, &pubkey );
697 0 : continue;
698 0 : }
699 132 : if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( vote_ro->meta ) ) ) {
700 0 : fd_top_votes_invalidate( top_votes_t_2, &pubkey );
701 0 : fd_accdb_close_ro( accdb, vote_ro );
702 0 : continue;
703 0 : }
704 :
705 132 : fd_vote_block_timestamp_t last_vote;
706 132 : FD_TEST( !fd_vote_account_last_timestamp( fd_account_data( vote_ro->meta ), vote_ro->meta->dlen, &last_vote ) );
707 132 : fd_top_votes_update( top_votes_t_2, &pubkey, last_vote.slot, last_vote.timestamp );
708 132 : fd_accdb_close_ro( accdb, vote_ro );
709 132 : }
710 :
711 : /* Now for each staked vote account, figure out if it is a valid
712 : account and insert into the vote stakes (an account can not exist
713 : but still be inserted into the vote stakes if it existed in the
714 : previous epoch or vice versa). The only condition an account is
715 : not inserted into the vote stakes is if it didn't exist at the end
716 : of the t-2 epoch and the end of the t-1 epoch assuming we are
717 : transitioning into epoch t. */
718 :
719 129 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
720 129 : ushort child_idx = fd_vote_stakes_new_child( vote_stakes );
721 129 : bank->vote_stakes_fork_id = child_idx;
722 :
723 129 : bank->f.total_epoch_stake = 0UL;
724 129 : for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( stake_accum_map, stake_accum_pool );
725 264 : !fd_stake_accum_map_iter_done( iter, stake_accum_map, stake_accum_pool );
726 135 : iter = fd_stake_accum_map_iter_next( iter, stake_accum_map, stake_accum_pool ) ) {
727 135 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_iter_ele( iter, stake_accum_map, stake_accum_pool );
728 :
729 135 : fd_pubkey_t node_account_t_2 = {0};
730 135 : ulong stake_t_2 = 0UL;
731 135 : uchar commission_t_2 = 0;
732 135 : int exists_t_2 = fd_vote_stakes_query_t_1( vote_stakes, parent_idx, &stake_accum->pubkey, &stake_t_2, &node_account_t_2, &commission_t_2 );
733 :
734 135 : fd_pubkey_t node_account_t_1 = {0};
735 135 : ulong stake_t_1 = 0UL;
736 135 : uchar commission_t_1 = 0;
737 :
738 135 : fd_accdb_ro_t vote_ro[1];
739 135 : int exists_t_1 = 1;
740 135 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, vote_ro, xid, &stake_accum->pubkey ) ) ) {
741 0 : exists_t_1 = 0;
742 135 : } else if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( vote_ro->meta ) ) ) {
743 0 : exists_t_1 = 0;
744 0 : fd_accdb_close_ro( accdb, vote_ro );
745 135 : } else {
746 :
747 135 : FD_TEST( !fd_vote_account_commission( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), &commission_t_1 ) );
748 135 : FD_TEST( !fd_vote_account_node_pubkey( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), &node_account_t_1 ) );
749 :
750 135 : stake_t_1 = stake_accum->stake;
751 135 : bank->f.total_epoch_stake += stake_t_1;
752 :
753 135 : fd_pubkey_t node_account_t_3 = {0};
754 135 : ulong stake_t_3 = 0UL;
755 135 : uchar commission_t_3 = 0;
756 135 : int exists_t_3 = fd_vote_stakes_query_t_2( vote_stakes, parent_idx, &stake_accum->pubkey, &stake_t_3, &node_account_t_3, &commission_t_3 );
757 :
758 135 : fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[ vote_reward_cnt ];
759 135 : get_vote_credits( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), epoch_credits );
760 135 : fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
761 135 : vote_ele->pubkey = stake_accum->pubkey;
762 135 : vote_ele->vote_rewards = 0UL;
763 135 : if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
764 0 : vote_ele->commission = exists_t_3 ? commission_t_3 : (exists_t_2 ? commission_t_2 : commission_t_1);
765 135 : } else {
766 135 : vote_ele->commission = commission_t_1;
767 135 : }
768 135 : fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
769 135 : vote_reward_cnt++;
770 :
771 135 : fd_top_votes_insert( top_votes_t_1, &stake_accum->pubkey, &node_account_t_1, stake_t_1, commission_t_1 );
772 135 : fd_accdb_close_ro( accdb, vote_ro );
773 135 : }
774 :
775 135 : if( FD_UNLIKELY( !exists_t_1 && !exists_t_2 ) ) continue;
776 135 : fd_vote_stakes_insert(
777 135 : vote_stakes, child_idx, &stake_accum->pubkey,
778 135 : &node_account_t_1, &node_account_t_2,
779 135 : stake_t_1, stake_t_2,
780 135 : commission_t_1, commission_t_2,
781 135 : (uchar)exists_t_1, (uchar)exists_t_2,
782 135 : bank->f.epoch );
783 135 : }
784 129 : *fd_bank_epoch_credits_len( bank ) = vote_reward_cnt;
785 129 : }
786 :
787 : /* We need to update the amount of stake that each vote account has for
788 : the given epoch. This can only be done after the stake history
789 : sysvar has been updated. We also cache the stakes for each of the
790 : vote accounts for the previous epoch.
791 :
792 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L471 */
793 : void
794 : fd_refresh_vote_accounts( fd_bank_t * bank,
795 : fd_accdb_user_t * accdb,
796 : fd_funk_txn_xid_t const * xid,
797 : fd_runtime_stack_t * runtime_stack,
798 : fd_stake_delegations_t const * stake_delegations,
799 : fd_stake_history_t const * history,
800 129 : ulong * new_rate_activation_epoch ) {
801 : /* If validator_admission_ticket is enabled, the top 2000 vote
802 : accounts for every epoch (the agave epoch stakes), are stored in
803 : the top votes set. If the feature is not active, there is no
804 : stake-based filtering on the vote accounts that are eligible for
805 : receving rewards/being included in the leader schedule computation.
806 : Once the feature is active, only the top vote accounts will be
807 : tracked for historical stake/node_account/commission lookups.
808 : The non vat code path uses the vote stakes data structure as it
809 : considers all vote/stake accounts. */
810 129 : if( FD_FEATURE_ACTIVE_BANK( bank, validator_admission_ticket ) ) {
811 0 : fd_refresh_vote_accounts_vat( bank, accdb, xid, runtime_stack, stake_delegations, history, new_rate_activation_epoch );
812 129 : } else {
813 129 : fd_refresh_vote_accounts_no_vat( bank, accdb, xid, runtime_stack, stake_delegations, history, new_rate_activation_epoch );
814 129 : }
815 129 : }
816 :
817 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L280 */
818 : void
819 : fd_stakes_activate_epoch( fd_bank_t * bank,
820 : fd_runtime_stack_t * runtime_stack,
821 : fd_accdb_user_t * accdb,
822 : fd_funk_txn_xid_t const * xid,
823 : fd_capture_ctx_t * capture_ctx,
824 : fd_stake_delegations_t const * stake_delegations,
825 129 : ulong * new_rate_activation_epoch ) {
826 :
827 : /* We can update our stake history sysvar based on the bank stake values.
828 : Afterward, we can refresh the stake values for the vote accounts. */
829 :
830 129 : fd_stake_history_entry_t elem = {
831 129 : .epoch = bank->f.epoch,
832 129 : .effective = stake_delegations->effective_stake,
833 129 : .activating = stake_delegations->activating_stake,
834 129 : .deactivating = stake_delegations->deactivating_stake,
835 129 : };
836 129 : fd_sysvar_stake_history_update( bank, accdb, xid, capture_ctx, &elem );
837 :
838 129 : fd_accdb_ro_t sh_ro[1];
839 129 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, sh_ro, xid, &fd_sysvar_stake_history_id ) ) ) {
840 0 : FD_LOG_ERR(( "StakeHistory sysvar is missing" ));
841 0 : }
842 129 : fd_stake_history_t stake_history[1];
843 129 : if( FD_UNLIKELY( !fd_sysvar_stake_history_view( stake_history, fd_accdb_ref_data_const( sh_ro ), fd_accdb_ref_data_sz( sh_ro ) ) ) ) {
844 0 : FD_LOG_HEXDUMP_ERR(( "Invalid StakeHistory sysvar", fd_accdb_ref_data_const( sh_ro ), fd_accdb_ref_data_sz( sh_ro ) ));
845 0 : }
846 :
847 : /* Now increment the epoch and recompute the stakes for the vote
848 : accounts for the new epoch value. */
849 :
850 129 : bank->f.epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.slot, NULL );
851 :
852 129 : fd_refresh_vote_accounts( bank,
853 129 : accdb,
854 129 : xid,
855 129 : runtime_stack,
856 129 : stake_delegations,
857 129 : stake_history,
858 129 : new_rate_activation_epoch );
859 :
860 129 : fd_accdb_close_ro( accdb, sh_ro );
861 129 : }
862 :
863 :
864 : void
865 : fd_stakes_update_stake_delegation( fd_pubkey_t const * pubkey,
866 : fd_account_meta_t const * meta,
867 3 : fd_bank_t * bank ) {
868 :
869 3 : fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
870 :
871 : /* fd_stakes_get_state returns NULL for closed/invalid accounts. */
872 3 : fd_stake_state_t const * stake_state = fd_stakes_get_state( meta );
873 3 : if( FD_LIKELY( stake_state != NULL &&
874 3 : stake_state->stake_type == FD_STAKE_STATE_STAKE &&
875 3 : stake_state->stake.stake.delegation.stake != 0UL ) ) {
876 :
877 0 : ulong new_stake = stake_state->stake.stake.delegation.stake;
878 0 : fd_stake_delegations_fork_update( stake_delegations, bank->stake_delegations_fork_id, pubkey,
879 0 : &stake_state->stake.stake.delegation.voter_pubkey,
880 0 : new_stake,
881 0 : stake_state->stake.stake.delegation.activation_epoch,
882 0 : stake_state->stake.stake.delegation.deactivation_epoch,
883 0 : stake_state->stake.stake.credits_observed,
884 0 : fd_stake_warmup_cooldown_rate( bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch ) );
885 :
886 3 : } else {
887 3 : fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, pubkey );
888 3 : }
889 3 : }
|