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