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