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