Line data Source code
1 : #include "fd_stakes.h"
2 : #include "../runtime/fd_system_ids.h"
3 : #include "../runtime/context/fd_exec_epoch_ctx.h"
4 : #include "../runtime/context/fd_exec_slot_ctx.h"
5 : #include "../runtime/program/fd_stake_program.h"
6 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
7 :
8 : /* fd_stakes_accum_by_node converts Stakes (unordered list of (vote acc,
9 : active stake) tuples) to StakedNodes (rbtree mapping (node identity)
10 : => (active stake) ordered by node identity). Returns the tree root. */
11 :
12 : static fd_stake_weight_t_mapnode_t *
13 : fd_stakes_accum_by_node( fd_vote_accounts_t const * in,
14 : fd_stake_weight_t_mapnode_t * out_pool,
15 0 : fd_spad_t * runtime_spad ) {
16 :
17 : /* Stakes::staked_nodes(&self: Stakes) -> HashMap<Pubkey, u64> */
18 :
19 0 : fd_vote_accounts_pair_t_mapnode_t * in_pool = in->vote_accounts_pool;
20 0 : fd_vote_accounts_pair_t_mapnode_t * in_root = in->vote_accounts_root;
21 :
22 : /* VoteAccounts::staked_nodes(&self: VoteAccounts) -> HashMap<Pubkey, u64> */
23 :
24 : /* For each active vote account, accumulate (node_identity, stake) by
25 : summing stake. */
26 :
27 0 : fd_stake_weight_t_mapnode_t * out_root = NULL;
28 :
29 0 : for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( in_pool, in_root );
30 0 : n;
31 0 : n = fd_vote_accounts_pair_t_map_successor( in_pool, n ) ) {
32 :
33 : /* ... filter(|(stake, _)| *stake != 0u64) */
34 0 : if( n->elem.stake == 0UL ) continue;
35 :
36 0 : fd_bincode_decode_ctx_t ctx = {
37 0 : .data = n->elem.value.data,
38 0 : .dataend = n->elem.value.data + n->elem.value.data_len,
39 0 : };
40 :
41 0 : ulong total_sz = 0UL;
42 0 : int err = fd_vote_state_versioned_decode_footprint( &ctx, &total_sz );
43 0 : if( FD_UNLIKELY( err ) ) {
44 0 : FD_LOG_ERR(( "Failed to decode vote account %s (%d)", FD_BASE58_ENC_32_ALLOCA( n->elem.key.key ), err ));
45 0 : }
46 :
47 0 : uchar * mem = fd_spad_alloc( runtime_spad, fd_vote_state_versioned_align(), total_sz );
48 0 : if( FD_UNLIKELY( !mem ) ) {
49 0 : FD_LOG_ERR(( "Failed to allocate memory for vote account %s", FD_BASE58_ENC_32_ALLOCA( n->elem.key.key ) ));
50 0 : }
51 :
52 0 : fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( mem, &ctx );
53 :
54 0 : fd_pubkey_t node_pubkey;
55 0 : switch( vsv->discriminant ) {
56 0 : case fd_vote_state_versioned_enum_v0_23_5:
57 0 : node_pubkey = vsv->inner.v0_23_5.node_pubkey;
58 0 : break;
59 0 : case fd_vote_state_versioned_enum_v1_14_11:
60 0 : node_pubkey = vsv->inner.v1_14_11.node_pubkey;
61 0 : break;
62 0 : case fd_vote_state_versioned_enum_current:
63 0 : node_pubkey = vsv->inner.current.node_pubkey;
64 0 : break;
65 0 : default:
66 0 : __builtin_unreachable();
67 0 : }
68 :
69 :
70 : /* Extract node pubkey */
71 :
72 0 : fd_pubkey_t null_key = {0};
73 0 : if( memcmp( &node_pubkey, null_key.uc, sizeof(fd_pubkey_t) ) == 0 ) {
74 0 : FD_LOG_WARNING(( "vote account %s skipped", FD_BASE58_ENC_32_ALLOCA( n->elem.key.key ) ));
75 0 : continue;
76 0 : }
77 : /* Check if node identity was previously visited */
78 0 : fd_stake_weight_t_mapnode_t * query = fd_stake_weight_t_map_acquire( out_pool );
79 0 : if( FD_UNLIKELY( !query ) ) {
80 0 : FD_LOG_ERR(( "fd_stakes_accum_by_node() failed" ));
81 0 : }
82 :
83 0 : query->elem.key = node_pubkey;
84 0 : fd_stake_weight_t_mapnode_t * node = fd_stake_weight_t_map_find( out_pool, out_root, query );
85 :
86 0 : if( FD_UNLIKELY( node ) ) {
87 : /* Accumulate to previously created entry */
88 0 : fd_stake_weight_t_map_release( out_pool, query );
89 0 : node->elem.stake += n->elem.stake;
90 0 : } else {
91 : /* Create new entry */
92 0 : node = query;
93 0 : node->elem.stake = n->elem.stake;
94 0 : fd_stake_weight_t_map_insert( out_pool, &out_root, node );
95 0 : }
96 0 : }
97 :
98 0 : return out_root;
99 0 : }
100 :
101 : /* fd_stake_weight_sort sorts the given array of stake weights with
102 : length stakes_cnt by tuple (stake, pubkey) in descending order. */
103 :
104 : FD_FN_CONST static int
105 : fd_stakes_sort_before( fd_stake_weight_t a,
106 0 : fd_stake_weight_t b ) {
107 :
108 0 : if( a.stake > b.stake ) return 1;
109 0 : if( a.stake < b.stake ) return 0;
110 0 : if( memcmp( &a.key, &b.key, 32UL )>0 ) return 1;
111 0 : return 0;
112 0 : }
113 :
114 : #define SORT_NAME fd_stakes_sort
115 0 : #define SORT_KEY_T fd_stake_weight_t
116 0 : #define SORT_BEFORE(a,b) fd_stakes_sort_before( (a), (b) )
117 : #include "../../util/tmpl/fd_sort.c"
118 :
119 : void
120 : fd_stake_weight_sort( fd_stake_weight_t * stakes,
121 0 : ulong stakes_cnt ) {
122 0 : fd_stakes_sort_inplace( stakes, stakes_cnt );
123 0 : }
124 :
125 : /* fd_stakes_export_sorted converts StakedNodes (rbtree mapping
126 : (node identity) => (active stake) from fd_stakes_accum_by_node) to
127 : a list of fd_stake_weights_t. */
128 :
129 : static ulong
130 : fd_stakes_export( fd_stake_weight_t_mapnode_t const * const in_pool,
131 : fd_stake_weight_t_mapnode_t const * const root,
132 0 : fd_stake_weight_t * const out ) {
133 :
134 0 : fd_stake_weight_t * out_end = out;
135 :
136 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 ) ) {
137 0 : *out_end++ = ele->elem;
138 0 : }
139 :
140 0 : return (ulong)( out_end - out );
141 0 : }
142 :
143 : ulong
144 : fd_stake_weights_by_node( fd_vote_accounts_t const * accs,
145 : fd_stake_weight_t * weights,
146 0 : fd_spad_t * runtime_spad ) {
147 :
148 : /* Estimate size required to store temporary data structures */
149 :
150 : /* TODO size is the wrong method name for this */
151 0 : ulong vote_acc_cnt = fd_vote_accounts_pair_t_map_size( accs->vote_accounts_pool, accs->vote_accounts_root );
152 :
153 0 : ulong rb_align = fd_stake_weight_t_map_align();
154 0 : ulong rb_footprint = fd_stake_weight_t_map_footprint( vote_acc_cnt );
155 :
156 : /* Create rb tree */
157 :
158 0 : void * pool_mem = fd_spad_alloc( runtime_spad, rb_align, rb_footprint );
159 0 : pool_mem = fd_stake_weight_t_map_new( pool_mem, vote_acc_cnt );
160 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( pool_mem );
161 0 : if( FD_UNLIKELY( !pool_mem ) ) FD_LOG_CRIT(( "fd_stake_weights_new() failed" ));
162 :
163 : /* Accumulate stakes to rb tree */
164 :
165 0 : fd_stake_weight_t_mapnode_t const * root = fd_stakes_accum_by_node( accs, pool, runtime_spad );
166 :
167 : /* Export to sorted list */
168 :
169 0 : ulong weights_cnt = fd_stakes_export( pool, root, weights );
170 0 : fd_stake_weight_sort( weights, weights_cnt );
171 :
172 0 : return weights_cnt;
173 0 : }
174 :
175 : /* Helper function to deserialize a vote account. If successful, populates vote account info in `elem`
176 : and saves the decoded vote state in `vote_state` */
177 : static fd_vote_state_versioned_t *
178 : deserialize_and_update_vote_account( fd_exec_slot_ctx_t * slot_ctx,
179 : fd_vote_accounts_pair_t_mapnode_t * elem,
180 : fd_stake_weight_t_mapnode_t * stake_delegations_root,
181 : fd_stake_weight_t_mapnode_t * stake_delegations_pool,
182 : fd_pubkey_t const * vote_account_pubkey,
183 0 : fd_spad_t * runtime_spad ) {
184 :
185 0 : FD_TXN_ACCOUNT_DECL( vote_account );
186 0 : if( FD_UNLIKELY( fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, vote_account_pubkey, vote_account ) ) ) {
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 : fd_bincode_decode_ctx_t decode = {
193 0 : .data = vote_account->const_data,
194 0 : .dataend = vote_account->const_data + vote_account->const_meta->dlen,
195 0 : };
196 :
197 0 : ulong total_sz = 0UL;
198 0 : int err = fd_vote_state_versioned_decode_footprint( &decode, &total_sz );
199 0 : if( FD_UNLIKELY( err ) ) {
200 0 : return NULL;
201 0 : }
202 :
203 0 : uchar * mem = fd_spad_alloc( runtime_spad, fd_vote_state_versioned_align(), total_sz );
204 0 : if( FD_UNLIKELY( !mem ) ) {
205 0 : FD_LOG_ERR(( "Unable to allocate memory" ));
206 0 : }
207 :
208 0 : fd_vote_state_versioned_decode( mem, &decode );
209 :
210 : // Get the stake amount from the stake delegations map
211 0 : fd_stake_weight_t_mapnode_t temp;
212 0 : fd_memcpy( &temp.elem.key, vote_account_pubkey, sizeof(fd_pubkey_t) );
213 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( stake_delegations_pool, stake_delegations_root, &temp );
214 0 : elem->elem.stake = ( entry==NULL ) ? 0UL : entry->elem.stake;
215 :
216 0 : return (fd_vote_state_versioned_t *)mem;
217 0 : }
218 :
219 : static void
220 : compute_stake_delegations_tpool( void *tpool,
221 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
222 : void *args,
223 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
224 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
225 : ulong m0, ulong m1,
226 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
227 0 : fd_epoch_info_t * temp_info = (fd_epoch_info_t *)tpool;
228 0 : fd_compute_stake_delegations_t * task_args = (fd_compute_stake_delegations_t *)args;
229 0 : ulong worker_idx = fd_tile_idx();
230 :
231 0 : fd_spad_t * spad = task_args->spads[worker_idx];
232 0 : fd_epoch_info_pair_t const * stake_infos = temp_info->stake_infos;
233 0 : ulong epoch = task_args->epoch;
234 0 : fd_stake_history_t const * history = task_args->stake_history;
235 0 : ulong * new_rate_activation_epoch = task_args->new_rate_activation_epoch;
236 0 : fd_stake_weight_t_mapnode_t * delegation_pool = task_args->delegation_pool;
237 0 : fd_stake_weight_t_mapnode_t * delegation_root = task_args->delegation_root;
238 0 : ulong vote_states_pool_sz = task_args->vote_states_pool_sz;
239 :
240 0 : FD_SPAD_FRAME_BEGIN( spad ) {
241 :
242 : /* Create a temporary <pubkey, stake> map to hold delegations */
243 0 : void * mem = fd_spad_alloc( spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
244 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 ) );
245 0 : fd_stake_weight_t_mapnode_t * temp_root = NULL;
246 :
247 0 : fd_stake_weight_t_mapnode_t temp;
248 0 : for( ulong i=m0; i<m1; i++ ) {
249 0 : fd_delegation_t const * delegation = &stake_infos[i].stake.delegation;
250 0 : fd_memcpy( &temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t) );
251 :
252 : // Skip any delegations that are not in the delegation pool
253 0 : fd_stake_weight_t_mapnode_t * delegation_entry = fd_stake_weight_t_map_find( delegation_pool, delegation_root, &temp );
254 0 : if( FD_UNLIKELY( delegation_entry==NULL ) ) {
255 0 : continue;
256 0 : }
257 :
258 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
259 0 : delegation_entry = fd_stake_weight_t_map_find( temp_pool, temp_root, &temp );
260 0 : if( FD_UNLIKELY( delegation_entry==NULL ) ) {
261 0 : delegation_entry = fd_stake_weight_t_map_acquire( temp_pool );
262 0 : fd_memcpy( &delegation_entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t) );
263 0 : delegation_entry->elem.stake = new_entry.effective;
264 0 : fd_stake_weight_t_map_insert( temp_pool, &temp_root, delegation_entry );
265 0 : } else {
266 0 : delegation_entry->elem.stake += new_entry.effective;
267 0 : }
268 0 : }
269 :
270 : // Update the parent delegation pool with the calculated delegation values
271 0 : for( fd_stake_weight_t_mapnode_t * elem = fd_stake_weight_t_map_minimum( temp_pool, temp_root );
272 0 : elem;
273 0 : elem = fd_stake_weight_t_map_successor( temp_pool, elem ) ) {
274 0 : fd_stake_weight_t_mapnode_t * output_delegation_node = fd_stake_weight_t_map_find( delegation_pool, delegation_root, elem );
275 0 : FD_ATOMIC_FETCH_AND_ADD( &output_delegation_node->elem.stake, elem->elem.stake );
276 0 : }
277 :
278 0 : } FD_SPAD_FRAME_END;
279 0 : }
280 :
281 : /*
282 : Refresh vote accounts.
283 :
284 : This updates the epoch bank stakes vote_accounts cache - that is, the total amount
285 : of delegated stake each vote account has, using the current delegation values from inside each
286 : stake account. Contrary to the Agave equivalent, it also merges the stakes cache vote accounts with the
287 : new vote account keys from this epoch.
288 :
289 : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L562 */
290 : void
291 : fd_refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx,
292 : fd_stake_history_t const * history,
293 : ulong * new_rate_activation_epoch,
294 : fd_epoch_info_t * temp_info,
295 : fd_tpool_t * tpool,
296 : fd_spad_t * * exec_spads,
297 : ulong exec_spad_cnt,
298 0 : fd_spad_t * runtime_spad ) {
299 :
300 0 : fd_slot_bank_t * slot_bank = &slot_ctx->slot_bank;
301 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
302 0 : fd_stakes_t * stakes = &epoch_bank->stakes;
303 :
304 : // Initialize a temporary vote states cache
305 0 : ulong vote_states_pool_sz = fd_vote_accounts_pair_t_map_size( stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root )
306 0 : + fd_account_keys_pair_t_map_size( slot_bank->vote_account_keys.account_keys_pool, slot_bank->vote_account_keys.account_keys_root );
307 0 : temp_info->vote_states_root = NULL;
308 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 ) );
309 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 ) );
310 :
311 : /* Create a map of <pubkey, stake> to store the total stake of each vote account. */
312 0 : void * mem = fd_spad_alloc( runtime_spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
313 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 ) );
314 0 : fd_stake_weight_t_mapnode_t * root = NULL;
315 :
316 : /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all
317 : of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */
318 0 : for( fd_vote_accounts_pair_t_mapnode_t * elem = fd_vote_accounts_pair_t_map_minimum( stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root );
319 0 : elem;
320 0 : elem = fd_vote_accounts_pair_t_map_successor( stakes->vote_accounts.vote_accounts_pool, elem ) ) {
321 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool );
322 0 : fd_memcpy( &entry->elem.key, &elem->elem.key, sizeof(fd_pubkey_t) );
323 0 : entry->elem.stake = 0UL;
324 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
325 0 : }
326 :
327 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( slot_bank->vote_account_keys.account_keys_pool, slot_bank->vote_account_keys.account_keys_root );
328 0 : n;
329 0 : n = fd_account_keys_pair_t_map_successor( slot_bank->vote_account_keys.account_keys_pool, n ) ) {
330 0 : fd_stake_weight_t_mapnode_t temp;
331 0 : fd_memcpy( &temp.elem.key, &n->elem.key, sizeof(fd_pubkey_t) );
332 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( pool, root, &temp );
333 0 : if( FD_LIKELY( entry==NULL ) ) {
334 0 : entry = fd_stake_weight_t_map_acquire( pool );
335 0 : fd_memcpy( &entry->elem.key, &n->elem.key, sizeof(fd_pubkey_t) );
336 0 : entry->elem.stake = 0UL;
337 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
338 0 : }
339 0 : }
340 :
341 0 : ulong worker_cnt = fd_ulong_min( temp_info->stake_infos_len,
342 0 : fd_ulong_min( fd_tpool_worker_cnt( tpool ), exec_spad_cnt ) );
343 0 : fd_compute_stake_delegations_t task_args = {
344 0 : .epoch = stakes->epoch,
345 0 : .stake_history = history,
346 0 : .new_rate_activation_epoch = new_rate_activation_epoch,
347 0 : .delegation_pool = pool,
348 0 : .delegation_root = root,
349 0 : .vote_states_pool_sz = vote_states_pool_sz,
350 0 : .spads = exec_spads,
351 0 : };
352 :
353 : // Now we can iterate over each stake delegation in parallel and fill the delegations map
354 0 : fd_tpool_exec_all_batch( tpool, 0UL, worker_cnt, compute_stake_delegations_tpool, temp_info, &task_args, NULL, 1UL, 0UL, temp_info->stake_infos_len );
355 :
356 : // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool
357 0 : ulong total_epoch_stake = 0UL;
358 0 : for( fd_vote_accounts_pair_t_mapnode_t * elem = fd_vote_accounts_pair_t_map_minimum( stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root );
359 0 : elem;
360 0 : elem = fd_vote_accounts_pair_t_map_successor( stakes->vote_accounts.vote_accounts_pool, elem ) ) {
361 0 : fd_pubkey_t const * vote_account_pubkey = &elem->elem.key;
362 0 : fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx,
363 0 : elem,
364 0 : root,
365 0 : pool,
366 0 : vote_account_pubkey,
367 0 : runtime_spad );
368 0 : if( FD_LIKELY( vote_state ) ) {
369 0 : total_epoch_stake += elem->elem.stake;
370 : // Insert into the temporary vote states cache
371 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
372 0 : fd_memcpy( &new_vote_state_node->elem.account, vote_account_pubkey, sizeof(fd_pubkey_t) );
373 0 : fd_memcpy( &new_vote_state_node->elem.state, vote_state, sizeof(fd_vote_state_versioned_t) );
374 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
375 0 : } else {
376 0 : FD_LOG_WARNING(( "Failed to deserialize vote account" ));
377 0 : }
378 0 : }
379 :
380 : // Update the epoch stakes cache with new vote accounts from the epoch
381 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( slot_bank->vote_account_keys.account_keys_pool, slot_bank->vote_account_keys.account_keys_root );
382 0 : n;
383 0 : n = fd_account_keys_pair_t_map_successor( slot_bank->vote_account_keys.account_keys_pool, n ) ) {
384 :
385 0 : fd_pubkey_t const * vote_account_pubkey = &n->elem.key;
386 0 : fd_vote_accounts_pair_t_mapnode_t key;
387 0 : fd_memcpy( &key.elem.key, vote_account_pubkey, sizeof(fd_pubkey_t) );
388 :
389 : /* No need to process duplicate vote account keys. This is a mostly redundant check
390 : since upserting vote accounts also checks against the vote stakes, but this is
391 : there anyways in case that ever changes */
392 0 : if( FD_UNLIKELY( fd_vote_accounts_pair_t_map_find( stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root, &key ) ) ) {
393 0 : continue;
394 0 : }
395 :
396 0 : fd_vote_accounts_pair_t_mapnode_t * new_vote_node = fd_vote_accounts_pair_t_map_acquire( stakes->vote_accounts.vote_accounts_pool );
397 0 : fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx,
398 0 : new_vote_node,
399 0 : root,
400 0 : pool,
401 0 : vote_account_pubkey,
402 0 : runtime_spad );
403 :
404 0 : if( FD_UNLIKELY( !vote_state ) ) {
405 0 : fd_vote_accounts_pair_t_map_release( stakes->vote_accounts.vote_accounts_pool, new_vote_node );
406 0 : continue;
407 0 : }
408 :
409 : // Insert into the epoch stakes cache and temporary vote states cache
410 0 : fd_vote_accounts_pair_t_map_insert( stakes->vote_accounts.vote_accounts_pool, &stakes->vote_accounts.vote_accounts_root, new_vote_node );
411 0 : total_epoch_stake += new_vote_node->elem.stake;
412 :
413 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
414 0 : fd_memcpy( &new_vote_state_node->elem.account, vote_account_pubkey, sizeof(fd_pubkey_t) );
415 0 : fd_memcpy( &new_vote_state_node->elem.state, vote_state, sizeof(fd_vote_state_versioned_t) );
416 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
417 0 : }
418 :
419 0 : slot_ctx->epoch_ctx->total_epoch_stake = total_epoch_stake;
420 :
421 0 : fd_account_keys_pair_t_map_release_tree( slot_bank->vote_account_keys.account_keys_pool, slot_bank->vote_account_keys.account_keys_root );
422 0 : slot_bank->vote_account_keys.account_keys_root = NULL;
423 0 : }
424 :
425 : static void
426 : accumulate_stake_cache_delegations_tpool( void *tpool,
427 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
428 : void *args,
429 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
430 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
431 : ulong m0 FD_PARAM_UNUSED, ulong m1 FD_PARAM_UNUSED,
432 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
433 0 : ulong worker_idx = fd_tile_idx();
434 0 : fd_delegation_pair_t_mapnode_t ** delegations_roots = (fd_delegation_pair_t_mapnode_t **)tpool;
435 0 : fd_accumulate_delegations_task_args_t * task_args = (fd_accumulate_delegations_task_args_t *)args;
436 :
437 0 : fd_exec_slot_ctx_t const * slot_ctx = task_args->slot_ctx;
438 0 : fd_stake_history_t const * history = task_args->stake_history;
439 0 : ulong * new_rate_activation_epoch = task_args->new_rate_activation_epoch;
440 0 : fd_stake_history_entry_t * accumulator = task_args->accumulator;
441 0 : fd_spad_t * spad = task_args->spads[worker_idx];
442 0 : fd_delegation_pair_t_mapnode_t * delegations_pool = task_args->stake_delegations_pool;
443 0 : fd_epoch_info_t * temp_info = task_args->temp_info;
444 0 : ulong epoch = task_args->epoch;
445 :
446 0 : ulong effective = 0UL;
447 0 : ulong activating = 0UL;
448 0 : ulong deactivating = 0UL;
449 :
450 0 : FD_SPAD_FRAME_BEGIN( spad ) {
451 0 : for( fd_delegation_pair_t_mapnode_t * n = delegations_roots[worker_idx];
452 0 : n != delegations_roots[worker_idx+1];
453 0 : n = fd_delegation_pair_t_map_successor( delegations_pool, n ) ) {
454 :
455 0 : FD_TXN_ACCOUNT_DECL( acc );
456 0 : int rc = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.account, acc );
457 0 : if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports==0UL ) ) {
458 0 : continue;
459 0 : }
460 :
461 0 : fd_stake_state_v2_t stake_state;
462 0 : rc = fd_stake_get_state( acc, &stake_state );
463 0 : if( FD_UNLIKELY( rc != 0 ) ) {
464 0 : continue;
465 0 : }
466 :
467 0 : if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
468 0 : continue;
469 0 : }
470 :
471 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake == 0 ) ) {
472 0 : continue;
473 0 : }
474 :
475 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
476 :
477 0 : ulong delegation_idx = FD_ATOMIC_FETCH_AND_ADD( &temp_info->stake_infos_len, 1UL );
478 0 : fd_memcpy( &temp_info->stake_infos[delegation_idx].stake, &stake_state.inner.stake.stake, sizeof(fd_stake_t) );
479 0 : fd_memcpy( &temp_info->stake_infos[delegation_idx].account, &n->elem.account, sizeof(fd_pubkey_t) );
480 :
481 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
482 0 : effective += new_entry.effective;
483 0 : activating += new_entry.activating;
484 0 : deactivating += new_entry.deactivating;
485 0 : }
486 :
487 0 : FD_ATOMIC_FETCH_AND_ADD( &accumulator->effective, effective );
488 0 : FD_ATOMIC_FETCH_AND_ADD( &accumulator->activating, activating );
489 0 : FD_ATOMIC_FETCH_AND_ADD( &accumulator->deactivating, deactivating );
490 :
491 0 : } FD_SPAD_FRAME_END;
492 0 : }
493 :
494 : /* Accumulates information about epoch stakes into `temp_info`, which is a temporary cache
495 : used to save intermediate state about stake and vote accounts to avoid them from having to
496 : be recomputed on every access, especially at the epoch boundary. Also collects stats in `accumulator` */
497 : void
498 : fd_accumulate_stake_infos( fd_exec_slot_ctx_t const * slot_ctx,
499 : fd_stakes_t const * stakes,
500 : fd_stake_history_t const * history,
501 : ulong * new_rate_activation_epoch,
502 : fd_stake_history_entry_t * accumulator,
503 : fd_epoch_info_t * temp_info,
504 : fd_tpool_t * tpool,
505 : fd_spad_t * * exec_spads,
506 : ulong exec_spads_cnt,
507 0 : fd_spad_t * runtime_spad ) {
508 :
509 0 : ulong stake_delegations_pool_sz = fd_delegation_pair_t_map_size( stakes->stake_delegations_pool, stakes->stake_delegations_root );
510 0 : if( FD_UNLIKELY( stake_delegations_pool_sz==0UL ) ) {
511 0 : return;
512 0 : }
513 :
514 : /* Batch up the stake info accumulations via tpool. Currently this is only marginally more efficient because we
515 : do not have access to iterators at a specific index in constant or logarithmic time. */
516 0 : ulong worker_cnt = fd_ulong_min( stake_delegations_pool_sz,
517 0 : fd_ulong_min( fd_tpool_worker_cnt( tpool ), exec_spads_cnt ) );
518 0 : fd_delegation_pair_t_mapnode_t ** batch_delegation_roots = fd_spad_alloc( runtime_spad, alignof(fd_delegation_pair_t_mapnode_t *),
519 0 : ( worker_cnt + 1 )*sizeof(fd_delegation_pair_t_mapnode_t *) );
520 :
521 0 : ulong * idx_starts = fd_spad_alloc( runtime_spad, alignof(ulong), worker_cnt * sizeof(ulong) );
522 :
523 : // Determine the logical index partitioning of the delegations pool so we know where to start iterating from
524 0 : for( ulong i=0UL; i<worker_cnt; i++ ) {
525 0 : ulong _idx_end;
526 0 : FD_TPOOL_PARTITION( 0UL, stake_delegations_pool_sz, 1UL, i, worker_cnt, idx_starts[i], _idx_end );
527 0 : (void)_idx_end;
528 0 : }
529 :
530 0 : ulong batch_idx = 0UL;
531 0 : ulong iter_idx = 0UL;
532 0 : for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum( stakes->stake_delegations_pool, stakes->stake_delegations_root );
533 0 : n;
534 0 : n = fd_delegation_pair_t_map_successor( stakes->stake_delegations_pool, n ) ) {
535 0 : if( iter_idx++==idx_starts[batch_idx] ) {
536 0 : batch_delegation_roots[batch_idx++] = n;
537 0 : }
538 0 : }
539 0 : batch_delegation_roots[worker_cnt] = NULL;
540 :
541 0 : fd_accumulate_delegations_task_args_t task_args = {
542 0 : .slot_ctx = slot_ctx,
543 0 : .stake_history = history,
544 0 : .new_rate_activation_epoch = new_rate_activation_epoch,
545 0 : .accumulator = accumulator,
546 0 : .temp_info = temp_info,
547 0 : .spads = exec_spads,
548 0 : .stake_delegations_pool = stakes->stake_delegations_pool,
549 0 : .epoch = stakes->epoch,
550 0 : };
551 :
552 0 : fd_tpool_exec_all_batch( tpool, 0UL, worker_cnt, accumulate_stake_cache_delegations_tpool, batch_delegation_roots, &task_args, NULL, 1UL, 0UL, stake_delegations_pool_sz );
553 0 : temp_info->stake_infos_new_keys_start_idx = temp_info->stake_infos_len;
554 :
555 : /* The number of account keys aggregated across the epoch is usually small, so there aren't much performance gains from tpooling here. */
556 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( slot_ctx->slot_bank.stake_account_keys.account_keys_pool, slot_ctx->slot_bank.stake_account_keys.account_keys_root );
557 0 : n;
558 0 : n = fd_account_keys_pair_t_map_successor( slot_ctx->slot_bank.stake_account_keys.account_keys_pool, n ) ) {
559 0 : FD_TXN_ACCOUNT_DECL( acc );
560 0 : int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.key, acc);
561 0 : if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports==0UL ) ) {
562 0 : continue;
563 0 : }
564 :
565 0 : fd_stake_state_v2_t stake_state;
566 0 : rc = fd_stake_get_state( acc, &stake_state );
567 0 : if( FD_UNLIKELY( rc != 0) ) {
568 0 : continue;
569 0 : }
570 :
571 0 : if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
572 0 : continue;
573 0 : }
574 :
575 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake==0UL ) ) {
576 0 : continue;
577 0 : }
578 :
579 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
580 0 : fd_memcpy(&temp_info->stake_infos[temp_info->stake_infos_len ].stake.delegation, &stake_state.inner.stake.stake, sizeof(fd_stake_t));
581 0 : fd_memcpy(&temp_info->stake_infos[temp_info->stake_infos_len++].account, &n->elem.key, sizeof(fd_pubkey_t));
582 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
583 0 : accumulator->effective += new_entry.effective;
584 0 : accumulator->activating += new_entry.activating;
585 0 : accumulator->deactivating += new_entry.deactivating;
586 0 : }
587 :
588 0 : }
589 :
590 : /* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */
591 : void
592 : fd_stakes_activate_epoch( fd_exec_slot_ctx_t * slot_ctx,
593 : ulong * new_rate_activation_epoch,
594 : fd_epoch_info_t * temp_info,
595 : fd_tpool_t * tpool,
596 : fd_spad_t * * exec_spads,
597 : ulong exec_spad_cnt,
598 0 : fd_spad_t * runtime_spad ) {
599 :
600 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
601 0 : fd_stakes_t * stakes = &epoch_bank->stakes;
602 :
603 : /* Current stake delegations: list of all current delegations in stake_delegations
604 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */
605 : /* Add a new entry to the Stake History sysvar for the previous epoch
606 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */
607 :
608 0 : fd_stake_history_t const * history = (fd_stake_history_t const *)fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache );
609 0 : if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
610 :
611 0 : ulong stake_delegations_size = fd_delegation_pair_t_map_size(
612 0 : stakes->stake_delegations_pool, stakes->stake_delegations_root );
613 0 : stake_delegations_size += fd_account_keys_pair_t_map_size(
614 0 : slot_ctx->slot_bank.stake_account_keys.account_keys_pool, slot_ctx->slot_bank.stake_account_keys.account_keys_root );
615 0 : temp_info->stake_infos_len = 0UL;
616 0 : temp_info->stake_infos = (fd_epoch_info_pair_t *)fd_spad_alloc( runtime_spad, FD_EPOCH_INFO_PAIR_ALIGN, FD_EPOCH_INFO_PAIR_FOOTPRINT*stake_delegations_size );
617 0 : fd_memset( temp_info->stake_infos, 0, FD_EPOCH_INFO_PAIR_FOOTPRINT*stake_delegations_size );
618 :
619 0 : fd_stake_history_entry_t accumulator = {
620 0 : .effective = 0UL,
621 0 : .activating = 0UL,
622 0 : .deactivating = 0UL
623 0 : };
624 :
625 : /* Accumulate stats for stake accounts */
626 0 : fd_accumulate_stake_infos( slot_ctx,
627 0 : stakes,
628 0 : history,
629 0 : new_rate_activation_epoch,
630 0 : &accumulator,
631 0 : temp_info,
632 0 : tpool,
633 0 : exec_spads,
634 0 : exec_spad_cnt,
635 0 : runtime_spad );
636 :
637 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L359 */
638 0 : fd_stake_history_entry_t new_elem = {
639 0 : .epoch = stakes->epoch,
640 0 : .effective = accumulator.effective,
641 0 : .activating = accumulator.activating,
642 0 : .deactivating = accumulator.deactivating
643 0 : };
644 :
645 0 : fd_sysvar_stake_history_update( slot_ctx, &new_elem, runtime_spad );
646 :
647 : /* Refresh the sysvar cache stake history entry after updating the sysvar.
648 : We need to do this here because it is used in subsequent places in the epoch boundary. */
649 : // fd_stake_history_destroy( slot_ctx->sysvar_cache->val_stake_history );
650 : /* TODO:FIXME: FIX THIS*/
651 0 : fd_sysvar_cache_restore_stake_history( slot_ctx->sysvar_cache,
652 0 : slot_ctx->acc_mgr,
653 0 : slot_ctx->funk_txn,
654 0 : runtime_spad,
655 0 : slot_ctx->runtime_wksp );
656 :
657 0 : }
658 :
659 : int
660 : write_stake_state( fd_txn_account_t * stake_acc_rec,
661 0 : fd_stake_state_v2_t * stake_state ) {
662 :
663 0 : ulong encoded_stake_state_size = fd_stake_state_v2_size(stake_state);
664 :
665 0 : fd_bincode_encode_ctx_t ctx = {
666 0 : .data = stake_acc_rec->data,
667 0 : .dataend = stake_acc_rec->data + encoded_stake_state_size,
668 0 : };
669 0 : if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx ) != FD_BINCODE_SUCCESS ) ) {
670 0 : FD_LOG_ERR(( "fd_stake_state_encode failed" ));
671 0 : }
672 :
673 0 : return 0;
674 0 : }
|