Line data Source code
1 : #include <limits.h>
2 :
3 : #include "fd_stakes.h"
4 : #include "../runtime/fd_bank.h"
5 : #include "../runtime/program/vote/fd_vote_state_versioned.h"
6 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
7 : #include "../runtime/sysvar/fd_sysvar_cache.h"
8 : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
9 : #include "../runtime/fd_runtime_stack.h"
10 : #include "../runtime/fd_system_ids.h"
11 : #include "fd_stake_delegations.h"
12 : #include "../accdb/fd_accdb_sync.h"
13 : #include "../../util/bits/fd_sat.h"
14 : #include "fd_stake_types.h"
15 :
16 : /**********************************************************************/
17 : /* Constants */
18 : /**********************************************************************/
19 :
20 0 : #define DEFAULT_WARMUP_COOLDOWN_RATE ( 0.25 )
21 0 : #define NEW_WARMUP_COOLDOWN_RATE ( 0.09 )
22 0 : #define DEFAULT_SLASH_PENALTY ( 12 )
23 :
24 : /**********************************************************************/
25 : /* Types */
26 : /**********************************************************************/
27 :
28 : struct effective_activating {
29 : ulong effective;
30 : ulong activating;
31 : };
32 : typedef struct effective_activating effective_activating_t;
33 :
34 : /**********************************************************************/
35 : /* Static helpers */
36 : /**********************************************************************/
37 :
38 : static inline double
39 0 : warmup_cooldown_rate( ulong current_epoch, ulong * new_rate_activation_epoch ) {
40 0 : ulong activation_epoch = new_rate_activation_epoch ? *new_rate_activation_epoch : ULONG_MAX;
41 0 : return current_epoch<activation_epoch ? DEFAULT_WARMUP_COOLDOWN_RATE : NEW_WARMUP_COOLDOWN_RATE;
42 0 : }
43 :
44 : static fd_stake_history_entry_t const *
45 : fd_stake_history_ele_binary_search_const( fd_stake_history_t const * history,
46 21 : ulong epoch ) {
47 21 : ulong start = 0UL;
48 21 : ulong end = history->fd_stake_history_len - 1;
49 :
50 54 : while ( start<=end ) {
51 33 : ulong mid = start + ( end - start ) / 2UL;
52 33 : if( history->fd_stake_history[mid].epoch==epoch ) {
53 0 : return &history->fd_stake_history[mid].entry;
54 33 : } else if( history->fd_stake_history[mid].epoch<epoch ) {
55 0 : if ( mid==0 ) return NULL;
56 0 : end = mid - 1;
57 33 : } else {
58 33 : start = mid + 1;
59 33 : }
60 33 : }
61 21 : return NULL;
62 21 : }
63 :
64 : static fd_stake_history_entry_t const *
65 : fd_stake_history_ele_query_const( fd_stake_history_t const * history,
66 204 : ulong epoch ) {
67 204 : if( 0 == history->fd_stake_history_len ) {
68 183 : return NULL;
69 183 : }
70 :
71 21 : if( epoch > history->fd_stake_history[0].epoch ) {
72 0 : return NULL;
73 0 : }
74 :
75 21 : ulong off = (history->fd_stake_history[0].epoch - epoch);
76 21 : if( off >= history->fd_stake_history_len ) {
77 21 : return fd_stake_history_ele_binary_search_const( history, epoch );
78 21 : }
79 :
80 0 : ulong e = (off + history->fd_stake_history_offset) & (history->fd_stake_history_size - 1);
81 :
82 0 : if ( history->fd_stake_history[e].epoch == epoch ) {
83 0 : return &history->fd_stake_history[e].entry;
84 0 : }
85 :
86 0 : return fd_stake_history_ele_binary_search_const( history, epoch );
87 0 : }
88 :
89 : // https://github.com/anza-xyz/agave/blob/c8685ce0e1bb9b26014f1024de2cd2b8c308cbde/sdk/program/src/stake/state.rs#L728
90 : static effective_activating_t
91 : stake_and_activating( fd_delegation_t const * self,
92 : ulong target_epoch,
93 : fd_stake_history_t const * history,
94 435 : ulong * new_rate_activation_epoch ) {
95 435 : ulong delegated_stake = self->stake;
96 :
97 435 : fd_stake_history_entry_t const * cluster_stake_at_activation_epoch;
98 435 : if( self->activation_epoch==ULONG_MAX ) {
99 84 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
100 351 : } else if( self->activation_epoch==self->deactivation_epoch ) {
101 282 : return ( effective_activating_t ){ .effective = 0, .activating = 0 };
102 282 : } else if( target_epoch==self->activation_epoch ) {
103 3 : return ( effective_activating_t ){ .effective = 0, .activating = delegated_stake };
104 66 : } else if( target_epoch<self->activation_epoch ) {
105 18 : return ( effective_activating_t ){ .effective = 0, .activating = 0 };
106 48 : } else if( history &&
107 48 : ( cluster_stake_at_activation_epoch = fd_stake_history_ele_query_const(
108 39 : history, self->activation_epoch ) ) ) {
109 0 : ulong prev_epoch = self->activation_epoch;
110 0 : fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_activation_epoch;
111 :
112 0 : ulong current_epoch;
113 0 : ulong current_effective_stake = 0;
114 0 : for( ;; ) {
115 0 : current_epoch = prev_epoch + 1;
116 0 : if( FD_LIKELY( prev_cluster_stake->activating==0 ) ) {
117 0 : break;
118 0 : }
119 :
120 0 : ulong remaining_activating_stake = delegated_stake - current_effective_stake;
121 0 : double weight = (double)remaining_activating_stake / (double)prev_cluster_stake->activating;
122 0 : double warmup_cooldown_rate_ =
123 0 : warmup_cooldown_rate( current_epoch, new_rate_activation_epoch );
124 :
125 0 : double newly_effective_cluster_stake =
126 0 : (double)prev_cluster_stake->effective * warmup_cooldown_rate_;
127 0 : ulong newly_effective_stake =
128 0 : fd_ulong_max( fd_rust_cast_double_to_ulong( weight * newly_effective_cluster_stake ), 1 );
129 :
130 0 : current_effective_stake += newly_effective_stake;
131 0 : if( FD_LIKELY( current_effective_stake>=delegated_stake ) ) {
132 0 : current_effective_stake = delegated_stake;
133 0 : break;
134 0 : }
135 :
136 0 : if( FD_LIKELY( current_epoch>=target_epoch ||
137 0 : current_epoch>=self->deactivation_epoch ) ) {
138 0 : break;
139 0 : }
140 :
141 0 : fd_stake_history_entry_t const * current_cluster_stake =
142 0 : fd_stake_history_ele_query_const( history, current_epoch );
143 0 : if( FD_LIKELY( current_cluster_stake ) ) {
144 0 : prev_epoch = current_epoch;
145 0 : prev_cluster_stake = current_cluster_stake;
146 0 : } else {
147 0 : break;
148 0 : }
149 0 : }
150 0 : return ( effective_activating_t ){ .effective = current_effective_stake,
151 0 : .activating = delegated_stake - current_effective_stake };
152 48 : } else {
153 48 : return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
154 48 : }
155 435 : }
156 :
157 : // https://github.com/anza-xyz/agave/blob/c8685ce0e1bb9b26014f1024de2cd2b8c308cbde/sdk/program/src/stake/state.rs#L641
158 : fd_stake_history_entry_t
159 : stake_activating_and_deactivating( fd_delegation_t const * self,
160 : ulong target_epoch,
161 : fd_stake_history_t const * stake_history,
162 435 : ulong * new_rate_activation_epoch ) {
163 :
164 435 : effective_activating_t effective_activating =
165 435 : stake_and_activating( self, target_epoch, stake_history, new_rate_activation_epoch );
166 :
167 435 : ulong effective_stake = effective_activating.effective;
168 435 : ulong activating_stake = effective_activating.activating;
169 :
170 435 : fd_stake_history_entry_t const * cluster_stake_at_deactivation_epoch = NULL;
171 :
172 435 : if( target_epoch<self->deactivation_epoch ) {
173 213 : if( activating_stake==0 ) {
174 210 : return ( fd_stake_history_entry_t ){
175 210 : .effective = effective_stake, .deactivating = 0, .activating = 0 };
176 210 : } else {
177 3 : return ( fd_stake_history_entry_t ){
178 3 : .effective = effective_stake, .deactivating = 0, .activating = activating_stake };
179 3 : }
180 222 : } else if( target_epoch==self->deactivation_epoch ) {
181 0 : return ( fd_stake_history_entry_t ){
182 0 : .effective = effective_stake, .deactivating = effective_stake, .activating = 0 };
183 222 : } else if( stake_history &&
184 222 : ( cluster_stake_at_deactivation_epoch = fd_stake_history_ele_query_const( stake_history, self->deactivation_epoch ) ) ) {
185 0 : ulong prev_epoch = self->deactivation_epoch;
186 0 : fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_deactivation_epoch;
187 :
188 0 : ulong current_epoch;
189 0 : ulong current_effective_stake = effective_stake;
190 0 : for( ;; ) {
191 0 : current_epoch = prev_epoch + 1;
192 0 : if( prev_cluster_stake->deactivating==0 ) break;
193 :
194 0 : double weight = (double)current_effective_stake / (double)prev_cluster_stake->deactivating;
195 0 : double warmup_cooldown_rate_ =
196 0 : warmup_cooldown_rate( current_epoch, new_rate_activation_epoch );
197 :
198 0 : double newly_not_effective_cluster_stake =
199 0 : (double)prev_cluster_stake->effective * warmup_cooldown_rate_;
200 0 : ulong newly_not_effective_stake =
201 0 : fd_ulong_max( fd_rust_cast_double_to_ulong( weight * newly_not_effective_cluster_stake ), 1 );
202 :
203 0 : current_effective_stake =
204 0 : fd_ulong_sat_sub( current_effective_stake, newly_not_effective_stake );
205 0 : if( current_effective_stake==0 ) break;
206 :
207 0 : if( current_epoch>=target_epoch ) break;
208 :
209 0 : fd_stake_history_entry_t const * current_cluster_stake = NULL;
210 0 : if( ( current_cluster_stake = fd_stake_history_ele_query_const(stake_history, current_epoch ) ) ) {
211 0 : prev_epoch = current_epoch;
212 0 : prev_cluster_stake = current_cluster_stake;
213 0 : } else {
214 0 : break;
215 0 : }
216 0 : }
217 0 : return ( fd_stake_history_entry_t ){ .effective = current_effective_stake,
218 0 : .deactivating = current_effective_stake,
219 0 : .activating = 0 };
220 222 : } else {
221 222 : return ( fd_stake_history_entry_t ){ .effective = 0, .activating = 0, .deactivating = 0 };
222 222 : }
223 435 : }
224 :
225 : static void
226 : write_stake_config( fd_accdb_user_t * accdb,
227 : fd_funk_txn_xid_t const * xid,
228 0 : fd_stake_config_t const * stake_config ) {
229 0 : ulong data_sz = fd_stake_config_size( stake_config );
230 0 : fd_pubkey_t const * address = &fd_solana_stake_program_config_id;
231 :
232 0 : fd_accdb_rw_t rw[1];
233 0 : fd_accdb_open_rw( accdb, rw, xid, address, data_sz, FD_ACCDB_FLAG_CREATE );
234 :
235 : /* FIXME update capitalization? */
236 : /* FIXME set owner to Config program? */
237 : /* FIXME Agave reflink? */
238 : /* FIXME derive lamport balance from rent instead of hardcoding */
239 :
240 0 : fd_accdb_ref_lamports_set( rw, 960480UL );
241 0 : fd_accdb_ref_exec_bit_set( rw, 0 );
242 0 : fd_accdb_ref_data_sz_set( accdb, rw, data_sz, 0 );
243 0 : fd_bincode_encode_ctx_t ctx = {
244 0 : .data = fd_accdb_ref_data( rw ),
245 0 : .dataend = (uchar *)fd_accdb_ref_data( rw ) + data_sz
246 0 : };
247 0 : if( fd_stake_config_encode( stake_config, &ctx ) )
248 0 : FD_LOG_ERR( ( "fd_stake_config_encode failed" ) );
249 :
250 0 : fd_accdb_close_rw( accdb, rw );
251 0 : }
252 :
253 : /**********************************************************************/
254 : /* Public API */
255 : /**********************************************************************/
256 :
257 : void
258 : fd_stakes_config_init( fd_accdb_user_t * accdb,
259 0 : fd_funk_txn_xid_t const * xid ) {
260 0 : fd_stake_config_t stake_config = {
261 0 : .warmup_cooldown_rate = DEFAULT_WARMUP_COOLDOWN_RATE,
262 0 : .slash_penalty = DEFAULT_SLASH_PENALTY,
263 0 : };
264 0 : write_stake_config( accdb, xid, &stake_config );
265 0 : }
266 :
267 : fd_stake_state_t const *
268 : fd_stake_state_view( uchar const * data,
269 6 : ulong data_sz ) {
270 6 : if( FD_UNLIKELY( data_sz<4UL ) ) return NULL;
271 6 : uint stake_type = FD_LOAD( uint, data );
272 6 : switch( stake_type ) {
273 0 : case FD_STAKE_STATE_UNINITIALIZED:
274 0 : break;
275 0 : case FD_STAKE_STATE_INITIALIZED:
276 0 : if( FD_UNLIKELY( data_sz<124 ) ) return NULL;
277 0 : break;
278 6 : case FD_STAKE_STATE_STAKE:
279 6 : if( FD_UNLIKELY( data_sz<197 ) ) return NULL;
280 6 : break;
281 6 : case FD_STAKE_STATE_REWARDS_POOL:
282 0 : break;
283 0 : default:
284 0 : return NULL;
285 6 : }
286 6 : return fd_type_pun_const( data );
287 6 : }
288 :
289 : fd_stake_state_t const *
290 6 : fd_stakes_get_state( fd_account_meta_t const * meta ) {
291 6 : if( FD_UNLIKELY( 0!=memcmp( meta->owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) ) {
292 0 : return NULL;
293 0 : }
294 6 : if( FD_UNLIKELY( meta->lamports==0UL ) ) return NULL;
295 6 : return fd_stake_state_view( fd_account_data( meta ), meta->dlen );
296 6 : }
297 :
298 : fd_stake_history_entry_t
299 : fd_stakes_activating_and_deactivating( fd_stake_delegation_t const * stake_delegation,
300 : ulong target_epoch,
301 : fd_stake_history_t const * stake_history,
302 435 : ulong * new_rate_activation_epoch ) {
303 435 : fd_delegation_t delegation = {
304 435 : .voter_pubkey = stake_delegation->vote_account,
305 435 : .stake = stake_delegation->stake,
306 435 : .deactivation_epoch = stake_delegation->deactivation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->deactivation_epoch,
307 435 : .activation_epoch = stake_delegation->activation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->activation_epoch,
308 435 : .warmup_cooldown_rate = fd_stake_delegations_warmup_cooldown_rate_to_double( stake_delegation->warmup_cooldown_rate ),
309 435 : };
310 :
311 435 : return stake_activating_and_deactivating(
312 435 : &delegation, target_epoch, stake_history, new_rate_activation_epoch );
313 435 : }
314 :
315 : ulong
316 : fd_stake_weights_by_node( fd_top_votes_t const * top_votes_t_2,
317 : fd_vote_stakes_t * vote_stakes,
318 : ushort fork_idx,
319 : fd_vote_stake_weight_t * weights,
320 9 : int vat_enabled ) {
321 9 : ulong weights_cnt = 0;
322 9 : if( vat_enabled ) {
323 0 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
324 0 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_2, iter_mem );
325 0 : !fd_top_votes_iter_done( top_votes_t_2, iter );
326 0 : fd_top_votes_iter_next( top_votes_t_2, iter ) ) {
327 0 : fd_pubkey_t pubkey;
328 0 : ulong stake_t_2;
329 0 : fd_pubkey_t node_account_t_2;
330 0 : fd_top_votes_iter_ele( top_votes_t_2, iter, &pubkey, &node_account_t_2, &stake_t_2, NULL, NULL, NULL );
331 :
332 0 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
333 0 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_2, sizeof(fd_pubkey_t) );
334 0 : weights[ weights_cnt ].stake = stake_t_2;
335 0 : weights_cnt++;
336 0 : }
337 9 : } else {
338 9 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
339 9 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_fork_iter_init( vote_stakes, fork_idx, iter_mem );
340 18 : !fd_vote_stakes_fork_iter_done( vote_stakes, fork_idx, iter );
341 9 : fd_vote_stakes_fork_iter_next( vote_stakes, fork_idx, iter ) ) {
342 9 : fd_pubkey_t pubkey;
343 9 : ulong stake_t_2;
344 9 : fd_pubkey_t node_account_t_2;
345 9 : fd_vote_stakes_fork_iter_ele( vote_stakes, fork_idx, iter, &pubkey, NULL, &stake_t_2, NULL, &node_account_t_2, NULL, NULL );
346 9 : if( FD_UNLIKELY( !stake_t_2 ) ) continue;
347 :
348 9 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
349 9 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_2, sizeof(fd_pubkey_t) );
350 9 : weights[ weights_cnt ].stake = stake_t_2;
351 9 : weights_cnt++;
352 9 : }
353 9 : fd_vote_stakes_fork_iter_fini( vote_stakes );
354 9 : }
355 :
356 9 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
357 :
358 9 : return weights_cnt;
359 9 : }
360 :
361 : ulong
362 : fd_stake_weights_by_node_next( fd_top_votes_t const * top_votes_t_1,
363 : fd_vote_stakes_t * vote_stakes,
364 : ushort fork_idx,
365 : fd_vote_stake_weight_t * weights,
366 9 : int vat_enabled ) {
367 :
368 9 : ulong weights_cnt = 0;
369 9 : if( vat_enabled ) {
370 0 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
371 0 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_1, iter_mem );
372 0 : !fd_top_votes_iter_done( top_votes_t_1, iter );
373 0 : fd_top_votes_iter_next( top_votes_t_1, iter ) ) {
374 0 : fd_pubkey_t pubkey;
375 0 : ulong stake_t_1;
376 0 : fd_pubkey_t node_account_t_1;
377 0 : fd_top_votes_iter_ele( top_votes_t_1, iter, &pubkey, &node_account_t_1, &stake_t_1, NULL, NULL, NULL );
378 :
379 0 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
380 0 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_1, sizeof(fd_pubkey_t) );
381 0 : weights[ weights_cnt ].stake = stake_t_1;
382 0 : weights_cnt++;
383 0 : }
384 9 : } else {
385 9 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
386 9 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_fork_iter_init( vote_stakes, fork_idx, iter_mem );
387 18 : !fd_vote_stakes_fork_iter_done( vote_stakes, fork_idx, iter );
388 9 : fd_vote_stakes_fork_iter_next( vote_stakes, fork_idx, iter ) ) {
389 :
390 9 : fd_pubkey_t pubkey;
391 9 : ulong stake_t_1;
392 9 : fd_pubkey_t node_account_t_1;
393 9 : fd_vote_stakes_fork_iter_ele( vote_stakes, fork_idx, iter, &pubkey, &stake_t_1, NULL, &node_account_t_1, NULL, NULL, NULL );
394 9 : if( FD_UNLIKELY( !stake_t_1 ) ) continue;
395 :
396 9 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
397 9 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_1, sizeof(fd_pubkey_t) );
398 9 : weights[ weights_cnt ].stake = stake_t_1;
399 9 : weights_cnt++;
400 9 : }
401 9 : fd_vote_stakes_fork_iter_fini( vote_stakes );
402 9 : }
403 :
404 9 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
405 :
406 9 : return weights_cnt;
407 9 : }
408 :
409 : static void
410 : get_vote_credits_commission( uchar const * account_data,
411 : ulong account_data_len,
412 : uchar * buf,
413 : uchar * commission_t_1,
414 : fd_pubkey_t * node_account_t_1,
415 9 : fd_epoch_credits_t * epoch_credits_opt ) {
416 :
417 9 : fd_bincode_decode_ctx_t ctx = {
418 9 : .data = account_data,
419 9 : .dataend = account_data + account_data_len,
420 9 : };
421 :
422 9 : fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( buf, &ctx );
423 9 : if( FD_UNLIKELY( vsv==NULL ) ) {
424 0 : FD_LOG_CRIT(( "unable to decode vote state versioned" ));
425 0 : }
426 9 : fd_vote_epoch_credits_t * vote_epoch_credits = NULL;
427 :
428 9 : switch( vsv->discriminant ) {
429 0 : case fd_vote_state_versioned_enum_v1_14_11:
430 0 : *commission_t_1 = vsv->inner.v1_14_11.commission;
431 0 : *node_account_t_1 = vsv->inner.v1_14_11.node_pubkey;
432 0 : vote_epoch_credits = vsv->inner.v1_14_11.epoch_credits;
433 0 : break;
434 9 : case fd_vote_state_versioned_enum_v3:
435 9 : *commission_t_1 = vsv->inner.v3.commission;
436 9 : *node_account_t_1 = vsv->inner.v3.node_pubkey;
437 9 : vote_epoch_credits = vsv->inner.v3.epoch_credits;
438 9 : break;
439 0 : case fd_vote_state_versioned_enum_v4:
440 0 : *commission_t_1 = (uchar)(vsv->inner.v4.inflation_rewards_commission_bps/100);
441 0 : *node_account_t_1 = vsv->inner.v4.node_pubkey;
442 0 : vote_epoch_credits = vsv->inner.v4.epoch_credits;
443 0 : break;
444 0 : default:
445 0 : FD_LOG_CRIT(( "invalid vote state version %u", vsv->discriminant ));
446 9 : }
447 :
448 9 : if( !epoch_credits_opt ) return;
449 9 : epoch_credits_opt->cnt = 0UL;
450 9 : for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( vote_epoch_credits );
451 9 : !deq_fd_vote_epoch_credits_t_iter_done( vote_epoch_credits, iter );
452 9 : iter = deq_fd_vote_epoch_credits_t_iter_next( vote_epoch_credits, iter ) ) {
453 0 : fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( vote_epoch_credits, iter );
454 0 : epoch_credits_opt->epoch[ epoch_credits_opt->cnt ] = (ushort)ele->epoch;
455 0 : epoch_credits_opt->credits[ epoch_credits_opt->cnt ] = ele->credits;
456 0 : epoch_credits_opt->prev_credits[ epoch_credits_opt->cnt ] = ele->prev_credits;
457 0 : epoch_credits_opt->cnt++;
458 0 : }
459 9 : }
460 :
461 : /* We need to update the amount of stake that each vote account has for
462 : the given epoch. This can only be done after the stake history
463 : sysvar has been updated. We also cache the stakes for each of the
464 : vote accounts for the previous epoch.
465 :
466 : https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L471 */
467 : void
468 : fd_refresh_vote_accounts( fd_bank_t * bank,
469 : fd_accdb_user_t * accdb,
470 : fd_funk_txn_xid_t const * xid,
471 : fd_runtime_stack_t * runtime_stack,
472 : fd_stake_delegations_t const * stake_delegations,
473 : fd_stake_history_t const * history,
474 9 : ulong * new_rate_activation_epoch ) {
475 :
476 9 : fd_vote_rewards_map_t * vote_reward_map = runtime_stack->stakes.vote_map;
477 9 : fd_vote_rewards_map_reset( vote_reward_map );
478 9 : ulong vote_reward_cnt = 0UL;
479 :
480 9 : uchar __attribute__((aligned(128))) vsv_buf[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
481 :
482 : /* First accumulate stakes across all delegations for all vote
483 : accounts. At this point, don't care if they are valid accounts or
484 : if they will be inserted into the top votes set. */
485 :
486 9 : fd_stake_accum_t * stake_accum_pool = runtime_stack->stakes.stake_accum;
487 9 : fd_stake_accum_map_t * stake_accum_map = runtime_stack->stakes.stake_accum_map;
488 :
489 9 : fd_stake_accum_map_reset( runtime_stack->stakes.stake_accum_map );
490 9 : ulong epoch = bank->f.epoch;
491 9 : ulong total_stake = 0UL;
492 9 : ulong total_activating = 0UL;
493 9 : ulong total_deactivating = 0UL;
494 9 : ulong staked_accounts = 0UL;
495 9 : fd_stake_delegations_iter_t iter_[1];
496 9 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
497 18 : !fd_stake_delegations_iter_done( iter );
498 9 : fd_stake_delegations_iter_next( iter ) ) {
499 :
500 9 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
501 :
502 9 : fd_stake_history_entry_t new_entry = fd_stakes_activating_and_deactivating(
503 9 : stake_delegation,
504 9 : epoch,
505 9 : history,
506 9 : new_rate_activation_epoch );
507 9 : total_stake += new_entry.effective;
508 9 : total_activating += new_entry.activating;
509 9 : total_deactivating += new_entry.deactivating;
510 :
511 9 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_ele_query( stake_accum_map, &stake_delegation->vote_account, NULL, stake_accum_pool );
512 9 : if( FD_UNLIKELY( !stake_accum ) ) {
513 9 : if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_vote_accounts ) ) {
514 0 : FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
515 0 : }
516 9 : stake_accum = &runtime_stack->stakes.stake_accum[ staked_accounts ];
517 9 : stake_accum->pubkey = stake_delegation->vote_account;
518 9 : stake_accum->stake = new_entry.effective;
519 9 : fd_stake_accum_map_ele_insert( stake_accum_map, stake_accum, stake_accum_pool );
520 9 : staked_accounts++;
521 9 : } else {
522 0 : stake_accum->stake += new_entry.effective;
523 0 : }
524 9 : }
525 :
526 : /* Only update total_*_stake at the epoch boundary. These values
527 : are snapshots of the stake totals for the current epoch. */
528 9 : bank->f.total_activating_stake = total_activating;
529 9 : bank->f.total_deactivating_stake = total_deactivating;
530 9 : bank->f.total_epoch_stake = total_stake;
531 :
532 : /* Copy the top votes set for the t-1 epoch into the t-2 epoch now
533 : that the epoch boundary is being crossed. Reset the existing t-1
534 : top votes set to prepare it for insertion. Refresh the states of
535 : the t-2 top votes set: figure out if the account still exists and
536 : what the last vote timestamp and slot are. */
537 :
538 9 : fd_top_votes_t * top_votes_t_1 = fd_bank_top_votes_t_1_modify( bank );
539 9 : fd_top_votes_t * top_votes_t_2 = fd_bank_top_votes_t_2_modify( bank );
540 9 : fd_memcpy( top_votes_t_2, top_votes_t_1, FD_TOP_VOTES_MAX_FOOTPRINT );
541 9 : fd_top_votes_init( top_votes_t_1 );
542 :
543 9 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) top_votes_iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
544 9 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_2, top_votes_iter_mem );
545 15 : !fd_top_votes_iter_done( top_votes_t_2, iter );
546 9 : fd_top_votes_iter_next( top_votes_t_2, iter ) ) {
547 6 : fd_pubkey_t pubkey;
548 6 : uchar commission_t_2;
549 6 : fd_top_votes_iter_ele( top_votes_t_2, iter, &pubkey, NULL, NULL, &commission_t_2, NULL, NULL );
550 :
551 6 : fd_accdb_ro_t vote_ro[1];
552 6 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, vote_ro, xid, &pubkey ) ) ) {
553 0 : fd_top_votes_invalidate( top_votes_t_2, &pubkey );
554 0 : continue;
555 0 : }
556 6 : if( FD_UNLIKELY( !fd_vsv_is_correct_size_and_initialized( vote_ro->meta ) ) ) {
557 0 : fd_top_votes_invalidate( top_votes_t_2, &pubkey );
558 0 : fd_accdb_close_ro( accdb, vote_ro );
559 0 : continue;
560 0 : }
561 :
562 6 : fd_vote_block_timestamp_t last_vote = fd_vsv_get_vote_block_timestamp( fd_account_data( vote_ro->meta ), vote_ro->meta->dlen );
563 6 : fd_top_votes_update( top_votes_t_2, &pubkey, last_vote.slot, last_vote.timestamp );
564 :
565 6 : if( FD_FEATURE_ACTIVE_BANK( bank, validator_admission_ticket ) ) {
566 0 : uchar commission_t_1 = 0;
567 0 : fd_pubkey_t node_account_t_1 = {0};
568 0 : fd_epoch_credits_t * epoch_credits = &runtime_stack->stakes.epoch_credits[ vote_reward_cnt ];
569 0 : get_vote_credits_commission( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), vsv_buf, &commission_t_1, &node_account_t_1, epoch_credits );
570 0 : fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
571 0 : vote_ele->pubkey = pubkey;
572 0 : vote_ele->vote_rewards = 0UL;
573 0 : vote_ele->commission_t_1 = commission_t_1;
574 0 : vote_ele->commission_t_2 = commission_t_2;
575 0 : fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
576 0 : vote_reward_cnt++;
577 0 : }
578 6 : fd_accdb_close_ro( accdb, vote_ro );
579 6 : }
580 :
581 : /* Now for each staked vote account, figure out if it is a valid
582 : account and insert into the vote stakes (an account can not exist
583 : but still be inserted into the vote stakes if it existed in the
584 : previous epoch or vice versa). The only condition an account is
585 : not inserted into the vote stakes is if it didn't exist in the
586 : previous epoch and in the current one. */
587 :
588 9 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
589 9 : ushort parent_idx = bank->vote_stakes_fork_id;
590 9 : ushort child_idx = fd_vote_stakes_new_child( vote_stakes );
591 9 : bank->vote_stakes_fork_id = child_idx;
592 :
593 9 : for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( stake_accum_map, stake_accum_pool );
594 18 : !fd_stake_accum_map_iter_done( iter, stake_accum_map, stake_accum_pool );
595 9 : iter = fd_stake_accum_map_iter_next( iter, stake_accum_map, stake_accum_pool ) ) {
596 9 : fd_stake_accum_t * stake_accum = fd_stake_accum_map_iter_ele( iter, stake_accum_map, stake_accum_pool );
597 :
598 9 : fd_pubkey_t node_account_t_2 = {0};
599 9 : ulong stake_t_2 = 0UL;
600 9 : uchar commission_t_2 = 0;
601 9 : int exists_prev = fd_vote_stakes_query_t_1( vote_stakes, parent_idx, &stake_accum->pubkey, &stake_t_2, &node_account_t_2, &commission_t_2 );
602 :
603 9 : fd_pubkey_t node_account_t_1 = {0};
604 9 : ulong stake_t_1 = 0UL;
605 9 : uchar commission_t_1 = 0;
606 :
607 9 : fd_accdb_ro_t vote_ro[1];
608 9 : int exists_curr = 1;
609 9 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, vote_ro, xid, &stake_accum->pubkey ) ) ) {
610 0 : exists_curr = 0;
611 9 : } else if( FD_UNLIKELY( !fd_vsv_is_correct_size_and_initialized( vote_ro->meta ) ) ) {
612 0 : exists_curr = 0;
613 0 : fd_accdb_close_ro( accdb, vote_ro );
614 9 : } else {
615 9 : fd_epoch_credits_t * epoch_credits = vote_reward_cnt<runtime_stack->expected_vote_accounts ? &runtime_stack->stakes.epoch_credits[ vote_reward_cnt ] : NULL;
616 9 : get_vote_credits_commission( fd_accdb_ref_data_const( vote_ro ), fd_accdb_ref_data_sz( vote_ro ), vsv_buf, &commission_t_1, &node_account_t_1, epoch_credits );
617 :
618 9 : stake_t_1 = stake_accum->stake;
619 :
620 9 : if( !FD_FEATURE_ACTIVE_BANK( bank, validator_admission_ticket ) ) {
621 9 : fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
622 9 : vote_ele->pubkey = stake_accum->pubkey;
623 9 : vote_ele->vote_rewards = 0UL;
624 9 : vote_ele->commission_t_1 = commission_t_1;
625 9 : vote_ele->commission_t_2 = exists_prev ? commission_t_2 : commission_t_1;
626 9 : fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
627 9 : vote_reward_cnt++;
628 9 : }
629 :
630 :
631 9 : if( FD_FEATURE_ACTIVE_BANK( bank, validator_admission_ticket ) ) {
632 0 : if( FD_UNLIKELY( !fd_vsv_is_v4_with_bls_pubkey( vote_ro->meta ) ) ) {
633 0 : fd_accdb_close_ro( accdb, vote_ro );
634 0 : continue;
635 0 : }
636 0 : }
637 9 : fd_accdb_close_ro( accdb, vote_ro );
638 9 : fd_top_votes_insert( top_votes_t_1, &stake_accum->pubkey, &node_account_t_1, stake_t_1, commission_t_1 );
639 9 : }
640 :
641 9 : if( FD_UNLIKELY( !exists_curr && !exists_prev ) ) continue;
642 9 : fd_vote_stakes_insert(
643 9 : vote_stakes, child_idx, &stake_accum->pubkey,
644 9 : &node_account_t_1, &node_account_t_2,
645 9 : stake_t_1, stake_t_2,
646 9 : commission_t_1, commission_t_2,
647 9 : bank->f.epoch );
648 9 : }
649 9 : }
650 :
651 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L280 */
652 : void
653 : fd_stakes_activate_epoch( fd_bank_t * bank,
654 : fd_runtime_stack_t * runtime_stack,
655 : fd_accdb_user_t * accdb,
656 : fd_funk_txn_xid_t const * xid,
657 : fd_capture_ctx_t * capture_ctx,
658 : fd_stake_delegations_t const * stake_delegations,
659 9 : ulong * new_rate_activation_epoch ) {
660 :
661 : /* We can update our stake history sysvar based on the bank stake values.
662 : Afterward, we can refresh the stake values for the vote accounts. */
663 :
664 9 : fd_stake_history_t stake_history[1];
665 9 : if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, xid, stake_history ) ) ) {
666 0 : FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
667 0 : }
668 :
669 9 : fd_epoch_stake_history_entry_pair_t elem = {
670 9 : .epoch = bank->f.epoch,
671 9 : .entry = {
672 9 : .effective = stake_delegations->effective_stake,
673 9 : .activating = stake_delegations->activating_stake,
674 9 : .deactivating = stake_delegations->deactivating_stake,
675 9 : }
676 9 : };
677 9 : fd_sysvar_stake_history_update( bank, accdb, xid, capture_ctx, &elem );
678 :
679 9 : if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, xid, stake_history ) ) ) {
680 0 : FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
681 0 : }
682 :
683 : /* Now increment the epoch and recompute the stakes for the vote
684 : accounts for the new epoch value. */
685 :
686 9 : bank->f.epoch = bank->f.epoch + 1UL;
687 :
688 9 : fd_refresh_vote_accounts( bank,
689 9 : accdb,
690 9 : xid,
691 9 : runtime_stack,
692 9 : stake_delegations,
693 9 : stake_history,
694 9 : new_rate_activation_epoch );
695 :
696 9 : }
697 :
698 :
699 : void
700 : fd_stakes_update_stake_delegation( fd_pubkey_t const * pubkey,
701 : fd_account_meta_t const * meta,
702 0 : fd_bank_t * bank ) {
703 :
704 0 : fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
705 :
706 : /* fd_stakes_get_state returns NULL for closed/invalid accounts. */
707 0 : fd_stake_state_t const * stake_state = fd_stakes_get_state( meta );
708 0 : if( FD_LIKELY( stake_state != NULL &&
709 0 : stake_state->stake_type == FD_STAKE_STATE_STAKE &&
710 0 : stake_state->stake.stake.delegation.stake != 0UL ) ) {
711 :
712 0 : ulong new_stake = stake_state->stake.stake.delegation.stake;
713 0 : fd_stake_delegations_fork_update( stake_delegations, bank->stake_delegations_fork_id, pubkey,
714 0 : &stake_state->stake.stake.delegation.voter_pubkey,
715 0 : new_stake,
716 0 : stake_state->stake.stake.delegation.activation_epoch,
717 0 : stake_state->stake.stake.delegation.deactivation_epoch,
718 0 : stake_state->stake.stake.credits_observed,
719 0 : stake_state->stake.stake.delegation.warmup_cooldown_rate );
720 :
721 0 : } else {
722 0 : fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, pubkey );
723 0 : }
724 0 : }
|