Line data Source code
1 : #include "fd_vote_program.h"
2 : #include "../fd_borrowed_account.h"
3 : #include "../fd_executor.h"
4 : #include "../fd_pubkey_utils.h"
5 : #include "../sysvar/fd_sysvar_rent.h"
6 : #include "../sysvar/fd_sysvar.h"
7 : #include "../fd_system_ids.h"
8 :
9 : #include <limits.h>
10 : #include <math.h>
11 : #include <stdio.h>
12 : #include <string.h>
13 :
14 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L35
15 0 : #define MAX_LOCKOUT_HISTORY 31UL
16 :
17 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L36
18 0 : #define INITIAL_LOCKOUT 2UL
19 :
20 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L36
21 : #define MAX_EPOCH_CREDITS_HISTORY 64UL
22 :
23 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L42
24 0 : #define DEFAULT_PRIOR_VOTERS_OFFSET 114
25 :
26 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L45
27 0 : #define VOTE_CREDITS_GRACE_SLOTS 2
28 :
29 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L48
30 0 : #define VOTE_CREDITS_MAXIMUM_PER_SLOT 16
31 :
32 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L51
33 : #define VOTE_CREDITS_MAXIMUM_PER_SLOT_OLD 8
34 :
35 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/clock.rs#L147
36 : #define SLOT_DEFAULT 0UL
37 :
38 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/clock.rs#L147
39 : #define SLOT_MAX ULONG_MAX
40 :
41 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L886
42 0 : #define VERSION_OFFSET (4UL)
43 :
44 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L887
45 : #define DEFAULT_PRIOR_VOTERS_END (118)
46 :
47 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L6
48 0 : #define DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11 (82UL)
49 :
50 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L60
51 : #define DEFAULT_PRIOR_VOTERS_END_1_14_11 (86UL)
52 :
53 : #define ACCOUNTS_MAX 4 /* Vote instructions take in at most 4 accounts */
54 :
55 : #define DEFAULT_COMPUTE_UNITS 2100UL
56 :
57 : /**********************************************************************/
58 : /* size_of */
59 : /**********************************************************************/
60 :
61 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L82
62 : static inline ulong
63 0 : size_of_versioned( int is_current ) {
64 0 : return fd_ulong_if( is_current, FD_VOTE_STATE_V3_SZ, FD_VOTE_STATE_V2_SZ );
65 0 : }
66 :
67 : /**********************************************************************/
68 : /* impl Lockout */
69 : /**********************************************************************/
70 :
71 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L104
72 : static inline ulong
73 0 : lockout( fd_vote_lockout_t * self ) {
74 : /* Confirmation count can never be greater than MAX_LOCKOUT_HISTORY, preventing overflow.
75 : Although Agave does not consider overflow, we do for fuzzing conformance. */
76 0 : ulong confirmation_count = fd_ulong_min( self->confirmation_count, MAX_LOCKOUT_HISTORY );
77 0 : return 1UL<<confirmation_count;
78 0 : }
79 :
80 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L110
81 : static inline ulong
82 0 : last_locked_out_slot( fd_vote_lockout_t * self ) {
83 0 : return fd_ulong_sat_add( self->slot, lockout( self ) );
84 0 : }
85 :
86 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L114
87 : static inline ulong
88 0 : is_locked_out_at_slot( fd_vote_lockout_t * self, ulong slot ) {
89 0 : return last_locked_out_slot( self ) >= slot;
90 0 : }
91 :
92 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L122
93 : static void
94 0 : increase_confirmation_count( fd_vote_lockout_t * self, uint by ) {
95 0 : self->confirmation_count = fd_uint_sat_add( self->confirmation_count, by );
96 0 : }
97 :
98 : /**********************************************************************/
99 : /* impl From<VoteState> for VoteState1_14_11 */
100 : /**********************************************************************/
101 :
102 : /* from_vote_state_1_14_11 converts a "current" vote state object into
103 : the older "v1.14.11" version. This destroys the "current" object in
104 : the process. spad is the bump allocator to be used, which must be
105 : the same as the one used for v1.14.11.
106 : */
107 :
108 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L67
109 : static void
110 : from_vote_state_1_14_11( fd_vote_state_t * vote_state,
111 : fd_vote_state_1_14_11_t * vote_state_1_14_11, /* out */
112 0 : fd_spad_t * spad ) {
113 0 : vote_state_1_14_11->node_pubkey = vote_state->node_pubkey; /* copy */
114 0 : vote_state_1_14_11->authorized_withdrawer = vote_state->authorized_withdrawer; /* copy */
115 0 : vote_state_1_14_11->commission = vote_state->commission; /* copy */
116 :
117 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L72
118 0 : if( vote_state->votes ) {
119 0 : uchar * deque_mem = fd_spad_alloc( spad,
120 0 : deq_fd_vote_lockout_t_align(),
121 0 : deq_fd_vote_lockout_t_footprint( deq_fd_landed_vote_t_cnt( vote_state->votes ) ) );
122 0 : vote_state_1_14_11->votes = deq_fd_vote_lockout_t_join(
123 0 : deq_fd_vote_lockout_t_new( deque_mem, deq_fd_landed_vote_t_cnt( vote_state->votes ) ) );
124 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes );
125 0 : !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter );
126 0 : iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) {
127 0 : fd_landed_vote_t const * landed_vote = deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter );
128 0 : deq_fd_vote_lockout_t_push_tail_wrap( vote_state_1_14_11->votes, landed_vote->lockout );
129 0 : }
130 0 : }
131 :
132 0 : vote_state_1_14_11->has_root_slot = vote_state->has_root_slot; /* copy */
133 0 : vote_state_1_14_11->root_slot = vote_state->root_slot; /* copy */
134 0 : vote_state_1_14_11->authorized_voters = vote_state->authorized_voters; /* move */
135 0 : vote_state_1_14_11->prior_voters = vote_state->prior_voters; /* deep copy */
136 0 : vote_state_1_14_11->epoch_credits = vote_state->epoch_credits; /* move */
137 0 : vote_state_1_14_11->last_timestamp = vote_state->last_timestamp; /* deep copy */
138 :
139 : /* Clear moved objects */
140 0 : vote_state->authorized_voters.treap = NULL;
141 0 : vote_state->authorized_voters.pool = NULL;
142 0 : vote_state->epoch_credits = NULL;
143 :
144 0 : }
145 :
146 : /**********************************************************************/
147 : /* impl VoteAccount */
148 : /**********************************************************************/
149 :
150 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1074 */
151 : static fd_vote_state_versioned_t *
152 : get_state( fd_txn_account_t const * self,
153 : fd_spad_t * spad,
154 0 : int * err ) {
155 0 : int decode_err;
156 0 : fd_vote_state_versioned_t * res = fd_bincode_decode_spad(
157 0 : vote_state_versioned, spad,
158 0 : fd_txn_account_get_data( self ),
159 0 : fd_txn_account_get_data_len( self ),
160 0 : &decode_err );
161 0 : if( FD_UNLIKELY( decode_err ) ) {
162 0 : *err = FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
163 0 : return NULL;
164 0 : }
165 0 : *err = FD_EXECUTOR_INSTR_SUCCESS;
166 0 : return res;
167 0 : }
168 :
169 : static int
170 : set_state( fd_borrowed_account_t * self,
171 0 : fd_vote_state_versioned_t * state ) {
172 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L974 */
173 0 : uchar * data = NULL;
174 0 : ulong dlen = 0UL;
175 0 : int err = fd_borrowed_account_get_data_mut( self, &data, &dlen );
176 0 : if( FD_UNLIKELY( err ) ) {
177 0 : return err;
178 0 : }
179 :
180 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L978
181 0 : ulong serialized_size = fd_vote_state_versioned_size( state );
182 0 : if( FD_UNLIKELY( serialized_size > dlen ) )
183 0 : return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL;
184 :
185 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L983
186 0 : fd_bincode_encode_ctx_t encode =
187 0 : { .data = data,
188 0 : .dataend = data + dlen };
189 0 : do {
190 0 : int err = fd_vote_state_versioned_encode( state, &encode );
191 0 : if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fd_vote_state_versioned_encode failed (%d)", err ));
192 0 : } while(0);
193 :
194 0 : return FD_EXECUTOR_INSTR_SUCCESS;
195 0 : }
196 :
197 : /**********************************************************************/
198 : /* impl AuthorizedVoters */
199 : /**********************************************************************/
200 :
201 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L17
202 : static void
203 : authorized_voters_new( ulong epoch,
204 : fd_pubkey_t const * pubkey,
205 : fd_spad_t * spad,
206 0 : fd_vote_authorized_voters_t * authorized_voters /* out */ ) {
207 0 : uchar * pool_mem = fd_spad_alloc( spad,
208 0 : fd_vote_authorized_voters_pool_align(),
209 0 : fd_vote_authorized_voters_pool_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
210 0 : authorized_voters->pool = fd_vote_authorized_voters_pool_join(
211 0 : fd_vote_authorized_voters_pool_new( pool_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
212 :
213 0 : uchar * treap_mem = fd_spad_alloc( spad,
214 0 : fd_vote_authorized_voters_treap_align(),
215 0 : fd_vote_authorized_voters_treap_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
216 0 : authorized_voters->treap = fd_vote_authorized_voters_treap_join(
217 0 : fd_vote_authorized_voters_treap_new( treap_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) );
218 0 : if( 0 == fd_vote_authorized_voters_pool_free( authorized_voters->pool ) ) {
219 0 : FD_LOG_ERR(( "Authorized_voter pool is empty" ));
220 0 : }
221 0 : fd_vote_authorized_voter_t * ele =
222 0 : fd_vote_authorized_voters_pool_ele_acquire( authorized_voters->pool );
223 0 : ele->epoch = epoch;
224 0 : ele->pubkey = *pubkey;
225 0 : ele->prio = (ulong)&ele->pubkey;
226 0 : fd_vote_authorized_voters_treap_ele_insert(
227 0 : authorized_voters->treap, ele, authorized_voters->pool );
228 0 : }
229 :
230 : static inline int
231 0 : authorized_voters_is_empty( fd_vote_authorized_voters_t * self ) {
232 0 : return fd_vote_authorized_voters_treap_ele_cnt( self->treap ) == 0;
233 0 : }
234 :
235 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L80
236 : static inline int
237 0 : authorized_voters_contains( fd_vote_authorized_voters_t * self, ulong epoch ) {
238 0 : return !!fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool );
239 0 : }
240 :
241 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L72
242 : static inline fd_vote_authorized_voter_t *
243 0 : authorized_voters_last( fd_vote_authorized_voters_t * self ) {
244 0 : fd_vote_authorized_voters_treap_rev_iter_t iter =
245 0 : fd_vote_authorized_voters_treap_rev_iter_init( self->treap, self->pool );
246 0 : return fd_vote_authorized_voters_treap_rev_iter_ele( iter, self->pool );
247 0 : }
248 :
249 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L43
250 : static void
251 : authorized_voters_purge_authorized_voters( fd_vote_authorized_voters_t * self,
252 : ulong current_epoch,
253 0 : fd_exec_instr_ctx_t const * ctx /* spad */ ) {
254 :
255 0 : FD_SPAD_FRAME_BEGIN( ctx->txn_ctx->spad ) {
256 :
257 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L46
258 0 : ulong *expired_keys = fd_spad_alloc( ctx->txn_ctx->spad, alignof(ulong), fd_vote_authorized_voters_treap_ele_cnt(self->treap) * sizeof(ulong) );
259 0 : ulong key_cnt = 0;
260 0 : for( fd_vote_authorized_voters_treap_fwd_iter_t iter =
261 0 : fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool );
262 0 : !fd_vote_authorized_voters_treap_fwd_iter_done( iter );
263 0 : iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) {
264 0 : fd_vote_authorized_voter_t * ele =
265 0 : fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool );
266 0 : if( ele->epoch < current_epoch ) expired_keys[key_cnt++] = ele->epoch;
267 0 : }
268 :
269 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L52
270 0 : for( ulong i = 0; i < key_cnt; i++ ) {
271 0 : fd_vote_authorized_voter_t * ele =
272 0 : fd_vote_authorized_voters_treap_ele_query( self->treap, expired_keys[i], self->pool );
273 0 : fd_vote_authorized_voters_treap_ele_remove( self->treap, ele, self->pool );
274 0 : fd_vote_authorized_voters_pool_ele_release( self->pool, ele );
275 : // fd_vote_authorized_voter_destroy( &self->pool[i], &ctx3 );
276 0 : }
277 :
278 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L60
279 0 : FD_TEST( !authorized_voters_is_empty( self ) );
280 :
281 0 : } FD_SPAD_FRAME_END;
282 :
283 0 : }
284 :
285 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L91
286 : static fd_vote_authorized_voter_t *
287 : authorized_voters_get_or_calculate_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self,
288 : ulong epoch,
289 0 : int * existed ) {
290 0 : *existed = 0;
291 0 : ulong latest_epoch = 0;
292 0 : fd_vote_authorized_voter_t * res =
293 0 : fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool );
294 : // "predecessor" would be more big-O optimal here, but mirroring labs logic
295 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L93
296 0 : if( FD_UNLIKELY( !res ) ) {
297 0 : for( fd_vote_authorized_voters_treap_fwd_iter_t iter =
298 0 : fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool );
299 0 : !fd_vote_authorized_voters_treap_fwd_iter_done( iter );
300 0 : iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) {
301 0 : fd_vote_authorized_voter_t * ele =
302 0 : fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool );
303 0 : if( ele->epoch < epoch && ( latest_epoch == 0 || ele->epoch > latest_epoch ) ) {
304 0 : latest_epoch = ele->epoch;
305 0 : res = ele;
306 0 : }
307 0 : }
308 0 : *existed = 0;
309 0 : return res;
310 0 : } else {
311 0 : *existed = 1;
312 0 : return res;
313 0 : }
314 0 : return res;
315 0 : }
316 :
317 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L28
318 : static fd_vote_authorized_voter_t *
319 : authorized_voters_get_and_cache_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self,
320 0 : ulong epoch ) {
321 0 : int existed = 0;
322 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L29
323 0 : fd_vote_authorized_voter_t * res =
324 0 : authorized_voters_get_or_calculate_authorized_voter_for_epoch( self, epoch, &existed );
325 0 : if( !res ) return NULL;
326 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L32
327 0 : if( !existed ) {
328 : /* insert cannot fail because !existed */
329 0 : if( 0 == fd_vote_authorized_voters_pool_free( self->pool) ) {
330 0 : FD_LOG_ERR(( "Authorized_voter pool is empty" ));
331 0 : }
332 0 : fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_pool_ele_acquire( self->pool );
333 0 : ele->epoch = epoch;
334 0 : ele->pubkey = res->pubkey;
335 0 : ele->prio = (ulong)&res->pubkey;
336 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L33
337 0 : fd_vote_authorized_voters_treap_ele_insert( self->treap, ele, self->pool );
338 0 : }
339 0 : return res;
340 0 : }
341 :
342 : /**********************************************************************/
343 : /* impl VoteStateVersions */
344 : /**********************************************************************/
345 :
346 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L66
347 : static fd_landed_vote_t *
348 : landed_votes_from_lockouts( fd_vote_lockout_t * lockouts,
349 0 : fd_spad_t * spad ) {
350 0 : if( !lockouts ) return NULL;
351 :
352 : /* Allocate MAX_LOCKOUT_HISTORY (sane case) by default. In case the
353 : vote account is corrupt, allocate as many entries are needed. */
354 :
355 0 : ulong cnt = deq_fd_vote_lockout_t_cnt( lockouts );
356 0 : cnt = fd_ulong_max( cnt, MAX_LOCKOUT_HISTORY );
357 0 : uchar * deque_mem = fd_spad_alloc( spad,
358 0 : deq_fd_landed_vote_t_align(),
359 0 : deq_fd_landed_vote_t_footprint( cnt ) );
360 0 : fd_landed_vote_t * landed_votes = deq_fd_landed_vote_t_join( deq_fd_landed_vote_t_new( deque_mem, cnt ) );
361 :
362 0 : for( deq_fd_vote_lockout_t_iter_t iter = deq_fd_vote_lockout_t_iter_init( lockouts );
363 0 : !deq_fd_vote_lockout_t_iter_done( lockouts, iter );
364 0 : iter = deq_fd_vote_lockout_t_iter_next( lockouts, iter ) ) {
365 0 : fd_vote_lockout_t const * ele = deq_fd_vote_lockout_t_iter_ele_const( lockouts, iter );
366 :
367 0 : fd_landed_vote_t * elem = deq_fd_landed_vote_t_push_tail_nocopy( landed_votes );
368 0 : fd_landed_vote_new( elem );
369 :
370 0 : elem->latency = 0;
371 0 : elem->lockout.slot = ele->slot;
372 0 : elem->lockout.confirmation_count = ele->confirmation_count;
373 0 : }
374 :
375 0 : return landed_votes;
376 0 : }
377 :
378 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L70
379 : static inline int
380 0 : is_uninitialized( fd_vote_state_versioned_t * self ) {
381 0 : switch( self->discriminant ) {
382 0 : case fd_vote_state_versioned_enum_v0_23_5:;
383 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L73
384 0 : fd_pubkey_t pubkey_default = { 0 };
385 0 : return 0 ==
386 0 : memcmp( &self->inner.v0_23_5.authorized_voter, &pubkey_default, sizeof( fd_pubkey_t ) );
387 0 : case fd_vote_state_versioned_enum_v1_14_11:;
388 0 : return authorized_voters_is_empty( &self->inner.v1_14_11.authorized_voters );
389 0 : case fd_vote_state_versioned_enum_current:
390 0 : return authorized_voters_is_empty( &self->inner.current.authorized_voters );
391 0 : default:
392 0 : FD_LOG_ERR(( "missing handler or invalid vote state version: %u", self->discriminant ));
393 0 : }
394 0 : }
395 :
396 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L73
397 : static void
398 : convert_to_current( fd_vote_state_versioned_t * self,
399 0 : fd_spad_t * spad ) {
400 0 : switch( self->discriminant ) {
401 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L19
402 0 : case fd_vote_state_versioned_enum_v0_23_5: {
403 0 : fd_vote_state_0_23_5_t * state = &self->inner.v0_23_5;
404 0 : fd_vote_authorized_voters_t authorized_voters;
405 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L21
406 0 : authorized_voters_new(
407 0 : state->authorized_voter_epoch, &state->authorized_voter, spad, &authorized_voters );
408 :
409 : /* Temporary to hold current */
410 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L23
411 0 : fd_vote_state_t current = {
412 0 : .node_pubkey = state->node_pubkey, /* copy */
413 0 : .authorized_withdrawer = state->authorized_withdrawer, /* copy */
414 0 : .commission = state->commission, /* copy */
415 0 : .votes = landed_votes_from_lockouts( state->votes, spad ),
416 0 : .has_root_slot = state->has_root_slot, /* copy */
417 0 : .root_slot = state->root_slot, /* copy */
418 0 : .authorized_voters = authorized_voters,
419 0 : .prior_voters = (fd_vote_prior_voters_t) {
420 0 : .idx = 31UL,
421 0 : .is_empty = 1,
422 0 : },
423 0 : .epoch_credits = state->epoch_credits, /* move */
424 0 : .last_timestamp = state->last_timestamp, /* deep copy */
425 0 : };
426 :
427 : /* Move objects */
428 0 : state->epoch_credits = NULL;
429 :
430 : /* Emplace new vote state into target */
431 0 : self->discriminant = fd_vote_state_versioned_enum_current;
432 0 : self->inner.current = current;
433 :
434 0 : break;
435 0 : }
436 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L44
437 0 : case fd_vote_state_versioned_enum_v1_14_11: {
438 0 : fd_vote_state_1_14_11_t * state = &self->inner.v1_14_11;
439 :
440 : /* Temporary to hold current */
441 0 : fd_vote_state_t current = {
442 0 : .node_pubkey = state->node_pubkey, /* copy */
443 0 : .authorized_withdrawer = state->authorized_withdrawer, /* copy */
444 0 : .commission = state->commission, /* copy */
445 0 : .votes = landed_votes_from_lockouts( state->votes, spad ),
446 0 : .has_root_slot = state->has_root_slot, /* copy */
447 0 : .root_slot = state->root_slot, /* copy */
448 0 : .authorized_voters = state->authorized_voters, /* move */
449 0 : .prior_voters = state->prior_voters, /* deep copy */
450 0 : .epoch_credits = state->epoch_credits, /* move */
451 0 : .last_timestamp = state->last_timestamp /* deep copy */
452 0 : };
453 :
454 : /* Move objects */
455 0 : state->authorized_voters.treap = NULL;
456 0 : state->authorized_voters.pool = NULL;
457 0 : state->epoch_credits = NULL;
458 :
459 : /* Emplace new vote state into target */
460 0 : self->discriminant = fd_vote_state_versioned_enum_current;
461 0 : self->inner.current = current;
462 :
463 0 : break;
464 0 : }
465 0 : case fd_vote_state_versioned_enum_current:
466 0 : break;
467 0 : default:
468 0 : FD_LOG_ERR( ( "unsupported vote state version: %u", self->discriminant ) );
469 0 : }
470 0 : }
471 :
472 : /**********************************************************************/
473 : /* impl VoteState */
474 : /**********************************************************************/
475 :
476 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L428
477 : static void
478 : vote_state_new( fd_vote_init_t * vote_init,
479 : fd_sol_sysvar_clock_t const * clock,
480 : fd_spad_t * spad,
481 0 : fd_vote_state_t * vote_state /* out */ ) {
482 0 : vote_state->node_pubkey = vote_init->node_pubkey;
483 0 : authorized_voters_new(
484 0 : clock->epoch, &vote_init->authorized_voter, spad, &vote_state->authorized_voters );
485 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L431
486 0 : vote_state->authorized_withdrawer = vote_init->authorized_withdrawer;
487 0 : vote_state->commission = vote_init->commission;
488 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L434
489 0 : vote_state->prior_voters.idx = 31;
490 0 : vote_state->prior_voters.is_empty = 1;
491 0 : }
492 :
493 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L985
494 : static inline int
495 : verify_authorized_signer( fd_pubkey_t const * authorized,
496 0 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) {
497 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L989
498 0 : return fd_signers_contains( signers, authorized ) ?
499 0 : FD_EXECUTOR_INSTR_SUCCESS :
500 0 : FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
501 0 : }
502 :
503 : // lambda function: https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L873
504 : static inline int
505 : verify( fd_pubkey_t * epoch_authorized_voter,
506 : int authorized_withdrawer_signer,
507 0 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) {
508 0 : if( authorized_withdrawer_signer )
509 0 : return 0;
510 0 : else
511 0 : return verify_authorized_signer( epoch_authorized_voter, signers );
512 0 : }
513 :
514 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L845
515 : static void
516 0 : pop_expired_votes( fd_vote_state_t * self, ulong next_vote_slot ) {
517 0 : while( !deq_fd_landed_vote_t_empty( self->votes ) ) {
518 0 : fd_landed_vote_t * vote = deq_fd_landed_vote_t_peek_tail( self->votes );
519 0 : if( !( is_locked_out_at_slot( &vote->lockout, next_vote_slot ) ) ) {
520 0 : deq_fd_landed_vote_t_pop_tail( self->votes );
521 0 : } else {
522 0 : break;
523 0 : }
524 0 : }
525 0 : }
526 :
527 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L855
528 : static void
529 0 : double_lockouts( fd_vote_state_t * self ) {
530 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L856
531 0 : ulong stack_depth = deq_fd_landed_vote_t_cnt( self->votes );
532 0 : ulong i = 0;
533 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L857
534 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( self->votes );
535 0 : !deq_fd_landed_vote_t_iter_done( self->votes, iter );
536 0 : iter = deq_fd_landed_vote_t_iter_next( self->votes, iter ) ) {
537 0 : fd_landed_vote_t * v = deq_fd_landed_vote_t_iter_ele( self->votes, iter );
538 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L860
539 0 : if( stack_depth >
540 0 : fd_ulong_checked_add_expect(
541 0 : i,
542 0 : (ulong)v->lockout.confirmation_count,
543 0 : "`confirmation_count` and tower_size should be bounded by `MAX_LOCKOUT_HISTORY`" ) )
544 0 : {
545 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L864
546 0 : increase_confirmation_count( &v->lockout, 1 );
547 0 : }
548 0 : i++;
549 0 : }
550 0 : }
551 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L668
552 : static inline uchar
553 0 : compute_vote_latency( ulong voted_for_slot, ulong current_slot ) {
554 0 : return (uchar)fd_ulong_min( fd_ulong_sat_sub( current_slot, voted_for_slot ), UCHAR_MAX );
555 0 : }
556 :
557 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L673
558 : static ulong
559 0 : credits_for_vote_at_index( fd_vote_state_t * self, ulong index ) {
560 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L679
561 0 : fd_landed_vote_t * landed_vote = deq_fd_landed_vote_t_peek_index( self->votes, index );
562 0 : ulong latency = landed_vote == NULL ? 0 : landed_vote->latency;
563 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L683
564 0 : ulong max_credits = VOTE_CREDITS_MAXIMUM_PER_SLOT;
565 :
566 : // If latency is 0, this means that the Lockout was created and stored from a software version
567 : // that did not store vote latencies; in this case, 1 credit is awarded
568 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L691
569 0 : if( FD_UNLIKELY( latency == 0 ) ) {
570 0 : return 1;
571 0 : }
572 :
573 0 : ulong diff = 0;
574 0 : int cf = fd_ulong_checked_sub( latency, VOTE_CREDITS_GRACE_SLOTS, &diff );
575 0 : if( cf != 0 || diff == 0 ) {
576 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L697
577 0 : return max_credits;
578 0 : }
579 :
580 0 : ulong credits = 0;
581 0 : cf = fd_ulong_checked_sub( max_credits, diff, &credits );
582 0 : if( cf != 0 || credits == 0 ) {
583 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L705
584 0 : return 1;
585 0 : }
586 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L707
587 0 : return credits;
588 0 : }
589 :
590 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L282-L309 */
591 : static void
592 0 : increment_credits( fd_vote_state_t * self, ulong epoch, ulong credits ) {
593 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L305 */
594 0 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_empty( self->epoch_credits ) ) ) {
595 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L288 */
596 0 : deq_fd_vote_epoch_credits_t_push_tail_wrap(
597 0 : self->epoch_credits,
598 0 : ( fd_vote_epoch_credits_t ){ .epoch = epoch, .credits = 0, .prev_credits = 0 } );
599 0 : } else if( FD_LIKELY( epoch !=
600 0 : deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->epoch ) ) {
601 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L290 */
602 0 : fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits );
603 :
604 0 : ulong credits = last->credits;
605 0 : ulong prev_credits = last->prev_credits;
606 :
607 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L292-L299 */
608 0 : if( FD_LIKELY( credits!=prev_credits ) ) {
609 0 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( self->epoch_credits )>=MAX_EPOCH_CREDITS_HISTORY ) ) {
610 : /* Although Agave performs a `.remove(0)` AFTER the call to
611 : `.push()`, there is an edge case where the epoch credits is
612 : full, making the call to `_push_tail()` unsafe. Since Agave's
613 : structures are dynamically allocated, it is safe for them to
614 : simply call `.push()` and then popping afterwards. We have to
615 : reverse the order of operations to maintain correct behavior
616 : and avoid overflowing the deque.
617 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L303 */
618 0 : deq_fd_vote_epoch_credits_t_pop_head( self->epoch_credits );
619 0 : }
620 :
621 : /* This will not fail because we already popped if we're at
622 : capacity, since the epoch_credits deque is allocated with a
623 : minimum capacity of MAX_EPOCH_CREDITS_HISTORY. */
624 0 : deq_fd_vote_epoch_credits_t_push_tail(
625 0 : self->epoch_credits,
626 0 : ( fd_vote_epoch_credits_t ){
627 0 : .epoch = epoch, .credits = credits, .prev_credits = credits } );
628 0 : } else {
629 : /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L297-L298 */
630 0 : deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->epoch = epoch;
631 :
632 : /* Here we can perform the same deque size check and pop if
633 : we're beyond the maximum epoch credits len. */
634 0 : if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( self->epoch_credits )>MAX_EPOCH_CREDITS_HISTORY ) ) {
635 0 : deq_fd_vote_epoch_credits_t_pop_head( self->epoch_credits );
636 0 : }
637 0 : }
638 0 : }
639 :
640 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L663
641 0 : deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->credits = fd_ulong_sat_add(
642 0 : deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->credits, credits );
643 0 : }
644 :
645 : static inline ulong *
646 : last_voted_slot( fd_vote_state_t * self );
647 :
648 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L595
649 : static void
650 : process_next_vote_slot( fd_vote_state_t * self,
651 : ulong next_vote_slot,
652 : ulong epoch,
653 0 : ulong current_slot ) {
654 0 : ulong * last_voted_slot_ = last_voted_slot( self );
655 0 : if( FD_UNLIKELY( last_voted_slot_ && next_vote_slot <= *last_voted_slot_ ) ) return;
656 :
657 0 : pop_expired_votes( self, next_vote_slot );
658 :
659 0 : fd_landed_vote_t landed_vote = { .latency = compute_vote_latency( next_vote_slot, current_slot ),
660 0 : ( fd_vote_lockout_t ){ .slot = next_vote_slot } };
661 :
662 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L623
663 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( self->votes ) == MAX_LOCKOUT_HISTORY ) ) {
664 0 : ulong credits = credits_for_vote_at_index( self, 0 );
665 0 : fd_landed_vote_t landed_vote = deq_fd_landed_vote_t_pop_head( self->votes );
666 0 : self->has_root_slot = 1;
667 0 : self->root_slot = landed_vote.lockout.slot;
668 :
669 0 : increment_credits( self, epoch, credits );
670 0 : }
671 :
672 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L634
673 0 : deq_fd_landed_vote_t_push_tail_wrap( self->votes, landed_vote );
674 0 : double_lockouts( self );
675 0 : }
676 :
677 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L828
678 : static int
679 : get_and_update_authorized_voter( fd_vote_state_t * self,
680 : ulong current_epoch,
681 : fd_pubkey_t ** pubkey /* out */,
682 0 : fd_exec_instr_ctx_t const * ctx /* spad */ ) {
683 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L832
684 0 : fd_vote_authorized_voter_t * authorized_voter =
685 0 : authorized_voters_get_and_cache_authorized_voter_for_epoch( &self->authorized_voters,
686 0 : current_epoch );
687 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L835
688 0 : if( FD_UNLIKELY( !authorized_voter ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
689 0 : *pubkey = &authorized_voter->pubkey;
690 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L837
691 0 : authorized_voters_purge_authorized_voters( &self->authorized_voters, current_epoch, ctx );
692 0 : return FD_EXECUTOR_INSTR_SUCCESS;
693 0 : }
694 :
695 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L768
696 : static int
697 : set_new_authorized_voter( fd_vote_state_t * self,
698 : fd_pubkey_t const * authorized_pubkey,
699 : ulong current_epoch,
700 : ulong target_epoch,
701 : /* "verify" closure */ int authorized_withdrawer_signer,
702 : /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
703 0 : fd_exec_instr_ctx_t const * ctx ) {
704 0 : int rc;
705 0 : fd_pubkey_t * epoch_authorized_voter = NULL;
706 :
707 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L778
708 0 : rc = get_and_update_authorized_voter( self, current_epoch, &epoch_authorized_voter, ctx );
709 0 : if( FD_UNLIKELY( rc ) ) return rc;
710 :
711 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L779
712 0 : rc = verify( epoch_authorized_voter, authorized_withdrawer_signer, signers );
713 0 : if( FD_UNLIKELY( rc ) ) return rc;
714 :
715 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L786
716 0 : if( FD_UNLIKELY( authorized_voters_contains( &self->authorized_voters, target_epoch ) ) ) {
717 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_TOO_SOON_TO_REAUTHORIZE;
718 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
719 0 : }
720 :
721 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L791
722 0 : fd_vote_authorized_voter_t * latest_authorized =
723 0 : authorized_voters_last( &self->authorized_voters );
724 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L794
725 0 : if( FD_UNLIKELY( ( !latest_authorized ) ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
726 0 : ulong latest_epoch = latest_authorized->epoch;
727 0 : fd_pubkey_t * latest_authorized_pubkey = &latest_authorized->pubkey;
728 :
729 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L799
730 0 : if( 0 != memcmp( latest_authorized_pubkey, authorized_pubkey, sizeof( fd_pubkey_t ) ) ) {
731 0 : fd_vote_prior_voters_t * prior_voters = &self->prior_voters;
732 :
733 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L801
734 0 : ulong epoch_of_last_authorized_switch = 0UL;
735 0 : if( (!prior_voters->is_empty) & (prior_voters->idx < 32) ) {
736 0 : epoch_of_last_authorized_switch = prior_voters->buf[prior_voters->idx].epoch_end;
737 0 : }
738 :
739 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L810
740 0 : if( target_epoch <= latest_epoch )
741 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
742 :
743 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L815
744 0 : prior_voters->idx += 1UL;
745 0 : prior_voters->idx %= 32UL;
746 0 : prior_voters->buf[prior_voters->idx] =
747 0 : ( fd_vote_prior_voter_t ){ .pubkey = *latest_authorized_pubkey,
748 0 : .epoch_start = epoch_of_last_authorized_switch,
749 0 : .epoch_end = target_epoch };
750 0 : prior_voters->is_empty = 0;
751 0 : }
752 :
753 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L822
754 0 : if( 0 == fd_vote_authorized_voters_pool_free( self->authorized_voters.pool) ) {
755 0 : FD_LOG_ERR(( "Authorized_voter pool is empty" ));
756 0 : }
757 :
758 0 : fd_vote_authorized_voter_t * ele =
759 0 : fd_vote_authorized_voters_pool_ele_acquire( self->authorized_voters.pool );
760 0 : ele->epoch = target_epoch;
761 0 : ele->pubkey = *authorized_pubkey;
762 0 : ele->prio = (ulong)&ele->pubkey;
763 0 : fd_vote_authorized_voters_treap_ele_insert(
764 0 : self->authorized_voters.treap, ele, self->authorized_voters.pool );
765 :
766 0 : return 0;
767 0 : }
768 :
769 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L869
770 : static int
771 : process_timestamp( fd_vote_state_t * self,
772 : ulong slot,
773 : long timestamp,
774 0 : fd_exec_instr_ctx_t const * ctx ) {
775 0 : if( FD_UNLIKELY(
776 0 : ( slot < self->last_timestamp.slot || timestamp < self->last_timestamp.timestamp ) ||
777 0 : ( slot == self->last_timestamp.slot &&
778 0 : ( slot != self->last_timestamp.slot || timestamp != self->last_timestamp.timestamp ) &&
779 0 : self->last_timestamp.slot != 0 ) ) ) {
780 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_TIMESTAMP_TOO_OLD;
781 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
782 0 : }
783 0 : self->last_timestamp.slot = slot;
784 0 : self->last_timestamp.timestamp = timestamp;
785 :
786 0 : return 0;
787 0 : }
788 :
789 : /**********************************************************************/
790 : /* mod vote_state */
791 : /**********************************************************************/
792 :
793 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L166
794 : __attribute__((warn_unused_result)) static int
795 : set_vote_account_state( fd_borrowed_account_t * vote_account,
796 : fd_vote_state_t * vote_state,
797 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
798 : /* This is a horrible conditional expression in Agave.
799 : The terms were broken up into their own variables. */
800 :
801 0 : ulong vsz = size_of_versioned( 1 );
802 :
803 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L175
804 0 : fd_rent_t const rent = fd_sysvar_cache_rent_read_nofail( ctx->sysvar_cache );
805 0 : int resize_needed = fd_borrowed_account_get_data_len( vote_account ) < vsz;
806 0 : int resize_rent_exempt = fd_rent_exempt_minimum_balance( &rent, vsz ) <= fd_borrowed_account_get_lamports( vote_account );
807 :
808 : /* The resize operation itself is part of the horrible conditional,
809 : but behind a short-circuit operator. */
810 0 : int resize_failed = 0;
811 0 : if( resize_needed && resize_rent_exempt ) {
812 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L179
813 0 : resize_failed =
814 0 : fd_borrowed_account_set_data_length( vote_account, vsz ) != FD_EXECUTOR_INSTR_SUCCESS;
815 0 : }
816 :
817 0 : if( FD_UNLIKELY( resize_needed && ( !resize_rent_exempt || resize_failed ) ) ) {
818 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L184
819 0 : fd_vote_state_versioned_t v1_14_11;
820 0 : fd_vote_state_versioned_new_disc( &v1_14_11, fd_vote_state_versioned_enum_v1_14_11 );
821 0 : from_vote_state_1_14_11( vote_state, &v1_14_11.inner.v1_14_11, ctx->txn_ctx->spad );
822 0 : return set_state( vote_account, &v1_14_11 );
823 0 : }
824 :
825 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L189
826 : // TODO: This is stupid... optimize this...
827 0 : fd_vote_state_versioned_t new_current = { .discriminant = fd_vote_state_versioned_enum_current,
828 0 : .inner = { .current = *vote_state } };
829 0 : return set_state( vote_account, &new_current );
830 0 : }
831 :
832 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L727
833 : static inline fd_vote_lockout_t *
834 0 : last_lockout( fd_vote_state_t * self ) {
835 0 : if( deq_fd_landed_vote_t_empty( self->votes ) ) return NULL;
836 0 : fd_landed_vote_t * last_vote = deq_fd_landed_vote_t_peek_tail( self->votes );
837 0 : return &last_vote->lockout;
838 0 : }
839 :
840 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L731
841 : static inline ulong *
842 0 : last_voted_slot( fd_vote_state_t * self ) {
843 0 : fd_vote_lockout_t * last_lockout_ = last_lockout( self );
844 0 : if( FD_UNLIKELY( !last_lockout_ ) ) return NULL;
845 0 : return &last_lockout_->slot;
846 0 : }
847 :
848 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L573
849 : static uchar
850 0 : contains_slot( fd_vote_state_t * vote_state, ulong slot ) {
851 : /* Logic is copied from slice::binary_search_by() in Rust. While not fully optimized,
852 : it aims to achieve fuzzing conformance for both sorted and unsorted inputs. */
853 0 : ulong size = deq_fd_landed_vote_t_cnt( vote_state->votes );
854 0 : if( FD_UNLIKELY( size==0UL ) ) return 0;
855 :
856 0 : ulong base = 0UL;
857 0 : while( size>1UL ) {
858 0 : ulong half = size / 2UL;
859 0 : ulong mid = base + half;
860 0 : ulong mid_slot = deq_fd_landed_vote_t_peek_index_const( vote_state->votes, mid )->lockout.slot;
861 0 : base = (slot<mid_slot) ? base : mid;
862 0 : size -= half;
863 0 : }
864 :
865 0 : return deq_fd_landed_vote_t_peek_index_const( vote_state->votes, base )->lockout.slot==slot;
866 0 : }
867 :
868 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L201
869 : static int
870 : check_and_filter_proposed_vote_state( fd_vote_state_t * vote_state,
871 : fd_vote_lockout_t * proposed_lockouts,
872 : uchar * proposed_has_root,
873 : ulong * proposed_root,
874 : fd_hash_t const * proposed_hash,
875 : fd_slot_hash_t const * slot_hashes, /* deque */
876 0 : fd_exec_instr_ctx_t const * ctx ) {
877 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L208
878 0 : if( FD_UNLIKELY( deq_fd_vote_lockout_t_empty( proposed_lockouts ) ) ) {
879 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_EMPTY_SLOTS;
880 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
881 0 : }
882 0 : fd_landed_vote_t const * last_vote = NULL;
883 0 : if( !deq_fd_landed_vote_t_empty( vote_state->votes ) ) {
884 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L212
885 0 : last_vote = deq_fd_landed_vote_t_peek_tail( vote_state->votes );
886 0 : }
887 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L218
888 0 : if( FD_LIKELY( last_vote ) ) {
889 0 : if( FD_UNLIKELY( deq_fd_vote_lockout_t_peek_tail_const( proposed_lockouts )->slot <=
890 0 : last_vote->lockout.slot ) ) {
891 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_VOTE_TOO_OLD;
892 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
893 0 : }
894 0 : }
895 :
896 : /* must be nonempty, checked above */
897 0 : ulong last_vote_state_update_slot = deq_fd_vote_lockout_t_peek_tail_const( proposed_lockouts )->slot;
898 :
899 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L224
900 0 : if( FD_UNLIKELY( deq_fd_slot_hash_t_empty( slot_hashes ) ) ) {
901 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
902 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
903 0 : }
904 :
905 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L227
906 0 : ulong earliest_slot_hash_in_history = deq_fd_slot_hash_t_peek_tail_const( slot_hashes )->slot;
907 :
908 : /* Check if the proposed vote is too old to be in the SlotHash history */
909 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L230
910 0 : if( FD_UNLIKELY( last_vote_state_update_slot < earliest_slot_hash_in_history ) ) {
911 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_VOTE_TOO_OLD;
912 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
913 0 : }
914 :
915 : /* Check if the proposed root is too old */
916 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L237
917 0 : if( *proposed_has_root ) {
918 0 : ulong const proposed_root_ = *proposed_root;
919 : /* If the new proposed root `R` is less than the earliest slot hash in the history
920 : such that we cannot verify whether the slot was actually was on this fork, set
921 : the root to the latest vote in the current vote that's less than R. */
922 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L242
923 0 : if( proposed_root_ < earliest_slot_hash_in_history ) {
924 0 : *proposed_has_root = vote_state->has_root_slot;
925 0 : *proposed_root = vote_state->root_slot;
926 0 : for( deq_fd_landed_vote_t_iter_t iter =
927 0 : deq_fd_landed_vote_t_iter_init_rev( vote_state->votes );
928 0 : !deq_fd_landed_vote_t_iter_done_rev( vote_state->votes, iter );
929 0 : iter = deq_fd_landed_vote_t_iter_prev( vote_state->votes, iter ) ) {
930 : /* Ensure we're iterating from biggest to smallest vote in the
931 : current vote state */
932 0 : fd_landed_vote_t const * vote = deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter );
933 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L248
934 0 : if( vote->lockout.slot <= proposed_root_ ) {
935 0 : *proposed_has_root = 1;
936 0 : *proposed_root = vote->lockout.slot;
937 0 : break;
938 0 : }
939 :
940 0 : }
941 0 : }
942 0 : }
943 :
944 0 : FD_SPAD_FRAME_BEGIN( ctx->txn_ctx->spad ) {
945 :
946 : /* Index into the new proposed vote state's slots, starting with the root if it exists then
947 : we use this mutable root to fold checking the root slot into the below loop for performance */
948 0 : int has_root_to_check = *proposed_has_root;
949 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L259
950 0 : ulong root_to_check = *proposed_root;
951 0 : ulong proposed_lockouts_index = 0UL;
952 0 : ulong lockouts_len = deq_fd_vote_lockout_t_cnt( proposed_lockouts );
953 :
954 : /* Index into the slot_hashes, starting at the oldest known slot hash */
955 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L264
956 0 : ulong slot_hashes_index = deq_fd_slot_hash_t_cnt( slot_hashes );
957 0 : ulong * proposed_lockouts_indexes_to_filter = fd_spad_alloc( ctx->txn_ctx->spad, alignof(ulong), lockouts_len * sizeof(ulong) );
958 0 : ulong filter_index = 0UL;
959 :
960 :
961 : /* Note:
962 :
963 : 1) `vote_state_update.lockouts` is sorted from oldest/smallest vote to newest/largest
964 : vote, due to the way votes are applied to the vote state (newest votes
965 : pushed to the back).
966 :
967 : 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
968 : the oldest/smallest vote
969 :
970 : Unlike for vote updates, vote state updates here can't only check votes older than the last vote
971 : because have to ensure that every slot is actually part of the history, not just the most
972 : recent ones */
973 :
974 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L279
975 0 : while( proposed_lockouts_index < lockouts_len && slot_hashes_index > 0 ) {
976 0 : ulong proposed_vote_slot =
977 0 : fd_ulong_if( has_root_to_check,
978 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L281
979 0 : root_to_check,
980 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L283
981 0 : deq_fd_vote_lockout_t_peek_index_const( proposed_lockouts,
982 0 : proposed_lockouts_index )
983 0 : ->slot );
984 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L285
985 0 : if( !has_root_to_check && proposed_lockouts_index > 0UL &&
986 0 : proposed_vote_slot <=
987 0 : deq_fd_vote_lockout_t_peek_index_const(
988 0 : proposed_lockouts,
989 0 : fd_ulong_checked_sub_expect(
990 0 : proposed_lockouts_index,
991 0 : 1,
992 0 : "`proposed_lockouts_index` is positive when checking `SlotsNotOrdered`" ) )
993 0 : ->slot ) {
994 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L293
995 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_NOT_ORDERED;
996 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
997 0 : }
998 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L295
999 0 : ulong ancestor_slot =
1000 0 : deq_fd_slot_hash_t_peek_index_const(
1001 0 : slot_hashes,
1002 0 : fd_ulong_checked_sub_expect(
1003 0 : slot_hashes_index,
1004 0 : 1UL,
1005 0 : "`slot_hashes_index` is positive when computing `ancestor_slot`" ) )
1006 0 : ->slot;
1007 : /* Find if this slot in the proposed vote state exists in the SlotHashes history
1008 : to confirm if it was a valid ancestor on this fork */
1009 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L303
1010 0 : if( proposed_vote_slot < ancestor_slot ) {
1011 0 : if( slot_hashes_index == deq_fd_slot_hash_t_cnt( slot_hashes ) ) {
1012 : /* The vote slot does not exist in the SlotHashes history because it's too old,
1013 : i.e. older than the oldest slot in the history. */
1014 0 : if( proposed_vote_slot >= earliest_slot_hash_in_history ) {
1015 0 : ctx->txn_ctx->custom_err = 0;
1016 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1017 0 : }
1018 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L310
1019 0 : if( !contains_slot( vote_state, proposed_vote_slot ) && !has_root_to_check ) {
1020 : /* If the vote slot is both:
1021 : 1) Too old
1022 : 2) Doesn't already exist in vote state
1023 : Then filter it out */
1024 0 : proposed_lockouts_indexes_to_filter[filter_index++] = proposed_lockouts_index; }
1025 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L318
1026 0 : if( has_root_to_check ) {
1027 0 : ulong new_proposed_root = root_to_check;
1028 : /* 1. Because `root_to_check.is_some()`, then we know that
1029 : we haven't checked the root yet in this loop, so
1030 : `proposed_vote_slot` == `new_proposed_root` == `vote_state_update.root` */
1031 0 : FD_TEST( new_proposed_root == proposed_vote_slot );
1032 : /* 2. We know from the assert earlier in the function that
1033 : `proposed_vote_slot < earliest_slot_hash_in_history`,
1034 : so from 1. we know that `new_proposed_root < earliest_slot_hash_in_history` */
1035 0 : if( new_proposed_root >= earliest_slot_hash_in_history ) {
1036 0 : ctx->txn_ctx->custom_err = 0;
1037 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1038 0 : }
1039 :
1040 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L329
1041 0 : has_root_to_check = 0;
1042 0 : root_to_check = ULONG_MAX;
1043 0 : } else {
1044 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L331
1045 0 : proposed_lockouts_index = fd_ulong_checked_add_expect(
1046 0 : proposed_lockouts_index,
1047 0 : 1,
1048 0 : "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when "
1049 0 : "`proposed_vote_slot` is too old to be in SlotHashes history" );
1050 0 : }
1051 0 : continue;
1052 0 : } else {
1053 : /* If the vote slot is new enough to be in the slot history,
1054 : but is not part of the slot history, then it must belong to another fork,
1055 : which means this vote state update is invalid. */
1056 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L340
1057 0 : if( has_root_to_check ) {
1058 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_ROOT_ON_DIFFERENT_FORK;
1059 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1060 0 : } else {
1061 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
1062 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1063 0 : }
1064 0 : }
1065 0 : } else if( proposed_vote_slot > ancestor_slot ) {
1066 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L347
1067 :
1068 : /* Decrement `slot_hashes_index` to find newer slots in the SlotHashes history */
1069 0 : slot_hashes_index = fd_ulong_checked_sub_expect(
1070 0 : slot_hashes_index,
1071 0 : 1,
1072 0 : "`slot_hashes_index` is positive when finding newer slots in SlotHashes history" );
1073 0 : continue;
1074 0 : } else {
1075 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L354
1076 :
1077 : /* Once the slot in `vote_state_update.lockouts` is found, bump to the next slot
1078 : in `vote_state_update.lockouts` and continue. If we were checking the root,
1079 : start checking the vote state instead. */
1080 0 : if( has_root_to_check ) {
1081 0 : has_root_to_check = 0;
1082 0 : root_to_check = ULONG_MAX;
1083 0 : } else {
1084 0 : proposed_lockouts_index = fd_ulong_checked_add_expect(
1085 0 : proposed_lockouts_index,
1086 0 : 1,
1087 0 : "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` "
1088 0 : "when match is found in SlotHashes history" );
1089 0 : slot_hashes_index = fd_ulong_checked_sub_expect(
1090 0 : slot_hashes_index,
1091 0 : 1,
1092 0 : "`slot_hashes_index` is positive when match is found in SlotHashes history" );
1093 0 : }
1094 0 : }
1095 0 : }
1096 :
1097 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L372
1098 0 : if( proposed_lockouts_index != deq_fd_vote_lockout_t_cnt( proposed_lockouts ) ) {
1099 : /* The last vote slot in the update did not exist in SlotHashes */
1100 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
1101 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1102 0 : }
1103 :
1104 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L401
1105 0 : if( memcmp( &deq_fd_slot_hash_t_peek_index_const( slot_hashes, slot_hashes_index )->hash,
1106 0 : proposed_hash,
1107 0 : sizeof( fd_hash_t ) ) != 0 ) {
1108 : /* This means the newest vote in the slot has a match that
1109 : doesn't match the expected hash for that slot on this fork */
1110 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_HASH_MISMATCH;
1111 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1112 0 : }
1113 :
1114 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L418
1115 : /* Filter out the irrelevant votes */
1116 0 : proposed_lockouts_index = 0UL;
1117 0 : ulong filter_votes_index = deq_fd_vote_lockout_t_cnt( proposed_lockouts );
1118 :
1119 : /* We need to iterate backwards here because proposed_lockouts_indexes_to_filter[ i ] is a
1120 : strictly increasing value. Forward iterating can lead to the proposed lockout indicies to get
1121 : shifted leading to popping the wrong proposed lockouts or out of bounds accessing. We need
1122 : to be sure of handling underflow in this case. */
1123 :
1124 0 : for( ulong i=filter_index; i>0UL && filter_votes_index>0UL; i-- ) {
1125 0 : proposed_lockouts_index = i - 1UL;
1126 0 : if( FD_UNLIKELY(proposed_lockouts_indexes_to_filter[ proposed_lockouts_index ]>=filter_votes_index ) ) {
1127 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
1128 0 : }
1129 :
1130 0 : deq_fd_vote_lockout_t_pop_idx_tail( proposed_lockouts, proposed_lockouts_indexes_to_filter[ proposed_lockouts_index ] );
1131 0 : filter_votes_index--;
1132 0 : }
1133 0 : } FD_SPAD_FRAME_END;
1134 :
1135 0 : return FD_EXECUTOR_INSTR_SUCCESS;
1136 0 : }
1137 :
1138 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L440
1139 : static int
1140 : check_slots_are_valid( fd_vote_state_t * vote_state,
1141 : ulong const * vote_slots,
1142 : fd_hash_t const * vote_hash,
1143 : fd_slot_hash_t const * slot_hashes, /* deque */
1144 0 : fd_exec_instr_ctx_t const * ctx ) {
1145 0 : ulong i = 0;
1146 0 : ulong j = deq_fd_slot_hash_t_cnt( slot_hashes );
1147 0 : ulong vote_slots_len = deq_ulong_cnt( vote_slots );
1148 :
1149 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L462
1150 0 : while( i < vote_slots_len && j > 0 ) {
1151 0 : ulong * last_voted_slot_ = last_voted_slot( vote_state );
1152 0 : if( FD_UNLIKELY( last_voted_slot_ &&
1153 0 : *deq_ulong_peek_index_const( vote_slots, i ) <= *last_voted_slot_ ) ) {
1154 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L469
1155 0 : i = fd_ulong_checked_add_expect(
1156 0 : i, 1, "`i` is bounded by `MAX_LOCKOUT_HISTORY` when finding larger slots" );
1157 0 : continue;
1158 0 : }
1159 :
1160 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L476
1161 0 : if( FD_UNLIKELY(
1162 0 : *deq_ulong_peek_index_const( vote_slots, i ) !=
1163 0 : deq_fd_slot_hash_t_peek_index_const( slot_hashes,
1164 0 : fd_ulong_checked_sub_expect( j, 1, "`j` is positive" ) )
1165 0 : ->slot ) ) {
1166 0 : j = fd_ulong_checked_sub_expect( j, 1, "`j` is positive when finding newer slots" );
1167 0 : continue;
1168 0 : }
1169 :
1170 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L486
1171 0 : i = fd_ulong_checked_add_expect(
1172 0 : i, 1, "`i` is bounded by `MAX_LOCKOUT_HISTORY` when hash is found" );
1173 0 : j = fd_ulong_checked_sub_expect( j, 1, "`j` is positive when hash is found" );
1174 0 : }
1175 :
1176 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L494
1177 0 : if( FD_UNLIKELY( j == deq_fd_slot_hash_t_cnt( slot_hashes ) ) ) {
1178 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_VOTE_TOO_OLD;
1179 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1180 0 : }
1181 0 : if( FD_UNLIKELY( i != vote_slots_len ) ) {
1182 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
1183 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1184 0 : }
1185 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L514
1186 0 : if( FD_UNLIKELY( 0 != memcmp( &deq_fd_slot_hash_t_peek_index_const( slot_hashes, j )->hash,
1187 0 : vote_hash,
1188 0 : 32UL ) ) ) {
1189 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_HASH_MISMATCH;
1190 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1191 0 : }
1192 0 : return 0;
1193 0 : }
1194 :
1195 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L565
1196 : static int
1197 : process_new_vote_state( fd_vote_state_t * vote_state,
1198 : fd_landed_vote_t * new_state,
1199 : int has_new_root,
1200 : ulong new_root,
1201 : int has_timestamp,
1202 : long timestamp,
1203 : ulong epoch,
1204 : ulong current_slot,
1205 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1206 0 : int rc;
1207 :
1208 0 : FD_TEST( !deq_fd_landed_vote_t_empty( new_state ) );
1209 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L575
1210 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( new_state ) > MAX_LOCKOUT_HISTORY ) ) {
1211 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_TOO_MANY_VOTES;
1212 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1213 0 : };
1214 :
1215 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L579
1216 0 : if( FD_UNLIKELY( has_new_root && vote_state->has_root_slot ) ) {
1217 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L581
1218 0 : if( FD_UNLIKELY( new_root < vote_state->root_slot ) ) {
1219 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_ROOT_ROLL_BACK;
1220 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1221 0 : }
1222 0 : } else if( FD_UNLIKELY( !has_new_root && vote_state->has_root_slot ) ) {
1223 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L586
1224 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_ROOT_ROLL_BACK;
1225 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1226 0 : } else {
1227 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L588
1228 : /* no-op */
1229 0 : }
1230 :
1231 0 : fd_landed_vote_t * previous_vote = NULL;
1232 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( new_state );
1233 0 : !deq_fd_landed_vote_t_iter_done( new_state, iter );
1234 0 : iter = deq_fd_landed_vote_t_iter_next( new_state, iter ) ) {
1235 0 : fd_landed_vote_t * vote = deq_fd_landed_vote_t_iter_ele( new_state, iter );
1236 0 : if( FD_LIKELY( vote->lockout.confirmation_count == 0 ) ) {
1237 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_ZERO_CONFIRMATIONS;
1238 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1239 0 : } else if( FD_UNLIKELY( vote->lockout.confirmation_count > MAX_LOCKOUT_HISTORY ) ) {
1240 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_CONFIRMATION_TOO_LARGE;
1241 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1242 0 : } else if( FD_LIKELY( has_new_root ) ) {
1243 0 : if( FD_UNLIKELY( vote->lockout.slot <= new_root && new_root != SLOT_DEFAULT ) ) {
1244 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOT_SMALLER_THAN_ROOT;
1245 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1246 0 : }
1247 0 : }
1248 :
1249 0 : if( FD_LIKELY( previous_vote ) ) {
1250 0 : if( FD_UNLIKELY( previous_vote->lockout.slot >= vote->lockout.slot ) ) {
1251 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_SLOTS_NOT_ORDERED;
1252 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1253 0 : } else if( FD_UNLIKELY( previous_vote->lockout.confirmation_count <=
1254 0 : vote->lockout.confirmation_count ) ) {
1255 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_CONFIRMATIONS_NOT_ORDERED;
1256 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1257 0 : } else if( FD_UNLIKELY( vote->lockout.slot >
1258 0 : last_locked_out_slot( &previous_vote->lockout ) ) ) {
1259 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_NEW_VOTE_STATE_LOCKOUT_MISMATCH;
1260 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1261 0 : }
1262 0 : }
1263 0 : previous_vote = vote;
1264 0 : }
1265 :
1266 0 : ulong current_vote_state_index = 0;
1267 0 : ulong new_vote_state_index = 0;
1268 :
1269 : /* Accumulate credits earned by newly rooted slots. The behavior changes with
1270 : timely_vote_credits: prior to this feature, there was a bug that counted a new root slot as 1
1271 : credit even if it had never been voted on. timely_vote_credits fixes this bug by only awarding
1272 : credits for slots actually voted on and finalized. */
1273 :
1274 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L635
1275 :
1276 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L641
1277 0 : ulong earned_credits = 0;
1278 :
1279 0 : if( FD_LIKELY( has_new_root ) ) {
1280 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes );
1281 0 : !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter );
1282 0 : iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) {
1283 0 : fd_landed_vote_t * current_vote = deq_fd_landed_vote_t_iter_ele( vote_state->votes, iter );
1284 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L647
1285 0 : if( FD_UNLIKELY( current_vote->lockout.slot <= new_root ) ) {
1286 : // this is safe because we're inside if has_new_root
1287 0 : earned_credits = fd_ulong_checked_add_expect(
1288 0 : credits_for_vote_at_index( vote_state,
1289 0 : current_vote_state_index ),
1290 0 : earned_credits,
1291 0 : "`earned_credits` does not overflow" );
1292 0 : current_vote_state_index = fd_ulong_checked_add_expect(
1293 0 : current_vote_state_index,
1294 0 : 1,
1295 0 : "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` "
1296 0 : "when processing new root" );
1297 0 : continue;
1298 0 : }
1299 0 : break;
1300 0 : }
1301 0 : }
1302 :
1303 : // For any slots newly added to the new vote state, the vote latency of that slot is not provided by the
1304 : // vote instruction contents, but instead is computed from the actual latency of the vote
1305 : // instruction. This prevents other validators from manipulating their own vote latencies within their vote states
1306 : // and forcing the rest of the cluster to accept these possibly fraudulent latency values. If the
1307 : // timly_vote_credits feature is not enabled then vote latency is set to 0 for new votes.
1308 : //
1309 : // For any slot that is in both the new state and the current state, the vote latency of the new state is taken
1310 : // from the current state.
1311 : //
1312 : // Thus vote latencies are set here for any newly vote-on slots when a vote instruction is received.
1313 : // They are copied into the new vote state after every vote for already voted-on slots.
1314 : // And when voted-on slots are rooted, the vote latencies stored in the vote state of all the rooted slots is used
1315 : // to compute credits earned.
1316 : // All validators compute the same vote latencies because all process the same vote instruction at the
1317 : // same slot, and the only time vote latencies are ever computed is at the time that their slot is first voted on;
1318 : // after that, the latencies are retained unaltered until the slot is rooted.
1319 :
1320 : // All the votes in our current vote state that are missing from the new vote state
1321 : // must have been expired by later votes. Check that the lockouts match this assumption.
1322 :
1323 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L686
1324 0 : while( current_vote_state_index < deq_fd_landed_vote_t_cnt( vote_state->votes ) &&
1325 0 : new_vote_state_index < deq_fd_landed_vote_t_cnt( new_state ) ) {
1326 0 : fd_landed_vote_t * current_vote =
1327 0 : deq_fd_landed_vote_t_peek_index( vote_state->votes, current_vote_state_index );
1328 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L690
1329 0 : fd_landed_vote_t * new_vote =
1330 0 : deq_fd_landed_vote_t_peek_index( new_state, new_vote_state_index );
1331 :
1332 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L696
1333 0 : if( FD_LIKELY( current_vote->lockout.slot < new_vote->lockout.slot ) ) {
1334 : /* The agave implementation of calculating the last locked out
1335 : slot does not calculate a min between the current vote's
1336 : confirmation count and max lockout history. The reason we do
1337 : this is to make sure that the fuzzers continue working:
1338 : the max lockout history can not be > MAX_LOCKOUT_HISTORY. */
1339 0 : ulong confirmation_count = fd_ulong_min( current_vote->lockout.confirmation_count, MAX_LOCKOUT_HISTORY );
1340 0 : ulong last_locked_out_slot = fd_ulong_sat_add( current_vote->lockout.slot,
1341 0 : (ulong)pow( INITIAL_LOCKOUT, (double)confirmation_count ) );
1342 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L697
1343 0 : if( last_locked_out_slot >= new_vote->lockout.slot ) {
1344 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L698
1345 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_LOCKOUT_CONFLICT;
1346 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1347 0 : }
1348 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L700
1349 0 : current_vote_state_index =
1350 0 : fd_ulong_checked_add_expect( current_vote_state_index,
1351 0 : 1,
1352 0 : "`current_vote_state_index` is bounded by "
1353 0 : "`MAX_LOCKOUT_HISTORY` when slot is less than proposed" );
1354 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L704
1355 0 : } else if( FD_UNLIKELY( current_vote->lockout.slot == new_vote->lockout.slot ) ) {
1356 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L707
1357 0 : if( new_vote->lockout.confirmation_count < current_vote->lockout.confirmation_count ) {
1358 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_CONFIRMATION_ROLL_BACK;
1359 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1360 0 : }
1361 :
1362 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L712
1363 0 : new_vote->latency =
1364 0 : deq_fd_landed_vote_t_peek_index( vote_state->votes, current_vote_state_index )->latency;
1365 :
1366 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L714
1367 0 : current_vote_state_index =
1368 0 : fd_ulong_checked_add_expect( current_vote_state_index,
1369 0 : 1,
1370 0 : "`current_vote_state_index` is bounded by "
1371 0 : "`MAX_LOCKOUT_HISTORY` when slot is equal to proposed" );
1372 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L717
1373 0 : new_vote_state_index =
1374 0 : fd_ulong_checked_add_expect( new_vote_state_index,
1375 0 : 1,
1376 0 : "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` "
1377 0 : "when slot is equal to proposed" );
1378 0 : } else {
1379 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L722
1380 0 : new_vote_state_index =
1381 0 : fd_ulong_checked_add_expect( new_vote_state_index,
1382 0 : 1,
1383 0 : "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` "
1384 0 : "when slot is greater than proposed" );
1385 0 : }
1386 0 : }
1387 :
1388 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L737
1389 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( new_state );
1390 0 : !deq_fd_landed_vote_t_iter_done( new_state, iter );
1391 0 : iter = deq_fd_landed_vote_t_iter_next( new_state, iter ) ) {
1392 0 : fd_landed_vote_t * new_vote = deq_fd_landed_vote_t_iter_ele( new_state, iter );
1393 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L738
1394 0 : if( FD_UNLIKELY( new_vote->latency == 0 ) ) {
1395 : // this is unlikely because as validators upgrade, it should converge to the new vote state
1396 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L739
1397 0 : new_vote->latency = compute_vote_latency( new_vote->lockout.slot, current_slot );
1398 0 : }
1399 0 : }
1400 :
1401 : // doesn't matter what the value of slot if `is_some = 0` i.e. `Option::None`
1402 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L744
1403 0 : int both_none = !vote_state->has_root_slot && !has_new_root;
1404 0 : if( ( !both_none && ( vote_state->has_root_slot != has_new_root ||
1405 0 : vote_state->root_slot != new_root ) ) ) {
1406 0 : increment_credits( vote_state, epoch, earned_credits );
1407 0 : }
1408 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L750
1409 0 : if( FD_LIKELY( has_timestamp ) ) {
1410 : /* new_state asserted nonempty at function beginning */
1411 0 : if( deq_fd_landed_vote_t_empty( new_state ) ) {
1412 0 : FD_LOG_ERR(( "solana panic" ));
1413 : // TODO: solana panics ... unclear what to return
1414 0 : ctx->txn_ctx->custom_err = 0;
1415 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1416 0 : }
1417 0 : ulong last_slot = deq_fd_landed_vote_t_peek_tail( new_state )->lockout.slot;
1418 0 : rc = process_timestamp( vote_state, last_slot, timestamp, ctx );
1419 0 : if( FD_UNLIKELY( rc ) ) { return rc; }
1420 0 : vote_state->last_timestamp.timestamp = timestamp;
1421 0 : }
1422 :
1423 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L754
1424 0 : vote_state->has_root_slot = (uchar)has_new_root;
1425 0 : vote_state->root_slot = new_root;
1426 :
1427 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L755
1428 0 : deq_fd_landed_vote_t_remove_all( vote_state->votes );
1429 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( new_state );
1430 0 : !deq_fd_landed_vote_t_iter_done( new_state, iter );
1431 0 : iter = deq_fd_landed_vote_t_iter_next( new_state, iter ) ) {
1432 0 : fd_landed_vote_t * landed_vote = deq_fd_landed_vote_t_iter_ele( new_state, iter );
1433 0 : deq_fd_landed_vote_t_push_tail_wrap( vote_state->votes, *landed_vote );
1434 0 : }
1435 :
1436 0 : return FD_EXECUTOR_INSTR_SUCCESS;
1437 0 : }
1438 :
1439 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L849
1440 : static int
1441 : authorize( fd_borrowed_account_t * vote_account,
1442 : fd_pubkey_t const * authorized,
1443 : fd_vote_authorize_t vote_authorize,
1444 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1445 : fd_sol_sysvar_clock_t const * clock,
1446 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1447 0 : int rc = 0;
1448 :
1449 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L857
1450 :
1451 0 : fd_vote_state_versioned_t * vote_state_versioned = get_state( vote_account->acct,
1452 0 : ctx->txn_ctx->spad,
1453 0 : &rc );
1454 0 : if( FD_UNLIKELY( rc ) ) return rc;
1455 0 : convert_to_current( vote_state_versioned, ctx->txn_ctx->spad );
1456 0 : fd_vote_state_t * vote_state = &vote_state_versioned->inner.current;
1457 :
1458 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L861
1459 0 : switch( vote_authorize.discriminant ) {
1460 :
1461 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L862
1462 0 : case fd_vote_authorize_enum_voter:;
1463 :
1464 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L863
1465 0 : int authorized_withdrawer_signer =
1466 0 : FD_EXECUTOR_INSTR_SUCCESS ==
1467 0 : verify_authorized_signer( &vote_state->authorized_withdrawer, signers );
1468 :
1469 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L869-L872
1470 0 : ulong target_epoch;
1471 0 : rc = fd_ulong_checked_add( clock->leader_schedule_epoch, 1UL, &target_epoch );
1472 0 : if( FD_UNLIKELY( rc!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
1473 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
1474 0 : }
1475 :
1476 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L866
1477 0 : rc = set_new_authorized_voter( vote_state,
1478 0 : authorized,
1479 0 : clock->epoch,
1480 0 : target_epoch,
1481 0 : authorized_withdrawer_signer,
1482 0 : signers,
1483 0 : ctx );
1484 0 : if( FD_UNLIKELY( rc ) ) return rc;
1485 0 : break;
1486 :
1487 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L883
1488 0 : case fd_vote_authorize_enum_withdrawer:
1489 0 : rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers );
1490 0 : if( FD_UNLIKELY( rc ) ) return rc;
1491 0 : vote_state->authorized_withdrawer = *authorized;
1492 0 : break;
1493 :
1494 : // failing exhaustive check is fatal
1495 0 : default:
1496 0 : __builtin_unreachable();
1497 0 : }
1498 :
1499 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L890
1500 0 : return set_vote_account_state( vote_account, vote_state, ctx );
1501 0 : }
1502 :
1503 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L894
1504 : static int
1505 : update_validator_identity( fd_borrowed_account_t * vote_account,
1506 : fd_pubkey_t const * node_pubkey,
1507 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1508 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1509 0 : int rc = 0;
1510 :
1511 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L900
1512 0 : fd_vote_state_versioned_t * vote_state_versioned = get_state( vote_account->acct,
1513 0 : ctx->txn_ctx->spad,
1514 0 : &rc );
1515 0 : if( FD_UNLIKELY( rc ) ) return rc;
1516 0 : convert_to_current( vote_state_versioned, ctx->txn_ctx->spad );
1517 0 : fd_vote_state_t * vote_state = &vote_state_versioned->inner.current;
1518 :
1519 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L905
1520 0 : rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers );
1521 0 : if( FD_UNLIKELY( rc ) ) return rc;
1522 :
1523 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L908
1524 0 : rc = verify_authorized_signer( node_pubkey, signers );
1525 0 : if( FD_UNLIKELY( rc ) ) return rc;
1526 :
1527 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L910
1528 0 : vote_state->node_pubkey = *node_pubkey;
1529 :
1530 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L912
1531 0 : return set_vote_account_state( vote_account, vote_state, ctx );
1532 0 : }
1533 :
1534 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L971
1535 : static int
1536 0 : is_commission_update_allowed( ulong slot, fd_epoch_schedule_t const * epoch_schedule ) {
1537 0 : if( FD_LIKELY( epoch_schedule->slots_per_epoch > 0UL ) ) {
1538 0 : ulong relative_slot = fd_ulong_sat_sub( slot, epoch_schedule->first_normal_slot );
1539 : // TODO underflow and overflow edge cases in addition to div by 0
1540 0 : relative_slot %= epoch_schedule->slots_per_epoch;
1541 0 : return fd_ulong_sat_mul( relative_slot, 2 ) <= epoch_schedule->slots_per_epoch;
1542 0 : } else {
1543 0 : return 1;
1544 0 : }
1545 0 : }
1546 :
1547 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L916
1548 : static int
1549 : update_commission( fd_borrowed_account_t * vote_account,
1550 : uchar commission,
1551 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1552 : fd_epoch_schedule_t const * epoch_schedule,
1553 : fd_sol_sysvar_clock_t const * clock,
1554 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1555 0 : int rc = 0;
1556 :
1557 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L925
1558 0 : fd_vote_state_versioned_t * vote_state_versioned = NULL;
1559 0 : fd_vote_state_t * vote_state = NULL;
1560 :
1561 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L927
1562 0 : int enforce_commission_update_rule = 1;
1563 0 : vote_state_versioned = get_state( vote_account->acct, ctx->txn_ctx->spad, &rc );
1564 0 : if ( FD_LIKELY( rc==FD_EXECUTOR_INSTR_SUCCESS ) ) {
1565 0 : convert_to_current( vote_state_versioned, ctx->txn_ctx->spad );
1566 0 : vote_state = &vote_state_versioned->inner.current;
1567 0 : enforce_commission_update_rule = commission > vote_state->commission;
1568 0 : }
1569 :
1570 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L940
1571 0 : if( FD_LIKELY( enforce_commission_update_rule ) ) {
1572 0 : if( FD_UNLIKELY( !is_commission_update_allowed( clock->slot, epoch_schedule ) ) ) {
1573 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_COMMISSION_UPDATE_TOO_LATE;
1574 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1575 0 : }
1576 0 : }
1577 :
1578 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L949
1579 0 : if( !vote_state ) {
1580 0 : vote_state_versioned = get_state( vote_account->acct, ctx->txn_ctx->spad, &rc );
1581 0 : if( FD_UNLIKELY( rc ) ) return rc;
1582 0 : convert_to_current( vote_state_versioned, ctx->txn_ctx->spad );
1583 0 : vote_state = &vote_state_versioned->inner.current;
1584 0 : }
1585 :
1586 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L957
1587 0 : rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers );
1588 0 : if( FD_UNLIKELY( rc ) ) return rc;
1589 :
1590 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L959
1591 0 : vote_state->commission = commission;
1592 :
1593 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L961
1594 0 : return set_vote_account_state( vote_account, vote_state, ctx );
1595 0 : }
1596 :
1597 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L997
1598 : static int
1599 : withdraw( fd_exec_instr_ctx_t const * ctx,
1600 : fd_borrowed_account_t * vote_account,
1601 : ulong lamports,
1602 : ushort to_account_index,
1603 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1604 : fd_rent_t const * rent_sysvar,
1605 0 : fd_sol_sysvar_clock_t const * clock ) {
1606 0 : int rc = 0;
1607 :
1608 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1010
1609 0 : fd_vote_state_versioned_t * vote_state_versioned = get_state( vote_account->acct,
1610 0 : ctx->txn_ctx->spad,
1611 0 : &rc );
1612 0 : if( FD_UNLIKELY( rc ) ) return rc;
1613 0 : convert_to_current( vote_state_versioned, ctx->txn_ctx->spad );
1614 0 : fd_vote_state_t * vote_state = &vote_state_versioned->inner.current;
1615 :
1616 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1014
1617 0 : rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers );
1618 0 : if( FD_UNLIKELY( rc ) ) return rc;
1619 :
1620 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1016
1621 0 : if( FD_UNLIKELY( lamports > fd_borrowed_account_get_lamports( vote_account ) ) )
1622 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
1623 0 : ulong remaining_balance = fd_borrowed_account_get_lamports( vote_account ) - lamports;
1624 :
1625 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1021
1626 0 : if( FD_UNLIKELY( remaining_balance == 0 ) ) {
1627 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1014
1628 0 : int reject_active_vote_account_close = 0;
1629 :
1630 0 : ulong last_epoch_with_credits;
1631 0 : if( FD_LIKELY( !deq_fd_vote_epoch_credits_t_empty( vote_state->epoch_credits ) ) ) {
1632 0 : last_epoch_with_credits =
1633 0 : deq_fd_vote_epoch_credits_t_peek_tail_const( vote_state->epoch_credits )->epoch;
1634 0 : ulong current_epoch = clock->epoch;
1635 0 : reject_active_vote_account_close =
1636 0 : fd_ulong_sat_sub( current_epoch, last_epoch_with_credits ) < 2;
1637 0 : }
1638 :
1639 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1034
1640 0 : if( FD_UNLIKELY( reject_active_vote_account_close ) ) {
1641 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1036
1642 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_ACTIVE_VOTE_ACCOUNT_CLOSE;
1643 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1644 0 : } else {
1645 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1040
1646 0 : fd_vote_state_versioned_t vote_state_versions;
1647 0 : fd_vote_state_versioned_new_disc( &vote_state_versions,
1648 0 : fd_vote_state_versioned_enum_current );
1649 0 : vote_state_versions.inner.current.prior_voters.idx = 31;
1650 0 : vote_state_versions.inner.current.prior_voters.is_empty = 1;
1651 0 : fd_vote_state_t * default_vote_state = &vote_state_versions.inner.current;
1652 0 : rc = 0;
1653 0 : rc = set_vote_account_state( vote_account, default_vote_state, ctx );
1654 0 : if( FD_UNLIKELY( rc != 0 ) ) return rc;
1655 0 : }
1656 0 : } else {
1657 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1043
1658 0 : ulong min_rent_exempt_balance =
1659 0 : fd_rent_exempt_minimum_balance( rent_sysvar, fd_borrowed_account_get_data_len( vote_account ) );
1660 0 : if( remaining_balance < min_rent_exempt_balance ) {
1661 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
1662 0 : }
1663 0 : }
1664 :
1665 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1049
1666 0 : rc = fd_borrowed_account_checked_sub_lamports( vote_account, lamports );
1667 0 : if( FD_UNLIKELY( rc ) ) return rc;
1668 :
1669 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_state/mod.rs#L1019 */
1670 0 : fd_borrowed_account_drop( vote_account );
1671 :
1672 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_state/mod.rs#L1020-L1021 */
1673 0 : fd_guarded_borrowed_account_t to = {0};
1674 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, to_account_index, &to );
1675 :
1676 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1053
1677 0 : rc = fd_borrowed_account_checked_add_lamports( &to, lamports );
1678 0 : if( FD_UNLIKELY( rc ) ) return rc;
1679 :
1680 0 : return 0;
1681 0 : }
1682 :
1683 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L760
1684 : static int
1685 : process_vote_unfiltered( fd_vote_state_t * vote_state,
1686 : ulong * vote_slots,
1687 : fd_vote_t const * vote,
1688 : fd_slot_hash_t const * slot_hashes, /* deque */
1689 : ulong epoch,
1690 : ulong current_slot,
1691 0 : fd_exec_instr_ctx_t const * ctx ) {
1692 0 : int rc;
1693 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L770
1694 0 : rc = check_slots_are_valid( vote_state, vote_slots, &vote->hash, slot_hashes, ctx );
1695 0 : if( FD_UNLIKELY( rc ) ) return rc;
1696 0 : for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote_slots );
1697 0 : !deq_ulong_iter_done( vote_slots, iter );
1698 0 : iter = deq_ulong_iter_next( vote_slots, iter ) ) {
1699 0 : ulong * ele = deq_ulong_iter_ele( vote_slots, iter );
1700 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L772
1701 0 : process_next_vote_slot( vote_state, *ele, epoch, current_slot );
1702 0 : }
1703 0 : return 0;
1704 0 : }
1705 :
1706 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L783
1707 : static int
1708 : process_vote( fd_vote_state_t * vote_state,
1709 : fd_vote_t const * vote,
1710 : fd_slot_hash_t const * slot_hashes, /* deque */
1711 : ulong epoch,
1712 : ulong current_slot,
1713 0 : fd_exec_instr_ctx_t const * ctx ) {
1714 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L792
1715 0 : if( FD_UNLIKELY( deq_ulong_empty( vote->slots ) ) ) {
1716 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_EMPTY_SLOTS;
1717 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1718 0 : }
1719 :
1720 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L795
1721 0 : ulong earliest_slot_in_history = 0;
1722 0 : if( FD_UNLIKELY( !deq_fd_slot_hash_t_empty( slot_hashes ) ) ) {
1723 0 : earliest_slot_in_history = deq_fd_slot_hash_t_peek_tail_const( slot_hashes )->slot;
1724 0 : }
1725 :
1726 0 : ulong vote_slots_cnt = deq_ulong_cnt( vote->slots );
1727 0 : uchar * vote_slots_mem = fd_spad_alloc( ctx->txn_ctx->spad, deq_ulong_align(), deq_ulong_footprint( vote_slots_cnt ) );
1728 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L796
1729 0 : ulong * vote_slots = deq_ulong_join( deq_ulong_new( vote_slots_mem, vote_slots_cnt ) );
1730 0 : for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote->slots );
1731 0 : !deq_ulong_iter_done( vote->slots, iter );
1732 0 : iter = deq_ulong_iter_next( vote->slots, iter ) ) {
1733 0 : ulong * ele = deq_ulong_iter_ele( vote->slots, iter );
1734 0 : if( FD_UNLIKELY( *ele >= earliest_slot_in_history ) ) {
1735 0 : vote_slots = deq_ulong_push_tail_wrap( vote_slots, *ele );
1736 0 : }
1737 0 : }
1738 :
1739 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L802
1740 0 : if( FD_UNLIKELY( deq_ulong_cnt( vote_slots ) == 0 ) ) {
1741 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_VOTES_TOO_OLD_ALL_FILTERED;
1742 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1743 0 : }
1744 :
1745 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L805
1746 0 : return process_vote_unfiltered(
1747 0 : vote_state, vote_slots, vote, slot_hashes, epoch, current_slot, ctx );
1748 0 : }
1749 :
1750 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1060
1751 : static int
1752 : initialize_account( fd_borrowed_account_t * vote_account,
1753 : fd_vote_init_t * vote_init,
1754 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1755 : fd_sol_sysvar_clock_t const * clock,
1756 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1757 0 : int rc;
1758 :
1759 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1067
1760 0 : ulong data_len = fd_borrowed_account_get_data_len( vote_account );
1761 0 : if( FD_UNLIKELY( data_len != size_of_versioned( 1 ) ) ) {
1762 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
1763 0 : }
1764 :
1765 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1074
1766 0 : fd_vote_state_versioned_t * versioned = get_state( vote_account->acct,
1767 0 : ctx->txn_ctx->spad,
1768 0 : &rc );
1769 0 : if( FD_UNLIKELY( rc ) ) return rc;
1770 :
1771 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1076
1772 0 : if( FD_UNLIKELY( !is_uninitialized( versioned ) ) ) {
1773 0 : return FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED;
1774 0 : }
1775 :
1776 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1081
1777 0 : rc = verify_authorized_signer( &vote_init->node_pubkey, signers );
1778 0 : if( FD_UNLIKELY( rc ) ) {
1779 0 : return rc;
1780 0 : }
1781 :
1782 : /*
1783 : * N.B. Technically we should destroy() to release memory before
1784 : * newing, otherwise the pointers are wiped and memory is leaked.
1785 : * We are probably fine for now since we are bump allocating
1786 : * everything and the enclosing frame will free everything when
1787 : * popped.
1788 : */
1789 : // reset the object
1790 0 : fd_vote_state_versioned_new( versioned );
1791 :
1792 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1083
1793 0 : vote_state_new( vote_init, clock, ctx->txn_ctx->spad, &versioned->inner.current );
1794 0 : return set_vote_account_state( vote_account, &versioned->inner.current, ctx );
1795 0 : }
1796 :
1797 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1086
1798 : static int
1799 : verify_and_get_vote_state( fd_borrowed_account_t * vote_account,
1800 : fd_sol_sysvar_clock_t const * clock,
1801 : fd_pubkey_t const * signers[FD_TXN_SIG_MAX],
1802 : fd_vote_state_t * vote_state /* out */,
1803 0 : fd_exec_instr_ctx_t const * ctx /* spad */ ) {
1804 0 : int rc = 0;
1805 :
1806 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1091
1807 0 : fd_vote_state_versioned_t * versioned = get_state( vote_account->acct,
1808 0 : ctx->txn_ctx->spad,
1809 0 : &rc );
1810 0 : if( FD_UNLIKELY( rc ) ) return rc;
1811 :
1812 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1093
1813 0 : if( FD_UNLIKELY( is_uninitialized( versioned ) ) )
1814 0 : return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT;
1815 :
1816 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1097
1817 0 : convert_to_current( versioned, ctx->txn_ctx->spad );
1818 0 : *vote_state = versioned->inner.current;
1819 :
1820 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1098
1821 0 : fd_pubkey_t * authorized_voter = NULL;
1822 0 : rc = get_and_update_authorized_voter( vote_state, clock->epoch, &authorized_voter, ctx );
1823 0 : if( FD_UNLIKELY( rc ) ) return rc;
1824 :
1825 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1099
1826 0 : rc = verify_authorized_signer( authorized_voter, signers );
1827 0 : if( FD_UNLIKELY( rc ) ) return rc;
1828 :
1829 0 : return FD_EXECUTOR_INSTR_SUCCESS;
1830 0 : }
1831 :
1832 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1104
1833 : static int
1834 : process_vote_with_account( fd_borrowed_account_t * vote_account,
1835 : fd_slot_hash_t const * slot_hashes, /* deque */
1836 : fd_sol_sysvar_clock_t const * clock,
1837 : fd_vote_t * vote,
1838 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1839 0 : fd_exec_instr_ctx_t const * ctx ) {
1840 :
1841 0 : int rc;
1842 0 : fd_vote_state_t vote_state;
1843 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1112
1844 0 : rc = verify_and_get_vote_state( vote_account, clock, signers, &vote_state, ctx );
1845 0 : if( FD_UNLIKELY( rc ) ) return rc;
1846 :
1847 :
1848 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1117
1849 0 : rc = process_vote( &vote_state, vote, slot_hashes, clock->epoch, clock->slot, ctx );
1850 0 : if( FD_UNLIKELY( rc ) ) return rc;
1851 :
1852 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1126
1853 0 : if( FD_LIKELY( vote->has_timestamp ) ) {
1854 0 : if( FD_UNLIKELY( deq_ulong_cnt( vote->slots ) == 0 ) ) {
1855 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_EMPTY_SLOTS;
1856 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1857 0 : }
1858 :
1859 0 : ulong max = deq_ulong_peek_head( vote->slots ) ? *deq_ulong_peek_head( vote->slots ) : 0UL;
1860 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1127
1861 0 : for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote->slots );
1862 0 : !deq_ulong_iter_done( vote->slots, iter );
1863 0 : iter = deq_ulong_iter_next( vote->slots, iter ) ) {
1864 0 : ulong * ele = deq_ulong_iter_ele( vote->slots, iter );
1865 0 : max = fd_ulong_max( max, *ele );
1866 0 : }
1867 0 : if( FD_UNLIKELY( !max ) ) {
1868 0 : ctx->txn_ctx->custom_err = FD_VOTE_ERR_EMPTY_SLOTS;
1869 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1870 0 : }
1871 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1131
1872 0 : rc = process_timestamp( &vote_state, max, vote->timestamp, ctx );
1873 0 : if( FD_UNLIKELY( rc ) ) return rc;
1874 0 : }
1875 :
1876 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1133
1877 0 : return set_vote_account_state( vote_account, &vote_state, ctx );
1878 0 : }
1879 :
1880 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1156
1881 : static int
1882 : do_process_vote_state_update( fd_vote_state_t * vote_state,
1883 : fd_slot_hash_t const * slot_hashes, /* deque */
1884 : ulong epoch,
1885 : ulong slot,
1886 : fd_vote_state_update_t * vote_state_update,
1887 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1888 0 : int rc;
1889 :
1890 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1164
1891 0 : rc = check_and_filter_proposed_vote_state(
1892 0 : vote_state,
1893 0 : vote_state_update->lockouts, &vote_state_update->has_root, &vote_state_update->root, &vote_state_update->hash,
1894 0 : slot_hashes, ctx );
1895 0 : if( FD_UNLIKELY( rc ) ) return rc;
1896 :
1897 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1177
1898 0 : uchar * deque_mem = fd_spad_alloc( ctx->txn_ctx->spad,
1899 0 : deq_fd_landed_vote_t_align(),
1900 0 : deq_fd_landed_vote_t_footprint( deq_fd_vote_lockout_t_cnt( vote_state_update->lockouts ) ) );
1901 :
1902 0 : fd_landed_vote_t * landed_votes = deq_fd_landed_vote_t_join( deq_fd_landed_vote_t_new( deque_mem, deq_fd_vote_lockout_t_cnt( vote_state_update->lockouts ) ) );
1903 0 : for( deq_fd_vote_lockout_t_iter_t iter =
1904 0 : deq_fd_vote_lockout_t_iter_init( vote_state_update->lockouts );
1905 0 : !deq_fd_vote_lockout_t_iter_done( vote_state_update->lockouts, iter );
1906 0 : iter = deq_fd_vote_lockout_t_iter_next( vote_state_update->lockouts, iter ) ) {
1907 0 : fd_vote_lockout_t * lockout =
1908 0 : deq_fd_vote_lockout_t_iter_ele( vote_state_update->lockouts, iter );
1909 0 : deq_fd_landed_vote_t_push_tail_wrap( landed_votes,
1910 0 : ( fd_landed_vote_t ){ .latency = 0, .lockout = *lockout } );
1911 0 : }
1912 :
1913 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1171
1914 0 : return process_new_vote_state( vote_state,
1915 0 : landed_votes,
1916 0 : vote_state_update->has_root,
1917 0 : vote_state_update->root,
1918 0 : vote_state_update->has_timestamp,
1919 0 : vote_state_update->timestamp,
1920 0 : epoch,
1921 0 : slot,
1922 0 : ctx );
1923 0 : }
1924 :
1925 : static int
1926 : process_vote_state_update( fd_borrowed_account_t * vote_account,
1927 : fd_slot_hash_t const * slot_hashes,
1928 : fd_sol_sysvar_clock_t const * clock,
1929 : fd_vote_state_update_t * vote_state_update,
1930 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1931 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1932 0 : int rc;
1933 :
1934 : /* A temporary hack to accumulate the stake-weighted bank hash from
1935 : all vote transactions. This determines whether our validator has
1936 : bank hash mismatched. TODO: move to a tile. */
1937 0 : if( FD_LIKELY( !!ctx->txn_ctx->bank_hash_cmp ) ) {
1938 : // tie in code for fd_bank_hash_cmp that helps us detect if we have forked from the cluster.
1939 : //
1940 : // There is no corresponding code in anza
1941 :
1942 0 : fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( ctx->txn_ctx->bank );
1943 0 : if( !vote_states ) {
1944 0 : FD_LOG_CRIT(( "vote_states is NULL" ));
1945 0 : }
1946 :
1947 0 : fd_vote_state_ele_t const * vote_state_ele = fd_vote_states_query_const( vote_states, vote_account->acct->pubkey );
1948 0 : if( !vote_state_ele ) {
1949 0 : FD_LOG_CRIT(( "vote_state is NULL" ));
1950 0 : }
1951 :
1952 0 : if( !deq_fd_vote_lockout_t_empty( vote_state_update->lockouts ) ) {
1953 0 : fd_vote_lockout_t * lockout = deq_fd_vote_lockout_t_peek_tail( vote_state_update->lockouts );
1954 0 : fd_bank_hash_cmp_t * bank_hash_cmp = ctx->txn_ctx->bank_hash_cmp;
1955 0 : if( FD_LIKELY( lockout && bank_hash_cmp ) ) {
1956 0 : fd_bank_hash_cmp_lock( bank_hash_cmp );
1957 0 : fd_bank_hash_cmp_insert(
1958 0 : bank_hash_cmp,
1959 0 : lockout->slot,
1960 0 : &vote_state_update->hash,
1961 0 : 0,
1962 0 : vote_state_ele->stake );
1963 0 : fd_bank_hash_cmp_unlock( bank_hash_cmp );
1964 0 : }
1965 0 : }
1966 :
1967 0 : fd_bank_vote_states_end_locking_query( ctx->txn_ctx->bank );
1968 0 : }
1969 :
1970 0 : fd_vote_state_t vote_state;
1971 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1144
1972 0 : rc = verify_and_get_vote_state( vote_account, clock, signers, &vote_state, ctx );
1973 0 : if( FD_UNLIKELY( rc ) ) return rc;
1974 :
1975 :
1976 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1145
1977 0 : rc = do_process_vote_state_update(
1978 0 : &vote_state, slot_hashes, clock->epoch, clock->slot, vote_state_update, ctx );
1979 0 : if( FD_UNLIKELY( rc ) ) {
1980 0 : return rc;
1981 0 : }
1982 :
1983 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1153
1984 0 : rc = set_vote_account_state( vote_account, &vote_state, ctx );
1985 :
1986 0 : return rc;
1987 0 : }
1988 :
1989 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1206
1990 : static int
1991 : do_process_tower_sync( fd_vote_state_t * vote_state,
1992 : fd_slot_hash_t const * slot_hashes, /* deque */
1993 : ulong epoch,
1994 : ulong slot,
1995 : fd_tower_sync_t * tower_sync,
1996 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
1997 :
1998 0 : do {
1999 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1214
2000 0 : int err = check_and_filter_proposed_vote_state(
2001 0 : vote_state,
2002 0 : tower_sync->lockouts, &tower_sync->has_root, &tower_sync->root, &tower_sync->hash,
2003 0 : slot_hashes, ctx );
2004 0 : if( FD_UNLIKELY( err ) ) return err;
2005 0 : } while(0);
2006 :
2007 0 : int err;
2008 0 : FD_SPAD_FRAME_BEGIN( ctx->txn_ctx->spad ) {
2009 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1221
2010 0 : err = process_new_vote_state(
2011 0 : vote_state,
2012 0 : landed_votes_from_lockouts( tower_sync->lockouts, ctx->txn_ctx->spad ),
2013 0 : tower_sync->has_root,
2014 0 : tower_sync->root,
2015 0 : tower_sync->has_timestamp,
2016 0 : tower_sync->timestamp,
2017 0 : epoch,
2018 0 : slot,
2019 0 : ctx );
2020 0 : } FD_SPAD_FRAME_END;
2021 :
2022 0 : return err;
2023 0 : }
2024 :
2025 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1186
2026 : static int
2027 : process_tower_sync( fd_borrowed_account_t * vote_account,
2028 : fd_slot_hash_t const * slot_hashes, /* deque */
2029 : fd_sol_sysvar_clock_t const * clock,
2030 : fd_tower_sync_t * tower_sync,
2031 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
2032 0 : fd_exec_instr_ctx_t const * ctx /* feature_set */ ) {
2033 : /* A temporary hack to accumulate the stake-weighted bank hash from
2034 : all vote transactions. This determines whether our validator has
2035 : bank hash mismatched. TODO: move to a tile. */
2036 0 : if( FD_LIKELY( !!ctx->txn_ctx->bank_hash_cmp ) ) {
2037 0 : if( !deq_fd_vote_lockout_t_empty( tower_sync->lockouts ) ) {
2038 0 : fd_vote_lockout_t * lockout = deq_fd_vote_lockout_t_peek_tail( tower_sync->lockouts );
2039 0 : fd_bank_hash_cmp_t * bank_hash_cmp = ctx->txn_ctx->bank_hash_cmp;
2040 0 : fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( ctx->txn_ctx->bank );
2041 0 : if( !vote_states ) {
2042 0 : FD_LOG_CRIT(( "vote_states is NULL" ));
2043 0 : }
2044 0 : fd_vote_state_ele_t const * vote_state_ele = fd_vote_states_query_const( vote_states, vote_account->acct->pubkey );
2045 0 : if( FD_LIKELY( lockout && bank_hash_cmp && vote_state_ele ) ) {
2046 0 : fd_bank_hash_cmp_lock( bank_hash_cmp );
2047 0 : fd_bank_hash_cmp_insert(
2048 0 : bank_hash_cmp,
2049 0 : lockout->slot,
2050 0 : &tower_sync->hash,
2051 0 : 0,
2052 0 : vote_state_ele->stake );
2053 0 : fd_bank_hash_cmp_unlock( bank_hash_cmp );
2054 0 : }
2055 0 : fd_bank_vote_states_end_locking_query( ctx->txn_ctx->bank );
2056 0 : }
2057 0 : }
2058 :
2059 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1194
2060 0 : fd_vote_state_t vote_state;
2061 0 : do {
2062 0 : int err = verify_and_get_vote_state( vote_account, clock, signers, &vote_state, ctx );
2063 0 : if( FD_UNLIKELY( err ) ) return err;
2064 0 : } while(0);
2065 :
2066 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1195
2067 0 : do {
2068 0 : int err = do_process_tower_sync( &vote_state, slot_hashes, clock->epoch, clock->slot, tower_sync, ctx );
2069 0 : if( FD_UNLIKELY( err ) ) return err;
2070 0 : } while(0);
2071 :
2072 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1203
2073 0 : return set_vote_account_state( vote_account, &vote_state, ctx );
2074 0 : }
2075 :
2076 : /**********************************************************************/
2077 : /* FD-only encoders / decoders (doesn't map directly to Labs impl) */
2078 : /**********************************************************************/
2079 :
2080 : int
2081 : fd_vote_decode_compact_update( fd_compact_vote_state_update_t * compact_update,
2082 : fd_vote_state_update_t * vote_update,
2083 0 : fd_exec_instr_ctx_t const * ctx /* spad */ ) {
2084 : // Taken from:
2085 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L954
2086 0 : if( compact_update->root != ULONG_MAX ) {
2087 0 : vote_update->has_root = 1;
2088 0 : vote_update->root = compact_update->root;
2089 0 : } else {
2090 0 : vote_update->has_root = 0;
2091 0 : vote_update->root = ULONG_MAX;
2092 0 : }
2093 :
2094 0 : ulong lockouts_len = compact_update->lockouts_len;
2095 0 : ulong lockouts_max = fd_ulong_max( lockouts_len, MAX_LOCKOUT_HISTORY );
2096 :
2097 0 : uchar * deque_mem = fd_spad_alloc( ctx->txn_ctx->spad,
2098 0 : deq_fd_vote_lockout_t_align(),
2099 0 : deq_fd_vote_lockout_t_footprint( lockouts_max ) );
2100 0 : vote_update->lockouts = deq_fd_vote_lockout_t_join( deq_fd_vote_lockout_t_new( deque_mem, lockouts_max ) );
2101 0 : ulong slot = fd_ulong_if( vote_update->has_root, vote_update->root, 0 );
2102 :
2103 0 : for( ulong i=0; i < lockouts_len; ++i ) {
2104 0 : fd_vote_lockout_t * elem = deq_fd_vote_lockout_t_push_tail_nocopy( vote_update->lockouts );
2105 0 : fd_vote_lockout_new( elem );
2106 :
2107 0 : fd_lockout_offset_t * lock_offset = &compact_update->lockouts[i];
2108 :
2109 0 : ulong next_slot;
2110 0 : if( FD_UNLIKELY( __builtin_uaddl_overflow( slot, lock_offset->offset, &next_slot ) ) )
2111 0 : return 0;
2112 :
2113 0 : elem->slot = slot = next_slot;
2114 0 : elem->confirmation_count = (uint)lock_offset->confirmation_count;
2115 0 : }
2116 :
2117 0 : vote_update->hash = compact_update->hash;
2118 0 : vote_update->has_timestamp = compact_update->has_timestamp;
2119 0 : vote_update->timestamp = compact_update->timestamp;
2120 :
2121 0 : return 1;
2122 0 : }
2123 :
2124 : /// returns commission split as (voter_portion, staker_portion, was_split) tuple
2125 : ///
2126 : /// if commission calculation is 100% one way or other, indicate with false for was_split
2127 :
2128 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L543
2129 : void
2130 : fd_vote_commission_split( uchar commission,
2131 : ulong on,
2132 0 : fd_commission_split_t * result ) {
2133 0 : uint commission_split = fd_uint_min( (uint)commission, 100 );
2134 0 : result->is_split = (commission_split != 0 && commission_split != 100);
2135 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L545
2136 0 : if( commission_split==0U ) {
2137 0 : result->voter_portion = 0;
2138 0 : result->staker_portion = on;
2139 0 : return;
2140 0 : }
2141 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L546
2142 0 : if( commission_split==100U ) {
2143 0 : result->voter_portion = on;
2144 0 : result->staker_portion = 0;
2145 0 : return;
2146 0 : }
2147 : /* Note: order of operations may matter for int division. That's why I didn't make the
2148 : * optimization of getting out the common calculations */
2149 :
2150 : // ... This is copied from the solana comments...
2151 : //
2152 : // Calculate mine and theirs independently and symmetrically instead
2153 : // of using the remainder of the other to treat them strictly
2154 : // equally. This is also to cancel the rewarding if either of the
2155 : // parties should receive only fractional lamports, resulting in not
2156 : // being rewarded at all. Thus, note that we intentionally discard
2157 : // any residual fractional lamports.
2158 :
2159 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L548
2160 0 : result->voter_portion =
2161 0 : (ulong)((uint128)on * (uint128)commission_split / (uint128)100);
2162 0 : result->staker_portion =
2163 0 : (ulong)((uint128)on * (uint128)( 100 - commission_split ) / (uint128)100);
2164 0 : }
2165 :
2166 : /**********************************************************************/
2167 : /* mod vote_processor */
2168 : /**********************************************************************/
2169 :
2170 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L21
2171 : static int
2172 : process_authorize_with_seed_instruction(
2173 : /* invoke_context */
2174 : fd_exec_instr_ctx_t const * ctx,
2175 : /* transaction_context */
2176 : fd_borrowed_account_t * vote_account,
2177 : fd_pubkey_t const * new_authority,
2178 : fd_vote_authorize_t authorization_type,
2179 : fd_pubkey_t const * current_authority_derived_key_owner,
2180 : uchar const * current_authority_derived_key_seed,
2181 0 : ulong current_authority_derived_key_seed_len ) {
2182 0 : int rc = 0;
2183 :
2184 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L31
2185 0 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_clock_id );
2186 0 : if( FD_UNLIKELY( rc ) ) return rc;
2187 :
2188 0 : fd_sol_sysvar_clock_t clock_;
2189 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2190 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2191 :
2192 0 : fd_pubkey_t * expected_authority_keys[FD_TXN_SIG_MAX] = { 0 };
2193 0 : fd_pubkey_t single_signer = { 0 };
2194 :
2195 0 : if( ctx->instr->acct_cnt < 3 )
2196 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
2197 :
2198 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L33
2199 0 : if( fd_instr_acc_is_signer_idx( ctx->instr, 2, &rc ) ) {
2200 :
2201 : // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L34
2202 0 : fd_pubkey_t const * base_pubkey = NULL;
2203 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 2UL, &base_pubkey );
2204 0 : if( FD_UNLIKELY( rc ) ) return rc;
2205 :
2206 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L37
2207 0 : expected_authority_keys[0] = &single_signer;
2208 0 : rc = fd_pubkey_create_with_seed( ctx,
2209 0 : base_pubkey->uc,
2210 0 : (char const *)current_authority_derived_key_seed,
2211 0 : current_authority_derived_key_seed_len,
2212 0 : current_authority_derived_key_owner->uc,
2213 0 : /* insert */ expected_authority_keys[0]->uc );
2214 0 : if( FD_UNLIKELY( rc ) ) return rc;
2215 0 : }
2216 :
2217 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L43
2218 0 : return authorize( vote_account,
2219 0 : new_authority,
2220 0 : authorization_type,
2221 0 : (fd_pubkey_t const **)expected_authority_keys,
2222 0 : clock,
2223 0 : ctx );
2224 0 : }
2225 :
2226 : /**********************************************************************/
2227 : /* Entry point for the Vote Program */
2228 : /**********************************************************************/
2229 :
2230 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L57
2231 : int
2232 0 : fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) {
2233 : /* FD-specific init */
2234 0 : int rc = FD_EXECUTOR_INSTR_SUCCESS;
2235 :
2236 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L57
2237 0 : FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS );
2238 :
2239 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L64
2240 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 1 ) ) {
2241 0 : return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
2242 0 : }
2243 :
2244 0 : fd_guarded_borrowed_account_t me = {0};
2245 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, 0, &me );
2246 :
2247 0 : switch( rc ) {
2248 0 : case FD_ACC_MGR_SUCCESS:
2249 0 : break;
2250 0 : case FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT:
2251 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L637
2252 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
2253 0 : default:
2254 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L639
2255 0 : return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED;
2256 0 : }
2257 :
2258 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L65
2259 0 : if( FD_UNLIKELY( 0 != memcmp( fd_borrowed_account_get_owner( &me ),
2260 0 : fd_solana_vote_program_id.key,
2261 0 : sizeof( fd_pubkey_t ) ) ) ) {
2262 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L66
2263 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER;
2264 0 : }
2265 :
2266 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L69
2267 0 : fd_pubkey_t const * signers[FD_TXN_SIG_MAX] = { 0 };
2268 0 : fd_exec_instr_ctx_get_signers( ctx, signers );
2269 :
2270 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L70
2271 0 : if( FD_UNLIKELY( ctx->instr->data==NULL ) ) {
2272 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2273 0 : }
2274 :
2275 0 : int decode_result;
2276 0 : ulong decoded_sz;
2277 0 : fd_vote_instruction_t * instruction = fd_bincode_decode1_spad(
2278 0 : vote_instruction, ctx->txn_ctx->spad,
2279 0 : ctx->instr->data, ctx->instr->data_sz,
2280 0 : &decode_result,
2281 0 : &decoded_sz );
2282 0 : if( FD_UNLIKELY( decode_result != FD_BINCODE_SUCCESS ) ) {
2283 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2284 0 : }
2285 0 : if( FD_UNLIKELY( decoded_sz > FD_TXN_MTU ) ) {
2286 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2287 0 : }
2288 :
2289 : /* PLEASE PRESERVE SWITCH-CASE ORDERING TO MIRROR LABS IMPL:
2290 : */
2291 0 : switch( instruction->discriminant ) {
2292 :
2293 : /* InitializeAccount
2294 : *
2295 : * Instruction:
2296 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L32
2297 : *
2298 : * Processor:
2299 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L71
2300 : */
2301 0 : case fd_vote_instruction_enum_initialize_account: {
2302 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L72
2303 0 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_rent_id );
2304 0 : if( FD_UNLIKELY( rc ) ) return rc;
2305 0 : fd_rent_t rent_;
2306 0 : fd_rent_t const * rent = fd_sysvar_cache_rent_read( ctx->sysvar_cache, &rent_ );
2307 0 : if( FD_UNLIKELY( !rent ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2308 :
2309 0 : if( FD_UNLIKELY( fd_borrowed_account_get_lamports( &me ) <
2310 0 : fd_rent_exempt_minimum_balance( rent, fd_borrowed_account_get_data_len( &me ) ) ) )
2311 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
2312 :
2313 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L76
2314 0 : rc = fd_sysvar_instr_acct_check( ctx, 2, &fd_sysvar_clock_id );
2315 0 : if( FD_UNLIKELY( rc ) ) return rc;
2316 0 : fd_sol_sysvar_clock_t clock_;
2317 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2318 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2319 :
2320 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L78
2321 0 : rc = initialize_account( &me,
2322 0 : &instruction->inner.initialize_account,
2323 0 : signers,
2324 0 : clock,
2325 0 : ctx );
2326 :
2327 0 : break;
2328 0 : }
2329 :
2330 : /* Authorize
2331 : *
2332 : * Instruction:
2333 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L40
2334 : *
2335 : * Processor:
2336 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L86
2337 : *
2338 : * Notes:
2339 : * - Up to two signers: the vote authority and the authorized withdrawer.
2340 : */
2341 0 : case fd_vote_instruction_enum_authorize: {
2342 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L87
2343 0 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_clock_id );
2344 0 : if( FD_UNLIKELY( rc ) ) return rc;
2345 0 : fd_sol_sysvar_clock_t clock_;
2346 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2347 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2348 :
2349 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L89
2350 0 : fd_pubkey_t const * voter_pubkey = &instruction->inner.authorize.pubkey;
2351 0 : fd_vote_authorize_t vote_authorize = instruction->inner.authorize.vote_authorize;
2352 :
2353 0 : rc = authorize( &me, voter_pubkey, vote_authorize, signers, clock, ctx );
2354 :
2355 0 : break;
2356 0 : }
2357 :
2358 : /* AuthorizeWithSeed
2359 : *
2360 : * Instruction:
2361 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L117
2362 : *
2363 : * Processor:
2364 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L98
2365 : */
2366 0 : case fd_vote_instruction_enum_authorize_with_seed: {
2367 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L99
2368 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 3 ) ) {
2369 0 : rc = FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
2370 0 : break;
2371 0 : }
2372 :
2373 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L100
2374 0 : fd_vote_authorize_with_seed_args_t * args = &instruction->inner.authorize_with_seed;
2375 :
2376 0 : rc = process_authorize_with_seed_instruction( ctx,
2377 0 : &me,
2378 0 : &args->new_authority,
2379 0 : args->authorization_type,
2380 0 : &args->current_authority_derived_key_owner,
2381 0 : args->current_authority_derived_key_seed,
2382 0 : args->current_authority_derived_key_seed_len );
2383 :
2384 0 : break;
2385 0 : }
2386 :
2387 : /* AuthorizeCheckedWithSeed
2388 : *
2389 : * Instruction:
2390 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L131
2391 : *
2392 : * Processor:
2393 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L111
2394 : */
2395 0 : case fd_vote_instruction_enum_authorize_checked_with_seed: {
2396 0 : fd_vote_authorize_checked_with_seed_args_t const * args =
2397 0 : &instruction->inner.authorize_checked_with_seed;
2398 :
2399 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L112
2400 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 4 ) ) {
2401 0 : rc = FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
2402 0 : break;
2403 0 : }
2404 :
2405 : // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L99-L100
2406 0 : fd_pubkey_t const * new_authority = NULL;
2407 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 3UL, &new_authority );
2408 0 : if( FD_UNLIKELY( rc ) ) return rc;
2409 :
2410 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L116
2411 0 : if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( ctx->instr, 3, &rc ) ) ) {
2412 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/transaction-context/src/lib.rs#L789 */
2413 0 : if( FD_UNLIKELY( !!rc ) ) break;
2414 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L117
2415 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
2416 0 : break;
2417 0 : }
2418 :
2419 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L119
2420 0 : rc = process_authorize_with_seed_instruction( ctx,
2421 0 : &me,
2422 0 : new_authority,
2423 0 : args->authorization_type,
2424 0 : &args->current_authority_derived_key_owner,
2425 0 : args->current_authority_derived_key_seed,
2426 0 : args->current_authority_derived_key_seed_len );
2427 :
2428 0 : break;
2429 0 : }
2430 :
2431 : /* UpdateValidatorIdentity
2432 : *
2433 : * Instruction:
2434 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L65
2435 : *
2436 : * Processor:
2437 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L130
2438 : */
2439 0 : case fd_vote_instruction_enum_update_validator_identity: {
2440 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L131
2441 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 2 ) ) {
2442 0 : rc = FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
2443 0 : break;
2444 0 : }
2445 :
2446 : // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L118-L120
2447 0 : fd_pubkey_t const * node_pubkey = NULL;
2448 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 1UL, &node_pubkey );
2449 0 : if( FD_UNLIKELY( rc ) ) return rc;
2450 :
2451 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L135
2452 0 : rc = update_validator_identity( &me, node_pubkey, signers, ctx );
2453 :
2454 0 : break;
2455 0 : }
2456 :
2457 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L142
2458 0 : case fd_vote_instruction_enum_update_commission: {
2459 :
2460 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L149
2461 0 : fd_epoch_schedule_t epoch_schedule_;
2462 0 : fd_epoch_schedule_t const * epoch_schedule = fd_sysvar_cache_epoch_schedule_read( ctx->sysvar_cache, &epoch_schedule_ );
2463 0 : if( FD_UNLIKELY( !epoch_schedule ) ) {
2464 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2465 0 : }
2466 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L150
2467 :
2468 0 : fd_sol_sysvar_clock_t clock_;
2469 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2470 0 : if( FD_UNLIKELY( !clock ) ) {
2471 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2472 0 : }
2473 :
2474 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L145
2475 0 : rc = update_commission( &me,
2476 0 : instruction->inner.update_commission,
2477 0 : signers,
2478 0 : epoch_schedule,
2479 0 : clock,
2480 0 : ctx );
2481 :
2482 0 : break;
2483 0 : }
2484 :
2485 : /* Vote
2486 : *
2487 : * Instruction:
2488 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L49
2489 : */
2490 0 : case fd_vote_instruction_enum_vote:;
2491 : /* clang-format off */
2492 0 : __attribute__((fallthrough));
2493 : /* clang-format on */
2494 :
2495 : /* VoteSwitch
2496 : *
2497 : * Instruction:
2498 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L81
2499 : *
2500 : * Processor:
2501 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L154
2502 : */
2503 0 : case fd_vote_instruction_enum_vote_switch: {
2504 0 : if( FD_FEATURE_ACTIVE_BANK( ctx->txn_ctx->bank, deprecate_legacy_vote_ixs ) ) {
2505 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2506 0 : }
2507 :
2508 0 : fd_vote_t * vote;
2509 0 : if( instruction->discriminant == fd_vote_instruction_enum_vote ) {
2510 0 : vote = &instruction->inner.vote;
2511 0 : } else if( instruction->discriminant == fd_vote_instruction_enum_vote_switch ) {
2512 0 : vote = &instruction->inner.vote_switch.vote;
2513 0 : } else {
2514 0 : __builtin_unreachable();
2515 0 : }
2516 :
2517 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L155
2518 0 : int err;
2519 0 : err = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_slot_hashes_id );
2520 0 : if( FD_UNLIKELY( err ) ) return err;
2521 :
2522 0 : if( FD_UNLIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
2523 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2524 0 : }
2525 :
2526 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L157
2527 0 : err = fd_sysvar_instr_acct_check( ctx, 2, &fd_sysvar_clock_id );
2528 0 : if( FD_UNLIKELY( err ) ) return err;
2529 0 : fd_sol_sysvar_clock_t clock_;
2530 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2531 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2532 :
2533 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); /* guaranteed to succeed */
2534 0 : rc = process_vote_with_account( &me, slot_hashes, clock, vote, signers, ctx );
2535 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
2536 :
2537 0 : break;
2538 0 : }
2539 :
2540 : /* UpdateVoteState
2541 : *
2542 : * Instruction:
2543 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L100
2544 : */
2545 0 : case fd_vote_instruction_enum_update_vote_state:;
2546 : /* clang-format off */
2547 0 : __attribute__((fallthrough));
2548 : /* clang-format on */
2549 :
2550 : /* UpdateVoteStateSwitch
2551 : *
2552 : * Instruction:
2553 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L107
2554 : *
2555 : * Processor:
2556 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L169
2557 : */
2558 0 : case fd_vote_instruction_enum_update_vote_state_switch: {
2559 0 : if( FD_FEATURE_ACTIVE_BANK( ctx->txn_ctx->bank, deprecate_legacy_vote_ixs ) ) {
2560 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2561 0 : }
2562 :
2563 0 : fd_vote_state_update_t * vote_state_update;
2564 0 : switch( instruction->discriminant ) {
2565 0 : case fd_vote_instruction_enum_update_vote_state:
2566 0 : vote_state_update = &instruction->inner.update_vote_state;
2567 0 : break;
2568 0 : case fd_vote_instruction_enum_update_vote_state_switch:
2569 0 : vote_state_update = &instruction->inner.update_vote_state_switch.vote_state_update;
2570 0 : break;
2571 0 : default:
2572 0 : __builtin_unreachable();
2573 0 : }
2574 :
2575 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L171
2576 0 : if( FD_LIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
2577 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2578 0 : }
2579 :
2580 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L172
2581 0 : fd_sol_sysvar_clock_t clock_;
2582 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2583 0 : if( FD_UNLIKELY( !clock ) )
2584 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2585 :
2586 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L173
2587 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache );
2588 0 : rc = process_vote_state_update( &me, slot_hashes, clock, vote_state_update, signers, ctx );
2589 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
2590 :
2591 0 : break;
2592 0 : }
2593 :
2594 : /* CompactUpdateVoteState
2595 : *
2596 : * Instruction:
2597 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L139
2598 : *
2599 : * Notes:
2600 : * - Up to three signers: the vote authority, the authorized withdrawer, and the new authority.
2601 : * - Feature gated, but live on mainnet.
2602 : */
2603 0 : case fd_vote_instruction_enum_compact_update_vote_state:;
2604 0 : __attribute__((fallthrough));
2605 :
2606 : /* CompactUpdateVoteStateSwitch
2607 : *
2608 : * Instruction:
2609 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L146
2610 : *
2611 : * Processor:
2612 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L183
2613 : *
2614 : * Notes:
2615 : * - Up to three signers: the vote authority, the authorized withdrawer, and the new authority.
2616 : * - Feature gated, but live on mainnet.
2617 : */
2618 0 : case fd_vote_instruction_enum_compact_update_vote_state_switch: {
2619 : /* https://github.com/anza-xyz/agave/blob/dc4b9dcbbf859ff48f40d00db824bde063fdafcc/programs/vote/src/vote_processor.rs#L183-L191 */
2620 0 : if( FD_FEATURE_ACTIVE_BANK( ctx->txn_ctx->bank, deprecate_legacy_vote_ixs ) ) {
2621 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2622 0 : }
2623 :
2624 0 : fd_compact_vote_state_update_t * vote_state_update = NULL;
2625 0 : if( instruction->discriminant == fd_vote_instruction_enum_compact_update_vote_state ) {
2626 0 : vote_state_update = &instruction->inner.compact_update_vote_state;
2627 0 : } else if( instruction->discriminant ==
2628 0 : fd_vote_instruction_enum_compact_update_vote_state_switch ) {
2629 0 : vote_state_update =
2630 0 : &instruction->inner.compact_update_vote_state_switch.compact_vote_state_update;
2631 0 : }
2632 :
2633 0 : fd_vote_state_update_t vote_update;
2634 0 : fd_vote_state_update_new( &vote_update );
2635 0 : if( FD_UNLIKELY( !fd_vote_decode_compact_update( vote_state_update, &vote_update, ctx ) ) )
2636 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2637 :
2638 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L185
2639 0 : if( FD_LIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
2640 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2641 0 : }
2642 :
2643 0 : fd_sol_sysvar_clock_t clock_;
2644 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2645 0 : if( FD_UNLIKELY( !clock ) )
2646 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2647 :
2648 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L187
2649 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); /* guaranteed to succeed */
2650 0 : rc = process_vote_state_update( &me, slot_hashes, clock, &vote_update, signers, ctx );
2651 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
2652 :
2653 0 : break;
2654 0 : }
2655 :
2656 : /* TowerSync(Switch)
2657 : *
2658 : * Instruction:
2659 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L151-L157
2660 : *
2661 : * Processor:
2662 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L196-L215
2663 : */
2664 :
2665 0 : case fd_vote_instruction_enum_tower_sync:
2666 0 : case fd_vote_instruction_enum_tower_sync_switch: {
2667 0 : fd_tower_sync_t * tower_sync = (instruction->discriminant == fd_vote_instruction_enum_tower_sync)
2668 0 : ? &instruction->inner.tower_sync
2669 0 : : &instruction->inner.tower_sync_switch.tower_sync;
2670 :
2671 0 : if( FD_LIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
2672 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2673 0 : }
2674 :
2675 0 : fd_sol_sysvar_clock_t clock_;
2676 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2677 0 : if( FD_UNLIKELY( !clock ) ) {
2678 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2679 0 : }
2680 :
2681 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache );
2682 0 : FD_TEST( slot_hashes );
2683 0 : rc = process_tower_sync( &me, slot_hashes, clock, tower_sync, signers, ctx );
2684 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
2685 :
2686 0 : break;
2687 0 : }
2688 :
2689 : /* Withdraw
2690 : *
2691 : * Instruction:
2692 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L57
2693 : *
2694 : * Processor:
2695 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L216
2696 : */
2697 0 : case fd_vote_instruction_enum_withdraw: {
2698 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 2 ) ) {
2699 0 : rc = FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
2700 0 : break;
2701 0 : }
2702 0 : fd_rent_t rent_;
2703 0 : fd_rent_t const * rent_sysvar = fd_sysvar_cache_rent_read( ctx->sysvar_cache, &rent_ );
2704 0 : if( FD_UNLIKELY( !rent_sysvar ) )
2705 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2706 0 : fd_sol_sysvar_clock_t clock_;
2707 0 : fd_sol_sysvar_clock_t const * clock_sysvar = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2708 0 : if( FD_UNLIKELY( !clock_sysvar ) )
2709 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2710 :
2711 0 : rc = withdraw( ctx,
2712 0 : &me,
2713 0 : instruction->inner.withdraw,
2714 0 : 1UL,
2715 0 : signers,
2716 0 : rent_sysvar,
2717 0 : clock_sysvar );
2718 :
2719 0 : break;
2720 0 : }
2721 :
2722 : /* AuthorizeChecked
2723 : *
2724 : * Instruction:
2725 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L93
2726 : *
2727 : * Processor:
2728 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L234
2729 : *
2730 : * Notes:
2731 : * - Up to three signers: the vote authority, the authorized withdrawer, and the new authority.
2732 : * - Feature gated, but live on mainnet.
2733 : */
2734 0 : case fd_vote_instruction_enum_authorize_checked: {
2735 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 4 ) ) {
2736 0 : rc = FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
2737 0 : break;
2738 0 : }
2739 :
2740 : // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L243-L245
2741 0 : fd_pubkey_t const * voter_pubkey = NULL;
2742 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 3UL, &voter_pubkey );
2743 0 : if( FD_UNLIKELY( rc ) ) return rc;
2744 :
2745 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L239
2746 0 : if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( ctx->instr, 3, &rc ) ) ) {
2747 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/transaction-context/src/lib.rs#L789 */
2748 0 : if( FD_UNLIKELY( !!rc ) ) break;
2749 :
2750 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
2751 0 : break;
2752 0 : }
2753 :
2754 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L242
2755 0 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_clock_id );
2756 0 : if( FD_UNLIKELY( rc ) ) return rc;
2757 0 : fd_sol_sysvar_clock_t clock_;
2758 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2759 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2760 :
2761 0 : rc = authorize( &me,
2762 0 : voter_pubkey,
2763 0 : instruction->inner.authorize_checked,
2764 0 : signers,
2765 0 : clock,
2766 0 : ctx );
2767 0 : break;
2768 0 : }
2769 :
2770 0 : default:
2771 0 : FD_LOG_ERR(( "unsupported vote instruction: %u", instruction->discriminant ));
2772 0 : }
2773 :
2774 0 : return rc;
2775 0 : }
2776 :
2777 : /**********************************************************************/
2778 : /* Public API */
2779 : /**********************************************************************/
2780 :
2781 : uint
2782 0 : fd_vote_state_versions_is_correct_and_initialized( fd_txn_account_t * vote_account ) {
2783 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L885
2784 0 : uint data_len_check = fd_txn_account_get_data_len( vote_account )==FD_VOTE_STATE_V3_SZ;
2785 0 : uchar test_data[DEFAULT_PRIOR_VOTERS_OFFSET] = {0};
2786 0 : uint data_check = memcmp((
2787 0 : fd_txn_account_get_data( vote_account )+VERSION_OFFSET), test_data, DEFAULT_PRIOR_VOTERS_OFFSET)!=0;
2788 0 : if (data_check && data_len_check) {
2789 0 : return 1;
2790 0 : }
2791 :
2792 : // VoteState1_14_11::is_correct_size_and_initialized
2793 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L58
2794 0 : data_len_check = fd_txn_account_get_data_len( vote_account )==FD_VOTE_STATE_V2_SZ;
2795 0 : uchar test_data_1_14_11[DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11] = {0};
2796 0 : data_check = memcmp( (
2797 0 : fd_txn_account_get_data( vote_account )+VERSION_OFFSET), test_data_1_14_11, DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11)!=0;
2798 0 : return data_check && data_len_check;
2799 0 : }
2800 :
2801 : int
2802 : fd_vote_get_state( fd_txn_account_t const * self,
2803 : fd_spad_t * spad,
2804 0 : fd_vote_state_versioned_t * * versioned /* out */ ) {
2805 0 : int err = 0;
2806 0 : *versioned = get_state( self, spad, &err );
2807 0 : return err;
2808 0 : }
2809 :
2810 : void
2811 : fd_vote_convert_to_current( fd_vote_state_versioned_t * self,
2812 0 : fd_spad_t * spad ) {
2813 0 : convert_to_current( self, spad );
2814 0 : }
2815 :
2816 : void
2817 : fd_vote_store_account( fd_txn_account_t * vote_account,
2818 0 : fd_bank_t * bank ) {
2819 :
2820 0 : fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( bank );
2821 :
2822 0 : if( fd_txn_account_get_lamports( vote_account )==0UL ) {
2823 0 : fd_vote_states_remove( vote_states, vote_account->pubkey );
2824 0 : fd_bank_vote_states_end_locking_modify( bank );
2825 0 : return;
2826 0 : }
2827 :
2828 0 : if( !fd_vote_state_versions_is_correct_and_initialized( vote_account ) ) {
2829 0 : fd_vote_states_remove( vote_states, vote_account->pubkey );
2830 0 : fd_bank_vote_states_end_locking_modify( bank );
2831 0 : return;
2832 0 : }
2833 :
2834 0 : fd_vote_states_update_from_account( vote_states,
2835 0 : vote_account->pubkey,
2836 0 : fd_txn_account_get_data( vote_account ),
2837 0 : fd_txn_account_get_data_len( vote_account ) );
2838 0 : fd_bank_vote_states_end_locking_modify( bank );
2839 0 : }
|