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