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