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 0 : fd_stake_weight_t_mapnode_t * out_pool ) {
15 :
16 : /* Stakes::staked_nodes(&self: Stakes) -> HashMap<Pubkey, u64> */
17 :
18 0 : fd_vote_accounts_pair_t_mapnode_t * in_pool = in->vote_accounts_pool;
19 0 : fd_vote_accounts_pair_t_mapnode_t * in_root = in->vote_accounts_root;
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_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( in_pool, in_root );
29 0 : n;
30 0 : n = fd_vote_accounts_pair_t_map_successor( in_pool, n ) ) {
31 :
32 : /* ... filter(|(stake, _)| *stake != 0u64) */
33 0 : if( n->elem.stake == 0UL ) continue;
34 :
35 : /* Extract node pubkey */
36 0 : fd_pubkey_t const * node_pubkey = &n->elem.value.node_pubkey;
37 :
38 0 : fd_pubkey_t null_key = {0};
39 0 : if( memcmp( node_pubkey, null_key.uc, sizeof(fd_pubkey_t) ) == 0 ) {
40 0 : FD_LOG_WARNING(( "vote account %s skipped", FD_BASE58_ENC_32_ALLOCA( n->elem.key.key ) ));
41 0 : continue;
42 0 : }
43 : /* Check if node identity was previously visited */
44 0 : fd_stake_weight_t_mapnode_t * query = fd_stake_weight_t_map_acquire( out_pool );
45 0 : FD_TEST( query );
46 0 : query->elem.key = *node_pubkey;
47 0 : fd_stake_weight_t_mapnode_t * node = fd_stake_weight_t_map_find( out_pool, out_root, query );
48 :
49 0 : if( FD_UNLIKELY( node ) ) {
50 : /* Accumulate to previously created entry */
51 0 : fd_stake_weight_t_map_release( out_pool, query );
52 0 : node->elem.stake += n->elem.stake;
53 0 : } else {
54 : /* Create new entry */
55 0 : node = query;
56 0 : node->elem.stake = n->elem.stake;
57 0 : fd_stake_weight_t_map_insert( out_pool, &out_root, node );
58 0 : }
59 0 : }
60 :
61 0 : return out_root;
62 0 : }
63 :
64 : /* fd_stake_weight_sort sorts the given array of stake weights with
65 : length stakes_cnt by tuple (stake, pubkey) in descending order. */
66 :
67 : FD_FN_CONST static int
68 : fd_stakes_sort_before( fd_stake_weight_t a,
69 0 : fd_stake_weight_t b ) {
70 :
71 0 : if( a.stake > b.stake ) return 1;
72 0 : if( a.stake < b.stake ) return 0;
73 0 : if( memcmp( &a.key, &b.key, 32UL )>0 ) return 1;
74 0 : return 0;
75 0 : }
76 :
77 : #define SORT_NAME fd_stakes_sort
78 0 : #define SORT_KEY_T fd_stake_weight_t
79 0 : #define SORT_BEFORE(a,b) fd_stakes_sort_before( (a), (b) )
80 : #include "../../util/tmpl/fd_sort.c"
81 :
82 : void
83 : fd_stake_weight_sort( fd_stake_weight_t * stakes,
84 0 : ulong stakes_cnt ) {
85 0 : fd_stakes_sort_inplace( stakes, stakes_cnt );
86 0 : }
87 :
88 : /* fd_stakes_export_sorted converts StakedNodes (rbtree mapping
89 : (node identity) => (active stake) from fd_stakes_accum_by_node) to
90 : a list of fd_stake_weights_t. */
91 :
92 : static ulong
93 : fd_stakes_export( fd_stake_weight_t_mapnode_t const * const in_pool,
94 : fd_stake_weight_t_mapnode_t const * const root,
95 0 : fd_stake_weight_t * const out ) {
96 :
97 0 : fd_stake_weight_t * out_end = out;
98 :
99 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 ) ) {
100 0 : *out_end++ = ele->elem;
101 0 : }
102 :
103 0 : return (ulong)( out_end - out );
104 0 : }
105 :
106 : ulong
107 : fd_stake_weights_by_node( fd_vote_accounts_t const * accs,
108 0 : fd_stake_weight_t * weights ) {
109 :
110 : /* Enter scratch frame for duration for function */
111 :
112 0 : if( FD_UNLIKELY( !fd_scratch_push_is_safe() ) ) {
113 0 : FD_LOG_WARNING(( "fd_scratch_push() failed" ));
114 0 : return ULONG_MAX;
115 0 : }
116 :
117 0 : FD_SCRATCH_SCOPE_BEGIN {
118 :
119 : /* Estimate size required to store temporary data structures */
120 :
121 : /* TODO size is the wrong method name for this */
122 0 : ulong vote_acc_cnt = fd_vote_accounts_pair_t_map_size( accs->vote_accounts_pool, accs->vote_accounts_root );
123 :
124 0 : ulong rb_align = fd_stake_weight_t_map_align();
125 0 : ulong rb_footprint = fd_stake_weight_t_map_footprint( vote_acc_cnt );
126 :
127 0 : if( FD_UNLIKELY( !fd_scratch_alloc_is_safe( rb_align, rb_footprint ) ) ) {
128 0 : FD_LOG_WARNING(( "insufficient scratch space: need %lu align %lu footprint",
129 0 : rb_align, rb_footprint ));
130 0 : return ULONG_MAX;
131 0 : }
132 :
133 : /* Create rb tree */
134 :
135 0 : void * pool_mem = fd_scratch_alloc( rb_align, rb_footprint );
136 0 : pool_mem = fd_stake_weight_t_map_new( pool_mem, vote_acc_cnt );
137 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( pool_mem );
138 0 : if( FD_UNLIKELY( !pool_mem ) ) FD_LOG_CRIT(( "fd_stake_weights_new() failed" ));
139 :
140 : /* Accumulate stakes to rb tree */
141 :
142 0 : fd_stake_weight_t_mapnode_t const * root = fd_stakes_accum_by_node( accs, pool );
143 :
144 : /* Export to sorted list */
145 :
146 0 : ulong weights_cnt = fd_stakes_export( pool, root, weights );
147 0 : fd_stake_weight_sort( weights, weights_cnt );
148 :
149 0 : return weights_cnt;
150 0 : } FD_SCRATCH_SCOPE_END;
151 0 : }
152 :
153 : /*
154 : Refresh vote accounts.
155 :
156 : This updates the epoch bank stakes vote_accounts cache - that is, the total amount
157 : of delegated stake each vote account has, using the current delegation values from inside each
158 : stake account.
159 :
160 : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L562 */
161 : void
162 : refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx,
163 : fd_stake_history_t const * history,
164 : ulong * new_rate_activation_epoch,
165 : fd_epoch_info_t *temp_info
166 0 : ) {
167 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
168 0 : fd_stakes_t * stakes = &epoch_bank->stakes;
169 :
170 0 : FD_SCRATCH_SCOPE_BEGIN {
171 :
172 : // Create a map of <pubkey, stake> to store the total stake of each vote account.
173 0 : static const ulong maplen = 10000;
174 0 : void * mem = fd_scratch_alloc( fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint(maplen));
175 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join(fd_stake_weight_t_map_new(mem, maplen));
176 0 : fd_stake_weight_t_mapnode_t * root = NULL;
177 :
178 : // Iterate over each stake delegation and accumulate the stake amount associated with the given vote account.
179 0 : for ( ulong idx = 0; idx < temp_info->infos_len; idx++ ) {
180 : // Fetch the delegation associated with this stake account
181 0 : fd_delegation_t * delegation = &temp_info->infos[idx].stake.delegation;
182 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating(
183 0 : delegation, stakes->epoch, history, new_rate_activation_epoch );
184 :
185 : // Add this delegation amount to the total stake of the vote account
186 0 : ulong delegation_stake = new_entry.effective;
187 0 : fd_stake_weight_t_mapnode_t temp;
188 0 : fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
189 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
190 0 : if (entry != NULL) {
191 0 : entry->elem.stake += delegation_stake;
192 0 : } else {
193 0 : entry = fd_stake_weight_t_map_acquire( pool );
194 0 : fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
195 0 : entry->elem.stake = delegation_stake;
196 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
197 0 : }
198 0 : }
199 :
200 : // Copy the delegated stake values calculated above to the epoch bank stakes vote_accounts
201 0 : for ( fd_vote_accounts_pair_t_mapnode_t * n =
202 0 : fd_vote_accounts_pair_t_map_minimum(
203 0 : stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root);
204 0 : n;
205 0 : n = fd_vote_accounts_pair_t_map_successor(stakes->vote_accounts.vote_accounts_pool, n) ) {
206 0 : fd_stake_weight_t_mapnode_t temp;
207 0 : memcpy(&temp.elem.key, &n->elem.key, sizeof(fd_pubkey_t));
208 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
209 0 : n->elem.stake = (entry == NULL) ? 0 : entry->elem.stake;
210 0 : }
211 :
212 : // Copy the delegated stake values calculated above to the slot bank stakes vote_accounts
213 0 : for ( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool, slot_ctx->slot_bank.vote_account_keys.vote_accounts_root );
214 0 : n;
215 0 : n = fd_vote_accounts_pair_t_map_successor( slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool, n )) {
216 0 : fd_stake_weight_t_mapnode_t temp;
217 0 : memcpy(&temp.elem.key, &n->elem.key, sizeof(fd_pubkey_t));
218 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
219 0 : n->elem.stake = (entry == NULL) ? 0 : entry->elem.stake;
220 0 : }
221 :
222 0 : } FD_SCRATCH_SCOPE_END;
223 0 : }
224 :
225 : /* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */
226 : void
227 : fd_stakes_activate_epoch( fd_exec_slot_ctx_t * slot_ctx,
228 : ulong * new_rate_activation_epoch,
229 : fd_epoch_info_t *temp_info
230 0 : ) {
231 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
232 0 : fd_stakes_t * stakes = &epoch_bank->stakes;
233 :
234 : /* Current stake delegations: list of all current delegations in stake_delegations
235 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */
236 : /* Add a new entry to the Stake History sysvar for the previous epoch
237 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */
238 :
239 0 : fd_stake_history_t const * history = fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache );
240 0 : if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
241 :
242 0 : ulong stake_delegations_size = fd_delegation_pair_t_map_size(
243 0 : stakes->stake_delegations_pool, stakes->stake_delegations_root );
244 0 : stake_delegations_size += fd_stake_accounts_pair_t_map_size(
245 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, slot_ctx->slot_bank.stake_account_keys.stake_accounts_root );
246 0 : temp_info->infos_len = stake_delegations_size;
247 0 : temp_info->infos = (fd_epoch_info_pair_t *)fd_scratch_alloc( FD_EPOCH_INFO_PAIR_ALIGN, FD_EPOCH_INFO_PAIR_FOOTPRINT*stake_delegations_size );
248 0 : fd_memset( temp_info->infos, 0, FD_EPOCH_INFO_PAIR_FOOTPRINT*stake_delegations_size );
249 0 : ulong delegation_idx = 0;
250 :
251 0 : fd_stake_history_entry_t accumulator = {
252 0 : .effective = 0,
253 0 : .activating = 0,
254 0 : .deactivating = 0
255 0 : };
256 :
257 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_alloc(slot_ctx->valloc, 10000);
258 0 : fd_stake_weight_t_mapnode_t * root = NULL;
259 :
260 0 : for ( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum(stakes->stake_delegations_pool, stakes->stake_delegations_root); n; n = fd_delegation_pair_t_map_successor(stakes->stake_delegations_pool, n) ) {
261 0 : FD_BORROWED_ACCOUNT_DECL(acc);
262 0 : int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.account, acc);
263 0 : if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports == 0 ) ) {
264 0 : continue;
265 0 : }
266 :
267 0 : fd_stake_state_v2_t stake_state;
268 0 : rc = fd_stake_get_state( acc, &slot_ctx->valloc, &stake_state );
269 0 : if ( FD_UNLIKELY( rc != 0) ) {
270 0 : continue;
271 0 : }
272 :
273 0 : if ( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
274 0 : continue;
275 0 : }
276 :
277 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake == 0 ) ) {
278 0 : continue;
279 0 : }
280 :
281 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
282 0 : fd_memcpy(&temp_info->infos[delegation_idx ].stake, &stake_state.inner.stake.stake, sizeof(fd_stake_t));
283 0 : fd_memcpy(&temp_info->infos[delegation_idx++].account, &n->elem.account, sizeof(fd_pubkey_t));
284 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
285 0 : accumulator.effective += new_entry.effective;
286 0 : accumulator.activating += new_entry.activating;
287 0 : accumulator.deactivating += new_entry.deactivating;
288 :
289 0 : ulong delegation_stake = new_entry.effective;
290 0 : fd_stake_weight_t_mapnode_t temp;
291 0 : fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
292 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
293 0 : if (entry != NULL) {
294 0 : entry->elem.stake += delegation_stake;
295 0 : } else {
296 0 : entry = fd_stake_weight_t_map_acquire( pool );
297 0 : fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
298 0 : entry->elem.stake = delegation_stake;
299 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
300 0 : }
301 0 : }
302 :
303 0 : for ( fd_stake_accounts_pair_t_mapnode_t * n = fd_stake_accounts_pair_t_map_minimum( slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, slot_ctx->slot_bank.stake_account_keys.stake_accounts_root);
304 0 : n;
305 0 : n = fd_stake_accounts_pair_t_map_successor( slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, n ) ) {
306 0 : FD_BORROWED_ACCOUNT_DECL(acc);
307 0 : int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.key, acc);
308 0 : if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports == 0 ) ) {
309 0 : continue;
310 0 : }
311 :
312 0 : fd_stake_state_v2_t stake_state;
313 0 : rc = fd_stake_get_state( acc, &slot_ctx->valloc, &stake_state );
314 0 : if ( FD_UNLIKELY( rc != 0) ) {
315 0 : continue;
316 0 : }
317 :
318 0 : if ( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
319 0 : continue;
320 0 : }
321 :
322 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake == 0 ) ) {
323 0 : continue;
324 0 : }
325 :
326 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
327 0 : fd_memcpy(&temp_info->infos[delegation_idx ].stake.delegation, &stake_state.inner.stake.stake, sizeof(fd_stake_t));
328 0 : fd_memcpy(&temp_info->infos[delegation_idx++].account, &n->elem.key, sizeof(fd_pubkey_t));
329 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
330 0 : accumulator.effective += new_entry.effective;
331 0 : accumulator.activating += new_entry.activating;
332 0 : accumulator.deactivating += new_entry.deactivating;
333 :
334 0 : ulong delegation_stake = new_entry.effective;
335 0 : fd_stake_weight_t_mapnode_t temp;
336 0 : fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
337 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
338 0 : if (entry != NULL) {
339 0 : entry->elem.stake += delegation_stake;
340 0 : } else {
341 0 : entry = fd_stake_weight_t_map_acquire( pool );
342 0 : fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
343 0 : entry->elem.stake = delegation_stake;
344 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
345 0 : }
346 0 : }
347 :
348 0 : temp_info->infos_len = delegation_idx;
349 :
350 0 : fd_stake_history_entry_t new_elem = {
351 0 : .epoch = stakes->epoch,
352 0 : .effective = accumulator.effective,
353 0 : .activating = accumulator.activating,
354 0 : .deactivating = accumulator.deactivating
355 0 : };
356 :
357 0 : fd_sysvar_stake_history_update( slot_ctx, &new_elem);
358 :
359 0 : fd_valloc_free( slot_ctx->valloc,
360 0 : fd_stake_weight_t_map_delete( fd_stake_weight_t_map_leave ( pool ) ) );
361 :
362 : /* Refresh the sysvar cache stake history entry after updating the sysvar.
363 : We need to do this here because it is used in subsequent places in the epoch boundary. */
364 0 : fd_bincode_destroy_ctx_t sysvar_cache_destroy_ctx = { .valloc = slot_ctx->sysvar_cache->valloc };
365 0 : fd_stake_history_destroy( slot_ctx->sysvar_cache->val_stake_history, &sysvar_cache_destroy_ctx );
366 0 : fd_sysvar_cache_restore_stake_history( slot_ctx->sysvar_cache, slot_ctx->acc_mgr, slot_ctx->funk_txn );
367 0 : }
368 :
369 : int
370 : write_stake_state( fd_borrowed_account_t * stake_acc_rec,
371 0 : fd_stake_state_v2_t * stake_state ) {
372 :
373 0 : ulong encoded_stake_state_size = fd_stake_state_v2_size(stake_state);
374 :
375 0 : fd_bincode_encode_ctx_t ctx = {
376 0 : .data = stake_acc_rec->data,
377 0 : .dataend = stake_acc_rec->data + encoded_stake_state_size,
378 0 : };
379 0 : if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx ) != FD_BINCODE_SUCCESS ) ) {
380 0 : FD_LOG_ERR(( "fd_stake_state_encode failed" ));
381 0 : }
382 :
383 0 : return 0;
384 0 : }
|