Line data Source code
1 : #include "fd_vote_state_versioned.h"
2 : #include "fd_vote_codec_tmpl.h"
3 : #include "fd_vote_utils.h"
4 : #include "fd_vote_state_v3.h"
5 : #include "fd_vote_state_v4.h"
6 : #include "fd_vote_state_v5.h"
7 : #include "fd_authorized_voters.h"
8 : #include "../fd_vote_program.h"
9 : #include "../../fd_runtime.h"
10 : #include "../../fd_system_ids.h"
11 :
12 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L42 */
13 : #define DEFAULT_PRIOR_VOTERS_OFFSET 114
14 :
15 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L886 */
16 : #define VERSION_OFFSET (4UL)
17 :
18 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L887 */
19 : #define DEFAULT_PRIOR_VOTERS_END (118)
20 :
21 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L6 */
22 : #define DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11 (82UL)
23 :
24 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L60 */
25 : #define DEFAULT_PRIOR_VOTERS_END_1_14_11 (86UL)
26 :
27 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L780-L785 */
28 : static inline fd_vote_lockout_t *
29 34589 : last_lockout( fd_vote_state_versioned_t * self ) {
30 34589 : fd_landed_vote_t * votes = NULL;
31 34589 : switch( self->kind ) {
32 0 : case fd_vote_state_versioned_enum_v3:
33 0 : votes = self->v3.votes;
34 0 : break;
35 34592 : case fd_vote_state_versioned_enum_v4:
36 34592 : votes = self->v4.votes;
37 34592 : break;
38 0 : default:
39 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
40 34589 : }
41 :
42 34592 : if( deq_fd_landed_vote_t_empty( votes ) ) return NULL;
43 33834 : fd_landed_vote_t * last_vote = deq_fd_landed_vote_t_peek_tail( votes );
44 33834 : return &last_vote->lockout;
45 34592 : }
46 :
47 : /**********************************************************************/
48 : /* Getters */
49 : /**********************************************************************/
50 :
51 : int
52 : fd_vsv_get_state( fd_acc_t const * acc,
53 4013 : fd_vote_state_versioned_t * versioned ) {
54 4013 : if( FD_UNLIKELY( !fd_vote_state_versioned_deserialize( versioned, acc->data, acc->data_len ) ) ) {
55 528 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
56 528 : }
57 :
58 3485 : return FD_EXECUTOR_INSTR_SUCCESS;
59 4013 : }
60 :
61 : int
62 : fd_vsv_deserialize( fd_acc_t const * acc,
63 3850 : fd_vote_state_versioned_t * versioned ) {
64 3850 : int rc = fd_vsv_get_state( acc, versioned );
65 3850 : if( FD_UNLIKELY( rc ) ) return rc;
66 :
67 3322 : if( FD_UNLIKELY( versioned->kind==fd_vote_state_versioned_enum_uninitialized ) ) {
68 658 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
69 658 : }
70 :
71 2664 : return FD_EXECUTOR_INSTR_SUCCESS;
72 3322 : }
73 :
74 : fd_pubkey_t const *
75 1220 : fd_vsv_get_authorized_withdrawer( fd_vote_state_versioned_t * self ) {
76 1220 : switch( self->kind ) {
77 0 : case fd_vote_state_versioned_enum_v1_14_11:
78 0 : return &self->v1_14_11.authorized_withdrawer;
79 0 : case fd_vote_state_versioned_enum_v3:
80 0 : return &self->v3.authorized_withdrawer;
81 1220 : case fd_vote_state_versioned_enum_v4:
82 1220 : return &self->v4.authorized_withdrawer;
83 0 : case fd_vote_state_versioned_enum_v5:
84 0 : return &self->v5.authorized_withdrawer;
85 0 : default:
86 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
87 1220 : }
88 1220 : }
89 :
90 : static ushort
91 : v5_latest_commission_bps( fd_vote_state_v5_t const * v5,
92 0 : uint kind ) {
93 0 : fd_vote_state_v5_commission_t c;
94 0 : fd_vote_state_v5_commission_get( v5, kind, &c );
95 0 : return fd_vote_state_v5_commission_latest( &c );
96 0 : }
97 :
98 : static void
99 : v5_schedule_commission( fd_vote_state_v5_t * v5,
100 : uint kind,
101 : ulong current_epoch,
102 0 : ushort bps ) {
103 0 : fd_vote_state_v5_commission_t c;
104 0 : fd_vote_state_v5_commission_get( v5, kind, &c );
105 0 : fd_vote_state_v5_commission_schedule( &c, current_epoch+2UL, bps );
106 0 : fd_vote_state_v5_commission_set( v5, kind, &c );
107 0 : }
108 :
109 : uchar
110 0 : fd_vsv_get_commission( fd_vote_state_versioned_t * self ) {
111 0 : switch( self->kind ) {
112 0 : case fd_vote_state_versioned_enum_v3:
113 0 : return self->v3.commission;
114 0 : case fd_vote_state_versioned_enum_v4:
115 0 : return (uchar)fd_ushort_min( (ushort)UCHAR_MAX, (ushort)( self->v4.inflation_rewards_commission_bps/100 ) );
116 0 : case fd_vote_state_versioned_enum_v5:
117 0 : return (uchar)fd_ushort_min( (ushort)UCHAR_MAX, (ushort)( v5_latest_commission_bps( &self->v5, fd_commission_kind_enum_inflation_rewards )/100 ) );
118 0 : default:
119 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
120 0 : }
121 0 : }
122 :
123 : fd_vote_epoch_credits_t const *
124 9 : fd_vsv_get_epoch_credits( fd_vote_state_versioned_t * self ) {
125 9 : return fd_vsv_get_epoch_credits_mutable( self );
126 9 : }
127 :
128 : fd_vote_epoch_credits_t const *
129 9 : fd_vsv_get_last_epoch_credits( fd_vote_state_versioned_t * self ) {
130 9 : if( self->kind==fd_vote_state_versioned_enum_v5 ) {
131 0 : ulong cnt = self->v5.accumulated_lamports_len;
132 0 : return cnt ? &self->v5.accumulated_lamports[ cnt-1UL ] : NULL;
133 0 : }
134 9 : fd_vote_epoch_credits_t const * epoch_credits = fd_vsv_get_epoch_credits( self );
135 9 : if( deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) return NULL;
136 6 : return deq_fd_vote_epoch_credits_t_peek_tail_const( epoch_credits );
137 9 : }
138 :
139 : fd_landed_vote_t const *
140 377 : fd_vsv_get_votes( fd_vote_state_versioned_t * self ) {
141 377 : return fd_vsv_get_votes_mutable( self );
142 377 : }
143 :
144 : ulong const *
145 34588 : fd_vsv_get_last_voted_slot( fd_vote_state_versioned_t * self ) {
146 34588 : fd_vote_lockout_t * last_lockout_ = last_lockout( self );
147 34588 : if( FD_UNLIKELY( !last_lockout_ ) ) return NULL;
148 33830 : return &last_lockout_->slot;
149 34588 : }
150 :
151 : ulong const *
152 719 : fd_vsv_get_root_slot( fd_vote_state_versioned_t * self ) {
153 719 : switch( self->kind ) {
154 0 : case fd_vote_state_versioned_enum_v3:
155 0 : if( !self->v3.has_root_slot ) return NULL;
156 0 : return &self->v3.root_slot;
157 719 : case fd_vote_state_versioned_enum_v4:
158 719 : if( !self->v4.has_root_slot ) return NULL;
159 341 : return &self->v4.root_slot;
160 0 : default:
161 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
162 719 : }
163 719 : }
164 :
165 : fd_vote_block_timestamp_t const *
166 234 : fd_vsv_get_last_timestamp( fd_vote_state_versioned_t * self ) {
167 234 : switch( self->kind ) {
168 0 : case fd_vote_state_versioned_enum_v3:
169 0 : return &self->v3.last_timestamp;
170 234 : case fd_vote_state_versioned_enum_v4:
171 234 : return &self->v4.last_timestamp;
172 0 : default:
173 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
174 234 : }
175 234 : }
176 :
177 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L938 */
178 : int
179 238 : fd_vsv_has_bls_pubkey( fd_vote_state_versioned_t * self ) {
180 238 : switch( self->kind ) {
181 0 : case fd_vote_state_versioned_enum_uninitialized:
182 0 : return 0;
183 0 : case fd_vote_state_versioned_enum_v1_14_11:
184 0 : return 0;
185 0 : case fd_vote_state_versioned_enum_v3:
186 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L483 */
187 0 : return 0;
188 238 : case fd_vote_state_versioned_enum_v4:
189 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L676 */
190 238 : return !!self->v4.has_bls_pubkey_compressed;
191 0 : case fd_vote_state_versioned_enum_v5:
192 0 : return 1;
193 0 : default:
194 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
195 238 : }
196 238 : }
197 :
198 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L823-L828 */
199 : ulong
200 178 : fd_vsv_get_pending_delegator_rewards( fd_vote_state_versioned_t * self ) {
201 178 : switch( self->kind ) {
202 0 : case fd_vote_state_versioned_enum_v3:
203 0 : return 0UL;
204 178 : case fd_vote_state_versioned_enum_v4:
205 178 : return self->v4.pending_delegator_rewards;
206 0 : case fd_vote_state_versioned_enum_v5:
207 0 : return self->v5.pending_delegator_rewards;
208 0 : default:
209 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
210 178 : }
211 178 : }
212 :
213 : /**********************************************************************/
214 : /* Mutable getters */
215 : /**********************************************************************/
216 :
217 : fd_vote_epoch_credits_t *
218 7311 : fd_vsv_get_epoch_credits_mutable( fd_vote_state_versioned_t * self ) {
219 7311 : switch( self->kind ) {
220 0 : case fd_vote_state_versioned_enum_v3:
221 0 : return self->v3.epoch_credits;
222 7310 : case fd_vote_state_versioned_enum_v4:
223 7310 : return self->v4.epoch_credits;
224 0 : default:
225 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
226 7311 : }
227 7311 : }
228 :
229 : fd_landed_vote_t *
230 48797 : fd_vsv_get_votes_mutable( fd_vote_state_versioned_t * self ) {
231 48797 : switch( self->kind ) {
232 0 : case fd_vote_state_versioned_enum_v3:
233 0 : return self->v3.votes;
234 48798 : case fd_vote_state_versioned_enum_v4:
235 48798 : return self->v4.votes;
236 0 : default:
237 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
238 48797 : }
239 48797 : }
240 :
241 : /**********************************************************************/
242 : /* Setters */
243 : /**********************************************************************/
244 :
245 : int
246 : fd_vsv_set_state( fd_borrowed_account_t * self,
247 1086 : fd_vote_state_versioned_t * state ) {
248 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L974 */
249 1086 : uchar * data = NULL;
250 1086 : ulong dlen = 0UL;
251 1086 : int err = fd_borrowed_account_get_data_mut( self, &data, &dlen );
252 1086 : if( FD_UNLIKELY( err ) ) {
253 0 : return err;
254 0 : }
255 :
256 : /* Note that although the serialization method already performs bounds
257 : checks, the account data buffer should remain unmodified if the
258 : serialization would fail.
259 : https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L978 */
260 1086 : ulong serialized_size = fd_vote_state_versioned_serialized_size( state );
261 1086 : if( FD_UNLIKELY( serialized_size>dlen ) ) {
262 0 : return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL;
263 0 : }
264 :
265 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L983 */
266 1086 : if( FD_UNLIKELY( fd_vote_state_versioned_serialize( state, data, dlen ) ) ) {
267 0 : FD_LOG_CRIT(( "invariant violation: fd_vote_state_versioned_serialize failed" ));
268 0 : }
269 :
270 1086 : return FD_EXECUTOR_INSTR_SUCCESS;
271 1086 : }
272 :
273 : int
274 : fd_vsv_set_vote_account_state( fd_exec_instr_ctx_t const * ctx,
275 : fd_borrowed_account_t * vote_account,
276 931 : fd_vote_state_versioned_t * versioned ) {
277 931 : switch( versioned->kind ) {
278 0 : case fd_vote_state_versioned_enum_v3:
279 0 : return fd_vote_state_v3_set_vote_account_state( ctx, vote_account, versioned );
280 931 : case fd_vote_state_versioned_enum_v4:
281 931 : return fd_vote_state_v4_set_vote_account_state( ctx, vote_account, versioned );
282 0 : case fd_vote_state_versioned_enum_v5:
283 0 : return fd_vsv_set_state( vote_account, versioned );
284 0 : default:
285 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", versioned->kind ));
286 931 : }
287 931 : }
288 :
289 : void
290 : fd_vsv_set_authorized_withdrawer( fd_vote_state_versioned_t * self,
291 130 : fd_pubkey_t const * authorized_withdrawer ) {
292 130 : switch( self->kind ) {
293 0 : case fd_vote_state_versioned_enum_v3: {
294 0 : self->v3.authorized_withdrawer = *authorized_withdrawer;
295 0 : break;
296 0 : }
297 130 : case fd_vote_state_versioned_enum_v4: {
298 130 : self->v4.authorized_withdrawer = *authorized_withdrawer;
299 130 : break;
300 0 : }
301 0 : case fd_vote_state_versioned_enum_v5: {
302 0 : self->v5.authorized_withdrawer = *authorized_withdrawer;
303 0 : break;
304 0 : }
305 0 : default:
306 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
307 130 : }
308 130 : }
309 :
310 : int
311 : fd_vsv_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx,
312 : fd_vote_state_versioned_t * self,
313 : fd_pubkey_t const * authorized_pubkey,
314 : ulong current_epoch,
315 : ulong target_epoch,
316 : uchar const * bls_pubkey,
317 : int authorized_withdrawer_signer,
318 : fd_pubkey_t const * signers[ FD_INSTR_SIGNERS_MAX ],
319 215 : ulong signers_cnt ) {
320 215 : switch( self->kind ) {
321 0 : case fd_vote_state_versioned_enum_v3:
322 0 : return fd_vote_state_v3_set_new_authorized_voter(
323 0 : ctx,
324 0 : &self->v3,
325 0 : authorized_pubkey,
326 0 : current_epoch,
327 0 : target_epoch,
328 0 : bls_pubkey,
329 0 : authorized_withdrawer_signer,
330 0 : signers,
331 0 : signers_cnt
332 0 : );
333 215 : case fd_vote_state_versioned_enum_v4:
334 215 : return fd_vote_state_v4_set_new_authorized_voter(
335 215 : ctx,
336 215 : &self->v4,
337 215 : authorized_pubkey,
338 215 : current_epoch,
339 215 : target_epoch,
340 215 : bls_pubkey,
341 215 : authorized_withdrawer_signer,
342 215 : signers,
343 215 : signers_cnt
344 215 : );
345 0 : case fd_vote_state_versioned_enum_v5:
346 0 : return fd_vote_state_v5_set_new_authorized_voter(
347 0 : ctx,
348 0 : &self->v5,
349 0 : authorized_pubkey,
350 0 : current_epoch,
351 0 : target_epoch,
352 0 : bls_pubkey,
353 0 : authorized_withdrawer_signer,
354 0 : signers,
355 0 : signers_cnt
356 0 : );
357 0 : default:
358 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
359 215 : }
360 215 : }
361 :
362 : void
363 : fd_vsv_set_node_pubkey( fd_vote_state_versioned_t * self,
364 175 : fd_pubkey_t const * node_pubkey ) {
365 175 : switch( self->kind ) {
366 0 : case fd_vote_state_versioned_enum_v3:
367 0 : self->v3.node_pubkey = *node_pubkey;
368 0 : break;
369 175 : case fd_vote_state_versioned_enum_v4:
370 175 : self->v4.node_pubkey = *node_pubkey;
371 175 : break;
372 0 : case fd_vote_state_versioned_enum_v5:
373 0 : self->v5.node_pubkey = *node_pubkey;
374 0 : break;
375 0 : default:
376 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
377 175 : }
378 175 : }
379 :
380 : void
381 : fd_vsv_set_block_revenue_collector( fd_vote_state_versioned_t * self,
382 175 : fd_pubkey_t const * block_revenue_collector ) {
383 175 : switch( self->kind ) {
384 175 : case fd_vote_state_versioned_enum_v4:
385 175 : self->v4.block_revenue_collector = *block_revenue_collector;
386 175 : break;
387 0 : case fd_vote_state_versioned_enum_v5:
388 0 : self->v5.block_revenue_collector = *block_revenue_collector;
389 0 : break;
390 0 : case fd_vote_state_versioned_enum_v3:
391 : /* No-op for v3 */
392 0 : break;
393 0 : default:
394 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
395 175 : }
396 175 : }
397 :
398 : void
399 : fd_vsv_set_inflation_rewards_collector( fd_vote_state_versioned_t * self,
400 0 : fd_pubkey_t const * inflation_rewards_collector ) {
401 0 : switch( self->kind ) {
402 0 : case fd_vote_state_versioned_enum_v4:
403 0 : self->v4.inflation_rewards_collector = *inflation_rewards_collector;
404 0 : break;
405 0 : case fd_vote_state_versioned_enum_v5:
406 0 : self->v5.inflation_rewards_collector = *inflation_rewards_collector;
407 0 : break;
408 0 : case fd_vote_state_versioned_enum_v3:
409 : /* No-op for v3 */
410 0 : break;
411 0 : default:
412 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
413 0 : }
414 0 : }
415 :
416 : void
417 : fd_vsv_set_commission( fd_vote_state_versioned_t * self,
418 : uchar commission,
419 179 : ulong current_epoch ) {
420 179 : switch( self->kind ) {
421 0 : case fd_vote_state_versioned_enum_v3:
422 0 : self->v3.commission = commission;
423 0 : break;
424 179 : case fd_vote_state_versioned_enum_v4:
425 179 : self->v4.inflation_rewards_commission_bps = (ushort)( commission*100 );
426 179 : break;
427 0 : case fd_vote_state_versioned_enum_v5:
428 0 : v5_schedule_commission( &self->v5, fd_commission_kind_enum_inflation_rewards, current_epoch, (ushort)( commission*100 ) );
429 0 : break;
430 0 : default:
431 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
432 179 : }
433 179 : }
434 :
435 : void
436 : fd_vsv_set_inflation_rewards_commission_bps( fd_vote_state_versioned_t * self,
437 : ushort commission_bps,
438 0 : ulong current_epoch ) {
439 0 : switch( self->kind ) {
440 0 : case fd_vote_state_versioned_enum_v4:
441 0 : self->v4.inflation_rewards_commission_bps = commission_bps;
442 0 : break;
443 0 : case fd_vote_state_versioned_enum_v5:
444 0 : v5_schedule_commission( &self->v5, fd_commission_kind_enum_inflation_rewards, current_epoch, commission_bps );
445 0 : break;
446 0 : case fd_vote_state_versioned_enum_v3:
447 : /* No-op for v3 */
448 0 : break;
449 0 : default:
450 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
451 0 : }
452 0 : }
453 :
454 : void
455 : fd_vsv_set_block_revenue_commission_bps( fd_vote_state_versioned_t * self,
456 : ushort commission_bps,
457 0 : ulong current_epoch ) {
458 0 : switch( self->kind ) {
459 0 : case fd_vote_state_versioned_enum_v4:
460 0 : self->v4.block_revenue_commission_bps = commission_bps;
461 0 : break;
462 0 : case fd_vote_state_versioned_enum_v5:
463 0 : v5_schedule_commission( &self->v5, fd_commission_kind_enum_block_revenue, current_epoch, commission_bps );
464 0 : break;
465 0 : case fd_vote_state_versioned_enum_v3:
466 : /* No-op for v3 */
467 0 : break;
468 0 : default:
469 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
470 0 : }
471 0 : }
472 :
473 : void
474 7373 : fd_vsv_set_root_slot( fd_vote_state_versioned_t * self, ulong * root_slot ) {
475 7373 : switch( self->kind ) {
476 0 : case fd_vote_state_versioned_enum_v3:
477 0 : self->v3.has_root_slot = (root_slot!=NULL);
478 0 : if( FD_LIKELY( root_slot ) ) {
479 0 : self->v3.root_slot = *root_slot;
480 0 : }
481 0 : break;
482 7373 : case fd_vote_state_versioned_enum_v4:
483 7373 : self->v4.has_root_slot = (root_slot!=NULL);
484 7373 : if( FD_LIKELY( root_slot ) ) {
485 7289 : self->v4.root_slot = *root_slot;
486 7289 : }
487 7373 : break;
488 0 : default:
489 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
490 7373 : }
491 7373 : }
492 :
493 : void
494 : fd_vsv_set_last_timestamp( fd_vote_state_versioned_t * self,
495 86 : fd_vote_block_timestamp_t const * last_timestamp ) {
496 86 : switch( self->kind ) {
497 0 : case fd_vote_state_versioned_enum_v3:
498 0 : self->v3.last_timestamp = *last_timestamp;
499 0 : break;
500 86 : case fd_vote_state_versioned_enum_v4:
501 86 : self->v4.last_timestamp = *last_timestamp;
502 86 : break;
503 0 : default:
504 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
505 86 : }
506 86 : }
507 :
508 : /**********************************************************************/
509 : /* General functions */
510 : /**********************************************************************/
511 :
512 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L855
513 : static void
514 16044 : double_lockouts( fd_vote_state_versioned_t * self ) {
515 16044 : fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
516 :
517 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L856
518 16044 : ulong stack_depth = deq_fd_landed_vote_t_cnt( votes );
519 16044 : ulong i = 0;
520 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L857
521 16044 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( votes );
522 373575 : !deq_fd_landed_vote_t_iter_done( votes, iter );
523 357531 : iter = deq_fd_landed_vote_t_iter_next( votes, iter ) ) {
524 357531 : fd_landed_vote_t * v = deq_fd_landed_vote_t_iter_ele( votes, iter );
525 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L860
526 357531 : if( stack_depth >
527 357531 : fd_ulong_checked_add_expect(
528 357531 : i,
529 357531 : (ulong)v->lockout.confirmation_count,
530 357531 : "`confirmation_count` and tower_size should be bounded by `MAX_LOCKOUT_HISTORY`" ) )
531 357811 : {
532 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L864
533 357811 : fd_vote_lockout_increase_confirmation_count( &v->lockout, 1 );
534 357811 : }
535 357531 : i++;
536 357531 : }
537 16044 : }
538 :
539 : void
540 : fd_vsv_increment_credits( fd_vote_state_versioned_t * self,
541 : ulong epoch,
542 7302 : ulong credits ) {
543 7302 : fd_vote_epoch_credits_t * epoch_credits = fd_vsv_get_epoch_credits_mutable( self );
544 :
545 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L305 */
546 7302 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) {
547 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L288 */
548 2 : deq_fd_vote_epoch_credits_t_push_tail_wrap(
549 2 : epoch_credits,
550 2 : ( fd_vote_epoch_credits_t ){ .epoch = epoch, .credits = 0, .prev_credits = 0 } );
551 7300 : } else if( FD_LIKELY( epoch !=
552 7300 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch ) ) {
553 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L290 */
554 297 : fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits );
555 :
556 297 : ulong credits = last->credits;
557 297 : ulong prev_credits = last->prev_credits;
558 :
559 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L292-L299 */
560 297 : if( FD_LIKELY( credits!=prev_credits ) ) {
561 294 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>=MAX_EPOCH_CREDITS_HISTORY ) ) {
562 : /* Although Agave performs a `.remove(0)` AFTER the call to
563 : `.push()`, there is an edge case where the epoch credits is
564 : full, making the call to `_push_tail()` unsafe. Since Agave's
565 : structures are dynamically allocated, it is safe for them to
566 : simply call `.push()` and then popping afterwards. We have to
567 : reverse the order of operations to maintain correct behavior
568 : and avoid overflowing the deque.
569 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L303 */
570 3 : deq_fd_vote_epoch_credits_t_pop_head( epoch_credits );
571 3 : }
572 :
573 : /* This will not fail because we already popped if we're at
574 : capacity, since the epoch_credits deque is allocated with a
575 : minimum capacity of MAX_EPOCH_CREDITS_HISTORY. */
576 294 : deq_fd_vote_epoch_credits_t_push_tail(
577 294 : epoch_credits,
578 294 : ( fd_vote_epoch_credits_t ){
579 294 : .epoch = epoch, .credits = credits, .prev_credits = credits } );
580 294 : } else {
581 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L297-L298 */
582 3 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch = epoch;
583 :
584 : /* Here we can perform the same deque size check and pop if
585 : we're beyond the maximum epoch credits len. */
586 3 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>MAX_EPOCH_CREDITS_HISTORY ) ) {
587 0 : deq_fd_vote_epoch_credits_t_pop_head( epoch_credits );
588 0 : }
589 3 : }
590 297 : }
591 :
592 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L663
593 7302 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits = fd_ulong_sat_add(
594 7302 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits, credits );
595 7302 : }
596 :
597 : int
598 : fd_vsv_process_timestamp( fd_exec_instr_ctx_t * ctx,
599 : fd_vote_state_versioned_t * self,
600 : ulong slot,
601 234 : long timestamp ) {
602 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L160 */
603 234 : fd_vote_block_timestamp_t const * last_timestamp = fd_vsv_get_last_timestamp( self );
604 234 : if( FD_UNLIKELY(
605 234 : ( slot<last_timestamp->slot || timestamp<last_timestamp->timestamp ) ||
606 234 : ( slot==last_timestamp->slot &&
607 234 : ( slot!=last_timestamp->slot || timestamp!=last_timestamp->timestamp ) &&
608 234 : last_timestamp->slot!=0UL ) ) ) {
609 148 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_TIMESTAMP_TOO_OLD;
610 148 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
611 148 : }
612 :
613 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L168 */
614 86 : fd_vote_block_timestamp_t new_timestamp = {
615 86 : .slot = slot,
616 86 : .timestamp = timestamp,
617 86 : };
618 86 : fd_vsv_set_last_timestamp( self, &new_timestamp );
619 86 : return FD_EXECUTOR_INSTR_SUCCESS;
620 234 : }
621 :
622 : void
623 16053 : fd_vsv_pop_expired_votes( fd_vote_state_versioned_t * self, ulong next_vote_slot ) {
624 16053 : fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
625 :
626 20906 : while( !deq_fd_landed_vote_t_empty( votes ) ) {
627 20585 : fd_landed_vote_t * vote = deq_fd_landed_vote_t_peek_tail( votes );
628 20585 : if( !( fd_vote_lockout_is_locked_out_at_slot( &vote->lockout, next_vote_slot ) ) ) {
629 4853 : deq_fd_landed_vote_t_pop_tail( votes );
630 15732 : } else {
631 15732 : break;
632 15732 : }
633 20585 : }
634 16053 : }
635 :
636 : void
637 : fd_vsv_process_next_vote_slot( fd_vote_state_versioned_t * self,
638 : ulong next_vote_slot,
639 : ulong epoch,
640 16052 : ulong current_slot ) {
641 16052 : ulong const * last_voted_slot_ = fd_vsv_get_last_voted_slot( self );
642 16052 : if( FD_UNLIKELY( last_voted_slot_ && next_vote_slot <= *last_voted_slot_ ) ) return;
643 :
644 16052 : fd_vsv_pop_expired_votes( self, next_vote_slot );
645 :
646 16052 : fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
647 :
648 16052 : fd_landed_vote_t landed_vote = {
649 16052 : .latency = fd_vote_compute_vote_latency( next_vote_slot, current_slot ),
650 16052 : .lockout = ( fd_vote_lockout_t ){ .slot = next_vote_slot }
651 16052 : };
652 :
653 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L623
654 16052 : if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( votes ) == MAX_LOCKOUT_HISTORY ) ) {
655 7251 : ulong credits = fd_vote_credits_for_vote_at_index( votes, 0 );
656 7251 : fd_landed_vote_t landed_vote = deq_fd_landed_vote_t_pop_head( votes );
657 7251 : fd_vsv_set_root_slot( self, &landed_vote.lockout.slot );
658 :
659 7251 : fd_vsv_increment_credits( self, epoch, credits );
660 7251 : }
661 :
662 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L634
663 16052 : deq_fd_landed_vote_t_push_tail_wrap( votes, landed_vote );
664 16052 : double_lockouts( self );
665 16052 : }
666 :
667 : int
668 : fd_vsv_try_convert_to_v4( fd_vote_state_versioned_t * self,
669 2600 : fd_pubkey_t const * vote_pubkey ) {
670 2600 : switch( self->kind ) {
671 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L971-L974 */
672 0 : case fd_vote_state_versioned_enum_uninitialized: {
673 0 : return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT;
674 0 : }
675 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L975-L989 */
676 102 : case fd_vote_state_versioned_enum_v1_14_11: {
677 102 : fd_vote_state_1_14_11_t * state = &self->v1_14_11;
678 :
679 : /* Temporary to hold v4. Note that since v1_14_11 and v3/v4 use
680 : the same underlying types for votes, we can just directly set
681 : it and avoid an intermediate call to
682 : fd_vote_lockout_landed_votes_from_lockouts. */
683 102 : fd_vote_state_v4_t v4 = {
684 102 : .node_pubkey = state->node_pubkey,
685 102 : .authorized_withdrawer = state->authorized_withdrawer,
686 102 : .inflation_rewards_collector = *vote_pubkey,
687 102 : .block_revenue_collector = state->node_pubkey,
688 102 : .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ),
689 102 : .block_revenue_commission_bps = FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS,
690 102 : .pending_delegator_rewards = 0,
691 102 : .has_bls_pubkey_compressed = 0,
692 102 : .votes = state->votes,
693 102 : .has_root_slot = state->has_root_slot,
694 102 : .root_slot = state->root_slot,
695 102 : .authorized_voters = state->authorized_voters,
696 102 : .epoch_credits = state->epoch_credits,
697 102 : .last_timestamp = state->last_timestamp
698 102 : };
699 :
700 : /* Emplace new vote state into target */
701 102 : self->kind = fd_vote_state_versioned_enum_v4;
702 102 : self->v4 = v4;
703 :
704 102 : return FD_EXECUTOR_INSTR_SUCCESS;
705 0 : }
706 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L990-L1004 */
707 1244 : case fd_vote_state_versioned_enum_v3: {
708 1244 : fd_vote_state_v3_t * state = &self->v3;
709 1244 : fd_vote_state_v4_t v4 = {
710 1244 : .node_pubkey = state->node_pubkey,
711 1244 : .authorized_withdrawer = state->authorized_withdrawer,
712 1244 : .inflation_rewards_collector = *vote_pubkey,
713 1244 : .block_revenue_collector = state->node_pubkey,
714 1244 : .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ),
715 1244 : .block_revenue_commission_bps = FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS,
716 1244 : .pending_delegator_rewards = 0,
717 1244 : .has_bls_pubkey_compressed = 0,
718 1244 : .votes = state->votes,
719 1244 : .has_root_slot = state->has_root_slot,
720 1244 : .root_slot = state->root_slot,
721 1244 : .authorized_voters = state->authorized_voters,
722 1244 : .epoch_credits = state->epoch_credits,
723 1244 : .last_timestamp = state->last_timestamp
724 1244 : };
725 :
726 : /* Emplace new vote state into target */
727 1244 : self->kind = fd_vote_state_versioned_enum_v4;
728 1244 : self->v4 = v4;
729 :
730 1244 : return FD_EXECUTOR_INSTR_SUCCESS;
731 0 : }
732 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L1005 */
733 1257 : case fd_vote_state_versioned_enum_v4:
734 1257 : return FD_EXECUTOR_INSTR_SUCCESS;
735 : /* V5 can't be converted back to V4 */
736 0 : case fd_vote_state_versioned_enum_v5:
737 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
738 0 : default:
739 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
740 2600 : }
741 2600 : }
742 :
743 : int
744 1361 : fd_vsv_has_tower_votes( fd_vote_state_versioned_t const * self ) {
745 1361 : return self->kind!=fd_vote_state_versioned_enum_v5;
746 1361 : }
747 :
748 : int
749 : fd_vsv_try_convert_to_v5( fd_vote_state_versioned_t * self,
750 0 : ulong current_epoch ) {
751 0 : switch( self->kind ) {
752 0 : case fd_vote_state_versioned_enum_v5:
753 0 : return FD_EXECUTOR_INSTR_SUCCESS;
754 0 : case fd_vote_state_versioned_enum_v4: {
755 0 : fd_vote_state_v4_t * state = &self->v4;
756 0 : ulong av_cnt = fd_vote_authorized_voters_treap_ele_cnt( state->authorized_voters.treap );
757 0 : ulong ec_cnt = deq_fd_vote_epoch_credits_t_cnt( state->epoch_credits );
758 0 : ulong ec_skip = fd_ulong_sat_sub( ec_cnt, FD_VOTE_STATE_V5_ACCUMULATED_LAMPORTS_MAX );
759 0 : if( FD_UNLIKELY( !state->has_bls_pubkey_compressed ||
760 0 : av_cnt>MAX_AUTHORIZED_VOTERS ) ) {
761 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
762 0 : }
763 :
764 0 : fd_vote_state_v5_t v5 = {
765 0 : .node_pubkey = state->node_pubkey,
766 0 : .authorized_withdrawer = state->authorized_withdrawer,
767 0 : .inflation_rewards_collector = state->inflation_rewards_collector,
768 0 : .block_revenue_collector = state->block_revenue_collector,
769 0 : .inflation_rewards_commission_bps_len = 1,
770 0 : .block_revenue_commission_bps_len = 1,
771 0 : .pending_delegator_rewards = state->pending_delegator_rewards,
772 0 : .inflation_rewards_commission_bps_epochs = { current_epoch+2UL },
773 0 : .inflation_rewards_commission_bps = { state->inflation_rewards_commission_bps },
774 0 : .block_revenue_commission_bps_epochs = { current_epoch+2UL },
775 0 : .block_revenue_commission_bps = { state->block_revenue_commission_bps },
776 0 : .latest_vote = deq_fd_landed_vote_t_empty( state->votes ) ? ULONG_MAX : deq_fd_landed_vote_t_peek_tail_const( state->votes )->lockout.slot,
777 0 : .root_slot = state->has_root_slot ? state->root_slot : ULONG_MAX,
778 0 : .authorized_voters_len = (uint)av_cnt,
779 0 : .accumulated_lamports_len = (uint)( ec_cnt-ec_skip ),
780 0 : .last_timestamp = state->last_timestamp,
781 0 : .rank_epoch = ULONG_MAX,
782 0 : .rank = USHORT_MAX
783 0 : };
784 0 : fd_memcpy( v5.bls_pubkey_compressed, state->bls_pubkey_compressed, FD_BLS_PUBKEY_COMPRESSED_SZ );
785 :
786 0 : ulong i = 0UL;
787 0 : for( fd_vote_authorized_voters_treap_fwd_iter_t iter = fd_vote_authorized_voters_treap_fwd_iter_init( state->authorized_voters.treap, state->authorized_voters.pool );
788 0 : !fd_vote_authorized_voters_treap_fwd_iter_done( iter );
789 0 : iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, state->authorized_voters.pool ) ) {
790 0 : fd_vote_authorized_voter_t const * voter = fd_vote_authorized_voters_treap_fwd_iter_ele_const( iter, state->authorized_voters.pool );
791 0 : v5.authorized_voters[ i++ ] = (fd_vote_state_v5_authorized_voter_t){ .epoch=voter->epoch, .pubkey=voter->pubkey };
792 0 : }
793 :
794 0 : i = 0UL;
795 0 : for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( state->epoch_credits );
796 0 : !deq_fd_vote_epoch_credits_t_iter_done( state->epoch_credits, iter );
797 0 : iter = deq_fd_vote_epoch_credits_t_iter_next( state->epoch_credits, iter ) ) {
798 0 : if( i>=ec_skip ) v5.accumulated_lamports[ i-ec_skip ] = *deq_fd_vote_epoch_credits_t_iter_ele_const( state->epoch_credits, iter );
799 0 : i++;
800 0 : }
801 :
802 0 : self->kind = fd_vote_state_versioned_enum_v5;
803 0 : self->v5 = v5;
804 0 : return FD_EXECUTOR_INSTR_SUCCESS;
805 0 : }
806 0 : default:
807 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
808 0 : }
809 0 : }
810 :
811 : int
812 : fd_vsv_deinitialize_vote_account_state( fd_exec_instr_ctx_t * ctx,
813 : fd_borrowed_account_t * vote_account,
814 9 : int target_version ) {
815 : /* V4 clears the entire account data on deinitialize.
816 : https://github.com/anza-xyz/agave/blob/v4.1.0-alpha.0/programs/vote/src/vote_state/handler.rs#L366-L377 */
817 9 : (void)ctx;
818 9 : (void)target_version;
819 9 : uchar * data;
820 9 : ulong dlen;
821 9 : int rc = fd_borrowed_account_get_data_mut( vote_account, &data, &dlen );
822 9 : if( FD_UNLIKELY( rc ) ) return rc;
823 8 : fd_memset( data, 0, dlen );
824 8 : return FD_EXECUTOR_INSTR_SUCCESS;
825 9 : }
826 :
827 : int
828 2838 : fd_vsv_is_uninitialized( fd_vote_state_versioned_t * self ) {
829 2838 : switch( self->kind ) {
830 167 : case fd_vote_state_versioned_enum_uninitialized:
831 167 : return 1;
832 145 : case fd_vote_state_versioned_enum_v1_14_11:
833 145 : return fd_authorized_voters_is_empty( &self->v1_14_11.authorized_voters );
834 1270 : case fd_vote_state_versioned_enum_v3:
835 1270 : return fd_authorized_voters_is_empty( &self->v3.authorized_voters );
836 1257 : case fd_vote_state_versioned_enum_v4:
837 1257 : case fd_vote_state_versioned_enum_v5:
838 1257 : return 0; // v4 vote states are always initialized
839 0 : default:
840 0 : FD_LOG_CRIT(( "unsupported vote state versioned kind: %u", self->kind ));
841 2838 : }
842 2838 : }
843 :
844 : int
845 : fd_vsv_is_correct_size_and_initialized( uchar const * data,
846 1221 : ulong data_len ) {
847 : /* VoteStateV4::is_correct_size_and_initialized
848 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v4.rs#L207-L210 */
849 1221 : if( FD_LIKELY( data_len==FD_VOTE_STATE_V4_SZ && FD_LOAD( uint, data )==fd_vote_state_versioned_enum_v4 ) ) {
850 1221 : return 1;
851 1221 : }
852 :
853 0 : if( FD_LIKELY( data_len==FD_VOTE_STATE_V5_SZ && FD_LOAD( uint, data )==fd_vote_state_versioned_enum_v5 ) ) {
854 0 : return 1;
855 0 : }
856 :
857 : /* VoteStateV3::is_correct_size_and_initialized
858 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L509-L514 */
859 0 : if( FD_LIKELY( data_len==FD_VOTE_STATE_V3_SZ &&
860 0 : !fd_mem_iszero( data+VERSION_OFFSET, DEFAULT_PRIOR_VOTERS_OFFSET ) ) ) {
861 0 : return 1;
862 0 : }
863 :
864 : /* VoteState1_14_11::is_correct_size_and_initialized
865 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_1_14_11.rs#L63-L69 */
866 0 : if( FD_LIKELY( data_len==FD_VOTE_STATE_V2_SZ &&
867 0 : !fd_mem_iszero( data+VERSION_OFFSET, DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11 ) ) ) {
868 0 : return 1;
869 0 : }
870 :
871 0 : return 0;
872 0 : }
873 :
874 : int
875 : fd_vsv_is_correct_size_owner_and_init( uchar const * owner,
876 : uchar const * data,
877 1233 : ulong data_len ) {
878 1233 : if( FD_UNLIKELY( memcmp( owner, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
879 12 : return 0;
880 12 : }
881 :
882 1221 : return fd_vsv_is_correct_size_and_initialized( data, data_len );
883 1233 : }
|