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 6 : fd_vote_state_versioned_t * versioned ) {
52 6 : if( FD_UNLIKELY( !fd_vote_state_versioned_deserialize( versioned, acc->data, acc->data_len ) ) ) {
53 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
54 0 : }
55 :
56 6 : return FD_EXECUTOR_INSTR_SUCCESS;
57 6 : }
58 :
59 : int
60 : fd_vsv_deserialize( fd_acc_t const * acc,
61 0 : fd_vote_state_versioned_t * versioned ) {
62 0 : int rc = fd_vsv_get_state( acc, versioned );
63 0 : if( FD_UNLIKELY( rc ) ) return rc;
64 :
65 0 : if( FD_UNLIKELY( versioned->kind==fd_vote_state_versioned_enum_uninitialized ) ) {
66 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
67 0 : }
68 :
69 0 : return FD_EXECUTOR_INSTR_SUCCESS;
70 0 : }
71 :
72 : fd_pubkey_t const *
73 0 : fd_vsv_get_authorized_withdrawer( fd_vote_state_versioned_t * self ) {
74 0 : 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 0 : case fd_vote_state_versioned_enum_v4:
80 0 : return &self->v4.authorized_withdrawer;
81 0 : default:
82 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
83 0 : }
84 0 : }
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 0 : fd_vsv_get_epoch_credits_mutable( fd_vote_state_versioned_t * self ) {
179 0 : switch( self->kind ) {
180 0 : case fd_vote_state_versioned_enum_v3:
181 0 : return self->v3.epoch_credits;
182 0 : case fd_vote_state_versioned_enum_v4:
183 0 : return self->v4.epoch_credits;
184 0 : default:
185 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
186 0 : }
187 0 : }
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 6 : fd_vote_state_versioned_t * state ) {
208 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L974 */
209 6 : uchar * data = NULL;
210 6 : ulong dlen = 0UL;
211 6 : int err = fd_borrowed_account_get_data_mut( self, &data, &dlen );
212 6 : 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 6 : ulong serialized_size = fd_vote_state_versioned_serialized_size( state );
221 6 : 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 6 : 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 6 : return FD_EXECUTOR_INSTR_SUCCESS;
231 6 : }
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 0 : fd_vote_state_versioned_t * versioned ) {
237 0 : 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 0 : case fd_vote_state_versioned_enum_v4:
241 0 : 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 0 : }
245 0 : }
246 :
247 : void
248 : fd_vsv_set_authorized_withdrawer( fd_vote_state_versioned_t * self,
249 0 : fd_pubkey_t const * authorized_withdrawer ) {
250 0 : 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 0 : case fd_vote_state_versioned_enum_v4: {
256 0 : self->v4.authorized_withdrawer = *authorized_withdrawer;
257 0 : break;
258 0 : }
259 0 : default:
260 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
261 0 : }
262 0 : }
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_TXN_SIG_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 0 : fd_pubkey_t const * node_pubkey ) {
307 0 : switch( self->kind ) {
308 0 : case fd_vote_state_versioned_enum_v3:
309 0 : self->v3.node_pubkey = *node_pubkey;
310 0 : break;
311 0 : case fd_vote_state_versioned_enum_v4:
312 0 : self->v4.node_pubkey = *node_pubkey;
313 0 : break;
314 0 : default:
315 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
316 0 : }
317 0 : }
318 :
319 : void
320 : fd_vsv_set_block_revenue_collector( fd_vote_state_versioned_t * self,
321 0 : fd_pubkey_t const * block_revenue_collector ) {
322 0 : switch( self->kind ) {
323 0 : case fd_vote_state_versioned_enum_v4:
324 0 : self->v4.block_revenue_collector = *block_revenue_collector;
325 0 : 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 0 : }
332 0 : }
333 :
334 : void
335 : fd_vsv_set_commission( fd_vote_state_versioned_t * self,
336 0 : uchar commission ) {
337 0 : switch( self->kind ) {
338 0 : case fd_vote_state_versioned_enum_v3:
339 0 : self->v3.commission = commission;
340 0 : break;
341 0 : case fd_vote_state_versioned_enum_v4:
342 0 : self->v4.inflation_rewards_commission_bps = (ushort)( commission*100 );
343 0 : break;
344 0 : default:
345 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
346 0 : }
347 0 : }
348 :
349 : void
350 : fd_vsv_set_inflation_rewards_commission_bps( fd_vote_state_versioned_t * self,
351 0 : ushort commission_bps ) {
352 0 : switch( self->kind ) {
353 0 : case fd_vote_state_versioned_enum_v4:
354 0 : self->v4.inflation_rewards_commission_bps = commission_bps;
355 0 : break;
356 0 : case fd_vote_state_versioned_enum_v3:
357 : /* No-op for v3 */
358 0 : break;
359 0 : default:
360 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
361 0 : }
362 0 : }
363 :
364 : void
365 : fd_vsv_set_block_revenue_commission_bps( fd_vote_state_versioned_t * self,
366 0 : ushort commission_bps ) {
367 0 : switch( self->kind ) {
368 0 : case fd_vote_state_versioned_enum_v4:
369 0 : self->v4.block_revenue_commission_bps = commission_bps;
370 0 : 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 0 : }
377 0 : }
378 :
379 : void
380 0 : fd_vsv_set_root_slot( fd_vote_state_versioned_t * self, ulong * root_slot ) {
381 0 : switch( self->kind ) {
382 0 : case fd_vote_state_versioned_enum_v3:
383 0 : self->v3.has_root_slot = (root_slot!=NULL);
384 0 : if( FD_LIKELY( root_slot ) ) {
385 0 : self->v3.root_slot = *root_slot;
386 0 : }
387 0 : break;
388 0 : case fd_vote_state_versioned_enum_v4:
389 0 : self->v4.has_root_slot = (root_slot!=NULL);
390 0 : if( FD_LIKELY( root_slot ) ) {
391 0 : self->v4.root_slot = *root_slot;
392 0 : }
393 0 : break;
394 0 : default:
395 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
396 0 : }
397 0 : }
398 :
399 : static void
400 : fd_vsv_set_last_timestamp( fd_vote_state_versioned_t * self,
401 0 : fd_vote_block_timestamp_t const * last_timestamp ) {
402 0 : switch( self->kind ) {
403 0 : case fd_vote_state_versioned_enum_v3:
404 0 : self->v3.last_timestamp = *last_timestamp;
405 0 : break;
406 0 : case fd_vote_state_versioned_enum_v4:
407 0 : self->v4.last_timestamp = *last_timestamp;
408 0 : break;
409 0 : default:
410 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
411 0 : }
412 0 : }
413 :
414 : /**********************************************************************/
415 : /* General functions */
416 : /**********************************************************************/
417 :
418 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L855
419 : static void
420 0 : double_lockouts( fd_vote_state_versioned_t * self ) {
421 0 : fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
422 :
423 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L856
424 0 : ulong stack_depth = deq_fd_landed_vote_t_cnt( votes );
425 0 : ulong i = 0;
426 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L857
427 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( votes );
428 0 : !deq_fd_landed_vote_t_iter_done( votes, iter );
429 0 : iter = deq_fd_landed_vote_t_iter_next( votes, iter ) ) {
430 0 : fd_landed_vote_t * v = deq_fd_landed_vote_t_iter_ele( votes, iter );
431 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L860
432 0 : if( stack_depth >
433 0 : fd_ulong_checked_add_expect(
434 0 : i,
435 0 : (ulong)v->lockout.confirmation_count,
436 0 : "`confirmation_count` and tower_size should be bounded by `MAX_LOCKOUT_HISTORY`" ) )
437 0 : {
438 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L864
439 0 : fd_vote_lockout_increase_confirmation_count( &v->lockout, 1 );
440 0 : }
441 0 : i++;
442 0 : }
443 0 : }
444 :
445 : void
446 : fd_vsv_increment_credits( fd_vote_state_versioned_t * self,
447 : ulong epoch,
448 0 : ulong credits ) {
449 0 : fd_vote_epoch_credits_t * epoch_credits = fd_vsv_get_epoch_credits_mutable( self );
450 :
451 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L305 */
452 0 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) {
453 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L288 */
454 0 : deq_fd_vote_epoch_credits_t_push_tail_wrap(
455 0 : epoch_credits,
456 0 : ( fd_vote_epoch_credits_t ){ .epoch = epoch, .credits = 0, .prev_credits = 0 } );
457 0 : } else if( FD_LIKELY( epoch !=
458 0 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch ) ) {
459 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L290 */
460 0 : fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits );
461 :
462 0 : ulong credits = last->credits;
463 0 : ulong prev_credits = last->prev_credits;
464 :
465 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L292-L299 */
466 0 : if( FD_LIKELY( credits!=prev_credits ) ) {
467 0 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>=MAX_EPOCH_CREDITS_HISTORY ) ) {
468 : /* Although Agave performs a `.remove(0)` AFTER the call to
469 : `.push()`, there is an edge case where the epoch credits is
470 : full, making the call to `_push_tail()` unsafe. Since Agave's
471 : structures are dynamically allocated, it is safe for them to
472 : simply call `.push()` and then popping afterwards. We have to
473 : reverse the order of operations to maintain correct behavior
474 : and avoid overflowing the deque.
475 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L303 */
476 0 : deq_fd_vote_epoch_credits_t_pop_head( epoch_credits );
477 0 : }
478 :
479 : /* This will not fail because we already popped if we're at
480 : capacity, since the epoch_credits deque is allocated with a
481 : minimum capacity of MAX_EPOCH_CREDITS_HISTORY. */
482 0 : deq_fd_vote_epoch_credits_t_push_tail(
483 0 : epoch_credits,
484 0 : ( fd_vote_epoch_credits_t ){
485 0 : .epoch = epoch, .credits = credits, .prev_credits = credits } );
486 0 : } else {
487 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L297-L298 */
488 0 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch = epoch;
489 :
490 : /* Here we can perform the same deque size check and pop if
491 : we're beyond the maximum epoch credits len. */
492 0 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>MAX_EPOCH_CREDITS_HISTORY ) ) {
493 0 : deq_fd_vote_epoch_credits_t_pop_head( epoch_credits );
494 0 : }
495 0 : }
496 0 : }
497 :
498 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L663
499 0 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits = fd_ulong_sat_add(
500 0 : deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits, credits );
501 0 : }
502 :
503 : int
504 : fd_vsv_process_timestamp( fd_exec_instr_ctx_t * ctx,
505 : fd_vote_state_versioned_t * self,
506 : ulong slot,
507 0 : long timestamp ) {
508 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L160 */
509 0 : fd_vote_block_timestamp_t const * last_timestamp = fd_vsv_get_last_timestamp( self );
510 0 : if( FD_UNLIKELY(
511 0 : ( slot<last_timestamp->slot || timestamp<last_timestamp->timestamp ) ||
512 0 : ( slot==last_timestamp->slot &&
513 0 : ( slot!=last_timestamp->slot || timestamp!=last_timestamp->timestamp ) &&
514 0 : last_timestamp->slot!=0UL ) ) ) {
515 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_TIMESTAMP_TOO_OLD;
516 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
517 0 : }
518 :
519 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L168 */
520 0 : fd_vote_block_timestamp_t new_timestamp = {
521 0 : .slot = slot,
522 0 : .timestamp = timestamp,
523 0 : };
524 0 : fd_vsv_set_last_timestamp( self, &new_timestamp );
525 0 : return FD_EXECUTOR_INSTR_SUCCESS;
526 0 : }
527 :
528 : void
529 0 : fd_vsv_pop_expired_votes( fd_vote_state_versioned_t * self, ulong next_vote_slot ) {
530 0 : fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
531 :
532 0 : while( !deq_fd_landed_vote_t_empty( votes ) ) {
533 0 : fd_landed_vote_t * vote = deq_fd_landed_vote_t_peek_tail( votes );
534 0 : if( !( fd_vote_lockout_is_locked_out_at_slot( &vote->lockout, next_vote_slot ) ) ) {
535 0 : deq_fd_landed_vote_t_pop_tail( votes );
536 0 : } else {
537 0 : break;
538 0 : }
539 0 : }
540 0 : }
541 :
542 : void
543 : fd_vsv_process_next_vote_slot( fd_vote_state_versioned_t * self,
544 : ulong next_vote_slot,
545 : ulong epoch,
546 0 : ulong current_slot ) {
547 0 : ulong const * last_voted_slot_ = fd_vsv_get_last_voted_slot( self );
548 0 : if( FD_UNLIKELY( last_voted_slot_ && next_vote_slot <= *last_voted_slot_ ) ) return;
549 :
550 0 : fd_vsv_pop_expired_votes( self, next_vote_slot );
551 :
552 0 : fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
553 :
554 0 : fd_landed_vote_t landed_vote = {
555 0 : .latency = fd_vote_compute_vote_latency( next_vote_slot, current_slot ),
556 0 : .lockout = ( fd_vote_lockout_t ){ .slot = next_vote_slot }
557 0 : };
558 :
559 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L623
560 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( votes ) == MAX_LOCKOUT_HISTORY ) ) {
561 0 : ulong credits = fd_vote_credits_for_vote_at_index( votes, 0 );
562 0 : fd_landed_vote_t landed_vote = deq_fd_landed_vote_t_pop_head( votes );
563 0 : fd_vsv_set_root_slot( self, &landed_vote.lockout.slot );
564 :
565 0 : fd_vsv_increment_credits( self, epoch, credits );
566 0 : }
567 :
568 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L634
569 0 : deq_fd_landed_vote_t_push_tail_wrap( votes, landed_vote );
570 0 : double_lockouts( self );
571 0 : }
572 :
573 : int
574 0 : fd_vsv_try_convert_to_v3( fd_vote_state_versioned_t * self ) {
575 0 : switch( self->kind ) {
576 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L47-L73 */
577 0 : case fd_vote_state_versioned_enum_uninitialized: {
578 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
579 0 : }
580 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L75-L91 */
581 0 : case fd_vote_state_versioned_enum_v1_14_11: {
582 0 : fd_vote_state_1_14_11_t * state = &self->v1_14_11;
583 :
584 : /* Temporary to hold v3. Note that since v1_14_11 and v3/v4 use
585 : the same underlying types for votes, we can just directly set
586 : it and avoid an intermediate call to
587 : fd_vote_lockout_landed_votes_from_lockouts. */
588 0 : fd_vote_state_v3_t v3 = {
589 0 : .node_pubkey = state->node_pubkey,
590 0 : .authorized_withdrawer = state->authorized_withdrawer,
591 0 : .commission = state->commission,
592 0 : .votes = state->votes,
593 0 : .has_root_slot = state->has_root_slot,
594 0 : .root_slot = state->root_slot,
595 0 : .authorized_voters = state->authorized_voters,
596 0 : .prior_voters = state->prior_voters,
597 0 : .epoch_credits = state->epoch_credits,
598 0 : .last_timestamp = state->last_timestamp
599 0 : };
600 :
601 : /* Emplace new vote state into target */
602 0 : self->kind = fd_vote_state_versioned_enum_v3;
603 0 : self->v3 = v3;
604 :
605 0 : return FD_EXECUTOR_INSTR_SUCCESS;
606 0 : }
607 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L93 */
608 0 : case fd_vote_state_versioned_enum_v3:
609 0 : return FD_EXECUTOR_INSTR_SUCCESS;
610 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L96 */
611 0 : case fd_vote_state_versioned_enum_v4:
612 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
613 0 : default:
614 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
615 0 : }
616 0 : }
617 :
618 : int
619 : fd_vsv_try_convert_to_v4( fd_vote_state_versioned_t * self,
620 0 : fd_pubkey_t const * vote_pubkey ) {
621 0 : switch( self->kind ) {
622 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L971-L974 */
623 0 : case fd_vote_state_versioned_enum_uninitialized: {
624 0 : return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT;
625 0 : }
626 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L975-L989 */
627 0 : case fd_vote_state_versioned_enum_v1_14_11: {
628 0 : fd_vote_state_1_14_11_t * state = &self->v1_14_11;
629 :
630 : /* Temporary to hold v4. Note that since v1_14_11 and v3/v4 use
631 : the same underlying types for votes, we can just directly set
632 : it and avoid an intermediate call to
633 : fd_vote_lockout_landed_votes_from_lockouts. */
634 0 : fd_vote_state_v4_t v4 = {
635 0 : .node_pubkey = state->node_pubkey,
636 0 : .authorized_withdrawer = state->authorized_withdrawer,
637 0 : .inflation_rewards_collector = *vote_pubkey,
638 0 : .block_revenue_collector = state->node_pubkey,
639 0 : .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ),
640 0 : .block_revenue_commission_bps = DEFAULT_BLOCK_REVENUE_COMMISSION_BPS,
641 0 : .pending_delegator_rewards = 0,
642 0 : .has_bls_pubkey_compressed = 0,
643 0 : .votes = state->votes,
644 0 : .has_root_slot = state->has_root_slot,
645 0 : .root_slot = state->root_slot,
646 0 : .authorized_voters = state->authorized_voters,
647 0 : .epoch_credits = state->epoch_credits,
648 0 : .last_timestamp = state->last_timestamp
649 0 : };
650 :
651 : /* Emplace new vote state into target */
652 0 : self->kind = fd_vote_state_versioned_enum_v4;
653 0 : self->v4 = v4;
654 :
655 0 : return FD_EXECUTOR_INSTR_SUCCESS;
656 0 : }
657 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L990-L1004 */
658 0 : case fd_vote_state_versioned_enum_v3: {
659 0 : fd_vote_state_v3_t * state = &self->v3;
660 0 : fd_vote_state_v4_t v4 = {
661 0 : .node_pubkey = state->node_pubkey,
662 0 : .authorized_withdrawer = state->authorized_withdrawer,
663 0 : .inflation_rewards_collector = *vote_pubkey,
664 0 : .block_revenue_collector = state->node_pubkey,
665 0 : .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ),
666 0 : .block_revenue_commission_bps = DEFAULT_BLOCK_REVENUE_COMMISSION_BPS,
667 0 : .pending_delegator_rewards = 0,
668 0 : .has_bls_pubkey_compressed = 0,
669 0 : .votes = state->votes,
670 0 : .has_root_slot = state->has_root_slot,
671 0 : .root_slot = state->root_slot,
672 0 : .authorized_voters = state->authorized_voters,
673 0 : .epoch_credits = state->epoch_credits,
674 0 : .last_timestamp = state->last_timestamp
675 0 : };
676 :
677 : /* Emplace new vote state into target */
678 0 : self->kind = fd_vote_state_versioned_enum_v4;
679 0 : self->v4 = v4;
680 :
681 0 : return FD_EXECUTOR_INSTR_SUCCESS;
682 0 : }
683 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L1005 */
684 0 : case fd_vote_state_versioned_enum_v4:
685 0 : return FD_EXECUTOR_INSTR_SUCCESS;
686 0 : default:
687 0 : FD_LOG_CRIT(( "unsupported vote state version: %u", self->kind ));
688 0 : }
689 0 : }
690 :
691 : int
692 : fd_vsv_deinitialize_vote_account_state( fd_exec_instr_ctx_t * ctx,
693 : fd_borrowed_account_t * vote_account,
694 0 : int target_version ) {
695 : /* V4 clears the entire account data on deinitialize.
696 : https://github.com/anza-xyz/agave/blob/v4.1.0-alpha.0/programs/vote/src/vote_state/handler.rs#L366-L377 */
697 0 : (void)ctx;
698 0 : (void)target_version;
699 0 : uchar * data;
700 0 : ulong dlen;
701 0 : int rc = fd_borrowed_account_get_data_mut( vote_account, &data, &dlen );
702 0 : if( FD_UNLIKELY( rc ) ) return rc;
703 0 : fd_memset( data, 0, dlen );
704 0 : return FD_EXECUTOR_INSTR_SUCCESS;
705 0 : }
706 :
707 : int
708 6 : fd_vsv_is_uninitialized( fd_vote_state_versioned_t * self ) {
709 6 : switch( self->kind ) {
710 6 : case fd_vote_state_versioned_enum_uninitialized:
711 6 : return 1;
712 0 : case fd_vote_state_versioned_enum_v1_14_11:
713 0 : return fd_authorized_voters_is_empty( &self->v1_14_11.authorized_voters );
714 0 : case fd_vote_state_versioned_enum_v3:
715 0 : return fd_authorized_voters_is_empty( &self->v3.authorized_voters );
716 0 : case fd_vote_state_versioned_enum_v4:
717 0 : return 0; // v4 vote states are always initialized
718 0 : default:
719 0 : FD_LOG_CRIT(( "unsupported vote state versioned kind: %u", self->kind ));
720 6 : }
721 6 : }
722 :
723 : int
724 : fd_vsv_is_correct_size_and_initialized( uchar const * data,
725 594 : ulong data_len ) {
726 594 : uint const * disc_ptr = (uint const *)data; // NOT SAFE TO ACCESS YET!
727 :
728 : /* VoteStateV4::is_correct_size_and_initialized
729 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v4.rs#L207-L210 */
730 594 : if( FD_LIKELY( data_len==FD_VOTE_STATE_V4_SZ && *disc_ptr==fd_vote_state_versioned_enum_v4 ) ) {
731 255 : return 1;
732 255 : }
733 :
734 : /* VoteStateV3::is_correct_size_and_initialized
735 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L509-L514 */
736 339 : if( FD_LIKELY( data_len==FD_VOTE_STATE_V3_SZ &&
737 339 : !fd_mem_iszero( data+VERSION_OFFSET, DEFAULT_PRIOR_VOTERS_OFFSET ) ) ) {
738 339 : return 1;
739 339 : }
740 :
741 : /* VoteState1_14_11::is_correct_size_and_initialized
742 : 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 */
743 0 : if( FD_LIKELY( data_len==FD_VOTE_STATE_V2_SZ &&
744 0 : !fd_mem_iszero( data+VERSION_OFFSET, DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11 ) ) ) {
745 0 : return 1;
746 0 : }
747 :
748 0 : return 0;
749 0 : }
750 :
751 : int
752 : fd_vsv_is_correct_size_owner_and_init( uchar const * owner,
753 : uchar const * data,
754 600 : ulong data_len ) {
755 600 : if( FD_UNLIKELY( memcmp( owner, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
756 6 : return 0;
757 6 : }
758 :
759 594 : return fd_vsv_is_correct_size_and_initialized( data, data_len );
760 600 : }
|