Line data Source code
1 : #include "fd_stakes.h"
2 : #include "../runtime/fd_system_ids.h"
3 : #include "../runtime/context/fd_exec_slot_ctx.h"
4 : #include "../runtime/program/fd_stake_program.h"
5 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
6 :
7 : /* fd_stakes_accum_by_node converts Stakes (unordered list of (vote acc,
8 : active stake) tuples) to StakedNodes (rbtree mapping (node identity)
9 : => (active stake) ordered by node identity). Returns the tree root. */
10 :
11 : static fd_stake_weight_t_mapnode_t *
12 : fd_stakes_accum_by_node( fd_vote_accounts_global_t const * in,
13 : fd_stake_weight_t_mapnode_t * out_pool,
14 0 : fd_spad_t * runtime_spad ) {
15 :
16 : /* Stakes::staked_nodes(&self: Stakes) -> HashMap<Pubkey, u64> */
17 :
18 0 : fd_vote_accounts_pair_global_t_mapnode_t * in_pool = fd_vote_accounts_vote_accounts_pool_join( in );
19 0 : fd_vote_accounts_pair_global_t_mapnode_t * in_root = fd_vote_accounts_vote_accounts_root_join( in );
20 :
21 : /* VoteAccounts::staked_nodes(&self: VoteAccounts) -> HashMap<Pubkey, u64> */
22 :
23 : /* For each active vote account, accumulate (node_identity, stake) by
24 : summing stake. */
25 :
26 0 : fd_stake_weight_t_mapnode_t * out_root = NULL;
27 :
28 0 : for( fd_vote_accounts_pair_global_t_mapnode_t * n = fd_vote_accounts_pair_global_t_map_minimum( in_pool, in_root );
29 0 : n;
30 0 : n = fd_vote_accounts_pair_global_t_map_successor( in_pool, n ) ) {
31 :
32 : /* ... filter(|(stake, _)| *stake != 0u64) */
33 0 : if( n->elem.stake == 0UL ) continue;
34 :
35 0 : int err;
36 0 : uchar * data = fd_solana_account_data_join( &n->elem.value );
37 0 : ulong data_len = n->elem.value.data_len;
38 :
39 0 : fd_vote_state_versioned_t * vsv = fd_bincode_decode_spad(
40 0 : vote_state_versioned, runtime_spad,
41 0 : data,
42 0 : data_len,
43 0 : &err );
44 0 : if( FD_UNLIKELY( err ) ) {
45 0 : FD_LOG_ERR(( "Failed to decode vote account %s (%d)", FD_BASE58_ENC_32_ALLOCA( n->elem.key.key ), err ));
46 0 : }
47 :
48 0 : fd_pubkey_t node_pubkey;
49 0 : switch( vsv->discriminant ) {
50 0 : case fd_vote_state_versioned_enum_v0_23_5:
51 0 : node_pubkey = vsv->inner.v0_23_5.node_pubkey;
52 0 : break;
53 0 : case fd_vote_state_versioned_enum_v1_14_11:
54 0 : node_pubkey = vsv->inner.v1_14_11.node_pubkey;
55 0 : break;
56 0 : case fd_vote_state_versioned_enum_current:
57 0 : node_pubkey = vsv->inner.current.node_pubkey;
58 0 : break;
59 0 : default:
60 0 : __builtin_unreachable();
61 0 : }
62 :
63 :
64 : /* Extract node pubkey */
65 :
66 0 : fd_pubkey_t null_key = {0};
67 0 : if( memcmp( &node_pubkey, null_key.uc, sizeof(fd_pubkey_t) ) == 0 ) {
68 0 : FD_LOG_WARNING(( "vote account %s skipped", FD_BASE58_ENC_32_ALLOCA( n->elem.key.key ) ));
69 0 : continue;
70 0 : }
71 : /* Check if node identity was previously visited */
72 0 : fd_stake_weight_t_mapnode_t * query = fd_stake_weight_t_map_acquire( out_pool );
73 0 : if( FD_UNLIKELY( !query ) ) {
74 0 : FD_LOG_ERR(( "fd_stakes_accum_by_node() failed" ));
75 0 : }
76 :
77 0 : query->elem.key = node_pubkey;
78 :
79 0 : fd_stake_weight_t_mapnode_t * node = fd_stake_weight_t_map_find( out_pool, out_root, query );
80 :
81 0 : if( FD_UNLIKELY( node ) ) {
82 : /* Accumulate to previously created entry */
83 0 : fd_stake_weight_t_map_release( out_pool, query );
84 0 : node->elem.stake += n->elem.stake;
85 0 : } else {
86 : /* Create new entry */
87 0 : node = query;
88 0 : node->elem.stake = n->elem.stake;
89 0 : fd_stake_weight_t_map_insert( out_pool, &out_root, node );
90 0 : }
91 0 : }
92 :
93 0 : return out_root;
94 0 : }
95 :
96 : /* fd_stake_weight_sort sorts the given array of stake weights with
97 : length stakes_cnt by tuple (stake, pubkey) in descending order. */
98 :
99 : FD_FN_CONST static int
100 : fd_stakes_sort_before( fd_stake_weight_t a,
101 0 : fd_stake_weight_t b ) {
102 :
103 0 : if( a.stake > b.stake ) return 1;
104 0 : if( a.stake < b.stake ) return 0;
105 0 : if( memcmp( &a.key, &b.key, 32UL )>0 ) return 1;
106 0 : return 0;
107 0 : }
108 :
109 : #define SORT_NAME fd_stakes_sort
110 0 : #define SORT_KEY_T fd_stake_weight_t
111 0 : #define SORT_BEFORE(a,b) fd_stakes_sort_before( (a), (b) )
112 : #include "../../util/tmpl/fd_sort.c"
113 :
114 : void
115 : fd_stake_weight_sort( fd_stake_weight_t * stakes,
116 0 : ulong stakes_cnt ) {
117 0 : fd_stakes_sort_inplace( stakes, stakes_cnt );
118 0 : }
119 :
120 : /* fd_stakes_export_sorted converts StakedNodes (rbtree mapping
121 : (node identity) => (active stake) from fd_stakes_accum_by_node) to
122 : a list of fd_stake_weights_t. */
123 :
124 : static ulong
125 : fd_stakes_export( fd_stake_weight_t_mapnode_t const * const in_pool,
126 : fd_stake_weight_t_mapnode_t const * const root,
127 0 : fd_stake_weight_t * const out ) {
128 :
129 0 : fd_stake_weight_t * out_end = out;
130 :
131 0 : for( fd_stake_weight_t_mapnode_t const * ele = fd_stake_weight_t_map_minimum( (fd_stake_weight_t_mapnode_t *)in_pool, (fd_stake_weight_t_mapnode_t *)root ); ele; ele = (fd_stake_weight_t_mapnode_t *)fd_stake_weight_t_map_successor( (fd_stake_weight_t_mapnode_t *)in_pool, (fd_stake_weight_t_mapnode_t *)ele ) ) {
132 0 : *out_end++ = ele->elem;
133 0 : }
134 :
135 0 : return (ulong)( out_end - out );
136 0 : }
137 :
138 : ulong
139 : fd_stake_weights_by_node( fd_vote_accounts_global_t const * accs,
140 : fd_stake_weight_t * weights,
141 0 : fd_spad_t * runtime_spad ) {
142 :
143 : /* Estimate size required to store temporary data structures */
144 :
145 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_acc_pool = fd_vote_accounts_vote_accounts_pool_join( accs );
146 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_acc_root = fd_vote_accounts_vote_accounts_root_join( accs );
147 :
148 0 : ulong vote_acc_cnt = fd_vote_accounts_pair_global_t_map_size( vote_acc_pool, vote_acc_root );
149 :
150 0 : ulong rb_align = fd_stake_weight_t_map_align();
151 0 : ulong rb_footprint = fd_stake_weight_t_map_footprint( vote_acc_cnt );
152 :
153 : /* Create rb tree */
154 :
155 0 : void * pool_mem = fd_spad_alloc( runtime_spad, rb_align, rb_footprint );
156 0 : pool_mem = fd_stake_weight_t_map_new( pool_mem, vote_acc_cnt );
157 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( pool_mem );
158 0 : if( FD_UNLIKELY( !pool_mem ) ) FD_LOG_CRIT(( "fd_stake_weights_new() failed" ));
159 :
160 : /* Accumulate stakes to rb tree */
161 :
162 0 : fd_stake_weight_t_mapnode_t const * root = fd_stakes_accum_by_node( accs, pool, runtime_spad );
163 :
164 : /* Export to sorted list */
165 :
166 0 : ulong weights_cnt = fd_stakes_export( pool, root, weights );
167 0 : fd_stake_weight_sort( weights, weights_cnt );
168 :
169 0 : return weights_cnt;
170 0 : }
171 :
172 : /* Helper function to deserialize a vote account. If successful, populates vote account info in `elem`
173 : and saves the decoded vote state in `vote_state` */
174 : static fd_vote_state_versioned_t *
175 : deserialize_and_update_vote_account( fd_exec_slot_ctx_t * slot_ctx,
176 : fd_vote_accounts_pair_global_t_mapnode_t * elem,
177 : fd_stake_weight_t_mapnode_t * stake_delegations_root,
178 : fd_stake_weight_t_mapnode_t * stake_delegations_pool,
179 : fd_pubkey_t const * vote_account_pubkey,
180 0 : fd_spad_t * runtime_spad ) {
181 :
182 0 : FD_TXN_ACCOUNT_DECL( vote_account );
183 0 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( vote_account,
184 0 : vote_account_pubkey,
185 0 : slot_ctx->funk,
186 0 : slot_ctx->funk_txn ) ) ) {
187 0 : FD_LOG_DEBUG(( "Vote account not found" ));
188 0 : return NULL;
189 0 : }
190 :
191 : // Deserialize the vote account and ensure its in the correct state
192 0 : int err;
193 0 : fd_vote_state_versioned_t * res = fd_bincode_decode_spad(
194 0 : vote_state_versioned, runtime_spad,
195 0 : vote_account->vt->get_data( vote_account ),
196 0 : vote_account->vt->get_data_len( vote_account ),
197 0 : &err );
198 0 : if( FD_UNLIKELY( err ) ) {
199 0 : return NULL;
200 0 : }
201 :
202 : // Get the stake amount from the stake delegations map
203 0 : fd_stake_weight_t_mapnode_t temp;
204 0 : temp.elem.key = *vote_account_pubkey;
205 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( stake_delegations_pool, stake_delegations_root, &temp );
206 0 : elem->elem.stake = ( entry==NULL ) ? 0UL : entry->elem.stake;
207 :
208 0 : return res;
209 0 : }
210 :
211 : static void
212 : compute_stake_delegations( fd_epoch_info_t * temp_info,
213 : fd_compute_stake_delegations_t * task_args,
214 : ulong worker_idx,
215 : ulong start_idx,
216 0 : ulong end_idx ) {
217 :
218 :
219 0 : fd_spad_t * spad = task_args->spads[worker_idx];
220 0 : fd_epoch_info_pair_t const * stake_infos = temp_info->stake_infos;
221 0 : ulong epoch = task_args->epoch;
222 0 : fd_stake_history_t const * history = task_args->stake_history;
223 0 : ulong * new_rate_activation_epoch = task_args->new_rate_activation_epoch;
224 0 : fd_stake_weight_t_mapnode_t * delegation_pool = task_args->delegation_pool;
225 0 : fd_stake_weight_t_mapnode_t * delegation_root = task_args->delegation_root;
226 0 : ulong vote_states_pool_sz = task_args->vote_states_pool_sz;
227 :
228 0 : FD_SPAD_FRAME_BEGIN( spad ) {
229 :
230 : /* Create a temporary <pubkey, stake> map to hold delegations */
231 0 : void * mem = fd_spad_alloc( spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
232 0 : fd_stake_weight_t_mapnode_t * temp_pool = fd_stake_weight_t_map_join( fd_stake_weight_t_map_new( mem, vote_states_pool_sz ) );
233 0 : fd_stake_weight_t_mapnode_t * temp_root = NULL;
234 :
235 0 : fd_stake_weight_t_mapnode_t temp;
236 0 : for( ulong i=start_idx; i<end_idx; i++ ) {
237 0 : fd_delegation_t const * delegation = &stake_infos[i].stake.delegation;
238 0 : temp.elem.key = delegation->voter_pubkey;
239 :
240 : // Skip any delegations that are not in the delegation pool
241 0 : fd_stake_weight_t_mapnode_t * delegation_entry = fd_stake_weight_t_map_find( delegation_pool, delegation_root, &temp );
242 0 : if( FD_UNLIKELY( delegation_entry==NULL ) ) {
243 0 : continue;
244 0 : }
245 :
246 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
247 0 : delegation_entry = fd_stake_weight_t_map_find( temp_pool, temp_root, &temp );
248 0 : if( FD_UNLIKELY( delegation_entry==NULL ) ) {
249 0 : delegation_entry = fd_stake_weight_t_map_acquire( temp_pool );
250 0 : delegation_entry->elem.key = delegation->voter_pubkey;
251 0 : delegation_entry->elem.stake = new_entry.effective;
252 0 : fd_stake_weight_t_map_insert( temp_pool, &temp_root, delegation_entry );
253 0 : } else {
254 0 : delegation_entry->elem.stake += new_entry.effective;
255 0 : }
256 0 : }
257 :
258 : // Update the parent delegation pool with the calculated delegation values
259 0 : for( fd_stake_weight_t_mapnode_t * elem = fd_stake_weight_t_map_minimum( temp_pool, temp_root );
260 0 : elem;
261 0 : elem = fd_stake_weight_t_map_successor( temp_pool, elem ) ) {
262 0 : fd_stake_weight_t_mapnode_t * output_delegation_node = fd_stake_weight_t_map_find( delegation_pool, delegation_root, elem );
263 0 : FD_ATOMIC_FETCH_AND_ADD( &output_delegation_node->elem.stake, elem->elem.stake );
264 0 : }
265 :
266 0 : } FD_SPAD_FRAME_END;
267 :
268 0 : }
269 :
270 : static void
271 : compute_stake_delegations_tpool_task( void *tpool,
272 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
273 : void *args,
274 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
275 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
276 : ulong m0, ulong m1,
277 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
278 0 : fd_epoch_info_t * temp_info = (fd_epoch_info_t *)tpool;
279 0 : fd_compute_stake_delegations_t * task_args = (fd_compute_stake_delegations_t *)args;
280 0 : ulong worker_idx = fd_tile_idx();
281 :
282 0 : compute_stake_delegations( temp_info, task_args, worker_idx, m0, m1 );
283 0 : }
284 :
285 : /* Populates vote accounts with updated delegated stake from the next cached epoch stakes into temp_info */
286 : void
287 : fd_populate_vote_accounts( fd_exec_slot_ctx_t * slot_ctx,
288 : fd_stake_history_t const * history,
289 : ulong * new_rate_activation_epoch,
290 : fd_epoch_info_t * temp_info,
291 : fd_tpool_t * tpool,
292 : fd_spad_t * * exec_spads,
293 : ulong exec_spad_cnt,
294 0 : fd_spad_t * runtime_spad ) {
295 :
296 :
297 : /* Initialize a temporary vote states cache */
298 0 : fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank );
299 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
300 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
301 0 : ulong vote_account_keys_map_sz = vote_account_keys_pool ? fd_account_keys_pair_t_map_size( vote_account_keys_pool, vote_account_keys_root ) : 0UL;
302 :
303 0 : fd_stakes_global_t const * stakes = fd_bank_stakes_locking_query( slot_ctx->bank );
304 0 : fd_vote_accounts_global_t const * vote_accounts = &stakes->vote_accounts;
305 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts );
306 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts );
307 0 : ulong vote_accounts_stakes_map_sz = vote_accounts_pool ? fd_vote_accounts_pair_global_t_map_size( vote_accounts_pool, vote_accounts_root ) : 0UL;
308 :
309 0 : ulong vote_states_pool_sz = vote_accounts_stakes_map_sz + vote_account_keys_map_sz;
310 0 : temp_info->vote_states_root = NULL;
311 0 : uchar * pool_mem = fd_spad_alloc( runtime_spad, fd_vote_info_pair_t_map_align(), fd_vote_info_pair_t_map_footprint( vote_states_pool_sz ) );
312 0 : temp_info->vote_states_pool = fd_vote_info_pair_t_map_join( fd_vote_info_pair_t_map_new( pool_mem, vote_states_pool_sz ) );
313 :
314 : /* Create a map of <pubkey, stake> to store the total stake of each vote account. */
315 0 : void * mem = fd_spad_alloc( runtime_spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
316 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( fd_stake_weight_t_map_new( mem, vote_states_pool_sz ) );
317 0 : fd_stake_weight_t_mapnode_t * root = NULL;
318 :
319 : /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all
320 : of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */
321 0 : for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( vote_accounts_pool, vote_accounts_root );
322 0 : elem;
323 0 : elem = fd_vote_accounts_pair_global_t_map_successor( vote_accounts_pool, elem ) ) {
324 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool );
325 0 : entry->elem.key = elem->elem.key;
326 0 : entry->elem.stake = 0UL;
327 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
328 0 : }
329 :
330 0 : fd_bank_stakes_end_locking_query( slot_ctx->bank );
331 :
332 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root );
333 0 : n;
334 0 : n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
335 0 : fd_stake_weight_t_mapnode_t temp;
336 0 : temp.elem.key = n->elem.key;
337 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( pool, root, &temp );
338 0 : if( FD_LIKELY( entry==NULL ) ) {
339 0 : entry = fd_stake_weight_t_map_acquire( pool );
340 0 : entry->elem.key = n->elem.key;
341 0 : entry->elem.stake = 0UL;
342 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
343 0 : }
344 0 : }
345 :
346 0 : fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank );
347 :
348 0 : fd_compute_stake_delegations_t task_args = {
349 0 : .epoch = stakes->epoch,
350 0 : .stake_history = history,
351 0 : .new_rate_activation_epoch = new_rate_activation_epoch,
352 0 : .delegation_pool = pool,
353 0 : .delegation_root = root,
354 0 : .vote_states_pool_sz = vote_states_pool_sz,
355 0 : .spads = exec_spads,
356 0 : };
357 :
358 0 : if( !!tpool ) {
359 0 : ulong tpool_worker_cnt = fd_tpool_worker_cnt( tpool );
360 0 : ulong worker_cnt = fd_ulong_min( temp_info->stake_infos_len,
361 0 : fd_ulong_min( tpool_worker_cnt, exec_spad_cnt ) );
362 : // Now we can iterate over each stake delegation in parallel and fill the delegations map
363 0 : fd_tpool_exec_all_batch( tpool, 0UL, worker_cnt, compute_stake_delegations_tpool_task, temp_info, &task_args, NULL, 1UL, 0UL, temp_info->stake_infos_len );
364 :
365 0 : } else {
366 0 : compute_stake_delegations( temp_info, &task_args, 0UL, 0UL, temp_info->stake_infos_len );
367 0 : }
368 :
369 : // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool
370 : /* NOTE: we use epoch_bank->next_epoch_stakes because Agave indexes their epoch stakes cache by leader schedule epoch.
371 : This means that the epoch stakes for epoch E are indexed by epoch E+1.
372 : This is just a workaround for now.
373 : https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L309 */
374 0 : ulong total_epoch_stake = 0UL;
375 :
376 0 : fd_vote_accounts_global_t const * next_epoch_stakes = fd_bank_next_epoch_stakes_locking_query( slot_ctx->bank );
377 0 : fd_vote_accounts_pair_global_t_mapnode_t * next_epoch_stakes_pool = fd_vote_accounts_vote_accounts_pool_join( next_epoch_stakes );
378 0 : fd_vote_accounts_pair_global_t_mapnode_t * next_epoch_stakes_root = fd_vote_accounts_vote_accounts_root_join( next_epoch_stakes );
379 :
380 0 : for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( next_epoch_stakes_pool, next_epoch_stakes_root );
381 0 : elem;
382 0 : elem = fd_vote_accounts_pair_global_t_map_successor( next_epoch_stakes_pool, elem ) ) {
383 0 : fd_pubkey_t const * vote_account_pubkey = &elem->elem.key;
384 0 : FD_TXN_ACCOUNT_DECL( acc );
385 0 : int rc = fd_txn_account_init_from_funk_readonly( acc, vote_account_pubkey, slot_ctx->funk, slot_ctx->funk_txn );
386 0 : FD_TEST( rc == 0 );
387 0 : uchar * data = fd_solana_account_data_join( &elem->elem.value );
388 0 : ulong data_len = elem->elem.value.data_len;
389 :
390 0 : int err;
391 0 : fd_vote_state_versioned_t * vote_state = fd_bincode_decode_spad( vote_state_versioned,
392 0 : runtime_spad,
393 0 : data,
394 0 : data_len,
395 0 : &err );
396 :
397 0 : if( FD_LIKELY( vote_state ) ) {
398 0 : total_epoch_stake += elem->elem.stake;
399 : // Insert into the temporary vote states cache
400 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
401 0 : new_vote_state_node->elem.account = *vote_account_pubkey;
402 0 : new_vote_state_node->elem.state = *vote_state;
403 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
404 0 : } else {
405 0 : FD_LOG_WARNING(( "Failed to deserialize vote account" ));
406 0 : }
407 0 : }
408 0 : fd_bank_next_epoch_stakes_end_locking_query( slot_ctx->bank );
409 :
410 0 : fd_bank_total_epoch_stake_set( slot_ctx->bank, total_epoch_stake );
411 0 : }
412 :
413 : /*
414 : Refresh vote accounts.
415 :
416 : This updates the epoch bank stakes vote_accounts cache - that is, the total amount
417 : of delegated stake each vote account has, using the current delegation values from inside each
418 : stake account. Contrary to the Agave equivalent, it also merges the stakes cache vote accounts with the
419 : new vote account keys from this epoch.
420 :
421 : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L562 */
422 : void
423 : fd_refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx,
424 : fd_stake_history_t const * history,
425 : ulong * new_rate_activation_epoch,
426 : fd_epoch_info_t * temp_info,
427 : fd_tpool_t * tpool,
428 : fd_spad_t * * exec_spads,
429 : ulong exec_spad_cnt,
430 0 : fd_spad_t * runtime_spad ) {
431 :
432 0 : fd_stakes_global_t * stakes = fd_bank_stakes_locking_modify( slot_ctx->bank );
433 0 : fd_vote_accounts_global_t * vote_accounts = &stakes->vote_accounts;
434 0 : fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts );
435 0 : fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts );
436 :
437 0 : fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank );
438 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
439 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
440 :
441 0 : ulong vote_account_keys_map_sz = !!vote_account_keys_pool ? fd_account_keys_pair_t_map_size( vote_account_keys_pool, vote_account_keys_root ) : 0UL;
442 0 : ulong vote_accounts_stakes_map_sz = !!stakes_vote_accounts_pool ? fd_vote_accounts_pair_global_t_map_size( stakes_vote_accounts_pool, stakes_vote_accounts_root ) : 0UL;
443 0 : ulong vote_states_pool_sz = vote_accounts_stakes_map_sz + vote_account_keys_map_sz;
444 :
445 : /* Initialize a temporary vote states cache */
446 0 : temp_info->vote_states_root = NULL;
447 0 : uchar * pool_mem = fd_spad_alloc( runtime_spad, fd_vote_info_pair_t_map_align(), fd_vote_info_pair_t_map_footprint( vote_states_pool_sz ) );
448 0 : temp_info->vote_states_pool = fd_vote_info_pair_t_map_join( fd_vote_info_pair_t_map_new( pool_mem, vote_states_pool_sz ) );
449 :
450 : /* Create a map of <pubkey, stake> to store the total stake of each vote account. */
451 0 : void * mem = fd_spad_alloc( runtime_spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
452 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( fd_stake_weight_t_map_new( mem, vote_states_pool_sz ) );
453 0 : fd_stake_weight_t_mapnode_t * root = NULL;
454 :
455 : /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all
456 : of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */
457 0 : for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( stakes_vote_accounts_pool, stakes_vote_accounts_root );
458 0 : elem;
459 0 : elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) {
460 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool );
461 0 : entry->elem.key = elem->elem.key;
462 0 : entry->elem.stake = 0UL;
463 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
464 0 : }
465 :
466 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root );
467 0 : n;
468 0 : n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
469 0 : fd_stake_weight_t_mapnode_t temp;
470 0 : temp.elem.key = n->elem.key;
471 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( pool, root, &temp );
472 0 : if( FD_LIKELY( entry==NULL ) ) {
473 0 : entry = fd_stake_weight_t_map_acquire( pool );
474 0 : entry->elem.key = n->elem.key;
475 0 : entry->elem.stake = 0UL;
476 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
477 0 : }
478 0 : }
479 :
480 0 : fd_compute_stake_delegations_t task_args = {
481 0 : .epoch = stakes->epoch,
482 0 : .stake_history = history,
483 0 : .new_rate_activation_epoch = new_rate_activation_epoch,
484 0 : .delegation_pool = pool,
485 0 : .delegation_root = root,
486 0 : .vote_states_pool_sz = vote_states_pool_sz,
487 0 : .spads = exec_spads,
488 0 : };
489 :
490 0 : if( !!tpool ) {
491 0 : ulong tpool_worker_cnt = fd_tpool_worker_cnt( tpool );
492 0 : ulong worker_cnt = fd_ulong_min( temp_info->stake_infos_len,
493 0 : fd_ulong_min( tpool_worker_cnt, exec_spad_cnt ) );
494 : // Now we can iterate over each stake delegation in parallel and fill the delegations map
495 0 : fd_tpool_exec_all_batch( tpool, 0UL, worker_cnt, compute_stake_delegations_tpool_task, temp_info, &task_args, NULL, 1UL, 0UL, temp_info->stake_infos_len );
496 :
497 0 : } else {
498 0 : compute_stake_delegations( temp_info, &task_args, 0UL, 0UL, temp_info->stake_infos_len );
499 0 : }
500 :
501 : // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool
502 0 : ulong total_epoch_stake = 0UL;
503 0 : for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( stakes_vote_accounts_pool, stakes_vote_accounts_root );
504 0 : elem;
505 0 : elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) {
506 0 : fd_pubkey_t const * vote_account_pubkey = &elem->elem.key;
507 0 : fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx,
508 0 : elem,
509 0 : root,
510 0 : pool,
511 0 : vote_account_pubkey,
512 0 : runtime_spad );
513 0 : if( FD_LIKELY( vote_state ) ) {
514 0 : total_epoch_stake += elem->elem.stake;
515 : // Insert into the temporary vote states cache
516 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
517 0 : new_vote_state_node->elem.account = *vote_account_pubkey;
518 0 : new_vote_state_node->elem.state = *vote_state;
519 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
520 0 : } else {
521 0 : FD_LOG_WARNING(( "Failed to deserialize vote account" ));
522 0 : }
523 0 : }
524 :
525 : // Update the epoch stakes cache with new vote accounts from the epoch
526 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root );
527 0 : n;
528 0 : n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
529 :
530 0 : fd_pubkey_t const * vote_account_pubkey = &n->elem.key;
531 0 : fd_vote_accounts_pair_global_t_mapnode_t key;
532 0 : key.elem.key = *vote_account_pubkey;
533 :
534 : /* No need to process duplicate vote account keys. This is a mostly redundant check
535 : since upserting vote accounts also checks against the vote stakes, but this is
536 : there anyways in case that ever changes */
537 0 : if( FD_UNLIKELY( fd_vote_accounts_pair_global_t_map_find( stakes_vote_accounts_pool, stakes_vote_accounts_root, &key ) ) ) {
538 0 : continue;
539 0 : }
540 :
541 0 : fd_vote_accounts_pair_global_t_mapnode_t * new_vote_node = fd_vote_accounts_pair_global_t_map_acquire( stakes_vote_accounts_pool );
542 0 : fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx,
543 0 : new_vote_node,
544 0 : root,
545 0 : pool,
546 0 : vote_account_pubkey,
547 0 : runtime_spad );
548 :
549 0 : if( FD_UNLIKELY( !vote_state ) ) {
550 0 : fd_vote_accounts_pair_global_t_map_release( stakes_vote_accounts_pool, new_vote_node );
551 0 : continue;
552 0 : }
553 :
554 : // Insert into the epoch stakes cache and temporary vote states cache
555 0 : fd_vote_accounts_pair_global_t_map_insert( stakes_vote_accounts_pool, &stakes_vote_accounts_root, new_vote_node );
556 0 : total_epoch_stake += new_vote_node->elem.stake;
557 :
558 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
559 0 : new_vote_state_node->elem.account = *vote_account_pubkey;
560 0 : new_vote_state_node->elem.state = *vote_state;
561 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
562 0 : }
563 0 : fd_vote_accounts_vote_accounts_pool_update( &stakes->vote_accounts, stakes_vote_accounts_pool );
564 0 : fd_vote_accounts_vote_accounts_root_update( &stakes->vote_accounts, stakes_vote_accounts_root );
565 :
566 0 : fd_bank_stakes_end_locking_modify( slot_ctx->bank );
567 :
568 0 : fd_bank_total_epoch_stake_set( slot_ctx->bank, total_epoch_stake );
569 :
570 : /* At this point, we need to flush the vote account keys cache */
571 0 : vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
572 0 : vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
573 0 : fd_account_keys_pair_t_map_release_tree( vote_account_keys_pool, vote_account_keys_root );
574 0 : vote_account_keys_root = NULL;
575 0 : fd_account_keys_account_keys_pool_update( vote_account_keys, vote_account_keys_pool );
576 0 : fd_account_keys_account_keys_root_update( vote_account_keys, vote_account_keys_root );
577 0 : fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank );
578 0 : }
579 :
580 : static void
581 : accumulate_stake_cache_delegations( fd_delegation_pair_t_mapnode_t * * delegations_roots,
582 : fd_accumulate_delegations_task_args_t * task_args,
583 : ulong worker_idx,
584 0 : fd_delegation_pair_t_mapnode_t * end_node ) {
585 :
586 0 : fd_exec_slot_ctx_t const * slot_ctx = task_args->slot_ctx;
587 0 : fd_stake_history_t const * history = task_args->stake_history;
588 0 : ulong * new_rate_activation_epoch = task_args->new_rate_activation_epoch;
589 0 : fd_stake_history_entry_t * accumulator = task_args->accumulator;
590 0 : fd_spad_t * spad = task_args->spads[worker_idx];
591 0 : fd_delegation_pair_t_mapnode_t * delegations_pool = task_args->stake_delegations_pool;
592 0 : fd_epoch_info_t * temp_info = task_args->temp_info;
593 0 : ulong epoch = task_args->epoch;
594 :
595 0 : ulong effective = 0UL;
596 0 : ulong activating = 0UL;
597 0 : ulong deactivating = 0UL;
598 :
599 0 : FD_SPAD_FRAME_BEGIN( spad ) {
600 0 : for( fd_delegation_pair_t_mapnode_t * n = delegations_roots[worker_idx];
601 0 : n != end_node;
602 0 : n = fd_delegation_pair_t_map_successor( delegations_pool, n ) ) {
603 :
604 0 : FD_TXN_ACCOUNT_DECL( acc );
605 0 : int rc = fd_txn_account_init_from_funk_readonly( acc,
606 0 : &n->elem.account,
607 0 : slot_ctx->funk,
608 0 : slot_ctx->funk_txn );
609 0 : if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->vt->get_lamports( acc )==0UL ) ) {
610 0 : FD_LOG_WARNING(("Failed to init account"));
611 0 : continue;
612 0 : }
613 :
614 0 : fd_stake_state_v2_t stake_state;
615 0 : rc = fd_stake_get_state( acc, &stake_state );
616 0 : if( FD_UNLIKELY( rc != 0 ) ) {
617 0 : FD_LOG_WARNING(("Failed to get stake state"));
618 0 : continue;
619 0 : }
620 :
621 0 : if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
622 0 : FD_LOG_WARNING(("Not a stake"));
623 0 : continue;
624 0 : }
625 :
626 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake == 0 ) ) {
627 0 : continue;
628 0 : }
629 :
630 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
631 :
632 0 : ulong delegation_idx = FD_ATOMIC_FETCH_AND_ADD( &temp_info->stake_infos_len, 1UL );
633 0 : temp_info->stake_infos[delegation_idx].stake = stake_state.inner.stake.stake;
634 0 : temp_info->stake_infos[delegation_idx].account = n->elem.account;
635 :
636 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
637 0 : effective += new_entry.effective;
638 0 : activating += new_entry.activating;
639 0 : deactivating += new_entry.deactivating;
640 0 : }
641 :
642 0 : FD_ATOMIC_FETCH_AND_ADD( &accumulator->effective, effective );
643 0 : FD_ATOMIC_FETCH_AND_ADD( &accumulator->activating, activating );
644 0 : FD_ATOMIC_FETCH_AND_ADD( &accumulator->deactivating, deactivating );
645 :
646 0 : } FD_SPAD_FRAME_END;
647 :
648 0 : }
649 :
650 : static void FD_FN_UNUSED
651 : accumulate_stake_cache_delegations_tpool_task( void *tpool,
652 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
653 : void *args,
654 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
655 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
656 : ulong m0 FD_PARAM_UNUSED, ulong m1 FD_PARAM_UNUSED,
657 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
658 0 : fd_delegation_pair_t_mapnode_t * * delegations_roots = (fd_delegation_pair_t_mapnode_t * *)tpool;
659 0 : fd_accumulate_delegations_task_args_t * task_args = (fd_accumulate_delegations_task_args_t *)args;
660 0 : ulong worker_idx = fd_tile_idx();
661 :
662 0 : accumulate_stake_cache_delegations( delegations_roots,
663 0 : task_args,
664 0 : worker_idx,
665 0 : delegations_roots[ worker_idx+1UL ] );
666 :
667 0 : }
668 :
669 : /* Accumulates information about epoch stakes into `temp_info`, which is a temporary cache
670 : used to save intermediate state about stake and vote accounts to avoid them from having to
671 : be recomputed on every access, especially at the epoch boundary. Also collects stats in `accumulator` */
672 : void
673 : fd_accumulate_stake_infos( fd_exec_slot_ctx_t const * slot_ctx,
674 : fd_stakes_global_t const * stakes,
675 : fd_stake_history_t const * history,
676 : ulong * new_rate_activation_epoch,
677 : fd_stake_history_entry_t * accumulator,
678 : fd_epoch_info_t * temp_info,
679 : fd_tpool_t * tpool,
680 : fd_spad_t * * exec_spads,
681 : ulong exec_spads_cnt,
682 0 : fd_spad_t * runtime_spad ) {
683 :
684 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
685 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
686 :
687 0 : ulong stake_delegations_pool_sz = fd_delegation_pair_t_map_size( stake_delegations_pool, stake_delegations_root );
688 0 : if( FD_UNLIKELY( stake_delegations_pool_sz==0UL ) ) {
689 0 : return;
690 0 : }
691 :
692 : /* Batch up the stake info accumulations via tpool. Currently this is only marginally more efficient because we
693 : do not have access to iterators at a specific index in constant or logarithmic time. */
694 0 : ulong tpool_worker_cnt = !!tpool ? fd_tpool_worker_cnt( tpool ) : 1UL;
695 0 : ulong worker_cnt = fd_ulong_min( stake_delegations_pool_sz,
696 0 : fd_ulong_min( tpool_worker_cnt, exec_spads_cnt ) );
697 0 : fd_delegation_pair_t_mapnode_t ** batch_delegation_roots = fd_spad_alloc( runtime_spad, alignof(fd_delegation_pair_t_mapnode_t *),
698 0 : ( worker_cnt + 1 )*sizeof(fd_delegation_pair_t_mapnode_t *) );
699 :
700 0 : ulong * idx_starts = fd_spad_alloc( runtime_spad, alignof(ulong), worker_cnt * sizeof(ulong) );
701 :
702 : // Determine the logical index partitioning of the delegations pool so we know where to start iterating from
703 0 : for( ulong i=0UL; i<worker_cnt; i++ ) {
704 0 : ulong _idx_end;
705 0 : FD_TPOOL_PARTITION( 0UL, stake_delegations_pool_sz, 1UL, i, worker_cnt, idx_starts[i], _idx_end );
706 0 : (void)_idx_end;
707 0 : }
708 :
709 0 : ulong batch_idx = 0UL;
710 0 : ulong iter_idx = 0UL;
711 0 : for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum( stake_delegations_pool, stake_delegations_root );
712 0 : n;
713 0 : n = fd_delegation_pair_t_map_successor( stake_delegations_pool, n ) ) {
714 0 : if( iter_idx++==idx_starts[batch_idx] ) {
715 0 : batch_delegation_roots[batch_idx++] = n;
716 0 : }
717 0 : }
718 0 : batch_delegation_roots[worker_cnt] = NULL;
719 :
720 0 : fd_accumulate_delegations_task_args_t task_args = {
721 0 : .slot_ctx = slot_ctx,
722 0 : .stake_history = history,
723 0 : .new_rate_activation_epoch = new_rate_activation_epoch,
724 0 : .accumulator = accumulator,
725 0 : .temp_info = temp_info,
726 0 : .spads = exec_spads,
727 0 : .stake_delegations_pool = stake_delegations_pool,
728 0 : .epoch = stakes->epoch,
729 0 : };
730 :
731 0 : if( !!tpool ) {
732 0 : fd_tpool_exec_all_batch( tpool, 0UL, worker_cnt, accumulate_stake_cache_delegations_tpool_task,
733 0 : batch_delegation_roots, &task_args, NULL,
734 0 : 1UL, 0UL, stake_delegations_pool_sz );
735 0 : } else {
736 0 : accumulate_stake_cache_delegations( batch_delegation_roots,
737 0 : &task_args,
738 0 : 0UL,
739 0 : NULL );
740 0 : }
741 0 : temp_info->stake_infos_new_keys_start_idx = temp_info->stake_infos_len;
742 :
743 0 : fd_account_keys_global_t const * stake_account_keys = fd_bank_stake_account_keys_locking_query( slot_ctx->bank );
744 0 : fd_account_keys_pair_t_mapnode_t * account_keys_pool = fd_account_keys_account_keys_pool_join( stake_account_keys );
745 0 : fd_account_keys_pair_t_mapnode_t * account_keys_root = fd_account_keys_account_keys_root_join( stake_account_keys );
746 :
747 0 : if( !account_keys_pool ) {
748 0 : fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
749 0 : return;
750 0 : }
751 :
752 : /* The number of account keys aggregated across the epoch is usually small, so there aren't much performance gains from tpooling here. */
753 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( account_keys_pool, account_keys_root );
754 0 : n;
755 0 : n = fd_account_keys_pair_t_map_successor( account_keys_pool, n ) ) {
756 0 : FD_TXN_ACCOUNT_DECL( acc );
757 0 : int rc = fd_txn_account_init_from_funk_readonly(acc, &n->elem.key, slot_ctx->funk, slot_ctx->funk_txn );
758 0 : if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->vt->get_lamports( acc )==0UL ) ) {
759 0 : continue;
760 0 : }
761 :
762 0 : fd_stake_state_v2_t stake_state;
763 0 : rc = fd_stake_get_state( acc, &stake_state );
764 0 : if( FD_UNLIKELY( rc != 0 ) ) {
765 0 : continue;
766 0 : }
767 :
768 0 : if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
769 0 : continue;
770 0 : }
771 :
772 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake==0UL ) ) {
773 0 : continue;
774 0 : }
775 :
776 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
777 0 : temp_info->stake_infos[temp_info->stake_infos_len ].stake = stake_state.inner.stake.stake;
778 0 : temp_info->stake_infos[temp_info->stake_infos_len++].account = n->elem.key;
779 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
780 0 : accumulator->effective += new_entry.effective;
781 0 : accumulator->activating += new_entry.activating;
782 0 : accumulator->deactivating += new_entry.deactivating;
783 0 : }
784 :
785 0 : fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
786 0 : }
787 :
788 : /* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */
789 : void
790 : fd_stakes_activate_epoch( fd_exec_slot_ctx_t * slot_ctx,
791 : ulong * new_rate_activation_epoch,
792 : fd_epoch_info_t * temp_info,
793 : fd_tpool_t * tpool,
794 : fd_spad_t * * exec_spads,
795 : ulong exec_spad_cnt,
796 0 : fd_spad_t * runtime_spad ) {
797 :
798 0 : fd_stakes_global_t const * stakes = fd_bank_stakes_locking_query( slot_ctx->bank );
799 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
800 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
801 :
802 0 : fd_account_keys_global_t const * stake_account_keys = fd_bank_stake_account_keys_locking_query( slot_ctx->bank );
803 :
804 0 : fd_account_keys_pair_t_mapnode_t * account_keys_pool = NULL;
805 0 : fd_account_keys_pair_t_mapnode_t * account_keys_root = NULL;
806 :
807 0 : if( stake_account_keys ) {
808 0 : account_keys_pool = fd_account_keys_account_keys_pool_join( stake_account_keys );
809 0 : account_keys_root = fd_account_keys_account_keys_root_join( stake_account_keys );
810 0 : }
811 :
812 : /* Current stake delegations: list of all current delegations in stake_delegations
813 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */
814 : /* Add a new entry to the Stake History sysvar for the previous epoch
815 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */
816 :
817 0 : fd_stake_history_t const * history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
818 0 : if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
819 :
820 0 : ulong stake_delegations_size = fd_delegation_pair_t_map_size(
821 0 : stake_delegations_pool, stake_delegations_root );
822 :
823 0 : stake_delegations_size += !!account_keys_pool ? fd_account_keys_pair_t_map_size( account_keys_pool, account_keys_root ) : 0UL;
824 :
825 0 : fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
826 :
827 0 : temp_info->stake_infos_len = 0UL;
828 0 : temp_info->stake_infos = (fd_epoch_info_pair_t *)fd_spad_alloc( runtime_spad, FD_EPOCH_INFO_PAIR_ALIGN, sizeof(fd_epoch_info_pair_t)*stake_delegations_size );
829 0 : fd_memset( temp_info->stake_infos, 0, sizeof(fd_epoch_info_pair_t)*stake_delegations_size );
830 :
831 0 : fd_stake_history_entry_t accumulator = {
832 0 : .effective = 0UL,
833 0 : .activating = 0UL,
834 0 : .deactivating = 0UL
835 0 : };
836 :
837 : /* Accumulate stats for stake accounts */
838 0 : fd_accumulate_stake_infos( slot_ctx,
839 0 : stakes,
840 0 : history,
841 0 : new_rate_activation_epoch,
842 0 : &accumulator,
843 0 : temp_info,
844 0 : tpool,
845 0 : exec_spads,
846 0 : exec_spad_cnt,
847 0 : runtime_spad );
848 :
849 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L359 */
850 0 : fd_epoch_stake_history_entry_pair_t new_elem = {
851 0 : .epoch = stakes->epoch,
852 0 : .entry = {
853 0 : .effective = accumulator.effective,
854 0 : .activating = accumulator.activating,
855 0 : .deactivating = accumulator.deactivating
856 0 : }
857 0 : };
858 :
859 0 : fd_sysvar_stake_history_update( slot_ctx, &new_elem, runtime_spad );
860 :
861 0 : fd_bank_stakes_end_locking_query( slot_ctx->bank );
862 :
863 0 : }
864 :
865 : int
866 : write_stake_state( fd_txn_account_t * stake_acc_rec,
867 0 : fd_stake_state_v2_t * stake_state ) {
868 :
869 0 : ulong encoded_stake_state_size = fd_stake_state_v2_size(stake_state);
870 :
871 0 : fd_bincode_encode_ctx_t ctx = {
872 0 : .data = stake_acc_rec->vt->get_data_mut( stake_acc_rec ),
873 0 : .dataend = stake_acc_rec->vt->get_data_mut( stake_acc_rec ) + encoded_stake_state_size,
874 0 : };
875 0 : if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx ) != FD_BINCODE_SUCCESS ) ) {
876 0 : FD_LOG_ERR(( "fd_stake_state_encode failed" ));
877 0 : }
878 :
879 0 : return 0;
880 0 : }
|