Line data Source code
1 : #include "fd_stakes.h"
2 : #include "../runtime/fd_acc_mgr.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 : ulong
8 : fd_stake_weights_by_node( fd_vote_accounts_global_t const * accs,
9 0 : fd_vote_stake_weight_t * weights ) {
10 0 : fd_vote_accounts_pair_global_t_mapnode_t * pool = fd_vote_accounts_vote_accounts_pool_join( accs );
11 0 : fd_vote_accounts_pair_global_t_mapnode_t * root = fd_vote_accounts_vote_accounts_root_join( accs );
12 :
13 : /* For each active vote account, return (vote_key, node_identity, stake), sorted by (stake, vote) */
14 0 : ulong weights_cnt = 0;
15 0 : for( fd_vote_accounts_pair_global_t_mapnode_t * n = fd_vote_accounts_pair_global_t_map_minimum( pool, root );
16 0 : n;
17 0 : n = fd_vote_accounts_pair_global_t_map_successor( pool, n ) ) {
18 :
19 : /* ... filter(|(stake, _)| *stake != 0u64) */
20 0 : if( n->elem.stake == 0UL ) continue;
21 :
22 : /* Copy output values */
23 0 : memcpy( weights[ weights_cnt ].vote_key.uc, n->elem.key.uc, sizeof(fd_pubkey_t) );
24 0 : weights[ weights_cnt ].stake = n->elem.stake;
25 0 : uchar * vote_account_data = fd_solana_account_data_join( &n->elem.value );
26 : /* node_pubkey is at offset 4, no need to fully deserialize the account(s) */
27 0 : memcpy( weights[ weights_cnt ].id_key.uc, vote_account_data+4UL, sizeof(fd_pubkey_t) );
28 0 : weights_cnt++;
29 0 : }
30 :
31 0 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
32 0 : return weights_cnt;
33 0 : }
34 :
35 : /* Helper function to deserialize a vote account. If successful, populates vote account info in `elem`
36 : and saves the decoded vote state in `vote_state` */
37 : static fd_vote_state_versioned_t *
38 : deserialize_and_update_vote_account( fd_exec_slot_ctx_t * slot_ctx,
39 : fd_vote_accounts_pair_global_t_mapnode_t * elem,
40 : fd_stake_weight_t_mapnode_t * stake_delegations_root,
41 : fd_stake_weight_t_mapnode_t * stake_delegations_pool,
42 : fd_pubkey_t const * vote_account_pubkey,
43 0 : fd_spad_t * runtime_spad ) {
44 :
45 0 : FD_TXN_ACCOUNT_DECL( vote_account );
46 0 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( vote_account,
47 0 : vote_account_pubkey,
48 0 : slot_ctx->funk,
49 0 : slot_ctx->funk_txn ) ) ) {
50 0 : FD_LOG_DEBUG(( "Vote account not found" ));
51 0 : return NULL;
52 0 : }
53 :
54 : // Deserialize the vote account and ensure its in the correct state
55 0 : int err;
56 0 : fd_vote_state_versioned_t * res = fd_bincode_decode_spad(
57 0 : vote_state_versioned, runtime_spad,
58 0 : vote_account->vt->get_data( vote_account ),
59 0 : vote_account->vt->get_data_len( vote_account ),
60 0 : &err );
61 0 : if( FD_UNLIKELY( err ) ) {
62 0 : return NULL;
63 0 : }
64 :
65 : // Get the stake amount from the stake delegations map
66 0 : fd_stake_weight_t_mapnode_t temp;
67 0 : temp.elem.key = *vote_account_pubkey;
68 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( stake_delegations_pool, stake_delegations_root, &temp );
69 0 : elem->elem.stake = ( entry==NULL ) ? 0UL : entry->elem.stake;
70 :
71 0 : return res;
72 0 : }
73 :
74 : static void
75 : compute_stake_delegations(
76 : fd_epoch_info_t * temp_info,
77 : ulong const epoch,
78 : fd_stake_history_t const * history,
79 : ulong * new_rate_activation_epoch,
80 : fd_stake_weight_t_mapnode_t * delegation_pool,
81 : fd_stake_weight_t_mapnode_t * delegation_root,
82 : ulong vote_states_pool_sz,
83 : fd_spad_t * spad,
84 : ulong end_idx
85 0 : ) {
86 0 : fd_epoch_info_pair_t const * stake_infos = temp_info->stake_infos;
87 :
88 0 : FD_SPAD_FRAME_BEGIN( spad ) {
89 :
90 : /* Create a temporary <pubkey, stake> map to hold delegations */
91 0 : void * mem = fd_spad_alloc( spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
92 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 ) );
93 0 : fd_stake_weight_t_mapnode_t * temp_root = NULL;
94 :
95 0 : fd_stake_weight_t_mapnode_t temp;
96 0 : for( ulong i=0UL; i<end_idx; i++ ) {
97 0 : fd_delegation_t const * delegation = &stake_infos[i].stake.delegation;
98 0 : temp.elem.key = delegation->voter_pubkey;
99 :
100 : // Skip any delegations that are not in the delegation pool
101 0 : fd_stake_weight_t_mapnode_t * delegation_entry = fd_stake_weight_t_map_find( delegation_pool, delegation_root, &temp );
102 0 : if( FD_UNLIKELY( delegation_entry==NULL ) ) {
103 0 : continue;
104 0 : }
105 :
106 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
107 0 : delegation_entry = fd_stake_weight_t_map_find( temp_pool, temp_root, &temp );
108 0 : if( FD_UNLIKELY( delegation_entry==NULL ) ) {
109 0 : delegation_entry = fd_stake_weight_t_map_acquire( temp_pool );
110 0 : delegation_entry->elem.key = delegation->voter_pubkey;
111 0 : delegation_entry->elem.stake = new_entry.effective;
112 0 : fd_stake_weight_t_map_insert( temp_pool, &temp_root, delegation_entry );
113 0 : } else {
114 0 : delegation_entry->elem.stake += new_entry.effective;
115 0 : }
116 0 : }
117 :
118 : // Update the parent delegation pool with the calculated delegation values
119 0 : for( fd_stake_weight_t_mapnode_t * elem = fd_stake_weight_t_map_minimum( temp_pool, temp_root );
120 0 : elem;
121 0 : elem = fd_stake_weight_t_map_successor( temp_pool, elem ) ) {
122 0 : fd_stake_weight_t_mapnode_t * output_delegation_node = fd_stake_weight_t_map_find( delegation_pool, delegation_root, elem );
123 0 : output_delegation_node->elem.stake += elem->elem.stake;
124 0 : }
125 :
126 0 : } FD_SPAD_FRAME_END;
127 :
128 0 : }
129 :
130 :
131 : /* Populates vote accounts with updated delegated stake from the next cached epoch stakes into temp_info */
132 : void
133 : fd_populate_vote_accounts( fd_exec_slot_ctx_t * slot_ctx,
134 : fd_stake_history_t const * history,
135 : ulong * new_rate_activation_epoch,
136 : fd_epoch_info_t * temp_info,
137 0 : fd_spad_t * runtime_spad ) {
138 :
139 :
140 : /* Initialize a temporary vote states cache */
141 0 : fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank );
142 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
143 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
144 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;
145 :
146 0 : fd_stakes_global_t const * stakes = fd_bank_stakes_locking_query( slot_ctx->bank );
147 0 : fd_vote_accounts_global_t const * vote_accounts = &stakes->vote_accounts;
148 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts );
149 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts );
150 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;
151 :
152 0 : ulong vote_states_pool_sz = vote_accounts_stakes_map_sz + vote_account_keys_map_sz;
153 0 : temp_info->vote_states_root = NULL;
154 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 ) );
155 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 ) );
156 :
157 : /* Create a map of <pubkey, stake> to store the total stake of each vote account. */
158 0 : void * mem = fd_spad_alloc( runtime_spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
159 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 ) );
160 0 : fd_stake_weight_t_mapnode_t * root = NULL;
161 :
162 : /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all
163 : of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */
164 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 );
165 0 : elem;
166 0 : elem = fd_vote_accounts_pair_global_t_map_successor( vote_accounts_pool, elem ) ) {
167 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool );
168 0 : entry->elem.key = elem->elem.key;
169 0 : entry->elem.stake = 0UL;
170 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
171 0 : }
172 :
173 0 : fd_bank_stakes_end_locking_query( slot_ctx->bank );
174 :
175 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 );
176 0 : n;
177 0 : n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
178 0 : fd_stake_weight_t_mapnode_t temp;
179 0 : temp.elem.key = n->elem.key;
180 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( pool, root, &temp );
181 0 : if( FD_LIKELY( entry==NULL ) ) {
182 0 : entry = fd_stake_weight_t_map_acquire( pool );
183 0 : entry->elem.key = n->elem.key;
184 0 : entry->elem.stake = 0UL;
185 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
186 0 : }
187 0 : }
188 :
189 0 : fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank );
190 :
191 0 : compute_stake_delegations(
192 0 : temp_info,
193 0 : stakes->epoch,
194 0 : history,
195 0 : new_rate_activation_epoch,
196 0 : pool,
197 0 : root,
198 0 : vote_states_pool_sz,
199 0 : runtime_spad,
200 0 : temp_info->stake_infos_len
201 0 : );
202 :
203 : // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool
204 : /* NOTE: we use epoch_bank->next_epoch_stakes because Agave indexes their epoch stakes cache by leader schedule epoch.
205 : This means that the epoch stakes for epoch E are indexed by epoch E+1.
206 : This is just a workaround for now.
207 : https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L309 */
208 0 : ulong total_epoch_stake = 0UL;
209 :
210 0 : fd_vote_accounts_global_t const * next_epoch_stakes = fd_bank_next_epoch_stakes_locking_query( slot_ctx->bank );
211 0 : fd_vote_accounts_pair_global_t_mapnode_t * next_epoch_stakes_pool = fd_vote_accounts_vote_accounts_pool_join( next_epoch_stakes );
212 0 : fd_vote_accounts_pair_global_t_mapnode_t * next_epoch_stakes_root = fd_vote_accounts_vote_accounts_root_join( next_epoch_stakes );
213 :
214 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 );
215 0 : elem;
216 0 : elem = fd_vote_accounts_pair_global_t_map_successor( next_epoch_stakes_pool, elem ) ) {
217 0 : fd_pubkey_t const * vote_account_pubkey = &elem->elem.key;
218 0 : FD_TXN_ACCOUNT_DECL( acc );
219 0 : int rc = fd_txn_account_init_from_funk_readonly( acc, vote_account_pubkey, slot_ctx->funk, slot_ctx->funk_txn );
220 0 : FD_TEST( rc == 0 );
221 0 : uchar * data = fd_solana_account_data_join( &elem->elem.value );
222 0 : ulong data_len = elem->elem.value.data_len;
223 :
224 0 : int err;
225 0 : fd_vote_state_versioned_t * vote_state = fd_bincode_decode_spad( vote_state_versioned,
226 0 : runtime_spad,
227 0 : data,
228 0 : data_len,
229 0 : &err );
230 :
231 0 : if( FD_LIKELY( vote_state ) ) {
232 0 : total_epoch_stake += elem->elem.stake;
233 : // Insert into the temporary vote states cache
234 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
235 0 : new_vote_state_node->elem.account = *vote_account_pubkey;
236 0 : new_vote_state_node->elem.state = *vote_state;
237 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
238 0 : } else {
239 0 : FD_LOG_WARNING(( "Failed to deserialize vote account" ));
240 0 : }
241 0 : }
242 0 : fd_bank_next_epoch_stakes_end_locking_query( slot_ctx->bank );
243 :
244 0 : fd_bank_total_epoch_stake_set( slot_ctx->bank, total_epoch_stake );
245 0 : }
246 :
247 : /*
248 : Refresh vote accounts.
249 :
250 : This updates the epoch bank stakes vote_accounts cache - that is, the total amount
251 : of delegated stake each vote account has, using the current delegation values from inside each
252 : stake account. Contrary to the Agave equivalent, it also merges the stakes cache vote accounts with the
253 : new vote account keys from this epoch.
254 :
255 : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L562 */
256 : void
257 : fd_refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx,
258 : fd_stake_history_t const * history,
259 : ulong * new_rate_activation_epoch,
260 : fd_epoch_info_t * temp_info,
261 0 : fd_spad_t * runtime_spad ) {
262 :
263 0 : fd_stakes_global_t * stakes = fd_bank_stakes_locking_modify( slot_ctx->bank );
264 0 : fd_vote_accounts_global_t * vote_accounts = &stakes->vote_accounts;
265 0 : fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts );
266 0 : fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts );
267 :
268 0 : fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank );
269 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
270 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
271 :
272 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;
273 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;
274 0 : ulong vote_states_pool_sz = vote_accounts_stakes_map_sz + vote_account_keys_map_sz;
275 :
276 : /* Initialize a temporary vote states cache */
277 0 : temp_info->vote_states_root = NULL;
278 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 ) );
279 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 ) );
280 :
281 : /* Create a map of <pubkey, stake> to store the total stake of each vote account. */
282 0 : void * mem = fd_spad_alloc( runtime_spad, fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint( vote_states_pool_sz ) );
283 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 ) );
284 0 : fd_stake_weight_t_mapnode_t * root = NULL;
285 :
286 : /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all
287 : of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */
288 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 );
289 0 : elem;
290 0 : elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) {
291 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool );
292 0 : entry->elem.key = elem->elem.key;
293 0 : entry->elem.stake = 0UL;
294 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
295 0 : }
296 :
297 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 );
298 0 : n;
299 0 : n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
300 0 : fd_stake_weight_t_mapnode_t temp;
301 0 : temp.elem.key = n->elem.key;
302 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find( pool, root, &temp );
303 0 : if( FD_LIKELY( entry==NULL ) ) {
304 0 : entry = fd_stake_weight_t_map_acquire( pool );
305 0 : entry->elem.key = n->elem.key;
306 0 : entry->elem.stake = 0UL;
307 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
308 0 : }
309 0 : }
310 :
311 0 : compute_stake_delegations(
312 0 : temp_info,
313 0 : stakes->epoch,
314 0 : history,
315 0 : new_rate_activation_epoch,
316 0 : pool,
317 0 : root,
318 0 : vote_states_pool_sz,
319 0 : runtime_spad,
320 0 : temp_info->stake_infos_len
321 0 : );
322 :
323 : // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool
324 0 : ulong total_epoch_stake = 0UL;
325 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 );
326 0 : elem;
327 0 : elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) {
328 :
329 0 : fd_pubkey_t const * vote_account_pubkey = &elem->elem.key;
330 0 : fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx,
331 0 : elem,
332 0 : root,
333 0 : pool,
334 0 : vote_account_pubkey,
335 0 : runtime_spad );
336 0 : if( FD_LIKELY( vote_state ) ) {
337 0 : total_epoch_stake += elem->elem.stake;
338 : // Insert into the temporary vote states cache
339 : /* FIXME: This copy copies over some local pointers, which means
340 : that the allocation done when deserializing the vote account
341 : is not freed until the end of the epoch boundary processing. */
342 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
343 0 : new_vote_state_node->elem.account = *vote_account_pubkey;
344 0 : new_vote_state_node->elem.state = *vote_state;
345 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
346 0 : } else {
347 0 : FD_LOG_WARNING(( "Failed to deserialize vote account" ));
348 0 : }
349 0 : }
350 :
351 : // Update the epoch stakes cache with new vote accounts from the epoch
352 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 );
353 0 : n;
354 0 : n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) {
355 :
356 0 : fd_pubkey_t const * vote_account_pubkey = &n->elem.key;
357 0 : fd_vote_accounts_pair_global_t_mapnode_t key;
358 0 : key.elem.key = *vote_account_pubkey;
359 :
360 : /* No need to process duplicate vote account keys. This is a mostly redundant check
361 : since upserting vote accounts also checks against the vote stakes, but this is
362 : there anyways in case that ever changes */
363 0 : if( FD_UNLIKELY( fd_vote_accounts_pair_global_t_map_find( stakes_vote_accounts_pool, stakes_vote_accounts_root, &key ) ) ) {
364 0 : continue;
365 0 : }
366 :
367 0 : fd_vote_accounts_pair_global_t_mapnode_t * new_vote_node = fd_vote_accounts_pair_global_t_map_acquire( stakes_vote_accounts_pool );
368 0 : fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx,
369 0 : new_vote_node,
370 0 : root,
371 0 : pool,
372 0 : vote_account_pubkey,
373 0 : runtime_spad );
374 :
375 0 : if( FD_UNLIKELY( !vote_state ) ) {
376 0 : fd_vote_accounts_pair_global_t_map_release( stakes_vote_accounts_pool, new_vote_node );
377 0 : continue;
378 0 : }
379 :
380 : // Insert into the epoch stakes cache and temporary vote states cache
381 0 : fd_vote_accounts_pair_global_t_map_insert( stakes_vote_accounts_pool, &stakes_vote_accounts_root, new_vote_node );
382 0 : total_epoch_stake += new_vote_node->elem.stake;
383 :
384 0 : fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool );
385 0 : new_vote_state_node->elem.account = *vote_account_pubkey;
386 0 : new_vote_state_node->elem.state = *vote_state;
387 0 : fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node );
388 0 : }
389 0 : fd_vote_accounts_vote_accounts_pool_update( &stakes->vote_accounts, stakes_vote_accounts_pool );
390 0 : fd_vote_accounts_vote_accounts_root_update( &stakes->vote_accounts, stakes_vote_accounts_root );
391 :
392 0 : fd_bank_stakes_end_locking_modify( slot_ctx->bank );
393 :
394 0 : fd_bank_total_epoch_stake_set( slot_ctx->bank, total_epoch_stake );
395 :
396 : /* At this point, we need to flush the vote account keys cache */
397 0 : vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys );
398 0 : vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys );
399 0 : fd_account_keys_pair_t_map_release_tree( vote_account_keys_pool, vote_account_keys_root );
400 0 : vote_account_keys_root = NULL;
401 0 : fd_account_keys_account_keys_pool_update( vote_account_keys, vote_account_keys_pool );
402 0 : fd_account_keys_account_keys_root_update( vote_account_keys, vote_account_keys_root );
403 0 : fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank );
404 0 : }
405 :
406 : static void
407 : accumulate_stake_cache_delegations(
408 : fd_delegation_pair_t_mapnode_t * delegation_min,
409 : fd_exec_slot_ctx_t const * slot_ctx,
410 : fd_stake_history_t const * history,
411 : ulong * new_rate_activation_epoch,
412 : fd_stake_history_entry_t * accumulator,
413 : fd_delegation_pair_t_mapnode_t * delegations_pool,
414 : fd_epoch_info_t * temp_info,
415 : ulong epoch
416 0 : ) {
417 0 : ulong effective = 0UL;
418 0 : ulong activating = 0UL;
419 0 : ulong deactivating = 0UL;
420 :
421 0 : for( fd_delegation_pair_t_mapnode_t * n = delegation_min;
422 0 : n != NULL;
423 0 : n = fd_delegation_pair_t_map_successor( delegations_pool, n ) ) {
424 :
425 0 : FD_TXN_ACCOUNT_DECL( acc );
426 0 : int rc = fd_txn_account_init_from_funk_readonly( acc,
427 0 : &n->elem.account,
428 0 : slot_ctx->funk,
429 0 : slot_ctx->funk_txn );
430 0 : if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->vt->get_lamports( acc )==0UL ) ) {
431 0 : FD_LOG_WARNING(("Failed to init account"));
432 0 : continue;
433 0 : }
434 :
435 0 : fd_stake_state_v2_t stake_state;
436 0 : rc = fd_stake_get_state( acc, &stake_state );
437 0 : if( FD_UNLIKELY( rc != 0 ) ) {
438 0 : FD_LOG_WARNING(("Failed to get stake state"));
439 0 : continue;
440 0 : }
441 :
442 0 : if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
443 0 : FD_LOG_WARNING(("Not a stake"));
444 0 : continue;
445 0 : }
446 :
447 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake == 0 ) ) {
448 0 : continue;
449 0 : }
450 :
451 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
452 :
453 0 : ulong delegation_idx = temp_info->stake_infos_len++;
454 0 : temp_info->stake_infos[delegation_idx].stake = stake_state.inner.stake.stake;
455 0 : temp_info->stake_infos[delegation_idx].account = n->elem.account;
456 :
457 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, epoch, history, new_rate_activation_epoch );
458 0 : effective += new_entry.effective;
459 0 : activating += new_entry.activating;
460 0 : deactivating += new_entry.deactivating;
461 0 : }
462 :
463 0 : accumulator->effective += effective;
464 0 : accumulator->activating += activating;
465 0 : accumulator->deactivating += deactivating;
466 :
467 0 : }
468 :
469 : /* Accumulates information about epoch stakes into `temp_info`, which is a temporary cache
470 : used to save intermediate state about stake and vote accounts to avoid them from having to
471 : be recomputed on every access, especially at the epoch boundary. Also collects stats in `accumulator` */
472 : void
473 : fd_accumulate_stake_infos( fd_exec_slot_ctx_t const * slot_ctx,
474 : fd_stakes_global_t const * stakes,
475 : fd_stake_history_t const * history,
476 : ulong * new_rate_activation_epoch,
477 : fd_stake_history_entry_t * accumulator,
478 : fd_epoch_info_t * temp_info,
479 0 : fd_spad_t * runtime_spad ) {
480 :
481 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
482 :
483 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
484 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
485 :
486 0 : ulong stake_delegations_pool_sz = fd_delegation_pair_t_map_size( stake_delegations_pool, stake_delegations_root );
487 0 : if( FD_UNLIKELY( stake_delegations_pool_sz==0UL ) ) {
488 0 : return;
489 0 : }
490 :
491 0 : fd_delegation_pair_t_mapnode_t * batch_delegation_min = fd_delegation_pair_t_map_minimum( stake_delegations_pool, stake_delegations_root );
492 :
493 0 : accumulate_stake_cache_delegations(
494 0 : batch_delegation_min,
495 0 : slot_ctx,
496 0 : history,
497 0 : new_rate_activation_epoch,
498 0 : accumulator,
499 0 : stake_delegations_pool,
500 0 : temp_info,
501 0 : stakes->epoch
502 0 : );
503 :
504 0 : temp_info->stake_infos_new_keys_start_idx = temp_info->stake_infos_len;
505 :
506 0 : fd_account_keys_global_t const * stake_account_keys = fd_bank_stake_account_keys_locking_query( slot_ctx->bank );
507 0 : fd_account_keys_pair_t_mapnode_t * account_keys_pool = fd_account_keys_account_keys_pool_join( stake_account_keys );
508 0 : fd_account_keys_pair_t_mapnode_t * account_keys_root = fd_account_keys_account_keys_root_join( stake_account_keys );
509 :
510 0 : if( !account_keys_pool ) {
511 0 : fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
512 0 : return;
513 0 : }
514 :
515 : /* The number of account keys aggregated across the epoch is usually small, so there aren't much performance gains from tpooling here. */
516 0 : for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( account_keys_pool, account_keys_root );
517 0 : n;
518 0 : n = fd_account_keys_pair_t_map_successor( account_keys_pool, n ) ) {
519 0 : FD_TXN_ACCOUNT_DECL( acc );
520 0 : int rc = fd_txn_account_init_from_funk_readonly(acc, &n->elem.key, slot_ctx->funk, slot_ctx->funk_txn );
521 0 : if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS || acc->vt->get_lamports( acc )==0UL ) ) {
522 0 : continue;
523 0 : }
524 :
525 0 : fd_stake_state_v2_t stake_state;
526 0 : rc = fd_stake_get_state( acc, &stake_state );
527 0 : if( FD_UNLIKELY( rc != 0 ) ) {
528 0 : continue;
529 0 : }
530 :
531 0 : if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
532 0 : continue;
533 0 : }
534 :
535 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake==0UL ) ) {
536 0 : continue;
537 0 : }
538 :
539 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
540 0 : temp_info->stake_infos[temp_info->stake_infos_len ].stake = stake_state.inner.stake.stake;
541 0 : temp_info->stake_infos[temp_info->stake_infos_len++].account = n->elem.key;
542 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
543 0 : accumulator->effective += new_entry.effective;
544 0 : accumulator->activating += new_entry.activating;
545 0 : accumulator->deactivating += new_entry.deactivating;
546 0 : }
547 :
548 0 : fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
549 :
550 0 : } FD_SPAD_FRAME_END;
551 0 : }
552 :
553 : /* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */
554 : void
555 : fd_stakes_activate_epoch( fd_exec_slot_ctx_t * slot_ctx,
556 : ulong * new_rate_activation_epoch,
557 : fd_epoch_info_t * temp_info,
558 0 : fd_spad_t * runtime_spad ) {
559 :
560 0 : fd_stakes_global_t const * stakes = fd_bank_stakes_locking_query( slot_ctx->bank );
561 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
562 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
563 :
564 0 : fd_account_keys_global_t const * stake_account_keys = fd_bank_stake_account_keys_locking_query( slot_ctx->bank );
565 :
566 0 : fd_account_keys_pair_t_mapnode_t * account_keys_pool = NULL;
567 0 : fd_account_keys_pair_t_mapnode_t * account_keys_root = NULL;
568 :
569 0 : if( stake_account_keys ) {
570 0 : account_keys_pool = fd_account_keys_account_keys_pool_join( stake_account_keys );
571 0 : account_keys_root = fd_account_keys_account_keys_root_join( stake_account_keys );
572 0 : }
573 :
574 : /* Current stake delegations: list of all current delegations in stake_delegations
575 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */
576 : /* Add a new entry to the Stake History sysvar for the previous epoch
577 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */
578 :
579 0 : fd_stake_history_t const * history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
580 0 : if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
581 :
582 0 : ulong stake_delegations_size = fd_delegation_pair_t_map_size(
583 0 : stake_delegations_pool, stake_delegations_root );
584 :
585 0 : stake_delegations_size += !!account_keys_pool ? fd_account_keys_pair_t_map_size( account_keys_pool, account_keys_root ) : 0UL;
586 :
587 0 : fd_bank_stake_account_keys_end_locking_query( slot_ctx->bank );
588 :
589 0 : temp_info->stake_infos_len = 0UL;
590 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 );
591 0 : fd_memset( temp_info->stake_infos, 0, sizeof(fd_epoch_info_pair_t)*stake_delegations_size );
592 :
593 0 : fd_stake_history_entry_t accumulator = {
594 0 : .effective = 0UL,
595 0 : .activating = 0UL,
596 0 : .deactivating = 0UL
597 0 : };
598 :
599 : /* Accumulate stats for stake accounts */
600 0 : fd_accumulate_stake_infos( slot_ctx,
601 0 : stakes,
602 0 : history,
603 0 : new_rate_activation_epoch,
604 0 : &accumulator,
605 0 : temp_info,
606 0 : runtime_spad );
607 :
608 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L359 */
609 0 : fd_epoch_stake_history_entry_pair_t new_elem = {
610 0 : .epoch = stakes->epoch,
611 0 : .entry = {
612 0 : .effective = accumulator.effective,
613 0 : .activating = accumulator.activating,
614 0 : .deactivating = accumulator.deactivating
615 0 : }
616 0 : };
617 :
618 0 : fd_sysvar_stake_history_update( slot_ctx, &new_elem, runtime_spad );
619 :
620 0 : fd_bank_stakes_end_locking_query( slot_ctx->bank );
621 :
622 0 : }
623 :
624 : int
625 : write_stake_state( fd_txn_account_t * stake_acc_rec,
626 0 : fd_stake_state_v2_t * stake_state ) {
627 :
628 0 : ulong encoded_stake_state_size = fd_stake_state_v2_size(stake_state);
629 :
630 0 : fd_bincode_encode_ctx_t ctx = {
631 0 : .data = stake_acc_rec->vt->get_data_mut( stake_acc_rec ),
632 0 : .dataend = stake_acc_rec->vt->get_data_mut( stake_acc_rec ) + encoded_stake_state_size,
633 0 : };
634 0 : if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx ) != FD_BINCODE_SUCCESS ) ) {
635 0 : FD_LOG_ERR(( "fd_stake_state_encode failed" ));
636 0 : }
637 :
638 0 : return 0;
639 0 : }
|