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