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