Line data Source code
1 : #include "fd_vote_program.h"
2 : #include "../fd_runtime.h"
3 : #include "../fd_borrowed_account.h"
4 : #include "../fd_executor.h"
5 : #include "../fd_pubkey_utils.h"
6 : #include "../sysvar/fd_sysvar_rent.h"
7 : #include "../sysvar/fd_sysvar.h"
8 : #include "../fd_system_ids.h"
9 : #include "vote/fd_authorized_voters.h"
10 : #include "vote/fd_vote_common.h"
11 : #include "vote/fd_vote_lockout.h"
12 : #include "vote/fd_vote_state_versioned.h"
13 : #include "vote/fd_vote_state_v3.h"
14 : #include "vote/fd_vote_state_v4.h"
15 : #include "../../../ballet/bls/fd_bls12_381.h"
16 :
17 : #include <limits.h>
18 : #include <math.h>
19 : #include <stdio.h>
20 : #include <string.h>
21 :
22 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L36
23 0 : #define INITIAL_LOCKOUT 2UL
24 :
25 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L51
26 : #define VOTE_CREDITS_MAXIMUM_PER_SLOT_OLD 8
27 :
28 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/clock.rs#L147
29 : #define SLOT_DEFAULT 0UL
30 :
31 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/clock.rs#L147
32 : #define SLOT_MAX ULONG_MAX
33 :
34 : #define ACCOUNTS_MAX 4 /* Vote instructions take in at most 4 accounts */
35 :
36 : #define DEFAULT_COMPUTE_UNITS 2100UL
37 : #define COMPUTE_UNITS_POP 34500UL
38 :
39 : /**********************************************************************/
40 : /* VoteStateHandler */
41 : /**********************************************************************/
42 :
43 : /* This is a temporary method in Agave (until the vote state v4 feature
44 : is cleaned up) to check the vote state and, in some cases, check
45 : if the vote account is uninitialized or not. Initializes a v3 or v4
46 : vote account depending on the target version.
47 : https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L45-L77 */
48 : static int
49 : get_vote_state_handler_checked( fd_borrowed_account_t const * vote_account,
50 : int target_version,
51 : uchar check_initialized,
52 : uchar * vote_state_mem,
53 : uchar * authorized_voters_mem,
54 0 : uchar * landed_votes_mem ) {
55 0 : int rc;
56 0 : switch( target_version ) {
57 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L50-L62 */
58 0 : case VOTE_STATE_TARGET_VERSION_V3: {
59 0 : rc = fd_vote_state_v3_deserialize( vote_account, vote_state_mem, authorized_voters_mem, landed_votes_mem );
60 0 : if( FD_UNLIKELY( rc ) ) return rc;
61 :
62 0 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem;
63 0 : if( FD_UNLIKELY( check_initialized && fd_vsv_is_uninitialized( versioned ) ) ) {
64 0 : return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT;
65 0 : }
66 :
67 0 : return FD_EXECUTOR_INSTR_SUCCESS;
68 0 : }
69 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L63-L75 */
70 0 : case VOTE_STATE_TARGET_VERSION_V4: {
71 0 : rc = fd_vsv_deserialize( vote_account, vote_state_mem );
72 0 : if( FD_UNLIKELY( rc ) ) return rc;
73 :
74 0 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem;
75 0 : if( FD_UNLIKELY( fd_vsv_is_uninitialized( versioned ) ) ) {
76 0 : return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT;
77 0 : }
78 :
79 0 : rc = fd_vsv_try_convert_to_v4( versioned, vote_account->pubkey, landed_votes_mem );
80 0 : if( FD_UNLIKELY( rc ) ) return rc;
81 :
82 0 : return FD_EXECUTOR_INSTR_SUCCESS;
83 0 : }
84 0 : default:
85 0 : FD_LOG_CRIT(( "unsupported version: %d", target_version ));
86 0 : }
87 0 : }
88 :
89 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L888-L902 */
90 : static int
91 : check_vote_account_length( fd_borrowed_account_t const * vote_account,
92 9 : int target_version ) {
93 9 : ulong length = fd_borrowed_account_get_data_len( vote_account );
94 9 : ulong expected;
95 9 : switch( target_version ) {
96 3 : case VOTE_STATE_TARGET_VERSION_V3:
97 3 : expected = FD_VOTE_STATE_V3_SZ;
98 3 : break;
99 6 : case VOTE_STATE_TARGET_VERSION_V4:
100 6 : expected = FD_VOTE_STATE_V4_SZ;
101 6 : break;
102 0 : default:
103 0 : FD_LOG_CRIT(( "unsupported version: %d", target_version ));
104 9 : }
105 9 : if( FD_UNLIKELY( length!=expected ) ) {
106 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
107 0 : }
108 9 : return FD_EXECUTOR_INSTR_SUCCESS;
109 9 : }
110 :
111 : /* The versioned parameter must point to a buffer that is aligned to
112 : FD_VOTE_STATE_VERSIONED_ALIGN and is at least
113 : FD_VOTE_STATE_VERSIONED_FOOTPRINT bytes in size.
114 : https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L855-L870 */
115 : static int
116 : init_vote_account_state( fd_exec_instr_ctx_t * ctx,
117 : fd_borrowed_account_t * vote_account,
118 : fd_vote_state_versioned_t * versioned,
119 : int target_version,
120 : fd_vote_init_t * vote_init,
121 3 : fd_sol_sysvar_clock_t const * clock ) {
122 : /* Reset the object */
123 3 : fd_vote_state_versioned_new( versioned );
124 :
125 3 : switch( target_version ) {
126 3 : case VOTE_STATE_TARGET_VERSION_V3:
127 3 : fd_vote_program_v3_create_new(
128 3 : vote_init,
129 3 : clock,
130 3 : ctx->runtime->vote_program.init_account.authorized_voters_mem,
131 3 : versioned
132 3 : );
133 3 : return fd_vote_state_v3_set_vote_account_state(
134 3 : ctx,
135 3 : vote_account,
136 3 : versioned,
137 3 : ctx->runtime->vote_program.init_account.vote_lockout_mem
138 3 : );
139 0 : case VOTE_STATE_TARGET_VERSION_V4:
140 0 : fd_vote_state_v4_create_new_with_defaults(
141 0 : vote_account->pubkey,
142 0 : vote_init,
143 0 : clock,
144 0 : ctx->runtime->vote_program.init_account.authorized_voters_mem,
145 0 : versioned
146 0 : );
147 0 : return fd_vote_state_v4_set_vote_account_state( ctx, vote_account, versioned );
148 0 : default:
149 0 : FD_LOG_CRIT(( "unsupported version: %d", target_version ));
150 3 : }
151 3 : }
152 :
153 : /* The versioned parameter must point to a buffer that is aligned to
154 : https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/handler.rs#L965 */
155 : static int
156 : init_vote_account_state_v2( fd_exec_instr_ctx_t * ctx,
157 : fd_borrowed_account_t * vote_account,
158 : fd_vote_state_versioned_t * versioned,
159 : int target_version,
160 : fd_vote_init_v2_t * vote_init_v2,
161 3 : fd_sol_sysvar_clock_t const * clock ) {
162 : /* Reset the object */
163 3 : fd_vote_state_versioned_new( versioned );
164 :
165 3 : switch( target_version ) {
166 0 : case VOTE_STATE_TARGET_VERSION_V3:
167 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
168 :
169 3 : case VOTE_STATE_TARGET_VERSION_V4:
170 3 : fd_vote_state_v4_create_new(
171 3 : vote_init_v2,
172 3 : clock,
173 3 : ctx->runtime->vote_program.init_account.authorized_voters_mem,
174 3 : versioned
175 3 : );
176 3 : return fd_vote_state_v4_set_vote_account_state( ctx, vote_account, versioned );
177 0 : default:
178 0 : FD_LOG_CRIT(( "unsupported version: %d", target_version ));
179 3 : }
180 3 : }
181 :
182 : /**********************************************************************/
183 : /* mod vote_state */
184 : /**********************************************************************/
185 :
186 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L82-L324 */
187 : static int
188 : check_and_filter_proposed_vote_state( fd_exec_instr_ctx_t * ctx,
189 : fd_vote_state_versioned_t * versioned,
190 : fd_vote_lockout_t * proposed_lockouts,
191 : uchar * proposed_has_root,
192 : ulong * proposed_root,
193 : fd_hash_t const * proposed_hash,
194 0 : fd_slot_hash_t const * slot_hashes /* deque */ ) {
195 0 : fd_landed_vote_t const * votes = fd_vsv_get_votes( versioned );
196 0 : ulong const * root_slot = fd_vsv_get_root_slot( versioned );
197 0 : uchar has_root_slot = !!(root_slot!=NULL);
198 :
199 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L208
200 0 : if( FD_UNLIKELY( deq_fd_vote_lockout_t_empty( proposed_lockouts ) ) ) {
201 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_EMPTY_SLOTS;
202 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
203 0 : }
204 0 : fd_landed_vote_t const * last_vote = NULL;
205 0 : if( !deq_fd_landed_vote_t_empty( votes ) ) {
206 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L212
207 0 : last_vote = deq_fd_landed_vote_t_peek_tail_const( votes );
208 0 : }
209 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L218
210 0 : if( FD_LIKELY( last_vote ) ) {
211 0 : if( FD_UNLIKELY( deq_fd_vote_lockout_t_peek_tail_const( proposed_lockouts )->slot <=
212 0 : last_vote->lockout.slot ) ) {
213 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_VOTE_TOO_OLD;
214 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
215 0 : }
216 0 : }
217 :
218 : /* must be nonempty, checked above */
219 0 : ulong last_vote_state_update_slot = deq_fd_vote_lockout_t_peek_tail_const( proposed_lockouts )->slot;
220 :
221 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L224
222 0 : if( FD_UNLIKELY( deq_fd_slot_hash_t_empty( slot_hashes ) ) ) {
223 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
224 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
225 0 : }
226 :
227 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L227
228 0 : ulong earliest_slot_hash_in_history = deq_fd_slot_hash_t_peek_tail_const( slot_hashes )->slot;
229 :
230 : /* Check if the proposed vote is too old to be in the SlotHash history */
231 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L230
232 0 : if( FD_UNLIKELY( last_vote_state_update_slot < earliest_slot_hash_in_history ) ) {
233 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_VOTE_TOO_OLD;
234 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
235 0 : }
236 :
237 : /* Check if the proposed root is too old */
238 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L237
239 0 : if( *proposed_has_root ) {
240 0 : ulong const proposed_root_ = *proposed_root;
241 : /* If the new proposed root `R` is less than the earliest slot hash in the history
242 : such that we cannot verify whether the slot was actually was on this fork, set
243 : the root to the latest vote in the current vote that's less than R. */
244 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L242
245 0 : if( proposed_root_ < earliest_slot_hash_in_history ) {
246 0 : *proposed_has_root = has_root_slot;
247 0 : if( has_root_slot ) {
248 0 : *proposed_root = *root_slot;
249 0 : }
250 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init_rev( votes );
251 0 : !deq_fd_landed_vote_t_iter_done_rev( votes, iter );
252 0 : iter = deq_fd_landed_vote_t_iter_prev( votes, iter ) ) {
253 : /* Ensure we're iterating from biggest to smallest vote in the
254 : current vote state */
255 0 : fd_landed_vote_t const * vote = deq_fd_landed_vote_t_iter_ele_const( votes, iter );
256 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L248
257 0 : if( vote->lockout.slot <= proposed_root_ ) {
258 0 : *proposed_has_root = 1;
259 0 : *proposed_root = vote->lockout.slot;
260 0 : break;
261 0 : }
262 0 : }
263 0 : }
264 0 : }
265 :
266 : /* Index into the new proposed vote state's slots, starting with the root if it exists then
267 : we use this mutable root to fold checking the root slot into the below loop for performance */
268 0 : int has_root_to_check = *proposed_has_root;
269 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L259
270 0 : ulong root_to_check = *proposed_root;
271 0 : ulong proposed_lockouts_index = 0UL;
272 0 : ulong lockouts_len = deq_fd_vote_lockout_t_cnt( proposed_lockouts );
273 :
274 : /* Index into the slot_hashes, starting at the oldest known slot hash */
275 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L264
276 0 : ulong slot_hashes_index = deq_fd_slot_hash_t_cnt( slot_hashes );
277 :
278 : /* proposed_lockouts_indexes_to_filter's size is bounded by the length
279 : of the proposed lockouts provided in the instruction data, which is
280 : capped at roughly 10KB / sizeof(ulong). */
281 0 : ulong proposed_lockouts_indexes_to_filter[ FD_INSTR_DATA_MAX/sizeof(ulong) ];
282 0 : ulong filter_index = 0UL;
283 :
284 : /* Note:
285 :
286 : 1) `vote_state_update.lockouts` is sorted from oldest/smallest vote to newest/largest
287 : vote, due to the way votes are applied to the vote state (newest votes
288 : pushed to the back).
289 :
290 : 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
291 : the oldest/smallest vote.
292 :
293 : Unlike for vote updates, vote state updates here can't only check votes older than the last vote
294 : because have to ensure that every slot is actually part of the history, not just the most
295 : recent ones */
296 :
297 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L279
298 0 : while( proposed_lockouts_index < lockouts_len && slot_hashes_index > 0 ) {
299 0 : ulong proposed_vote_slot =
300 0 : fd_ulong_if( has_root_to_check,
301 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L281
302 0 : root_to_check,
303 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L283
304 0 : deq_fd_vote_lockout_t_peek_index_const( proposed_lockouts,
305 0 : proposed_lockouts_index )
306 0 : ->slot );
307 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L285
308 0 : if( !has_root_to_check && proposed_lockouts_index > 0UL &&
309 0 : proposed_vote_slot <=
310 0 : deq_fd_vote_lockout_t_peek_index_const(
311 0 : proposed_lockouts,
312 0 : fd_ulong_checked_sub_expect(
313 0 : proposed_lockouts_index,
314 0 : 1,
315 0 : "`proposed_lockouts_index` is positive when checking `SlotsNotOrdered`" ) )
316 0 : ->slot ) {
317 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L293
318 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_NOT_ORDERED;
319 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
320 0 : }
321 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L295
322 0 : ulong ancestor_slot =
323 0 : deq_fd_slot_hash_t_peek_index_const(
324 0 : slot_hashes,
325 0 : fd_ulong_checked_sub_expect(
326 0 : slot_hashes_index,
327 0 : 1UL,
328 0 : "`slot_hashes_index` is positive when computing `ancestor_slot`" ) )
329 0 : ->slot;
330 : /* Find if this slot in the proposed vote state exists in the SlotHashes history
331 : to confirm if it was a valid ancestor on this fork */
332 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L303
333 0 : if( proposed_vote_slot < ancestor_slot ) {
334 0 : if( slot_hashes_index == deq_fd_slot_hash_t_cnt( slot_hashes ) ) {
335 : /* The vote slot does not exist in the SlotHashes history because it's too old,
336 : i.e. older than the oldest slot in the history. */
337 0 : if( proposed_vote_slot >= earliest_slot_hash_in_history ) {
338 0 : ctx->txn_out->err.custom_err = 0;
339 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
340 0 : }
341 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L310
342 0 : if( !fd_vote_contains_slot( votes, proposed_vote_slot ) && !has_root_to_check ) {
343 : /* If the vote slot is both:
344 : 1) Too old
345 : 2) Doesn't already exist in vote state
346 : Then filter it out */
347 0 : proposed_lockouts_indexes_to_filter[filter_index++] = proposed_lockouts_index; }
348 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L318
349 0 : if( has_root_to_check ) {
350 0 : ulong new_proposed_root = root_to_check;
351 : /* 1. Because `root_to_check.is_some()`, then we know that
352 : we haven't checked the root yet in this loop, so
353 : `proposed_vote_slot` == `new_proposed_root` == `vote_state_update.root` */
354 0 : FD_TEST( new_proposed_root == proposed_vote_slot );
355 : /* 2. We know from the assert earlier in the function that
356 : `proposed_vote_slot < earliest_slot_hash_in_history`,
357 : so from 1. we know that `new_proposed_root < earliest_slot_hash_in_history` */
358 0 : if( new_proposed_root >= earliest_slot_hash_in_history ) {
359 0 : ctx->txn_out->err.custom_err = 0;
360 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
361 0 : }
362 :
363 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L329
364 0 : has_root_to_check = 0;
365 0 : root_to_check = ULONG_MAX;
366 0 : } else {
367 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L331
368 0 : proposed_lockouts_index = fd_ulong_checked_add_expect(
369 0 : proposed_lockouts_index,
370 0 : 1,
371 0 : "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` when "
372 0 : "`proposed_vote_slot` is too old to be in SlotHashes history" );
373 0 : }
374 0 : continue;
375 0 : } else {
376 : /* If the vote slot is new enough to be in the slot history,
377 : but is not part of the slot history, then it must belong to another fork,
378 : which means this vote state update is invalid. */
379 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L340
380 0 : if( has_root_to_check ) {
381 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_ROOT_ON_DIFFERENT_FORK;
382 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
383 0 : } else {
384 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
385 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
386 0 : }
387 0 : }
388 0 : } else if( proposed_vote_slot > ancestor_slot ) {
389 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L347
390 :
391 : /* Decrement `slot_hashes_index` to find newer slots in the SlotHashes history */
392 0 : slot_hashes_index = fd_ulong_checked_sub_expect(
393 0 : slot_hashes_index,
394 0 : 1,
395 0 : "`slot_hashes_index` is positive when finding newer slots in SlotHashes history" );
396 0 : continue;
397 0 : } else {
398 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L354
399 :
400 : /* Once the slot in `vote_state_update.lockouts` is found, bump to the next slot
401 : in `vote_state_update.lockouts` and continue. If we were checking the root,
402 : start checking the vote state instead. */
403 0 : if( has_root_to_check ) {
404 0 : has_root_to_check = 0;
405 0 : root_to_check = ULONG_MAX;
406 0 : } else {
407 0 : proposed_lockouts_index = fd_ulong_checked_add_expect(
408 0 : proposed_lockouts_index,
409 0 : 1,
410 0 : "`proposed_lockouts_index` is bounded by `MAX_LOCKOUT_HISTORY` "
411 0 : "when match is found in SlotHashes history" );
412 0 : slot_hashes_index = fd_ulong_checked_sub_expect(
413 0 : slot_hashes_index,
414 0 : 1,
415 0 : "`slot_hashes_index` is positive when match is found in SlotHashes history" );
416 0 : }
417 0 : }
418 0 : }
419 :
420 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L372
421 0 : if( proposed_lockouts_index != deq_fd_vote_lockout_t_cnt( proposed_lockouts ) ) {
422 : /* The last vote slot in the update did not exist in SlotHashes */
423 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
424 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
425 0 : }
426 :
427 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L401
428 0 : if( memcmp( &deq_fd_slot_hash_t_peek_index_const( slot_hashes, slot_hashes_index )->hash,
429 0 : proposed_hash,
430 0 : sizeof( fd_hash_t ) ) != 0 ) {
431 : /* This means the newest vote in the slot has a match that
432 : doesn't match the expected hash for that slot on this fork */
433 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_HASH_MISMATCH;
434 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
435 0 : }
436 :
437 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L418
438 : /* Filter out the irrelevant votes */
439 0 : proposed_lockouts_index = 0UL;
440 0 : ulong filter_votes_index = deq_fd_vote_lockout_t_cnt( proposed_lockouts );
441 :
442 : /* We need to iterate backwards here because proposed_lockouts_indexes_to_filter[ i ] is a
443 : strictly increasing value. Forward iterating can lead to the proposed lockout indicies to get
444 : shifted leading to popping the wrong proposed lockouts or out of bounds accessing. We need
445 : to be sure of handling underflow in this case. */
446 :
447 0 : for( ulong i=filter_index; i>0UL && filter_votes_index>0UL; i-- ) {
448 0 : proposed_lockouts_index = i - 1UL;
449 0 : if( FD_UNLIKELY( proposed_lockouts_indexes_to_filter[ proposed_lockouts_index ]>=filter_votes_index ) ) {
450 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
451 0 : }
452 :
453 0 : deq_fd_vote_lockout_t_pop_idx_tail( proposed_lockouts, proposed_lockouts_indexes_to_filter[ proposed_lockouts_index ] );
454 0 : filter_votes_index--;
455 0 : }
456 :
457 0 : return FD_EXECUTOR_INSTR_SUCCESS;
458 0 : }
459 :
460 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L440
461 : static int
462 : check_slots_are_valid( fd_exec_instr_ctx_t * ctx,
463 : fd_vote_state_versioned_t * versioned,
464 : ulong const * vote_slots,
465 : fd_hash_t const * vote_hash,
466 0 : fd_slot_hash_t const * slot_hashes /* deque */ ) {
467 0 : ulong i = 0;
468 0 : ulong j = deq_fd_slot_hash_t_cnt( slot_hashes );
469 0 : ulong vote_slots_len = deq_ulong_cnt( vote_slots );
470 :
471 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L462
472 0 : while( i < vote_slots_len && j > 0 ) {
473 0 : ulong const * last_voted_slot_ = fd_vsv_get_last_voted_slot( versioned );
474 0 : if( FD_UNLIKELY( last_voted_slot_ &&
475 0 : *deq_ulong_peek_index_const( vote_slots, i ) <= *last_voted_slot_ ) ) {
476 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L469
477 0 : i = fd_ulong_checked_add_expect(
478 0 : i, 1, "`i` is bounded by `MAX_LOCKOUT_HISTORY` when finding larger slots" );
479 0 : continue;
480 0 : }
481 :
482 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L476
483 0 : if( FD_UNLIKELY(
484 0 : *deq_ulong_peek_index_const( vote_slots, i ) !=
485 0 : deq_fd_slot_hash_t_peek_index_const( slot_hashes,
486 0 : fd_ulong_checked_sub_expect( j, 1, "`j` is positive" ) )
487 0 : ->slot ) ) {
488 0 : j = fd_ulong_checked_sub_expect( j, 1, "`j` is positive when finding newer slots" );
489 0 : continue;
490 0 : }
491 :
492 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L486
493 0 : i = fd_ulong_checked_add_expect(
494 0 : i, 1, "`i` is bounded by `MAX_LOCKOUT_HISTORY` when hash is found" );
495 0 : j = fd_ulong_checked_sub_expect( j, 1, "`j` is positive when hash is found" );
496 0 : }
497 :
498 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L494
499 0 : if( FD_UNLIKELY( j == deq_fd_slot_hash_t_cnt( slot_hashes ) ) ) {
500 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_VOTE_TOO_OLD;
501 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
502 0 : }
503 0 : if( FD_UNLIKELY( i != vote_slots_len ) ) {
504 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_MISMATCH;
505 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
506 0 : }
507 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L514
508 0 : if( FD_UNLIKELY( 0 != memcmp( &deq_fd_slot_hash_t_peek_index_const( slot_hashes, j )->hash,
509 0 : vote_hash,
510 0 : 32UL ) ) ) {
511 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_HASH_MISMATCH;
512 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
513 0 : }
514 0 : return 0;
515 0 : }
516 :
517 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L565
518 : static int
519 : process_new_vote_state( fd_exec_instr_ctx_t * ctx,
520 : fd_vote_state_versioned_t * versioned,
521 : fd_landed_vote_t * new_state,
522 : int has_new_root,
523 : ulong new_root,
524 : int has_timestamp,
525 : long timestamp,
526 : ulong epoch,
527 0 : ulong current_slot ) {
528 0 : int rc;
529 0 : fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( versioned );
530 0 : ulong const * root_slot = fd_vsv_get_root_slot( versioned );
531 0 : uchar has_root_slot = !!(root_slot!=NULL);
532 :
533 0 : FD_TEST( !deq_fd_landed_vote_t_empty( new_state ) );
534 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L575
535 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( new_state ) > MAX_LOCKOUT_HISTORY ) ) {
536 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_TOO_MANY_VOTES;
537 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
538 0 : };
539 :
540 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L579
541 0 : if( FD_UNLIKELY( has_new_root && has_root_slot ) ) {
542 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L581
543 0 : if( FD_UNLIKELY( new_root<*root_slot ) ) {
544 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_ROOT_ROLL_BACK;
545 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
546 0 : }
547 0 : } else if( FD_UNLIKELY( !has_new_root && has_root_slot ) ) {
548 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L586
549 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_ROOT_ROLL_BACK;
550 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
551 0 : } else {
552 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L588
553 : /* no-op */
554 0 : }
555 :
556 0 : fd_landed_vote_t * previous_vote = NULL;
557 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( new_state );
558 0 : !deq_fd_landed_vote_t_iter_done( new_state, iter );
559 0 : iter = deq_fd_landed_vote_t_iter_next( new_state, iter ) ) {
560 0 : fd_landed_vote_t * vote = deq_fd_landed_vote_t_iter_ele( new_state, iter );
561 0 : if( FD_LIKELY( vote->lockout.confirmation_count == 0 ) ) {
562 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_ZERO_CONFIRMATIONS;
563 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
564 0 : } else if( FD_UNLIKELY( vote->lockout.confirmation_count > MAX_LOCKOUT_HISTORY ) ) {
565 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_CONFIRMATION_TOO_LARGE;
566 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
567 0 : } else if( FD_LIKELY( has_new_root ) ) {
568 0 : if( FD_UNLIKELY( vote->lockout.slot <= new_root && new_root != SLOT_DEFAULT ) ) {
569 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOT_SMALLER_THAN_ROOT;
570 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
571 0 : }
572 0 : }
573 :
574 0 : if( FD_LIKELY( previous_vote ) ) {
575 0 : if( FD_UNLIKELY( previous_vote->lockout.slot >= vote->lockout.slot ) ) {
576 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_SLOTS_NOT_ORDERED;
577 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
578 0 : } else if( FD_UNLIKELY( previous_vote->lockout.confirmation_count <=
579 0 : vote->lockout.confirmation_count ) ) {
580 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_CONFIRMATIONS_NOT_ORDERED;
581 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
582 0 : } else if( FD_UNLIKELY( vote->lockout.slot >
583 0 : fd_vote_lockout_last_locked_out_slot( &previous_vote->lockout ) ) ) {
584 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_NEW_VOTE_STATE_LOCKOUT_MISMATCH;
585 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
586 0 : }
587 0 : }
588 0 : previous_vote = vote;
589 0 : }
590 :
591 0 : ulong current_vote_state_index = 0;
592 0 : ulong new_vote_state_index = 0;
593 :
594 : /* Accumulate credits earned by newly rooted slots. The behavior changes with
595 : timely_vote_credits: prior to this feature, there was a bug that counted a new root slot as 1
596 : credit even if it had never been voted on. timely_vote_credits fixes this bug by only awarding
597 : credits for slots actually voted on and finalized. */
598 :
599 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L635
600 :
601 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L641
602 0 : ulong earned_credits = 0;
603 :
604 0 : if( FD_LIKELY( has_new_root ) ) {
605 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( votes );
606 0 : !deq_fd_landed_vote_t_iter_done( votes, iter );
607 0 : iter = deq_fd_landed_vote_t_iter_next( votes, iter ) ) {
608 0 : fd_landed_vote_t const * current_vote = deq_fd_landed_vote_t_iter_ele_const( votes, iter );
609 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L647
610 0 : if( FD_UNLIKELY( current_vote->lockout.slot <= new_root ) ) {
611 : // this is safe because we're inside if has_new_root
612 0 : earned_credits = fd_ulong_checked_add_expect(
613 0 : fd_vote_credits_for_vote_at_index(
614 0 : votes,
615 0 : current_vote_state_index
616 0 : ),
617 0 : earned_credits,
618 0 : "`earned_credits` does not overflow" );
619 0 : current_vote_state_index = fd_ulong_checked_add_expect(
620 0 : current_vote_state_index,
621 0 : 1,
622 0 : "`current_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` "
623 0 : "when processing new root" );
624 0 : continue;
625 0 : }
626 0 : break;
627 0 : }
628 0 : }
629 :
630 : // For any slots newly added to the new vote state, the vote latency of that slot is not provided by the
631 : // vote instruction contents, but instead is computed from the actual latency of the vote
632 : // instruction. This prevents other validators from manipulating their own vote latencies within their vote states
633 : // and forcing the rest of the cluster to accept these possibly fraudulent latency values. If the
634 : // timly_vote_credits feature is not enabled then vote latency is set to 0 for new votes.
635 : //
636 : // For any slot that is in both the new state and the current state, the vote latency of the new state is taken
637 : // from the current state.
638 : //
639 : // Thus vote latencies are set here for any newly vote-on slots when a vote instruction is received.
640 : // They are copied into the new vote state after every vote for already voted-on slots.
641 : // And when voted-on slots are rooted, the vote latencies stored in the vote state of all the rooted slots is used
642 : // to compute credits earned.
643 : // All validators compute the same vote latencies because all process the same vote instruction at the
644 : // same slot, and the only time vote latencies are ever computed is at the time that their slot is first voted on;
645 : // after that, the latencies are retained unaltered until the slot is rooted.
646 :
647 : // All the votes in our current vote state that are missing from the new vote state
648 : // must have been expired by later votes. Check that the lockouts match this assumption.
649 :
650 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L686
651 0 : while( current_vote_state_index < deq_fd_landed_vote_t_cnt( votes ) &&
652 0 : new_vote_state_index < deq_fd_landed_vote_t_cnt( new_state ) ) {
653 0 : fd_landed_vote_t const * current_vote = deq_fd_landed_vote_t_peek_index_const( votes, current_vote_state_index );
654 :
655 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L690
656 0 : fd_landed_vote_t * new_vote =
657 0 : deq_fd_landed_vote_t_peek_index( new_state, new_vote_state_index );
658 :
659 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L696
660 0 : if( FD_LIKELY( current_vote->lockout.slot < new_vote->lockout.slot ) ) {
661 : /* The agave implementation of calculating the last locked out
662 : slot does not calculate a min between the current vote's
663 : confirmation count and max lockout history. The reason we do
664 : this is to make sure that the fuzzers continue working:
665 : the max lockout history can not be > MAX_LOCKOUT_HISTORY. */
666 0 : ulong confirmation_count = fd_ulong_min( current_vote->lockout.confirmation_count, MAX_LOCKOUT_HISTORY );
667 0 : ulong last_locked_out_slot = fd_ulong_sat_add( current_vote->lockout.slot,
668 0 : (ulong)pow( INITIAL_LOCKOUT, (double)confirmation_count ) );
669 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L697
670 0 : if( last_locked_out_slot >= new_vote->lockout.slot ) {
671 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L698
672 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_LOCKOUT_CONFLICT;
673 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
674 0 : }
675 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L700
676 0 : current_vote_state_index =
677 0 : fd_ulong_checked_add_expect( current_vote_state_index,
678 0 : 1,
679 0 : "`current_vote_state_index` is bounded by "
680 0 : "`MAX_LOCKOUT_HISTORY` when slot is less than proposed" );
681 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L704
682 0 : } else if( FD_UNLIKELY( current_vote->lockout.slot == new_vote->lockout.slot ) ) {
683 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L707
684 0 : if( new_vote->lockout.confirmation_count < current_vote->lockout.confirmation_count ) {
685 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_CONFIRMATION_ROLL_BACK;
686 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
687 0 : }
688 :
689 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L712
690 0 : new_vote->latency = deq_fd_landed_vote_t_peek_index_const( votes, current_vote_state_index )->latency;
691 :
692 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L714
693 0 : current_vote_state_index =
694 0 : fd_ulong_checked_add_expect( current_vote_state_index,
695 0 : 1,
696 0 : "`current_vote_state_index` is bounded by "
697 0 : "`MAX_LOCKOUT_HISTORY` when slot is equal to proposed" );
698 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L717
699 0 : new_vote_state_index =
700 0 : fd_ulong_checked_add_expect( new_vote_state_index,
701 0 : 1,
702 0 : "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` "
703 0 : "when slot is equal to proposed" );
704 0 : } else {
705 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L722
706 0 : new_vote_state_index =
707 0 : fd_ulong_checked_add_expect( new_vote_state_index,
708 0 : 1,
709 0 : "`new_vote_state_index` is bounded by `MAX_LOCKOUT_HISTORY` "
710 0 : "when slot is greater than proposed" );
711 0 : }
712 0 : }
713 :
714 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L737
715 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( new_state );
716 0 : !deq_fd_landed_vote_t_iter_done( new_state, iter );
717 0 : iter = deq_fd_landed_vote_t_iter_next( new_state, iter ) ) {
718 0 : fd_landed_vote_t * new_vote = deq_fd_landed_vote_t_iter_ele( new_state, iter );
719 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L738
720 0 : if( FD_UNLIKELY( new_vote->latency == 0 ) ) {
721 : // this is unlikely because as validators upgrade, it should converge to the new vote state
722 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L739
723 0 : new_vote->latency = fd_vote_compute_vote_latency( new_vote->lockout.slot, current_slot );
724 0 : }
725 0 : }
726 :
727 : // doesn't matter what the value of slot if `is_some = 0` i.e. `Option::None`
728 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L744
729 0 : int both_none = !has_root_slot && !has_new_root;
730 0 : if( ( !both_none && ( has_root_slot!=has_new_root ||
731 0 : *root_slot!=new_root ) ) ) {
732 0 : fd_vsv_increment_credits( versioned, epoch, earned_credits );
733 0 : }
734 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L750
735 0 : if( FD_LIKELY( has_timestamp ) ) {
736 : /* new_state asserted nonempty at function beginning */
737 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_empty( new_state ) ) ) {
738 0 : FD_LOG_CRIT(( "invariant violation: landed votes is empty" ));
739 0 : }
740 0 : ulong last_slot = deq_fd_landed_vote_t_peek_tail( new_state )->lockout.slot;
741 0 : rc = fd_vsv_process_timestamp( ctx, versioned, last_slot, timestamp );
742 0 : if( FD_UNLIKELY( rc ) ) { return rc; }
743 0 : fd_vsv_process_timestamp( ctx, versioned, last_slot, timestamp );
744 0 : }
745 :
746 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L754
747 0 : fd_vsv_set_root_slot( versioned, has_new_root ? &new_root : NULL );
748 :
749 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L755
750 0 : deq_fd_landed_vote_t_remove_all( votes );
751 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( new_state );
752 0 : !deq_fd_landed_vote_t_iter_done( new_state, iter );
753 0 : iter = deq_fd_landed_vote_t_iter_next( new_state, iter ) ) {
754 0 : fd_landed_vote_t * landed_vote = deq_fd_landed_vote_t_iter_ele( new_state, iter );
755 0 : deq_fd_landed_vote_t_push_tail_wrap( votes, *landed_vote );
756 0 : }
757 :
758 0 : return FD_EXECUTOR_INSTR_SUCCESS;
759 0 : }
760 :
761 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L1053-L1102 */
762 : #define FD_VOTE_BLS_MSG_SZ ( 9 + 32 + 48 )
763 : static int
764 : fd_vote_verify_bls_proof_of_possession( fd_exec_instr_ctx_t * ctx,
765 : uchar const vote_account_pubkey[ 32 ],
766 : uchar const bls_pubkey[ 48 ],
767 6 : uchar const bls_proof[ 96 ] ) {
768 : /* Consume CUs for proof of possession */
769 6 : FD_EXEC_CU_UPDATE( ctx, COMPUTE_UNITS_POP );
770 :
771 : /* Build message */
772 6 : uchar msg[ FD_VOTE_BLS_MSG_SZ ];
773 6 : memcpy( msg, "ALPENGLOW", 9 );
774 6 : memcpy( msg+9, vote_account_pubkey, 32 );
775 6 : memcpy( msg+9+32, bls_pubkey, 48 );
776 :
777 : /* Verify */
778 6 : if( FD_UNLIKELY( fd_bls12_381_proof_of_possession_verify(
779 6 : msg,
780 6 : FD_VOTE_BLS_MSG_SZ,
781 6 : bls_proof,
782 6 : bls_pubkey
783 6 : )!=FD_BLS_SUCCESS ) ) {
784 3 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
785 3 : }
786 3 : return 0;
787 6 : }
788 : #undef FD_VOTE_BLS_MSG_SZ
789 :
790 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L716-L759 */
791 : static int
792 : authorize( fd_exec_instr_ctx_t * ctx,
793 : fd_borrowed_account_t * vote_account,
794 : int target_version,
795 : fd_pubkey_t const * authorized,
796 : fd_vote_authorize_t vote_authorize,
797 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
798 : ulong signers_cnt,
799 : fd_sol_sysvar_clock_t const * clock,
800 0 : int is_bls_pubkey_feature_enabled ) {
801 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L724-L727 */
802 0 : int rc = get_vote_state_handler_checked(
803 0 : vote_account,
804 0 : target_version,
805 0 : 0,
806 0 : ctx->runtime->vote_program.authorize.vote_state_mem,
807 0 : ctx->runtime->vote_program.authorize.authorized_voters_mem,
808 0 : ctx->runtime->vote_program.authorize.landed_votes_mem
809 0 : );
810 0 : if( FD_UNLIKELY( rc ) ) return rc;
811 :
812 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L729-L756 */
813 0 : fd_vote_state_versioned_t * vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.authorize.vote_state_mem;
814 0 : switch( vote_authorize.discriminant ) {
815 :
816 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L730-L750 */
817 0 : case fd_vote_authorize_enum_voter: {
818 :
819 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L741-L743 */
820 0 : if( FD_UNLIKELY( is_bls_pubkey_feature_enabled && fd_vsv_has_bls_pubkey( vote_state_versioned ) ) ) {
821 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
822 0 : }
823 :
824 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L731-L732 */
825 0 : int authorized_withdrawer_signer = !fd_vote_verify_authorized_signer(
826 0 : fd_vsv_get_authorized_withdrawer( vote_state_versioned ),
827 0 : signers,
828 0 : signers_cnt
829 0 : );
830 :
831 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L737-L740 */
832 0 : ulong target_epoch;
833 0 : rc = fd_ulong_checked_add( clock->leader_schedule_epoch, 1UL, &target_epoch );
834 0 : if( FD_UNLIKELY( rc!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
835 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
836 0 : }
837 :
838 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L747 */
839 0 : rc = fd_vsv_set_new_authorized_voter(
840 0 : ctx,
841 0 : vote_state_versioned,
842 0 : authorized,
843 0 : clock->epoch,
844 0 : target_epoch,
845 0 : NULL, /* no BLS pubkey */
846 0 : authorized_withdrawer_signer,
847 0 : signers,
848 0 : signers_cnt
849 0 : );
850 0 : if( FD_UNLIKELY( rc ) ) return rc;
851 0 : break;
852 0 : }
853 :
854 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L751-L756 */
855 0 : case fd_vote_authorize_enum_withdrawer: {
856 0 : rc = fd_vote_verify_authorized_signer(
857 0 : fd_vsv_get_authorized_withdrawer( vote_state_versioned ),
858 0 : signers,
859 0 : signers_cnt
860 0 : );
861 0 : if( FD_UNLIKELY( rc ) ) return rc;
862 0 : fd_vsv_set_authorized_withdrawer( vote_state_versioned, authorized );
863 0 : break;
864 0 : }
865 :
866 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L770 */
867 0 : case fd_vote_authorize_enum_voter_with_bls: {
868 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L771-L773 */
869 : //FIXME
870 0 : if( FD_UNLIKELY( !is_bls_pubkey_feature_enabled ) ) {
871 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
872 0 : }
873 :
874 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L774-L775 */
875 0 : int authorized_withdrawer_signer = !fd_vote_verify_authorized_signer(
876 0 : fd_vsv_get_authorized_withdrawer( vote_state_versioned ),
877 0 : signers,
878 0 : signers_cnt
879 0 : );
880 :
881 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L777-L782 */
882 0 : rc = fd_vote_verify_bls_proof_of_possession(
883 0 : ctx,
884 0 : vote_account->pubkey->uc,
885 0 : vote_authorize.inner.voter_with_bls.bls_pubkey.buf,
886 0 : vote_authorize.inner.voter_with_bls.bls_proof_of_possession.buf
887 0 : );
888 0 : if( FD_UNLIKELY( rc ) ) {
889 0 : return rc;
890 0 : }
891 :
892 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L787-L790 */
893 0 : ulong target_epoch;
894 0 : rc = fd_ulong_checked_add( clock->leader_schedule_epoch, 1UL, &target_epoch );
895 0 : if( FD_UNLIKELY( rc!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
896 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
897 0 : }
898 :
899 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L784 */
900 0 : rc = fd_vsv_set_new_authorized_voter(
901 0 : ctx,
902 0 : vote_state_versioned,
903 0 : authorized,
904 0 : clock->epoch,
905 0 : target_epoch,
906 0 : &vote_authorize.inner.voter_with_bls.bls_pubkey,
907 0 : authorized_withdrawer_signer,
908 0 : signers,
909 0 : signers_cnt
910 0 : );
911 0 : if( FD_UNLIKELY( rc ) ) return rc;
912 :
913 0 : break;
914 0 : }
915 :
916 0 : default:
917 0 : FD_LOG_CRIT(( "unsupported vote_authorize discriminant: %u", vote_authorize.discriminant ));
918 0 : }
919 :
920 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L758 */
921 0 : return fd_vsv_set_vote_account_state(
922 0 : ctx,
923 0 : vote_account,
924 0 : vote_state_versioned,
925 0 : ctx->runtime->vote_program.authorize.vote_lockout_mem
926 0 : );
927 0 : }
928 :
929 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L761-L785 */
930 : static int
931 : update_validator_identity( fd_exec_instr_ctx_t * ctx,
932 : int target_version,
933 : fd_borrowed_account_t * vote_account,
934 : fd_pubkey_t const * node_pubkey,
935 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
936 0 : ulong signers_cnt ) {
937 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L768-L771 */
938 0 : int rc = get_vote_state_handler_checked(
939 0 : vote_account,
940 0 : target_version,
941 0 : 0,
942 0 : ctx->runtime->vote_program.update_validator_identity.vote_state_mem,
943 0 : ctx->runtime->vote_program.update_validator_identity.authorized_voters_mem,
944 0 : ctx->runtime->vote_program.update_validator_identity.landed_votes_mem
945 0 : );
946 0 : if( FD_UNLIKELY( rc ) ) return rc;
947 :
948 0 : fd_vote_state_versioned_t * vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.update_validator_identity.vote_state_mem;
949 :
950 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L774 */
951 0 : rc = fd_vote_verify_authorized_signer(
952 0 : fd_vsv_get_authorized_withdrawer( vote_state_versioned ),
953 0 : signers,
954 0 : signers_cnt
955 0 : );
956 0 : if( FD_UNLIKELY( rc ) ) return rc;
957 :
958 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L777 */
959 0 : rc = fd_vote_verify_authorized_signer( node_pubkey, signers, signers_cnt );
960 0 : if( FD_UNLIKELY( rc ) ) return rc;
961 :
962 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L779 */
963 0 : fd_vsv_set_node_pubkey( vote_state_versioned, node_pubkey );
964 :
965 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L782 */
966 0 : fd_vsv_set_block_revenue_collector( vote_state_versioned, node_pubkey );
967 :
968 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L784 */
969 0 : return fd_vsv_set_vote_account_state(
970 0 : ctx,
971 0 : vote_account,
972 0 : vote_state_versioned,
973 0 : ctx->runtime->vote_program.update_validator_identity.vote_lockout_mem
974 0 : );
975 0 : }
976 :
977 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L971
978 : static int
979 0 : is_commission_update_allowed( ulong slot, fd_epoch_schedule_t const * epoch_schedule ) {
980 0 : if( FD_LIKELY( epoch_schedule->slots_per_epoch > 0UL ) ) {
981 0 : ulong relative_slot = fd_ulong_sat_sub( slot, epoch_schedule->first_normal_slot );
982 : // TODO underflow and overflow edge cases in addition to div by 0
983 0 : relative_slot %= epoch_schedule->slots_per_epoch;
984 0 : return fd_ulong_sat_mul( relative_slot, 2 ) <= epoch_schedule->slots_per_epoch;
985 0 : } else {
986 0 : return 1;
987 0 : }
988 0 : }
989 :
990 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L787-L818 */
991 : static int
992 : update_commission( fd_exec_instr_ctx_t * ctx,
993 : int target_version,
994 : fd_borrowed_account_t * vote_account,
995 : uchar commission,
996 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
997 : ulong signers_cnt,
998 : fd_epoch_schedule_t const * epoch_schedule,
999 0 : fd_sol_sysvar_clock_t const * clock ) {
1000 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L796-L799 */
1001 0 : int rc = 0;
1002 0 : int get_vsv_rc = get_vote_state_handler_checked(
1003 0 : vote_account,
1004 0 : target_version,
1005 0 : false,
1006 0 : ctx->runtime->vote_program.update_commission.vote_state_mem,
1007 0 : ctx->runtime->vote_program.update_commission.authorized_voters_mem,
1008 0 : ctx->runtime->vote_program.update_commission.landed_votes_mem
1009 0 : );
1010 :
1011 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L800-L804 */
1012 0 : fd_vote_state_versioned_t * vote_state_versioned = NULL;
1013 0 : int enforce_commission_update_rule = 1;
1014 0 : if( FD_LIKELY( get_vsv_rc==FD_EXECUTOR_INSTR_SUCCESS ) ) {
1015 0 : vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.update_commission.vote_state_mem;
1016 0 : enforce_commission_update_rule = (commission>fd_vsv_get_commission( vote_state_versioned ));
1017 0 : }
1018 :
1019 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L806-L808 */
1020 0 : if( FD_UNLIKELY( enforce_commission_update_rule && !is_commission_update_allowed( clock->slot, epoch_schedule ) ) ) {
1021 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_COMMISSION_UPDATE_TOO_LATE;
1022 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1023 0 : }
1024 :
1025 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L810 */
1026 0 : if( FD_UNLIKELY( get_vsv_rc ) ) return get_vsv_rc;
1027 :
1028 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L813 */
1029 0 : rc = fd_vote_verify_authorized_signer(
1030 0 : fd_vsv_get_authorized_withdrawer( vote_state_versioned ),
1031 0 : signers,
1032 0 : signers_cnt
1033 0 : );
1034 0 : if( FD_UNLIKELY( rc ) ) return rc;
1035 :
1036 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L815 */
1037 0 : fd_vsv_set_commission( vote_state_versioned, commission );
1038 :
1039 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L817 */
1040 0 : return fd_vsv_set_vote_account_state(
1041 0 : ctx,
1042 0 : vote_account,
1043 0 : vote_state_versioned,
1044 0 : ctx->runtime->vote_program.update_commission.vote_lockout_mem
1045 0 : );
1046 0 : }
1047 :
1048 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L848C8-L903 */
1049 : static int
1050 : withdraw( fd_exec_instr_ctx_t * ctx,
1051 : fd_borrowed_account_t * vote_account,
1052 : int target_version,
1053 : ulong lamports,
1054 : ushort to_account_index,
1055 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1056 : ulong signers_cnt,
1057 : fd_rent_t const * rent_sysvar,
1058 0 : fd_sol_sysvar_clock_t const * clock ) {
1059 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L860-L863 */
1060 0 : int rc = get_vote_state_handler_checked(
1061 0 : vote_account,
1062 0 : target_version,
1063 0 : 0,
1064 0 : ctx->runtime->vote_program.withdraw.vote_state_mem,
1065 0 : ctx->runtime->vote_program.withdraw.authorized_voters_mem,
1066 0 : ctx->runtime->vote_program.withdraw.landed_votes_mem
1067 0 : );
1068 0 : if( FD_UNLIKELY( rc ) ) return rc;
1069 :
1070 0 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.withdraw.vote_state_mem;
1071 :
1072 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L865 */
1073 0 : rc = fd_vote_verify_authorized_signer(
1074 0 : fd_vsv_get_authorized_withdrawer( versioned ),
1075 0 : signers,
1076 0 : signers_cnt
1077 0 : );
1078 0 : if( FD_UNLIKELY( rc ) ) return rc;
1079 :
1080 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L867-L870 */
1081 0 : ulong vote_account_lamports = fd_borrowed_account_get_lamports( vote_account );
1082 0 : if( FD_UNLIKELY( lamports>vote_account_lamports ) ) {
1083 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
1084 0 : }
1085 0 : ulong remaining_balance = vote_account_lamports-lamports;
1086 :
1087 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L872-L896 */
1088 0 : if( FD_UNLIKELY( remaining_balance==0UL ) ) {
1089 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L873-L883 */
1090 0 : fd_vote_epoch_credits_t const * epoch_credits = fd_vsv_get_epoch_credits( versioned );
1091 0 : ulong last_epoch_with_credits;
1092 0 : int reject_active_vote_account_close = 0;
1093 0 : if( FD_LIKELY( !deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) {
1094 0 : ulong current_epoch = clock->epoch;
1095 0 : last_epoch_with_credits = deq_fd_vote_epoch_credits_t_peek_tail_const( epoch_credits )->epoch;
1096 0 : reject_active_vote_account_close = fd_ulong_sat_sub( current_epoch, last_epoch_with_credits )<2UL;
1097 0 : }
1098 :
1099 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L885-L890 */
1100 0 : if( FD_UNLIKELY( reject_active_vote_account_close ) ) {
1101 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_ACTIVE_VOTE_ACCOUNT_CLOSE;
1102 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1103 0 : } else {
1104 0 : rc = fd_vsv_deinitialize_vote_account_state(
1105 0 : ctx,
1106 0 : vote_account,
1107 0 : target_version,
1108 0 : ctx->runtime->vote_program.withdraw.vote_lockout_mem
1109 0 : );
1110 0 : if( FD_UNLIKELY( rc ) ) return rc;
1111 0 : }
1112 0 : } else {
1113 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L892-L895 */
1114 0 : ulong min_rent_exempt_balance = fd_rent_exempt_minimum_balance( rent_sysvar, fd_borrowed_account_get_data_len( vote_account ) );
1115 0 : if( FD_UNLIKELY( remaining_balance<min_rent_exempt_balance ) ) {
1116 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
1117 0 : }
1118 0 : }
1119 :
1120 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L898 */
1121 0 : rc = fd_borrowed_account_checked_sub_lamports( vote_account, lamports );
1122 0 : if( FD_UNLIKELY( rc ) ) return rc;
1123 :
1124 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L899 */
1125 0 : fd_borrowed_account_drop( vote_account );
1126 :
1127 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L900 */
1128 0 : fd_guarded_borrowed_account_t to = {0};
1129 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, to_account_index, &to );
1130 :
1131 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L901 */
1132 0 : rc = fd_borrowed_account_checked_add_lamports( &to, lamports );
1133 0 : if( FD_UNLIKELY( rc ) ) return rc;
1134 :
1135 0 : return FD_EXECUTOR_INSTR_SUCCESS;
1136 0 : }
1137 :
1138 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L638-L651 */
1139 : static int
1140 : process_vote_unfiltered( fd_exec_instr_ctx_t * ctx,
1141 : fd_vote_state_versioned_t * versioned,
1142 : ulong * vote_slots,
1143 : fd_vote_t const * vote,
1144 : fd_slot_hash_t const * slot_hashes, /* deque */
1145 : ulong epoch,
1146 0 : ulong current_slot ) {
1147 0 : int rc;
1148 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L770
1149 0 : rc = check_slots_are_valid( ctx, versioned, vote_slots, &vote->hash, slot_hashes );
1150 0 : if( FD_UNLIKELY( rc ) ) return rc;
1151 0 : for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote_slots );
1152 0 : !deq_ulong_iter_done( vote_slots, iter );
1153 0 : iter = deq_ulong_iter_next( vote_slots, iter ) ) {
1154 0 : ulong * ele = deq_ulong_iter_ele( vote_slots, iter );
1155 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L772
1156 0 : fd_vsv_process_next_vote_slot( versioned, *ele, epoch, current_slot );
1157 0 : }
1158 0 : return 0;
1159 0 : }
1160 :
1161 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L783
1162 : static int
1163 : process_vote( fd_exec_instr_ctx_t * ctx,
1164 : fd_vote_state_versioned_t * versioned,
1165 : fd_vote_t const * vote,
1166 : fd_slot_hash_t const * slot_hashes, /* deque */
1167 : ulong epoch,
1168 0 : ulong current_slot ) {
1169 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L792
1170 0 : if( FD_UNLIKELY( deq_ulong_empty( vote->slots ) ) ) {
1171 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_EMPTY_SLOTS;
1172 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1173 0 : }
1174 :
1175 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L795
1176 0 : ulong earliest_slot_in_history = 0;
1177 0 : if( FD_UNLIKELY( !deq_fd_slot_hash_t_empty( slot_hashes ) ) ) {
1178 0 : earliest_slot_in_history = deq_fd_slot_hash_t_peek_tail_const( slot_hashes )->slot;
1179 0 : }
1180 :
1181 : /* We know that the size of the vote_slots is bounded by the number of
1182 : slots that can fit inside of an instruction. A very loose bound is
1183 : assuming that the entire transaction is just filled with a vote
1184 : slot deque (1232 bytes per transaction/8 bytes per slot) == 154
1185 : slots. The footprint of a deque is as follows:
1186 : fd_ulong_align_up( fd_ulong_align_up( 32UL, alignof(DEQUE_T) ) + sizeof(DEQUE_T)*max, alignof(DEQUE_(private_t)) );
1187 : So, the footprint in our case is:
1188 : fd_ulong_align_up( fd_ulong_align_up( 32UL, alignof(ulong) ) + sizeof(ulong)*154, alignof(DEQUE_(private_t)) );
1189 : Which is equal to
1190 : fd_ulong_align_up( 32UL + 154 * 8UL, 8UL ) = 1264UL; */
1191 0 : #define VOTE_SLOTS_MAX (FD_TXN_MTU/sizeof(ulong))
1192 0 : #define VOTE_SLOTS_DEQUE_FOOTPRINT (1264UL )
1193 0 : #define VOTE_SLOTS_DEQUE_ALIGN (8UL)
1194 0 : FD_TEST( deq_ulong_footprint( VOTE_SLOTS_MAX ) == VOTE_SLOTS_DEQUE_FOOTPRINT );
1195 0 : FD_TEST( deq_ulong_align() == 8UL );
1196 0 : FD_TEST( deq_ulong_cnt( vote->slots ) <= VOTE_SLOTS_MAX );
1197 0 : uchar * vote_slots_mem[ VOTE_SLOTS_DEQUE_FOOTPRINT ] __attribute__((aligned(VOTE_SLOTS_DEQUE_ALIGN)));
1198 :
1199 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L796
1200 0 : ulong * vote_slots = deq_ulong_join( deq_ulong_new( vote_slots_mem, deq_ulong_cnt( vote->slots ) ) );
1201 0 : for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote->slots );
1202 0 : !deq_ulong_iter_done( vote->slots, iter );
1203 0 : iter = deq_ulong_iter_next( vote->slots, iter ) ) {
1204 0 : ulong * ele = deq_ulong_iter_ele( vote->slots, iter );
1205 0 : if( FD_UNLIKELY( *ele >= earliest_slot_in_history ) ) {
1206 0 : vote_slots = deq_ulong_push_tail_wrap( vote_slots, *ele );
1207 0 : }
1208 0 : }
1209 :
1210 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L802
1211 0 : if( FD_UNLIKELY( deq_ulong_cnt( vote_slots ) == 0 ) ) {
1212 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_VOTES_TOO_OLD_ALL_FILTERED;
1213 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1214 0 : }
1215 :
1216 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L805
1217 0 : return process_vote_unfiltered(
1218 0 : ctx,
1219 0 : versioned,
1220 0 : vote_slots,
1221 0 : vote,
1222 0 : slot_hashes,
1223 0 : epoch,
1224 0 : current_slot
1225 0 : );
1226 0 : }
1227 :
1228 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L905-L926 */
1229 : static int
1230 : initialize_account( fd_exec_instr_ctx_t * ctx,
1231 : fd_borrowed_account_t * vote_account,
1232 : int target_version,
1233 : fd_vote_init_t * vote_init,
1234 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1235 : ulong signers_cnt,
1236 3 : fd_sol_sysvar_clock_t const * clock ) {
1237 3 : int rc;
1238 :
1239 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L915 */
1240 3 : rc = check_vote_account_length( vote_account, target_version );
1241 3 : if( FD_UNLIKELY( rc ) ) return rc;
1242 :
1243 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L916 */
1244 3 : rc = fd_vsv_get_state( vote_account->meta, ctx->runtime->vote_program.init_account.vote_state_mem );
1245 3 : if( FD_UNLIKELY( rc ) ) return rc;
1246 3 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.init_account.vote_state_mem;
1247 :
1248 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L918-L920 */
1249 3 : if( FD_UNLIKELY( !fd_vsv_is_uninitialized( versioned ) ) ) {
1250 0 : return FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED;
1251 0 : }
1252 :
1253 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L923 */
1254 3 : rc = fd_vote_verify_authorized_signer( &vote_init->node_pubkey, signers, signers_cnt );
1255 3 : if( FD_UNLIKELY( rc ) ) {
1256 0 : return rc;
1257 0 : }
1258 :
1259 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L925 */
1260 3 : return init_vote_account_state( ctx, vote_account, versioned, target_version, vote_init, clock );
1261 3 : }
1262 :
1263 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L905-L926
1264 : Note: this is very similar to initialize_account() but also does verify_bls_proof_of_possession() */
1265 : static int
1266 : initialize_account_v2( fd_exec_instr_ctx_t * ctx,
1267 : fd_borrowed_account_t * vote_account,
1268 : int target_version,
1269 : fd_vote_init_v2_t * vote_init_v2,
1270 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1271 : ulong signers_cnt,
1272 6 : fd_sol_sysvar_clock_t const * clock ) {
1273 6 : int rc;
1274 :
1275 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L1180 */
1276 6 : rc = check_vote_account_length( vote_account, target_version );
1277 6 : if( FD_UNLIKELY( rc ) ) return rc;
1278 :
1279 6 : rc = fd_vsv_get_state( vote_account->meta, ctx->runtime->vote_program.init_account.vote_state_mem );
1280 6 : if( FD_UNLIKELY( rc ) ) return rc;
1281 6 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.init_account.vote_state_mem;
1282 :
1283 6 : if( FD_UNLIKELY( !fd_vsv_is_uninitialized( versioned ) ) ) {
1284 0 : return FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED;
1285 0 : }
1286 :
1287 6 : rc = fd_vote_verify_authorized_signer( &vote_init_v2->node_pubkey, signers, signers_cnt );
1288 6 : if( FD_UNLIKELY( rc ) ) {
1289 0 : return rc;
1290 0 : }
1291 :
1292 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L1201-L1207 */
1293 6 : rc = fd_vote_verify_bls_proof_of_possession(
1294 6 : ctx,
1295 6 : vote_account->pubkey->uc,
1296 6 : vote_init_v2->authorized_voter_bls_pubkey.buf,
1297 6 : vote_init_v2->authorized_voter_bls_proof_of_possession.buf
1298 6 : );
1299 6 : if( FD_UNLIKELY( rc ) ) {
1300 3 : return rc;
1301 3 : }
1302 :
1303 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L1209 */
1304 3 : return init_vote_account_state_v2( ctx, vote_account, versioned, target_version, vote_init_v2, clock );
1305 6 : }
1306 :
1307 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L928-L953 */
1308 : static int
1309 : process_vote_with_account( fd_exec_instr_ctx_t * ctx,
1310 : fd_borrowed_account_t * vote_account,
1311 : int target_version,
1312 : fd_slot_hash_t const * slot_hashes, /* deque */
1313 : fd_sol_sysvar_clock_t const * clock,
1314 : fd_vote_t * vote,
1315 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1316 0 : ulong signers_cnt ) {
1317 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L936-L939 */
1318 0 : int rc = get_vote_state_handler_checked(
1319 0 : vote_account,
1320 0 : target_version,
1321 0 : 1,
1322 0 : ctx->runtime->vote_program.process_vote.vote_state_mem,
1323 0 : ctx->runtime->vote_program.process_vote.authorized_voters_mem,
1324 0 : ctx->runtime->vote_program.process_vote.landed_votes_mem
1325 0 : );
1326 0 : if( FD_UNLIKELY( rc ) ) return rc;
1327 :
1328 0 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.process_vote.vote_state_mem;
1329 :
1330 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L941 */
1331 0 : fd_pubkey_t * authorized_voter = NULL;
1332 0 : rc = fd_authorized_voters_get_and_update_authorized_voter( versioned, clock->epoch, &authorized_voter );
1333 0 : if( FD_UNLIKELY( rc ) ) return rc;
1334 :
1335 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L942 */
1336 0 : rc = fd_vote_verify_authorized_signer( authorized_voter, signers, signers_cnt );
1337 0 : if( FD_UNLIKELY( rc ) ) return rc;
1338 :
1339 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L944 */
1340 0 : rc = process_vote( ctx, versioned, vote, slot_hashes, clock->epoch, clock->slot );
1341 0 : if( FD_UNLIKELY( rc ) ) return rc;
1342 :
1343 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L945-L951 */
1344 0 : if( FD_LIKELY( vote->has_timestamp ) ) {
1345 : /* Calling max() on an empty iterator returns None */
1346 0 : if( FD_UNLIKELY( deq_ulong_cnt( vote->slots )==0 ) ) {
1347 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_EMPTY_SLOTS;
1348 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
1349 0 : }
1350 :
1351 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L948 */
1352 0 : ulong max = 0UL;
1353 0 : for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote->slots );
1354 0 : !deq_ulong_iter_done( vote->slots, iter );
1355 0 : iter = deq_ulong_iter_next( vote->slots, iter ) ) {
1356 0 : ulong * ele = deq_ulong_iter_ele( vote->slots, iter );
1357 0 : max = fd_ulong_max( max, *ele );
1358 0 : }
1359 :
1360 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L950 */
1361 0 : rc = fd_vsv_process_timestamp( ctx, versioned, max, vote->timestamp );
1362 0 : if( FD_UNLIKELY( rc ) ) return rc;
1363 0 : }
1364 :
1365 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L952 */
1366 0 : return fd_vsv_set_vote_account_state(
1367 0 : ctx,
1368 0 : vote_account,
1369 0 : versioned,
1370 0 : ctx->runtime->vote_program.process_vote.vote_lockout_mem
1371 0 : );
1372 0 : }
1373 :
1374 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1156
1375 : static int
1376 : do_process_vote_state_update( fd_exec_instr_ctx_t * ctx,
1377 : fd_vote_state_versioned_t * versioned,
1378 : fd_slot_hash_t const * slot_hashes, /* deque */
1379 : ulong epoch,
1380 : ulong slot,
1381 0 : fd_vote_state_update_t * vote_state_update ) {
1382 0 : int rc;
1383 :
1384 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1164
1385 0 : rc = check_and_filter_proposed_vote_state(
1386 0 : ctx,
1387 0 : versioned,
1388 0 : vote_state_update->lockouts,
1389 0 : &vote_state_update->has_root,
1390 0 : &vote_state_update->root,
1391 0 : &vote_state_update->hash,
1392 0 : slot_hashes
1393 0 : );
1394 0 : if( FD_UNLIKELY( rc ) ) return rc;
1395 :
1396 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1177
1397 0 : fd_landed_vote_t * landed_votes = deq_fd_landed_vote_t_join( deq_fd_landed_vote_t_new( ctx->runtime->vote_program.process_vote.vs_update_landed_votes_mem, deq_fd_vote_lockout_t_cnt( vote_state_update->lockouts ) ) );
1398 0 : for( deq_fd_vote_lockout_t_iter_t iter =
1399 0 : deq_fd_vote_lockout_t_iter_init( vote_state_update->lockouts );
1400 0 : !deq_fd_vote_lockout_t_iter_done( vote_state_update->lockouts, iter );
1401 0 : iter = deq_fd_vote_lockout_t_iter_next( vote_state_update->lockouts, iter ) ) {
1402 0 : fd_vote_lockout_t * lockout =
1403 0 : deq_fd_vote_lockout_t_iter_ele( vote_state_update->lockouts, iter );
1404 0 : deq_fd_landed_vote_t_push_tail_wrap( landed_votes,
1405 0 : ( fd_landed_vote_t ){ .latency = 0, .lockout = *lockout } );
1406 0 : }
1407 :
1408 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1171
1409 0 : return process_new_vote_state(
1410 0 : ctx,
1411 0 : versioned,
1412 0 : landed_votes,
1413 0 : vote_state_update->has_root,
1414 0 : vote_state_update->root,
1415 0 : vote_state_update->has_timestamp,
1416 0 : vote_state_update->timestamp,
1417 0 : epoch,
1418 0 : slot
1419 0 : );
1420 0 : }
1421 :
1422 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L955-L979 */
1423 : static int
1424 : process_vote_state_update( fd_exec_instr_ctx_t * ctx,
1425 : fd_borrowed_account_t * vote_account,
1426 : int target_version,
1427 : fd_slot_hash_t const * slot_hashes,
1428 : fd_sol_sysvar_clock_t const * clock,
1429 : fd_vote_state_update_t * vote_state_update,
1430 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1431 0 : ulong signers_cnt ) {
1432 :
1433 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L963-L966 */
1434 0 : int rc = get_vote_state_handler_checked(
1435 0 : vote_account,
1436 0 : target_version,
1437 0 : 1,
1438 0 : ctx->runtime->vote_program.process_vote.vote_state_mem,
1439 0 : ctx->runtime->vote_program.process_vote.authorized_voters_mem,
1440 0 : ctx->runtime->vote_program.process_vote.landed_votes_mem
1441 0 : );
1442 0 : if( FD_UNLIKELY( rc ) ) return rc;
1443 :
1444 0 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.process_vote.vote_state_mem;
1445 :
1446 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L968 */
1447 0 : fd_pubkey_t * authorized_voter = NULL;
1448 0 : rc = fd_authorized_voters_get_and_update_authorized_voter( versioned, clock->epoch, &authorized_voter );
1449 0 : if( FD_UNLIKELY( rc ) ) return rc;
1450 :
1451 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L969 */
1452 0 : rc = fd_vote_verify_authorized_signer( authorized_voter, signers, signers_cnt );
1453 0 : if( FD_UNLIKELY( rc ) ) return rc;
1454 :
1455 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L971-L977 */
1456 0 : rc = do_process_vote_state_update(
1457 0 : ctx,
1458 0 : versioned,
1459 0 : slot_hashes,
1460 0 : clock->epoch,
1461 0 : clock->slot,
1462 0 : vote_state_update
1463 0 : );
1464 0 : if( FD_UNLIKELY( rc ) ) {
1465 0 : return rc;
1466 0 : }
1467 :
1468 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L978 */
1469 0 : return fd_vsv_set_vote_account_state(
1470 0 : ctx,
1471 0 : vote_account,
1472 0 : versioned,
1473 0 : ctx->runtime->vote_program.process_vote.vote_lockout_mem
1474 0 : );
1475 0 : }
1476 :
1477 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1035-L1061 */
1478 : static int
1479 : do_process_tower_sync( fd_exec_instr_ctx_t * ctx,
1480 : fd_vote_state_versioned_t * versioned,
1481 : fd_slot_hash_t const * slot_hashes, /* deque */
1482 : ulong epoch,
1483 : ulong slot,
1484 0 : fd_tower_sync_t * tower_sync ) {
1485 :
1486 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1042-L1048 */
1487 0 : int rc = check_and_filter_proposed_vote_state(
1488 0 : ctx,
1489 0 : versioned,
1490 0 : tower_sync->lockouts,
1491 0 : &tower_sync->has_root,
1492 0 : &tower_sync->root,
1493 0 : &tower_sync->hash,
1494 0 : slot_hashes
1495 0 : );
1496 0 : if( FD_UNLIKELY( rc ) ) return rc;
1497 :
1498 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1049-L1060 */
1499 0 : return process_new_vote_state(
1500 0 : ctx,
1501 0 : versioned,
1502 0 : fd_vote_lockout_landed_votes_from_lockouts( tower_sync->lockouts, ctx->runtime->vote_program.tower_sync.tower_sync_landed_votes_mem ),
1503 0 : tower_sync->has_root,
1504 0 : tower_sync->root,
1505 0 : tower_sync->has_timestamp,
1506 0 : tower_sync->timestamp,
1507 0 : epoch,
1508 0 : slot
1509 0 : );
1510 0 : }
1511 :
1512 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1009-L1033 */
1513 : static int
1514 : process_tower_sync( fd_exec_instr_ctx_t * ctx,
1515 : fd_borrowed_account_t * vote_account,
1516 : int target_version,
1517 : fd_slot_hash_t const * slot_hashes, /* deque */
1518 : fd_sol_sysvar_clock_t const * clock,
1519 : fd_tower_sync_t * tower_sync,
1520 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX],
1521 0 : ulong signers_cnt ) {
1522 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1017-L1020 */
1523 0 : int rc = get_vote_state_handler_checked(
1524 0 : vote_account,
1525 0 : target_version,
1526 0 : 1,
1527 0 : ctx->runtime->vote_program.tower_sync.vote_state_mem,
1528 0 : ctx->runtime->vote_program.tower_sync.authorized_voters_mem,
1529 0 : ctx->runtime->vote_program.tower_sync.vote_state_landed_votes_mem
1530 0 : );
1531 0 : if( FD_UNLIKELY( rc ) ) return rc;
1532 :
1533 0 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.tower_sync.vote_state_mem;
1534 :
1535 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1022 */
1536 0 : fd_pubkey_t * authorized_voter = NULL;
1537 0 : rc = fd_authorized_voters_get_and_update_authorized_voter( versioned, clock->epoch, &authorized_voter );
1538 0 : if( FD_UNLIKELY( rc ) ) return rc;
1539 :
1540 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1023 */
1541 0 : rc = fd_vote_verify_authorized_signer( authorized_voter, signers, signers_cnt );
1542 0 : if( FD_UNLIKELY( rc ) ) return rc;
1543 :
1544 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1025-L1031 */
1545 0 : rc = do_process_tower_sync(
1546 0 : ctx,
1547 0 : versioned,
1548 0 : slot_hashes,
1549 0 : clock->epoch,
1550 0 : clock->slot,
1551 0 : tower_sync
1552 0 : );
1553 0 : if( FD_UNLIKELY( rc ) ) return rc;
1554 :
1555 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1032 */
1556 0 : return fd_vsv_set_vote_account_state(
1557 0 : ctx,
1558 0 : vote_account,
1559 0 : versioned,
1560 0 : ctx->runtime->vote_program.tower_sync.vote_lockout_mem
1561 0 : );
1562 0 : }
1563 :
1564 : /**********************************************************************/
1565 : /* FD-only encoders / decoders (doesn't map directly to Labs impl) */
1566 : /**********************************************************************/
1567 :
1568 : int
1569 : fd_vote_decode_compact_update( fd_compact_vote_state_update_t * compact_update,
1570 : fd_vote_state_update_t * vote_update,
1571 0 : fd_exec_instr_ctx_t const * ctx ) {
1572 : // Taken from:
1573 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L954
1574 0 : if( compact_update->root != ULONG_MAX ) {
1575 0 : vote_update->has_root = 1;
1576 0 : vote_update->root = compact_update->root;
1577 0 : } else {
1578 0 : vote_update->has_root = 0;
1579 0 : vote_update->root = ULONG_MAX;
1580 0 : }
1581 :
1582 0 : ulong lockouts_len = compact_update->lockouts_len;
1583 0 : ulong lockouts_max = fd_ulong_max( lockouts_len, MAX_LOCKOUT_HISTORY );
1584 :
1585 0 : vote_update->lockouts = deq_fd_vote_lockout_t_join( deq_fd_vote_lockout_t_new( ctx->runtime->vote_program.process_vote.compact_vs_lockout_mem, lockouts_max ) );
1586 0 : ulong slot = fd_ulong_if( vote_update->has_root, vote_update->root, 0 );
1587 :
1588 0 : for( ulong i=0; i < lockouts_len; ++i ) {
1589 0 : fd_vote_lockout_t * elem = deq_fd_vote_lockout_t_push_tail_nocopy( vote_update->lockouts );
1590 0 : fd_vote_lockout_new( elem );
1591 :
1592 0 : fd_lockout_offset_t * lock_offset = &compact_update->lockouts[i];
1593 :
1594 0 : ulong next_slot;
1595 0 : if( FD_UNLIKELY( __builtin_uaddl_overflow( slot, lock_offset->offset, &next_slot ) ) )
1596 0 : return 0;
1597 :
1598 0 : elem->slot = slot = next_slot;
1599 0 : elem->confirmation_count = (uint)lock_offset->confirmation_count;
1600 0 : }
1601 :
1602 0 : vote_update->hash = compact_update->hash;
1603 0 : vote_update->has_timestamp = compact_update->has_timestamp;
1604 0 : vote_update->timestamp = compact_update->timestamp;
1605 :
1606 0 : return 1;
1607 0 : }
1608 :
1609 : /**********************************************************************/
1610 : /* mod vote_processor */
1611 : /**********************************************************************/
1612 :
1613 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L21-L51 */
1614 : static int
1615 : process_authorize_with_seed_instruction( /* invoke_context */
1616 : fd_exec_instr_ctx_t * ctx,
1617 : int target_version,
1618 : fd_borrowed_account_t * vote_account,
1619 : fd_pubkey_t const * new_authority,
1620 : fd_vote_authorize_t authorization_type,
1621 : fd_pubkey_t const * current_authority_derived_key_owner,
1622 : uchar const * current_authority_derived_key_seed,
1623 : ulong current_authority_derived_key_seed_len,
1624 0 : int is_bls_pubkey_feature_enabled ) {
1625 0 : int rc = 0;
1626 :
1627 : /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L31 */
1628 0 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_clock_id );
1629 0 : if( FD_UNLIKELY( rc ) ) return rc;
1630 :
1631 0 : fd_sol_sysvar_clock_t clock_;
1632 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
1633 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1634 :
1635 0 : fd_pubkey_t * expected_authority_keys[FD_TXN_SIG_MAX] = { 0 };
1636 0 : ulong expected_authority_keys_cnt = 0UL;
1637 0 : fd_pubkey_t single_signer = { 0 };
1638 :
1639 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L30-L42 */
1640 0 : if( fd_instr_acc_is_signer_idx( ctx->instr, 2, &rc ) ) {
1641 :
1642 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L31 */
1643 0 : fd_pubkey_t const * base_pubkey = NULL;
1644 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 2UL, &base_pubkey );
1645 0 : if( FD_UNLIKELY( rc ) ) return rc;
1646 :
1647 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L34-L40 */
1648 0 : expected_authority_keys[0] = &single_signer;
1649 0 : rc = fd_pubkey_create_with_seed( ctx,
1650 0 : base_pubkey->uc,
1651 0 : (char const *)current_authority_derived_key_seed,
1652 0 : current_authority_derived_key_seed_len,
1653 0 : current_authority_derived_key_owner->uc,
1654 0 : /* insert */ expected_authority_keys[0]->uc );
1655 0 : if( FD_UNLIKELY( rc ) ) return rc;
1656 0 : expected_authority_keys_cnt = 1UL;
1657 0 : }
1658 :
1659 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L43-L50 */
1660 0 : return authorize(
1661 0 : ctx,
1662 0 : vote_account,
1663 0 : target_version,
1664 0 : new_authority,
1665 0 : authorization_type,
1666 0 : (fd_pubkey_t const **)expected_authority_keys,
1667 0 : expected_authority_keys_cnt,
1668 0 : clock,
1669 0 : is_bls_pubkey_feature_enabled
1670 0 : );
1671 0 : }
1672 :
1673 : /**********************************************************************/
1674 : /* Entry point for the Vote Program */
1675 : /**********************************************************************/
1676 :
1677 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L57
1678 : int
1679 15 : fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) {
1680 : /* FD-specific init */
1681 15 : int rc = FD_EXECUTOR_INSTR_SUCCESS;
1682 :
1683 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L57
1684 15 : FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS );
1685 :
1686 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L64-L67 */
1687 15 : fd_guarded_borrowed_account_t me = {0};
1688 15 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, 0, &me );
1689 15 : if( FD_UNLIKELY( !fd_pubkey_eq( fd_borrowed_account_get_owner( &me ), &fd_solana_vote_program_id ) ) ) {
1690 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER;
1691 0 : }
1692 :
1693 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L69-L74 */
1694 15 : int target_version = FD_FEATURE_ACTIVE_BANK( ctx->bank, vote_state_v4 ) ? VOTE_STATE_TARGET_VERSION_V4 : VOTE_STATE_TARGET_VERSION_V3;
1695 :
1696 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L69
1697 15 : fd_pubkey_t const * signers[FD_TXN_SIG_MAX] = { 0 };
1698 15 : ulong signers_cnt = 0UL;
1699 15 : fd_exec_instr_ctx_get_signers( ctx, signers, &signers_cnt );
1700 :
1701 : /* https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_processor.rs#L97 */
1702 : //FIXME
1703 15 : int is_bls_pubkey_feature_enabled = FD_FEATURE_ACTIVE_BANK( ctx->bank, bls_pubkey_management_in_vote_account );
1704 :
1705 15 : uchar __attribute__((aligned(alignof(fd_vote_instruction_t)))) vote_instruction_mem[ FD_VOTE_INSTRUCTION_FOOTPRINT ];
1706 15 : fd_vote_instruction_t * instruction = fd_bincode_decode_static_limited_deserialize(
1707 15 : vote_instruction,
1708 15 : vote_instruction_mem,
1709 15 : ctx->instr->data,
1710 15 : ctx->instr->data_sz,
1711 15 : FD_TXN_MTU,
1712 15 : NULL );
1713 15 : if( FD_UNLIKELY( !instruction ) ) {
1714 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
1715 0 : }
1716 :
1717 : /* PLEASE PRESERVE SWITCH-CASE ORDERING TO MIRROR LABS IMPL:
1718 : */
1719 15 : switch( instruction->discriminant ) {
1720 :
1721 : /* InitializeAccount
1722 : *
1723 : * Instruction:
1724 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L32
1725 : *
1726 : * Processor:
1727 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L71
1728 : */
1729 6 : case fd_vote_instruction_enum_initialize_account: {
1730 : /* If the BLS pubkey feature is active, reject the instruction
1731 : https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_processor.rs#L106-L108 */
1732 : //FIXME
1733 6 : if( FD_UNLIKELY( is_bls_pubkey_feature_enabled ) ) {
1734 3 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
1735 3 : }
1736 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L72
1737 3 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_rent_id );
1738 3 : if( FD_UNLIKELY( rc ) ) return rc;
1739 3 : fd_rent_t rent_;
1740 3 : fd_rent_t const * rent = fd_sysvar_cache_rent_read( ctx->sysvar_cache, &rent_ );
1741 3 : if( FD_UNLIKELY( !rent ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1742 :
1743 3 : if( FD_UNLIKELY( fd_borrowed_account_get_lamports( &me ) <
1744 3 : fd_rent_exempt_minimum_balance( rent, fd_borrowed_account_get_data_len( &me ) ) ) )
1745 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
1746 :
1747 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L76
1748 3 : rc = fd_sysvar_instr_acct_check( ctx, 2, &fd_sysvar_clock_id );
1749 3 : if( FD_UNLIKELY( rc ) ) return rc;
1750 3 : fd_sol_sysvar_clock_t clock_;
1751 3 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
1752 3 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1753 :
1754 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L78
1755 3 : rc = initialize_account( ctx, &me, target_version, &instruction->inner.initialize_account, signers, signers_cnt, clock );
1756 :
1757 3 : break;
1758 3 : }
1759 :
1760 : /* Authorize
1761 : *
1762 : * Instruction:
1763 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L40
1764 : *
1765 : * Processor:
1766 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L86
1767 : *
1768 : * Notes:
1769 : * - Up to two signers: the vote authority and the authorized withdrawer.
1770 : */
1771 0 : case fd_vote_instruction_enum_authorize: {
1772 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L87
1773 0 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_clock_id );
1774 0 : if( FD_UNLIKELY( rc ) ) return rc;
1775 0 : fd_sol_sysvar_clock_t clock_;
1776 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
1777 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1778 :
1779 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L89
1780 0 : fd_pubkey_t const * voter_pubkey = &instruction->inner.authorize.pubkey;
1781 0 : fd_vote_authorize_t vote_authorize = instruction->inner.authorize.vote_authorize;
1782 :
1783 0 : rc = authorize( ctx, &me, target_version, voter_pubkey, vote_authorize, signers, signers_cnt, clock, is_bls_pubkey_feature_enabled );
1784 :
1785 0 : break;
1786 0 : }
1787 :
1788 : /* AuthorizeWithSeed
1789 : *
1790 : * Instruction:
1791 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L117
1792 : *
1793 : * Processor:
1794 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L98
1795 : */
1796 0 : case fd_vote_instruction_enum_authorize_with_seed: {
1797 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L99
1798 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 3 ) ) {
1799 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
1800 0 : break;
1801 0 : }
1802 :
1803 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L100
1804 0 : fd_vote_authorize_with_seed_args_t * args = &instruction->inner.authorize_with_seed;
1805 :
1806 0 : rc = process_authorize_with_seed_instruction(
1807 0 : ctx,
1808 0 : target_version,
1809 0 : &me,
1810 0 : &args->new_authority,
1811 0 : args->authorization_type,
1812 0 : &args->current_authority_derived_key_owner,
1813 0 : args->current_authority_derived_key_seed,
1814 0 : args->current_authority_derived_key_seed_len,
1815 0 : is_bls_pubkey_feature_enabled
1816 0 : );
1817 :
1818 0 : break;
1819 0 : }
1820 :
1821 : /* AuthorizeCheckedWithSeed
1822 : *
1823 : * Instruction:
1824 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L131
1825 : *
1826 : * Processor:
1827 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L111
1828 : */
1829 0 : case fd_vote_instruction_enum_authorize_checked_with_seed: {
1830 0 : fd_vote_authorize_checked_with_seed_args_t const * args =
1831 0 : &instruction->inner.authorize_checked_with_seed;
1832 :
1833 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L112
1834 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 4 ) ) {
1835 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
1836 0 : break;
1837 0 : }
1838 :
1839 : // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L99-L100
1840 0 : fd_pubkey_t const * new_authority = NULL;
1841 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 3UL, &new_authority );
1842 0 : if( FD_UNLIKELY( rc ) ) return rc;
1843 :
1844 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L116
1845 0 : if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( ctx->instr, 3, &rc ) ) ) {
1846 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/transaction-context/src/lib.rs#L789 */
1847 0 : if( FD_UNLIKELY( !!rc ) ) break;
1848 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L117
1849 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
1850 0 : break;
1851 0 : }
1852 :
1853 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L119
1854 0 : rc = process_authorize_with_seed_instruction(
1855 0 : ctx,
1856 0 : target_version,
1857 0 : &me,
1858 0 : new_authority,
1859 0 : args->authorization_type,
1860 0 : &args->current_authority_derived_key_owner,
1861 0 : args->current_authority_derived_key_seed,
1862 0 : args->current_authority_derived_key_seed_len,
1863 0 : is_bls_pubkey_feature_enabled );
1864 :
1865 0 : break;
1866 0 : }
1867 :
1868 : /* UpdateValidatorIdentity
1869 : *
1870 : * Instruction:
1871 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L65
1872 : *
1873 : * Processor:
1874 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L130
1875 : */
1876 0 : case fd_vote_instruction_enum_update_validator_identity: {
1877 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L131
1878 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 2 ) ) {
1879 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
1880 0 : break;
1881 0 : }
1882 :
1883 : // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L118-L120
1884 0 : fd_pubkey_t const * node_pubkey = NULL;
1885 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 1UL, &node_pubkey );
1886 0 : if( FD_UNLIKELY( rc ) ) return rc;
1887 :
1888 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L135
1889 0 : rc = update_validator_identity( ctx, target_version, &me, node_pubkey, signers, signers_cnt );
1890 :
1891 0 : break;
1892 0 : }
1893 :
1894 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L142
1895 0 : case fd_vote_instruction_enum_update_commission: {
1896 :
1897 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L149
1898 0 : fd_epoch_schedule_t epoch_schedule_;
1899 0 : fd_epoch_schedule_t const * epoch_schedule = fd_sysvar_cache_epoch_schedule_read( ctx->sysvar_cache, &epoch_schedule_ );
1900 0 : if( FD_UNLIKELY( !epoch_schedule ) ) {
1901 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1902 0 : }
1903 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L150
1904 :
1905 0 : fd_sol_sysvar_clock_t clock_;
1906 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
1907 0 : if( FD_UNLIKELY( !clock ) ) {
1908 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1909 0 : }
1910 :
1911 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L145
1912 0 : rc = update_commission(
1913 0 : ctx,
1914 0 : target_version,
1915 0 : &me,
1916 0 : instruction->inner.update_commission,
1917 0 : signers,
1918 0 : signers_cnt,
1919 0 : epoch_schedule,
1920 0 : clock
1921 0 : );
1922 :
1923 0 : break;
1924 0 : }
1925 :
1926 : /* Vote
1927 : *
1928 : * Instruction:
1929 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L49
1930 : */
1931 0 : case fd_vote_instruction_enum_vote:;
1932 : /* clang-format off */
1933 0 : __attribute__((fallthrough));
1934 : /* clang-format on */
1935 :
1936 : /* VoteSwitch
1937 : *
1938 : * Instruction:
1939 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L81
1940 : *
1941 : * Processor:
1942 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L154
1943 : */
1944 0 : case fd_vote_instruction_enum_vote_switch: {
1945 0 : if( FD_FEATURE_ACTIVE_BANK( ctx->bank, deprecate_legacy_vote_ixs ) ) {
1946 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
1947 0 : }
1948 :
1949 0 : fd_vote_t * vote;
1950 0 : if( instruction->discriminant == fd_vote_instruction_enum_vote ) {
1951 0 : vote = &instruction->inner.vote;
1952 0 : } else if( instruction->discriminant == fd_vote_instruction_enum_vote_switch ) {
1953 0 : vote = &instruction->inner.vote_switch.vote;
1954 0 : } else {
1955 0 : FD_LOG_CRIT(( "unsupported instruction discriminant: %u", instruction->discriminant ));
1956 0 : }
1957 :
1958 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L155
1959 0 : int err;
1960 0 : err = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_slot_hashes_id );
1961 0 : if( FD_UNLIKELY( err ) ) return err;
1962 :
1963 0 : if( FD_UNLIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
1964 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1965 0 : }
1966 :
1967 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L157
1968 0 : err = fd_sysvar_instr_acct_check( ctx, 2, &fd_sysvar_clock_id );
1969 0 : if( FD_UNLIKELY( err ) ) return err;
1970 0 : fd_sol_sysvar_clock_t clock_;
1971 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
1972 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
1973 :
1974 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); /* guaranteed to succeed */
1975 0 : rc = process_vote_with_account(
1976 0 : ctx,
1977 0 : &me,
1978 0 : target_version,
1979 0 : slot_hashes,
1980 0 : clock,
1981 0 : vote,
1982 0 : signers,
1983 0 : signers_cnt
1984 0 : );
1985 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
1986 :
1987 0 : break;
1988 0 : }
1989 :
1990 : /* UpdateVoteState
1991 : *
1992 : * Instruction:
1993 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L100
1994 : */
1995 0 : case fd_vote_instruction_enum_update_vote_state:;
1996 : /* clang-format off */
1997 0 : __attribute__((fallthrough));
1998 : /* clang-format on */
1999 :
2000 : /* UpdateVoteStateSwitch
2001 : *
2002 : * Instruction:
2003 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L107
2004 : *
2005 : * Processor:
2006 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L169
2007 : */
2008 0 : case fd_vote_instruction_enum_update_vote_state_switch: {
2009 0 : if( FD_FEATURE_ACTIVE_BANK( ctx->bank, deprecate_legacy_vote_ixs ) ) {
2010 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2011 0 : }
2012 :
2013 0 : fd_vote_state_update_t * vote_state_update;
2014 0 : switch( instruction->discriminant ) {
2015 0 : case fd_vote_instruction_enum_update_vote_state:
2016 0 : vote_state_update = &instruction->inner.update_vote_state;
2017 0 : break;
2018 0 : case fd_vote_instruction_enum_update_vote_state_switch:
2019 0 : vote_state_update = &instruction->inner.update_vote_state_switch.vote_state_update;
2020 0 : break;
2021 0 : default:
2022 0 : FD_LOG_CRIT(( "unsupported instruction discriminant: %u", instruction->discriminant ));
2023 0 : }
2024 :
2025 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L171
2026 0 : if( FD_LIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
2027 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2028 0 : }
2029 :
2030 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L172
2031 0 : fd_sol_sysvar_clock_t clock_;
2032 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2033 0 : if( FD_UNLIKELY( !clock ) )
2034 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2035 :
2036 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L173
2037 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache );
2038 0 : rc = process_vote_state_update(
2039 0 : ctx,
2040 0 : &me,
2041 0 : target_version,
2042 0 : slot_hashes,
2043 0 : clock,
2044 0 : vote_state_update,
2045 0 : signers,
2046 0 : signers_cnt
2047 0 : );
2048 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
2049 :
2050 0 : break;
2051 0 : }
2052 :
2053 : /* CompactUpdateVoteState
2054 : *
2055 : * Instruction:
2056 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L139
2057 : *
2058 : * Notes:
2059 : * - Up to three signers: the vote authority, the authorized withdrawer, and the new authority.
2060 : * - Feature gated, but live on mainnet.
2061 : */
2062 0 : case fd_vote_instruction_enum_compact_update_vote_state:;
2063 0 : __attribute__((fallthrough));
2064 :
2065 : /* CompactUpdateVoteStateSwitch
2066 : *
2067 : * Instruction:
2068 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L146
2069 : *
2070 : * Processor:
2071 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L183
2072 : *
2073 : * Notes:
2074 : * - Up to three signers: the vote authority, the authorized withdrawer, and the new authority.
2075 : * - Feature gated, but live on mainnet.
2076 : */
2077 0 : case fd_vote_instruction_enum_compact_update_vote_state_switch: {
2078 : /* https://github.com/anza-xyz/agave/blob/dc4b9dcbbf859ff48f40d00db824bde063fdafcc/programs/vote/src/vote_processor.rs#L183-L191 */
2079 0 : if( FD_FEATURE_ACTIVE_BANK( ctx->bank, deprecate_legacy_vote_ixs ) ) {
2080 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2081 0 : }
2082 :
2083 0 : fd_compact_vote_state_update_t * vote_state_update = NULL;
2084 0 : if( instruction->discriminant == fd_vote_instruction_enum_compact_update_vote_state ) {
2085 0 : vote_state_update = &instruction->inner.compact_update_vote_state;
2086 0 : } else if( instruction->discriminant ==
2087 0 : fd_vote_instruction_enum_compact_update_vote_state_switch ) {
2088 0 : vote_state_update =
2089 0 : &instruction->inner.compact_update_vote_state_switch.compact_vote_state_update;
2090 0 : }
2091 :
2092 0 : fd_vote_state_update_t vote_update;
2093 0 : fd_vote_state_update_new( &vote_update );
2094 0 : if( FD_UNLIKELY( !fd_vote_decode_compact_update( vote_state_update, &vote_update, ctx ) ) )
2095 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2096 :
2097 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L185
2098 0 : if( FD_LIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
2099 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2100 0 : }
2101 :
2102 0 : fd_sol_sysvar_clock_t clock_;
2103 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2104 0 : if( FD_UNLIKELY( !clock ) )
2105 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2106 :
2107 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L187
2108 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); /* guaranteed to succeed */
2109 0 : rc = process_vote_state_update(
2110 0 : ctx,
2111 0 : &me,
2112 0 : target_version,
2113 0 : slot_hashes,
2114 0 : clock,
2115 0 : &vote_update,
2116 0 : signers,
2117 0 : signers_cnt
2118 0 : );
2119 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
2120 :
2121 0 : break;
2122 0 : }
2123 :
2124 : /* TowerSync(Switch)
2125 : *
2126 : * Instruction:
2127 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L151-L157
2128 : *
2129 : * Processor:
2130 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L196-L215
2131 : */
2132 :
2133 0 : case fd_vote_instruction_enum_tower_sync:
2134 0 : case fd_vote_instruction_enum_tower_sync_switch: {
2135 0 : fd_tower_sync_t * tower_sync = (instruction->discriminant == fd_vote_instruction_enum_tower_sync)
2136 0 : ? &instruction->inner.tower_sync
2137 0 : : &instruction->inner.tower_sync_switch.tower_sync;
2138 :
2139 0 : if( FD_LIKELY( !fd_sysvar_cache_slot_hashes_is_valid( ctx->sysvar_cache ) ) ) {
2140 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2141 0 : }
2142 :
2143 0 : fd_sol_sysvar_clock_t clock_;
2144 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2145 0 : if( FD_UNLIKELY( !clock ) ) {
2146 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2147 0 : }
2148 :
2149 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache );
2150 0 : FD_TEST( slot_hashes );
2151 0 : rc = process_tower_sync(
2152 0 : ctx,
2153 0 : &me,
2154 0 : target_version,
2155 0 : slot_hashes,
2156 0 : clock,
2157 0 : tower_sync,
2158 0 : signers,
2159 0 : signers_cnt
2160 0 : );
2161 0 : fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes );
2162 :
2163 0 : break;
2164 0 : }
2165 :
2166 : /* Withdraw
2167 : *
2168 : * Instruction:
2169 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L57
2170 : *
2171 : * Processor:
2172 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L216
2173 : */
2174 0 : case fd_vote_instruction_enum_withdraw: {
2175 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 2 ) ) {
2176 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
2177 0 : break;
2178 0 : }
2179 0 : fd_rent_t rent_;
2180 0 : fd_rent_t const * rent_sysvar = fd_sysvar_cache_rent_read( ctx->sysvar_cache, &rent_ );
2181 0 : if( FD_UNLIKELY( !rent_sysvar ) )
2182 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2183 0 : fd_sol_sysvar_clock_t clock_;
2184 0 : fd_sol_sysvar_clock_t const * clock_sysvar = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2185 0 : if( FD_UNLIKELY( !clock_sysvar ) )
2186 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2187 :
2188 0 : rc = withdraw(
2189 0 : ctx,
2190 0 : &me,
2191 0 : target_version,
2192 0 : instruction->inner.withdraw,
2193 0 : 1UL,
2194 0 : signers,
2195 0 : signers_cnt,
2196 0 : rent_sysvar,
2197 0 : clock_sysvar
2198 0 : );
2199 :
2200 0 : break;
2201 0 : }
2202 :
2203 : /* AuthorizeChecked
2204 : *
2205 : * Instruction:
2206 : * https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/instruction.rs#L93
2207 : *
2208 : * Processor:
2209 : * https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L234
2210 : *
2211 : * Notes:
2212 : * - Up to three signers: the vote authority, the authorized withdrawer, and the new authority.
2213 : * - Feature gated, but live on mainnet.
2214 : */
2215 0 : case fd_vote_instruction_enum_authorize_checked: {
2216 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 4 ) ) {
2217 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
2218 0 : break;
2219 0 : }
2220 :
2221 : // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L243-L245
2222 0 : fd_pubkey_t const * voter_pubkey = NULL;
2223 0 : rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 3UL, &voter_pubkey );
2224 0 : if( FD_UNLIKELY( rc ) ) return rc;
2225 :
2226 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L239
2227 0 : if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( ctx->instr, 3, &rc ) ) ) {
2228 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/transaction-context/src/lib.rs#L789 */
2229 0 : if( FD_UNLIKELY( !!rc ) ) break;
2230 :
2231 0 : rc = FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
2232 0 : break;
2233 0 : }
2234 :
2235 : // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L242
2236 0 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_clock_id );
2237 0 : if( FD_UNLIKELY( rc ) ) return rc;
2238 0 : fd_sol_sysvar_clock_t clock_;
2239 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2240 0 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2241 :
2242 0 : rc = authorize(
2243 0 : ctx,
2244 0 : &me,
2245 0 : target_version,
2246 0 : voter_pubkey,
2247 0 : instruction->inner.authorize_checked,
2248 0 : signers,
2249 0 : signers_cnt,
2250 0 : clock,
2251 0 : is_bls_pubkey_feature_enabled
2252 0 : );
2253 0 : break;
2254 0 : }
2255 :
2256 : /* InitializeAccountV2
2257 : *
2258 : * Instruction:
2259 : * https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/mod.rs#L1180
2260 : * https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_state/handler.rs#L965
2261 : *
2262 : * Processor:
2263 : * https://github.com/firedancer-io/agave/blob/v4.0.0-prerelease/programs/vote/src/vote_processor.rs#L302
2264 : *
2265 : * Notes:
2266 : * - The code is identical to InitializeAccount, except calling the actual initialize_account_v2
2267 : * - Feature gated.
2268 : */
2269 9 : case fd_vote_instruction_enum_initialize_account_v2: {
2270 : //FIXME
2271 9 : if( FD_UNLIKELY( !is_bls_pubkey_feature_enabled ) ) {
2272 3 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA;
2273 3 : }
2274 :
2275 6 : rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_rent_id );
2276 6 : if( FD_UNLIKELY( rc ) ) return rc;
2277 6 : fd_rent_t rent_;
2278 6 : fd_rent_t const * rent = fd_sysvar_cache_rent_read( ctx->sysvar_cache, &rent_ );
2279 6 : if( FD_UNLIKELY( !rent ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2280 :
2281 6 : if( FD_UNLIKELY( fd_borrowed_account_get_lamports( &me ) <
2282 6 : fd_rent_exempt_minimum_balance( rent, fd_borrowed_account_get_data_len( &me ) ) ) )
2283 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
2284 :
2285 6 : rc = fd_sysvar_instr_acct_check( ctx, 2, &fd_sysvar_clock_id );
2286 6 : if( FD_UNLIKELY( rc ) ) return rc;
2287 6 : fd_sol_sysvar_clock_t clock_;
2288 6 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ );
2289 6 : if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
2290 :
2291 6 : rc = initialize_account_v2( ctx, &me, target_version, &instruction->inner.initialize_account_v2, signers, signers_cnt, clock );
2292 :
2293 6 : break;
2294 6 : }
2295 :
2296 0 : default:
2297 0 : FD_LOG_CRIT(( "unsupported vote instruction: %u", instruction->discriminant ));
2298 15 : }
2299 :
2300 9 : return rc;
2301 15 : }
2302 :
2303 : /**********************************************************************/
2304 : /* Public API */
2305 : /**********************************************************************/
2306 :
2307 : /* TODO: Old code, remove whenever stake program gets cleaned up */
2308 : void
2309 : fd_vote_convert_to_current( fd_vote_state_versioned_t * self,
2310 : uchar * authorized_voters_mem,
2311 0 : uchar * landed_votes_mem ) {
2312 0 : fd_vsv_try_convert_to_v3( self, authorized_voters_mem, landed_votes_mem );
2313 0 : }
2314 :
2315 : fd_vote_state_versioned_t *
2316 : fd_vote_get_state( fd_account_meta_t const * self,
2317 0 : uchar * mem ) {
2318 0 : int err = fd_vsv_get_state( self, mem );
2319 0 : return err ? NULL : (fd_vote_state_versioned_t *)mem;
2320 0 : }
|