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