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 0 : fd_stake_history_t const * history ) {
164 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
165 0 : fd_stakes_t * stakes = &epoch_bank->stakes;
166 :
167 0 : FD_SCRATCH_SCOPE_BEGIN {
168 :
169 : // Create a map of <pubkey, stake> to store the total stake of each vote account.
170 0 : static const ulong maplen = 10000;
171 0 : void * mem = fd_scratch_alloc( fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint(maplen));
172 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join(fd_stake_weight_t_map_new(mem, maplen));
173 0 : fd_stake_weight_t_mapnode_t * root = NULL;
174 0 : ulong * new_rate_activation_epoch = NULL;
175 :
176 : // Iterate over each stake delegation and accumulate the stake amount associated with the given vote account.
177 0 : for (
178 0 : fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum(stakes->stake_delegations_pool, stakes->stake_delegations_root);
179 0 : n;
180 0 : n = fd_delegation_pair_t_map_successor(stakes->stake_delegations_pool, n) ) {
181 :
182 : // Get the stake account
183 0 : FD_BORROWED_ACCOUNT_DECL(stake_acc);
184 0 : int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.account, stake_acc);
185 0 : if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || stake_acc->const_meta->info.lamports == 0 ) ) {
186 0 : continue;
187 0 : }
188 :
189 0 : fd_stake_state_v2_t stake_state;
190 0 : rc = fd_stake_get_state( stake_acc, &slot_ctx->valloc, &stake_state );
191 0 : if ( FD_UNLIKELY( rc != 0) ) {
192 0 : continue;
193 0 : }
194 :
195 : // Fetch the delegation associated with this stake account
196 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
197 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating(
198 0 : delegation, stakes->epoch, history, new_rate_activation_epoch );
199 :
200 : // Add this delegation amount to the total stake of the vote account
201 0 : ulong delegation_stake = new_entry.effective;
202 0 : fd_stake_weight_t_mapnode_t temp;
203 0 : fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
204 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
205 0 : if (entry != NULL) {
206 0 : entry->elem.stake += delegation_stake;
207 0 : } else {
208 0 : entry = fd_stake_weight_t_map_acquire( pool );
209 0 : fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
210 0 : entry->elem.stake = delegation_stake;
211 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
212 0 : }
213 0 : }
214 :
215 : // Also include delegations from the stake accounts in the current slot context's
216 : // slot_ctx->slot_bank.stake_account_keys (a set of the stake accounts which we have
217 : // from this epoch).
218 0 : for ( fd_stake_accounts_pair_t_mapnode_t * n = fd_stake_accounts_pair_t_map_minimum(
219 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool,
220 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_root);
221 0 : n;
222 0 : n = fd_stake_accounts_pair_t_map_successor( slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, n ) ) {
223 0 : FD_BORROWED_ACCOUNT_DECL(stake_acc);
224 0 : int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.key, stake_acc);
225 0 : if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || stake_acc->const_meta->info.lamports == 0 ) ) {
226 0 : continue;
227 0 : }
228 :
229 0 : fd_stake_state_v2_t stake_state;
230 0 : rc = fd_stake_get_state( stake_acc, &slot_ctx->valloc, &stake_state );
231 0 : if ( FD_UNLIKELY( rc != 0) ) {
232 0 : continue;
233 0 : }
234 :
235 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
236 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
237 :
238 0 : ulong delegation_stake = new_entry.effective;
239 0 : fd_stake_weight_t_mapnode_t temp;
240 0 : fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
241 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
242 0 : if (entry != NULL) {
243 0 : entry->elem.stake += delegation_stake;
244 0 : } else {
245 0 : entry = fd_stake_weight_t_map_acquire( pool );
246 0 : fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
247 0 : entry->elem.stake = delegation_stake;
248 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
249 0 : }
250 0 : }
251 :
252 : // Copy the delegated stake values calculated above to the epoch bank stakes vote_accounts
253 0 : for ( fd_vote_accounts_pair_t_mapnode_t * n =
254 0 : fd_vote_accounts_pair_t_map_minimum(
255 0 : stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root);
256 0 : n;
257 0 : n = fd_vote_accounts_pair_t_map_successor(stakes->vote_accounts.vote_accounts_pool, n) ) {
258 0 : fd_stake_weight_t_mapnode_t temp;
259 0 : memcpy(&temp.elem.key, &n->elem.key, sizeof(fd_pubkey_t));
260 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
261 0 : n->elem.stake = (entry == NULL) ? 0 : entry->elem.stake;
262 0 : }
263 :
264 : // Copy the delegated stake values calculated above to the slot bank stakes vote_accounts
265 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 );
266 0 : n;
267 0 : n = fd_vote_accounts_pair_t_map_successor( slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool, n )) {
268 0 : fd_stake_weight_t_mapnode_t temp;
269 0 : memcpy(&temp.elem.key, &n->elem.key, sizeof(fd_pubkey_t));
270 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
271 0 : n->elem.stake = (entry == NULL) ? 0 : entry->elem.stake;
272 0 : }
273 :
274 0 : } FD_SCRATCH_SCOPE_END;
275 0 : }
276 :
277 : /* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */
278 : void
279 0 : fd_stakes_activate_epoch( fd_exec_slot_ctx_t * slot_ctx) {
280 :
281 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
282 0 : fd_stakes_t * stakes = &epoch_bank->stakes;
283 :
284 : /* Current stake delegations: list of all current delegations in stake_delegations
285 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */
286 : /* Add a new entry to the Stake History sysvar for the previous epoch
287 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */
288 :
289 0 : fd_stake_history_t const * history = fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache );
290 0 : if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
291 :
292 0 : fd_stake_history_entry_t accumulator = {
293 0 : .effective = 0,
294 0 : .activating = 0,
295 0 : .deactivating = 0
296 0 : };
297 :
298 0 : fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_alloc(slot_ctx->valloc, 10000);
299 0 : fd_stake_weight_t_mapnode_t * root = NULL;
300 :
301 0 : ulong * new_rate_activation_epoch = NULL;
302 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) ) {
303 0 : FD_BORROWED_ACCOUNT_DECL(acc);
304 0 : int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.account, acc);
305 0 : if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports == 0 ) ) {
306 0 : continue;
307 0 : }
308 :
309 0 : fd_stake_state_v2_t stake_state;
310 0 : rc = fd_stake_get_state( acc, &slot_ctx->valloc, &stake_state );
311 0 : if ( FD_UNLIKELY( rc != 0) ) {
312 0 : continue;
313 0 : }
314 :
315 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
316 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
317 0 : accumulator.effective += new_entry.effective;
318 0 : accumulator.activating += new_entry.activating;
319 0 : accumulator.deactivating += new_entry.deactivating;
320 :
321 0 : ulong delegation_stake = new_entry.effective;
322 0 : fd_stake_weight_t_mapnode_t temp;
323 0 : fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
324 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
325 0 : if (entry != NULL) {
326 0 : entry->elem.stake += delegation_stake;
327 0 : } else {
328 0 : entry = fd_stake_weight_t_map_acquire( pool );
329 0 : fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
330 0 : entry->elem.stake = delegation_stake;
331 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
332 0 : }
333 0 : }
334 :
335 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);
336 0 : n;
337 0 : n = fd_stake_accounts_pair_t_map_successor( slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, n ) ) {
338 0 : FD_BORROWED_ACCOUNT_DECL(acc);
339 0 : int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.key, acc);
340 0 : if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports == 0 ) ) {
341 0 : continue;
342 0 : }
343 :
344 0 : fd_stake_state_v2_t stake_state;
345 0 : rc = fd_stake_get_state( acc, &slot_ctx->valloc, &stake_state );
346 0 : if ( FD_UNLIKELY( rc != 0) ) {
347 0 : continue;
348 0 : }
349 :
350 0 : fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation;
351 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch );
352 0 : accumulator.effective += new_entry.effective;
353 0 : accumulator.activating += new_entry.activating;
354 0 : accumulator.deactivating += new_entry.deactivating;
355 :
356 0 : ulong delegation_stake = new_entry.effective;
357 0 : fd_stake_weight_t_mapnode_t temp;
358 0 : fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
359 0 : fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp);
360 0 : if (entry != NULL) {
361 0 : entry->elem.stake += delegation_stake;
362 0 : } else {
363 0 : entry = fd_stake_weight_t_map_acquire( pool );
364 0 : fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t));
365 0 : entry->elem.stake = delegation_stake;
366 0 : fd_stake_weight_t_map_insert( pool, &root, entry );
367 0 : }
368 0 : }
369 :
370 0 : fd_stake_history_entry_t new_elem = {
371 0 : .epoch = stakes->epoch,
372 0 : .effective = accumulator.effective,
373 0 : .activating = accumulator.activating,
374 0 : .deactivating = accumulator.deactivating
375 0 : };
376 :
377 0 : fd_sysvar_stake_history_update( slot_ctx, &new_elem);
378 :
379 0 : fd_valloc_free( slot_ctx->valloc,
380 0 : fd_stake_weight_t_map_delete( fd_stake_weight_t_map_leave ( pool ) ) );
381 :
382 : /* Refresh the sysvar cache stake history entry after updating the sysvar.
383 : We need to do this here because it is used in subsequent places in the epoch boundary. */
384 0 : fd_bincode_destroy_ctx_t sysvar_cache_destroy_ctx = { .valloc = slot_ctx->sysvar_cache->valloc };
385 0 : fd_stake_history_destroy( slot_ctx->sysvar_cache->val_stake_history, &sysvar_cache_destroy_ctx );
386 0 : fd_sysvar_cache_restore_stake_history( slot_ctx->sysvar_cache, slot_ctx->acc_mgr, slot_ctx->funk_txn );
387 0 : }
388 :
389 : int
390 : write_stake_state( fd_borrowed_account_t * stake_acc_rec,
391 0 : fd_stake_state_v2_t * stake_state ) {
392 :
393 0 : ulong encoded_stake_state_size = fd_stake_state_v2_size(stake_state);
394 :
395 0 : fd_bincode_encode_ctx_t ctx = {
396 0 : .data = stake_acc_rec->data,
397 0 : .dataend = stake_acc_rec->data + encoded_stake_state_size,
398 0 : };
399 0 : if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx ) != FD_BINCODE_SUCCESS ) ) {
400 0 : FD_LOG_ERR(( "fd_stake_state_encode failed" ));
401 0 : }
402 :
403 0 : return 0;
404 0 : }
|